Hook dataflow.js into Syndicate/js; add "during ... actor { ... }"

This commit is contained in:
Tony Garnock-Jones 2016-08-07 15:33:09 -04:00
parent e2575c3ea1
commit 41693b897c
24 changed files with 408 additions and 208 deletions

View File

@ -42,15 +42,14 @@ var forEachChild = (function () {
return forEachChild; return forEachChild;
})(); })();
function buildActor(constructorES5, nameExpOpt, block) { function buildActor(nameExpOpt, block) {
var nameExpStr; var nameExpStr;
if (nameExpOpt.numChildren === 1) { if (nameExpOpt.numChildren === 1) {
nameExpStr = ', ' + nameExpOpt.asES5; nameExpStr = ', ' + nameExpOpt.asES5;
} else { } else {
nameExpStr = ''; nameExpStr = '';
} }
return 'Syndicate.Actor.spawnActor(new '+constructorES5+', '+ return 'Syndicate.Actor.spawnActor(function() ' + block.asES5 + nameExpStr + ');';
'function() ' + block.asES5 + nameExpStr + ');';
} }
function buildFacet(facetBlock, transitionBlock) { function buildFacet(facetBlock, transitionBlock) {
@ -58,11 +57,11 @@ function buildFacet(facetBlock, transitionBlock) {
'\nSyndicate.Actor.createFacet()' + '\nSyndicate.Actor.createFacet()' +
(facetBlock ? facetBlock.asES5 : '') + (facetBlock ? facetBlock.asES5 : '') +
(transitionBlock ? transitionBlock.asES5 : '') + (transitionBlock ? transitionBlock.asES5 : '') +
'.completeBuild(); })();'; '.completeBuild(); }).call(this);';
} }
function buildOnEvent(isTerminal, eventType, subscription, projection, bindings, body) { function buildOnEvent(isTerminal, eventType, subscription, projection, bindings, body) {
return '\n.onEvent(' + isTerminal + ', ' + JSON.stringify(eventType) + ', ' + return '\n.onEvent(Syndicate.Actor.PRIORITY_NORMAL, ' + isTerminal + ', ' + JSON.stringify(eventType) + ', ' +
subscription + ', ' + projection + subscription + ', ' + projection +
', (function(' + bindings.join(', ') + ') ' + body + '))'; ', (function(' + bindings.join(', ') + ') ' + body + '))';
} }
@ -86,11 +85,8 @@ function buildCaseEvent(eventPattern, body) {
} }
var modifiedSourceActions = { var modifiedSourceActions = {
ActorStatement_noConstructor: function(_actor, _namedOpt, nameExpOpt, block) { ActorStatement: function(_actor, _namedOpt, nameExpOpt, block) {
return buildActor('Object()', nameExpOpt, block); return buildActor(nameExpOpt, block);
},
ActorStatement_withConstructor: function(_actor, ctorExp, _namedOpt, nameExpOpt, block) {
return buildActor(ctorExp.asES5, nameExpOpt, block);
}, },
DataspaceStatement_ground: function(_ground, _dataspace, maybeId, block) { DataspaceStatement_ground: function(_ground, _dataspace, maybeId, block) {
@ -133,6 +129,22 @@ var modifiedSourceActions = {
label + ', ' + JSON.stringify(formals) + ');'; label + ', ' + JSON.stringify(formals) + ');';
}, },
FieldDeclarationStatement: function(_field, memberExpr, _eq, initExpr, sc) {
return 'Syndicate.Actor.declareField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
memberExpr.memberPropExpr.asES5 + ', ' + initExpr.asES5 + ')' +
sc.interval.contents;
},
MemberExpression_fieldRefExp: function (_field, memberExpr) {
return 'Syndicate.Actor.referenceField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
memberExpr.memberPropExpr.asES5 + ')';
},
UnaryExpression_fieldDelExp: function (_delete, _field, memberExpr) {
return 'Syndicate.Actor.deleteField(' + memberExpr.memberObjectExpr.asES5 + ', ' +
memberExpr.memberPropExpr.asES5 + ')';
},
SendMessageStatement: function(_colons, expr, sc) { SendMessageStatement: function(_colons, expr, sc) {
return 'Syndicate.Dataspace.send(' + expr.asES5 + ')' + sc.interval.contents; return 'Syndicate.Dataspace.send(' + expr.asES5 + ')' + sc.interval.contents;
}, },
@ -165,6 +177,9 @@ var modifiedSourceActions = {
FacetSituation_onEvent: function (_on, _event, id, block) { FacetSituation_onEvent: function (_on, _event, id, block) {
return '\n.addOnEventHandler((function(' + id.asES5 + ') ' + block.asES5 + '))'; return '\n.addOnEventHandler((function(' + id.asES5 + ') ' + block.asES5 + '))';
}, },
FacetSituation_dataflow: function (_dataflow, block) {
return '\n.addDataflow((function () ' + block.asES5 + '))';
},
FacetSituation_during: function(_during, pattern, facetBlock) { FacetSituation_during: function(_during, pattern, facetBlock) {
var cachedAssertionVar = gensym('cachedAssertion'); var cachedAssertionVar = gensym('cachedAssertion');
return buildOnEvent(false, return buildOnEvent(false,
@ -184,6 +199,28 @@ var modifiedSourceActions = {
'{}') + '{}') +
'.completeBuild(); }'); '.completeBuild(); }');
}, },
FacetSituation_duringActor: function(_during, pattern, _actor, _named, nameExpOpt, facetBlock) {
var cachedAssertionVar = gensym('cachedAssertion');
var actorBlock = {
asES5: '{ ' + facetBlock.facetVarDecls +
'\nSyndicate.Actor.createFacet()' +
facetBlock.asES5 +
buildOnEvent(true,
'retracted',
pattern.instantiatedSubscription(cachedAssertionVar),
pattern.instantiatedProjection(cachedAssertionVar),
[],
'{}') +
'.completeBuild(); }'
};
return buildOnEvent(false,
'asserted',
pattern.subscription,
pattern.projection,
pattern.bindings,
'{ var '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';'+
'\n' + buildActor(nameExpOpt, actorBlock) + ' }');
},
AssertWhenClause: function(_when, _lparen, expr, _rparen) { AssertWhenClause: function(_when, _lparen, expr, _rparen) {
return expr.asES5; return expr.asES5;
@ -199,6 +236,24 @@ var modifiedSourceActions = {
semantics.extendAttribute('modifiedSource', modifiedSourceActions); semantics.extendAttribute('modifiedSource', modifiedSourceActions);
semantics.addAttribute('memberObjectExpr', {
MemberExpression_propRefExp: function(objExpr, _dot, id) {
return objExpr;
},
MemberExpression_arrayRefExp: function(objExpr, _lbrack, propExpr, _rbrack) {
return objExpr;
}
});
semantics.addAttribute('memberPropExpr', {
MemberExpression_propRefExp: function(objExpr, _dot, id) {
return { asES5: JSON.stringify(id.interval.contents) };
},
MemberExpression_arrayRefExp: function(objExpr, _lbrack, propExpr, _rbrack) {
return propExpr;
}
});
semantics.addAttribute('facetVarDecls', { semantics.addAttribute('facetVarDecls', {
FacetBlock: function (_leftParen, varDecls, _init, _situations, _done, _rightParen) { FacetBlock: function (_leftParen, varDecls, _init, _situations, _done, _rightParen) {
return varDecls.asES5.join(' '); return varDecls.asES5.join(' ');
@ -269,7 +324,7 @@ semantics.addAttribute('instantiatedAssertion', {
var fragments = []; var fragments = [];
fragments.push('(function() { var _ = Syndicate.__; return '); fragments.push('(function() { var _ = Syndicate.__; return ');
children.forEach(function (c) { fragments.push(c.buildSubscription('instantiated')); }); children.forEach(function (c) { fragments.push(c.buildSubscription('instantiated')); });
fragments.push('; })()'); fragments.push('; }).call(this)');
return fragments.join(''); return fragments.join('');
} }
}); });
@ -345,6 +400,12 @@ semantics.addOperation('buildSubscription(mode)', {
} }
}, },
MemberExpression_fieldRefExp: function (_field, memberExpr) {
return 'Syndicate.Actor.referenceField(' +
memberExpr.memberObjectExpr.buildSubscription(this.args.mode) + ', ' +
memberExpr.memberPropExpr.buildSubscription(this.args.mode) + ')';
},
identifier: function(_name) { identifier: function(_name) {
var i = this.interval.contents; var i = this.interval.contents;
if (i[0] === '$' && i.length > 1) { if (i[0] === '$' && i.length > 1) {

View File

@ -7,10 +7,13 @@ message type deposit(amount);
ground dataspace { ground dataspace {
actor { actor {
this.balance = 0; field this.balance = 0;
react { react {
assert account(this.balance); assert account(this.balance);
dataflow {
console.log("Balance inside account is", this.balance);
}
on message deposit($amount) { on message deposit($amount) {
this.balance += amount; this.balance += amount;
} }

View File

@ -20,19 +20,19 @@ assertion type foo(x, y);
ground dataspace { ground dataspace {
actor { actor {
var x = 123; field this.x = 123;
react { react {
assert foo(x, 999); assert foo(this.x, 999);
during foo(x, $v) { during foo(this.x, $v) {
do { do {
console.log('x=', x, 'v=', v); console.log('x=', this.x, 'v=', v);
if (x === 123) { if (this.x === 123) {
x = 124; this.x = 124;
} }
} }
finally { finally {
console.log('finally for x=', x, 'v=', v); console.log('finally for x=', this.x, 'v=', v);
} }
} }
} }

View File

@ -1,5 +1,17 @@
// bin/syndicatec compiler/demo-filesystem.js | node // bin/syndicatec compiler/demo-filesystem.js | node
// Good output:
//
// At least one reader exists for: hello.txt
// hello.txt has content undefined
// hello.txt has content "a"
// hello.txt has content undefined
// hello.txt has content "c"
// hello.txt has content "quit demo"
// The hello.txt file contained 'quit demo', so we will quit
// second observer sees that hello.txt content is "final contents"
// No remaining readers exist for: hello.txt
var Syndicate = require('./src/main.js'); var Syndicate = require('./src/main.js');
assertion type file(name, content) = "file"; assertion type file(name, content) = "file";
@ -17,16 +29,16 @@ ground dataspace {
do { do {
console.log("At least one reader exists for:", name); console.log("At least one reader exists for:", name);
} }
assert file(name, this.files[name]); assert file(name, field this.files[name]);
finally { finally {
console.log("No remaining readers exist for:", name); console.log("No remaining readers exist for:", name);
} }
} }
on message saveFile($name, $newcontent) { on message saveFile($name, $newcontent) {
this.files[name] = newcontent; field this.files[name] = newcontent;
} }
on message deleteFile($name) { on message deleteFile($name) {
delete this.files[name]; delete field this.files[name];
} }
} }
} }

View File

@ -11,13 +11,13 @@ Syndicate <: ES5 {
| DataspaceStatement | DataspaceStatement
| ActorFacetStatement | ActorFacetStatement
| AssertionTypeDeclarationStatement | AssertionTypeDeclarationStatement
| FieldDeclarationStatement
| SendMessageStatement | SendMessageStatement
FunctionBodyBlock = "{" FunctionBody "}" // odd that this isn't in es5.ohm somewhere FunctionBodyBlock = "{" FunctionBody "}" // odd that this isn't in es5.ohm somewhere
ActorStatement ActorStatement
= actor ~named CallExpression (named Expression<withIn>)? FunctionBodyBlock -- withConstructor = actor (named Expression<withIn>)? FunctionBodyBlock
| actor (named Expression<withIn>)? FunctionBodyBlock -- noConstructor
DataspaceStatement DataspaceStatement
= ground dataspace identifier? FunctionBodyBlock -- ground = ground dataspace identifier? FunctionBodyBlock -- ground
@ -31,22 +31,33 @@ Syndicate <: ES5 {
AssertionTypeDeclarationStatement AssertionTypeDeclarationStatement
= (assertion | message) type identifier "(" FormalParameterList ")" ("=" stringLiteral)? #(sc) = (assertion | message) type identifier "(" FormalParameterList ")" ("=" stringLiteral)? #(sc)
FieldDeclarationStatement = field MemberExpression "=" AssignmentExpression<withIn> #(sc)
MemberExpression += field MemberExpression -- fieldRefExp
UnaryExpression += delete field MemberExpression -- fieldDelExp
SendMessageStatement = "::" Expression<withIn> #(sc) SendMessageStatement = "::" Expression<withIn> #(sc)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Ongoing event handlers. // Ongoing event handlers.
FacetBlock = "{" (VariableStatement | FunctionDeclaration)* FacetInitBlock? FacetSituation* FacetDoneBlock? "}" FacetBlock = "{"
(VariableStatement | FieldDeclarationStatement | FunctionDeclaration)*
FacetInitBlock?
FacetSituation*
FacetDoneBlock?
"}"
FacetStateTransitionBlock = "{" FacetStateTransition* "}" FacetStateTransitionBlock = "{" FacetStateTransition* "}"
FacetInitBlock = do FunctionBodyBlock FacetInitBlock = do FunctionBodyBlock
FacetDoneBlock = finally FunctionBodyBlock FacetDoneBlock = finally FunctionBodyBlock
FacetSituation FacetSituation
= assert FacetPattern AssertWhenClause? #(sc) -- assert = assert FacetPattern AssertWhenClause? #(sc) -- assert
| on FacetEventPattern FunctionBodyBlock -- event | on FacetEventPattern FunctionBodyBlock -- event
| on event identifier FunctionBodyBlock -- onEvent | on event identifier FunctionBodyBlock -- onEvent
| during FacetPattern FacetBlock -- during | dataflow FunctionBodyBlock -- dataflow
| during FacetPattern FacetBlock -- during
| during FacetPattern actor (named Expression<withIn>)? FacetBlock -- duringActor
AssertWhenClause = when "(" Expression<withIn> ")" AssertWhenClause = when "(" Expression<withIn> ")"
@ -76,9 +87,11 @@ Syndicate <: ES5 {
assert = "assert" ~identifierPart assert = "assert" ~identifierPart
asserted = "asserted" ~identifierPart asserted = "asserted" ~identifierPart
assertion = "assertion" ~identifierPart assertion = "assertion" ~identifierPart
dataflow = "dataflow" ~identifierPart
dataspace = "dataspace" ~identifierPart dataspace = "dataspace" ~identifierPart
during = "during" ~identifierPart during = "during" ~identifierPart
event = "event" ~identifierPart event = "event" ~identifierPart
field = "field" ~identifierPart
ground = "ground" ~identifierPart ground = "ground" ~identifierPart
message = "message" ~identifierPart message = "message" ~identifierPart
metalevel = "metalevel" ~identifierPart metalevel = "metalevel" ~identifierPart

1
js/examples/button/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.expanded.js

View File

@ -0,0 +1,7 @@
all: index.expanded.js
%.expanded.js: %.js
../../bin/syndicatec $< > $@ || (rm -f $@; false)
clean:
rm -f *.expanded.js

View File

@ -1,14 +0,0 @@
"use strict";
new Syndicate.Ground(function () {
Syndicate.UI.spawnUIDriver();
Syndicate.Actor.spawnActor(new Object(), function() {
var counter = 0;
var ui = new Syndicate.UI.Anchor();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#button-label',''+counter), 0); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#counter','click',_), metalevel: 0 }; }), (function() {
counter++;
})).completeBuild();
});
}).startStepping();

View File

@ -2,12 +2,12 @@ ground dataspace {
Syndicate.UI.spawnUIDriver(); Syndicate.UI.spawnUIDriver();
actor { actor {
var counter = 0; field this.counter = 0;
var ui = new Syndicate.UI.Anchor(); var ui = new Syndicate.UI.Anchor();
react { react {
assert ui.html('#button-label', '' + counter); assert ui.html('#button-label', '' + this.counter);
on message Syndicate.UI.globalEvent('#counter', 'click', _) { on message Syndicate.UI.globalEvent('#counter', 'click', _) {
counter++; this.counter++;
} }
} }
} }

View File

@ -17,6 +17,8 @@ function spawnChatApp() {
actor { actor {
var ui = new Syndicate.UI.Anchor(); var ui = new Syndicate.UI.Anchor();
field this.nym;
field this.status;
react { react {
on asserted inputValue('#nym', $v) { this.nym = v; } on asserted inputValue('#nym', $v) { this.nym = v; }
on asserted inputValue('#status', $v) { this.status = v; } on asserted inputValue('#status', $v) { this.status = v; }
@ -83,17 +85,11 @@ assertion type inputValue(selector, value);
function spawnInputChangeMonitor() { function spawnInputChangeMonitor() {
actor { actor {
react { react {
on asserted Syndicate.observe(inputValue($selector, _)) { during Syndicate.observe(inputValue($selector, _)) actor {
actor { field this.value = $(selector).val();
this.value = $(selector).val(); assert inputValue(selector, this.value);
react { on message Syndicate.UI.globalEvent(selector, 'change', $e) {
assert inputValue(selector, this.value); this.value = e.target.value;
on message Syndicate.UI.globalEvent(selector, 'change', $e) {
this.value = e.target.value;
}
} until {
case retracted Syndicate.observe(inputValue(selector, _));
}
} }
} }
} }

View File

@ -60,7 +60,7 @@ function spawnRemoteListener() {
function spawnStoveSwitch() { function spawnStoveSwitch() {
actor { actor {
this.powerOn = false; field this.powerOn = false;
this.ui = new Syndicate.UI.Anchor(); this.ui = new Syndicate.UI.Anchor();
react { react {
assert componentPresent('stove switch'); assert componentPresent('stove switch');
@ -85,7 +85,7 @@ function spawnStoveSwitch() {
function spawnPowerDrawMonitor() { function spawnPowerDrawMonitor() {
actor { actor {
this.watts = 0; field this.watts = 0;
this.ui = new Syndicate.UI.Anchor(); this.ui = new Syndicate.UI.Anchor();
react { react {
assert componentPresent('power draw monitor'); assert componentPresent('power draw monitor');

View File

@ -40,7 +40,7 @@ ground dataspace G {
var geocoder = new google.maps.Geocoder(); var geocoder = new google.maps.Geocoder();
var wsurl_base = 'wss://demo-broker.syndicate-lang.org:8443/location/'; var wsurl_base = 'wss://demo-broker.syndicate-lang.org:8443/location/';
var wsurl = wsurl_base + group_element.value.trim(); field this.wsurl = wsurl_base + group_element.value.trim();
var watchId = ('geolocation' in navigator) var watchId = ('geolocation' in navigator)
&& navigator.geolocation.watchPosition(Syndicate.Dataspace.wrap(function (pos) { && navigator.geolocation.watchPosition(Syndicate.Dataspace.wrap(function (pos) {
@ -62,21 +62,21 @@ ground dataspace G {
})); }));
react { react {
var currentLocation = null; field this.currentLocation = null;
var selectedMarker = null; var selectedMarker = null;
assert brokerConnection(wsurl); assert brokerConnection(this.wsurl);
assert toBroker(wsurl, currentLocation) when (currentLocation); assert toBroker(this.wsurl, this.currentLocation) when (this.currentLocation);
on message Syndicate.UI.globalEvent('#my_email', 'change', _) { on message Syndicate.UI.globalEvent('#my_email', 'change', _) {
var v = email_element.value.trim(); var v = email_element.value.trim();
if (currentLocation) currentLocation[1] = v; if (this.currentLocation) this.currentLocation = this.currentLocation.set(1, v);
localStorage.my_email = v; localStorage.my_email = v;
} }
on message Syndicate.UI.globalEvent('#group', 'change', _) { on message Syndicate.UI.globalEvent('#group', 'change', _) {
localStorage.group = group_element.value.trim(); localStorage.group = group_element.value.trim();
wsurl = wsurl_base + group_element.value.trim(); this.wsurl = wsurl_base + group_element.value.trim();
} }
on message Syndicate.UI.globalEvent('#findMarker', 'click', $e) { on message Syndicate.UI.globalEvent('#findMarker', 'click', $e) {
@ -87,10 +87,10 @@ ground dataspace G {
} }
on message ($loc = locationRecord(_, _, _, _, _)) { on message ($loc = locationRecord(_, _, _, _, _)) {
currentLocation = loc; this.currentLocation = loc;
} }
during fromBroker(wsurl, locationRecord($id, $email, _, _, _)) { during fromBroker(this.wsurl, locationRecord($id, $email, _, _, _)) {
var ui = new Syndicate.UI.Anchor(); var ui = new Syndicate.UI.Anchor();
var marker = new google.maps.Marker({ var marker = new google.maps.Marker({
map: map, map: map,
@ -131,7 +131,7 @@ ground dataspace G {
selectMarker(); selectMarker();
if (latestPosition) map.panTo(latestPosition); if (latestPosition) map.panTo(latestPosition);
} }
on asserted fromBroker(wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) { on asserted fromBroker(this.wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) {
latestTimestamp = new Date(timestamp); latestTimestamp = new Date(timestamp);
latestPosition = {lat: lat, lng: lng}; latestPosition = {lat: lat, lng: lng};
marker.setPosition(latestPosition); marker.setPosition(latestPosition);

View File

@ -14,8 +14,8 @@ ground dataspace G {
var color = tinycolor('hsl ' + (Math.random() * 360 | 0) + ' 100% 50%').toHexString(); var color = tinycolor('hsl ' + (Math.random() * 360 | 0) + ' 100% 50%').toHexString();
var x = 0; var x = 0;
var y = 0; var y = 0;
var publishedX = x; field this.publishedX = x;
var publishedY = y; field this.publishedY = y;
function clamp(v) { function clamp(v) {
var limit = 9.8; var limit = 9.8;
@ -28,10 +28,10 @@ ground dataspace G {
assert Syndicate.UI.uiAttribute('rect#my_color', 'fill', color); assert Syndicate.UI.uiAttribute('rect#my_color', 'fill', color);
assert toBroker(wsurl, point(color, publishedX, publishedY)); assert toBroker(wsurl, point(color, this.publishedX, this.publishedY));
on message Syndicate.Timer.periodicTick(100) { on message Syndicate.Timer.periodicTick(100) {
publishedX = x; this.publishedX = x;
publishedY = y; this.publishedY = y;
} }
on message Syndicate.UI.windowEvent('deviceorientation', $e) { on message Syndicate.UI.windowEvent('deviceorientation', $e) {

View File

@ -6,16 +6,16 @@ ground dataspace {
actor { actor {
react until { react until {
case asserted Syndicate.observe(beep(_)) { case asserted Syndicate.observe(beep(_)) {
var counter = 0; field this.counter = 0;
react { react {
do { do {
:: beep(counter++); :: beep(this.counter++);
} }
on message beep(_) { on message beep(_) {
:: beep(counter++); :: beep(this.counter++);
} }
} until { } until {
case (counter >= 10); case (this.counter > 10);
} }
} }
} }

View File

@ -4,6 +4,10 @@ ground dataspace G {
actor { actor {
var ui = new Syndicate.UI.Anchor(); var ui = new Syndicate.UI.Anchor();
field this.angle;
field this.handX;
field this.handY;
react { react {
assert ui.html('#clock', assert ui.html('#clock',
'<svg width="300px" viewBox="0 0 100 100">'+ '<svg width="300px" viewBox="0 0 100 100">'+

View File

@ -20,7 +20,7 @@ function spawnModel() {
function spawnView() { function spawnView() {
actor named 'view' { actor named 'view' {
var ui = new Syndicate.UI.Anchor(); var ui = new Syndicate.UI.Anchor();
var orderColumn = 2; field this.orderColumn = 2;
function cell(text) { function cell(text) {
// Should escape text in a real application. // Should escape text in a real application.
@ -28,13 +28,13 @@ function spawnView() {
} }
react { react {
on message setSortColumn($c) { orderColumn = c; } on message setSortColumn($c) { this.orderColumn = c; }
during person($id, $firstName, $lastName, $address, $age) { during person($id, $firstName, $lastName, $address, $age) {
assert ui.context(id) assert ui.context(id)
.html('table#the-table tbody', .html('table#the-table tbody',
'<tr>' + [id, firstName, lastName, address, age].map(cell).join('') + '</tr>', '<tr>' + [id, firstName, lastName, address, age].map(cell).join('') + '</tr>',
[id, firstName, lastName, address, age][orderColumn]); [id, firstName, lastName, address, age][this.orderColumn]);
} }
} }
} }

View File

@ -26,24 +26,24 @@ function piece(text, pos, lo, hi, cls) {
function spawnGui() { function spawnGui() {
actor { actor {
this.text = '';
this.pos = 0;
this.highlightState = false;
this.updateDisplay = function () {
var text = this.text;
var pos = this.pos;
var highlight = this.highlightState;
var hLeft = highlight ? highlight[0] : 0;
var hRight = highlight ? highlight[1] : 0;
document.getElementById("fieldContents").innerHTML = highlight
? piece(text, pos, 0, hLeft, "normal") +
piece(text, pos, hLeft, hRight, "highlight") +
piece(text, pos, hRight, text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
};
react { react {
field this.text = '';
field this.pos = 0;
field this.highlightState = false;
dataflow {
var text = this.text;
var pos = this.pos;
var highlight = this.highlightState;
var hLeft = highlight ? highlight[0] : 0;
var hRight = highlight ? highlight[1] : 0;
document.getElementById("fieldContents").innerHTML = highlight
? piece(text, pos, 0, hLeft, "normal") +
piece(text, pos, hLeft, hRight, "highlight") +
piece(text, pos, hRight, text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
}
on message globalEvent("#inputRow", "+keydown", $event) { on message globalEvent("#inputRow", "+keydown", $event) {
switch (event.keyCode) { switch (event.keyCode) {
case 37 /* left */: :: fieldCommand("cursorLeft"); break; case 37 /* left */: :: fieldCommand("cursorLeft"); break;
@ -67,12 +67,10 @@ function spawnGui() {
on asserted fieldContents($text, $pos) { on asserted fieldContents($text, $pos) {
this.text = text; this.text = text;
this.pos = pos; this.pos = pos;
this.updateDisplay();
} }
on asserted highlight($state) { on asserted highlight($state) {
this.highlightState = state; this.highlightState = state;
this.updateDisplay();
} }
} }
} }
@ -83,10 +81,10 @@ function spawnGui() {
function spawnModel() { function spawnModel() {
actor { actor {
this.fieldValue = "initial";
this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */
react { react {
field this.fieldValue = "initial";
field this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */
assert fieldContents(this.fieldValue, this.cursorPos); assert fieldContents(this.fieldValue, this.cursorPos);
on message fieldCommand("cursorLeft") { on message fieldCommand("cursorLeft") {
@ -126,29 +124,28 @@ function spawnModel() {
function spawnSearch() { function spawnSearch() {
actor { actor {
this.fieldValue = "";
this.highlight = false;
this.search = function () {
var searchtext = document.getElementById("searchBox").value;
if (searchtext) {
var pos = this.fieldValue.indexOf(searchtext);
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
} else {
this.highlight = false;
}
};
react { react {
field this.searchtext = document.getElementById("searchBox").value;
field this.fieldValue = "";
field this.highlight = false;
assert highlight(this.highlight); assert highlight(this.highlight);
dataflow {
if (this.searchtext) {
var pos = this.fieldValue.indexOf(this.searchtext);
this.highlight = (pos !== -1) && [pos, pos + this.searchtext.length];
} else {
this.highlight = false;
}
}
on message globalEvent("#searchBox", "input", $event) { on message globalEvent("#searchBox", "input", $event) {
this.search(); this.searchtext = document.getElementById("searchBox").value;
} }
on asserted fieldContents($text, _) { on asserted fieldContents($text, _) {
this.fieldValue = text; this.fieldValue = text;
this.search();
} }
} }
} }

View File

@ -25,11 +25,11 @@ assertion type show(completed);
function todoListItemModel(initialId, initialTitle, initialCompleted) { function todoListItemModel(initialId, initialTitle, initialCompleted) {
actor { actor {
this.id = initialId;
this.title = initialTitle;
this.completed = initialCompleted;
react { react {
field this.id = initialId;
field this.title = initialTitle;
field this.completed = initialCompleted;
assert todo(this.id, this.title, this.completed); assert todo(this.id, this.title, this.completed);
on message setCompleted(this.id, $v) { this.completed = v; } on message setCompleted(this.id, $v) { this.completed = v; }
@ -58,8 +58,9 @@ function getTemplate(id) {
function todoListItemView(id) { function todoListItemView(id) {
actor { actor {
this.ui = new Syndicate.UI.Anchor(); this.ui = new Syndicate.UI.Anchor();
this.editing = false;
react { react {
field this.editing = false;
during todo(id, $title, $completed) { during todo(id, $title, $completed) {
during show(completed) { during show(completed) {
assert this.ui.html('.todo-list', assert this.ui.html('.todo-list',
@ -158,15 +159,15 @@ ground dataspace G {
} }
actor { actor {
var completedCount = 0;
var activeCount = 0;
react { react {
on asserted todo($id, _, $completed) { if (completed) completedCount++; else activeCount++; } field this.completedCount = 0;
on retracted todo($id, _, $completed) { if (completed) completedCount--; else activeCount--; } field this.activeCount = 0;
assert activeTodoCount(activeCount); on asserted todo($id, _, $c) { if (c) this.completedCount++; else this.activeCount++; }
assert completedTodoCount(completedCount); on retracted todo($id, _, $c) { if (c) this.completedCount--; else this.activeCount--; }
assert totalTodoCount(activeCount + completedCount); assert activeTodoCount(this.activeCount);
assert allCompleted() when (completedCount > 0 && activeCount === 0); assert completedTodoCount(this.completedCount);
assert totalTodoCount(this.activeCount + this.completedCount);
assert allCompleted() when (this.completedCount > 0 && this.activeCount === 0);
} }
} }

View File

@ -91,19 +91,27 @@ function seller() {
/// We give our actor two state variables: a dictionary recording our /// We give our actor two state variables: a dictionary recording our
/// inventory of books (mapping title to price), and a counter /// inventory of books (mapping title to price), and a counter
/// tracking the next order ID to be allocated. /// tracking the next order ID to be allocated.
///
/// We mark each property (entry) in the `books` table as a *field* so
/// that dependency-tracking on a per-book basis is enabled. As a result,
/// when a book is sold, any client still interested in its price will
/// learn that the book is no longer available.
///
/// We do not enable dependency-tracking for either the `books` table
/// itself or the `nextOrderId` field: nothing depends on tracking
/// changes in their values.
this.books = { this.books = {};
"The Wind in the Willows": 3.95, field this.books["The Wind in the Willows"] = 3.95;
"Catch 22": 2.22, field this.books["Catch 22"] = 2.22;
"Candide": 34.95 field this.books["Candide"] = 34.95;
};
this.nextOrderId = 10001483; this.nextOrderId = 10001483;
/// Looking up a price yields `false` if no such book is in our /// Looking up a price yields `false` if no such book is in our
/// inventory. /// inventory.
this.priceOf = function (title) { this.priceOf = function (title) {
return (title in this.books) && this.books[title]; return (title in this.books) && field this.books[title];
}; };
/// The seller responds to interest in bookQuotes by asserting a /// The seller responds to interest in bookQuotes by asserting a
@ -134,7 +142,7 @@ function seller() {
/// replying to the orderer. /// replying to the orderer.
var orderId = this.nextOrderId++; var orderId = this.nextOrderId++;
delete this.books[title]; delete field this.books[title];
actor { actor {
whileRelevantAssert( whileRelevantAssert(

View File

@ -8,20 +8,22 @@ var Mux = require('./mux.js');
var Patch = require('./patch.js'); var Patch = require('./patch.js');
var Trie = require('./trie.js'); var Trie = require('./trie.js');
var Util = require('./util.js'); var Util = require('./util.js');
var Dataflow = require('./dataflow.js');
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
function spawnActor(state, bootFn, optName) { function spawnActor(bootFn, optName) {
Dataspace.spawn(new Actor(state, bootFn, optName)); Dataspace.spawn(new Actor(bootFn, optName));
} }
function Actor(state, bootFn, optName) { function Actor(bootFn, optName) {
this.state = state; this.fields = {};
this.facets = Immutable.Set(); this.facets = Immutable.Set();
this.mux = new Mux.Mux(); this.mux = new Mux.Mux();
this.previousKnowledge = Trie.emptyTrie; this.previousKnowledge = Trie.emptyTrie;
this.knowledge = Trie.emptyTrie; this.knowledge = Trie.emptyTrie;
this.pendingActions = []; this.pendingActions = [];
this.dataflowGraph = new Dataflow.Graph();
if (typeof optName !== 'undefined') { if (typeof optName !== 'undefined') {
this.name = optName; this.name = optName;
@ -29,36 +31,82 @@ function Actor(state, bootFn, optName) {
this.boot = function() { this.boot = function() {
var self = this; var self = this;
withCurrentFacet(null, function () { withCurrentFacet(self, null, function () {
bootFn.call(self.state); bootFn.call(self.fields);
}); });
self.checkForTermination(); this.quiesce();
}; };
} }
Actor.current = null;
(function () {
var priorities = ['PRIORITY_QUERY_HIGH',
'PRIORITY_QUERY',
'PRIORITY_QUERY_HANDLER',
'PRIORITY_NORMAL'];
for (var i = 0; i < priorities.length; i++) {
Actor[priorities[i]] = i;
}
})();
Actor.prototype.nextPendingAction = function (probe) {
for (var i = 0; i < this.pendingActions.length; i++) {
var q = this.pendingActions[i];
if (q.length > 0) {
return probe ? true : q.shift();
}
}
return false;
};
Actor.prototype.handleEvent = function(e) { Actor.prototype.handleEvent = function(e) {
var actor = this;
if (e.type === 'stateChange') { if (e.type === 'stateChange') {
this.previousKnowledge = this.knowledge; this.previousKnowledge = this.knowledge;
this.knowledge = e.patch.updateInterests(this.knowledge); this.knowledge = e.patch.updateInterests(this.knowledge);
} }
if (this.pendingActions.length > 0) { if (this.nextPendingAction(true)) {
throw new Error('Syndicate: pendingActions must not be nonempty at start of handleEvent'); throw new Error('Syndicate: pendingActions must not be nonempty at start of handleEvent');
} }
this.facets.forEach(function (f) { this.facets.forEach(function (f) {
withCurrentFacet(f, function () { f.handleEvent(e); }); withCurrentFacet(actor, f, function () { f.handleEvent(e); });
}); });
while (this.pendingActions.length) { this.quiesce();
var entry = this.pendingActions.shift(); };
withCurrentFacet(entry.facet, entry.action);
Actor.prototype.quiesce = function() {
var actor = this;
while (true) {
var entry = this.nextPendingAction(false);
if (!entry) break;
withCurrentFacet(actor, entry.facet, entry.action);
this.dataflowGraph.repairDamage(function (subjectId) {
var facet = subjectId[0];
var endpoint = subjectId[1];
withCurrentFacet(actor, facet, function () {
// TODO: coalesce patches within a single actor
var aggregate = Patch.emptyPatch;
var patch = Patch.retract(__).andThen(endpoint.subscriptionFn.call(facet.fields));
var r = facet.actor.mux.updateStream(endpoint.eid, patch);
aggregate = aggregate.andThen(r.deltaAggregate);
Dataspace.stateChange(aggregate);
});
});
} }
this.facets.forEach(function (f) {
withCurrentFacet(f, function () { f.refresh(); });
});
this.checkForTermination(); this.checkForTermination();
}; };
Actor.prototype.pushAction = function (a) { Actor.prototype.pushAction = function (a, priorityOpt) {
this.pendingActions.push({facet: Facet.current, action: a}); var priority = typeof priorityOpt === 'undefined' ? Actor.PRIORITY_NORMAL : priorityOpt;
while (this.pendingActions.length < priority + 1) {
this.pendingActions.push([]);
}
this.pendingActions[priority].push({facet: Facet.current, action: a});
}; };
Actor.prototype.addFacet = function(facet) { Actor.prototype.addFacet = function(facet) {
@ -88,29 +136,34 @@ function Facet(actor) {
this.doneBlocks = Immutable.List(); this.doneBlocks = Immutable.List();
this.children = Immutable.Set(); this.children = Immutable.Set();
this.parent = Facet.current; this.parent = Facet.current;
this.fields = Dataflow.Graph.newScope((this.parent && this.parent.fields) || actor.fields);
this.terminated = false; this.terminated = false;
} }
Facet.current = null; Facet.current = null;
function withCurrentFacet(facet, f) { function withCurrentFacet(actor, facet, f) {
var previous = Facet.current; var previousActor = Actor.current;
var previousFacet = Facet.current;
Actor.current = actor;
Facet.current = facet; Facet.current = facet;
var result; var result;
try { try {
result = f(); result = f();
} catch (e) { } catch (e) {
Facet.current = previous; Actor.current = previousActor;
Facet.current = previousFacet;
throw e; throw e;
} }
Facet.current = previous; Actor.current = previousActor;
Facet.current = previousFacet;
return result; return result;
} }
Facet.prototype.handleEvent = function(e) { Facet.prototype.handleEvent = function(e) {
var facet = this; var facet = this;
facet.endpoints.forEach(function(endpoint) { facet.endpoints.forEach(function(endpoint) {
endpoint.handlerFn.call(facet.actor.state, e); endpoint.handlerFn.call(facet.fields, e);
}); });
}; };
@ -118,27 +171,47 @@ Facet.prototype.addAssertion = function(assertionFn) {
return this.addEndpoint(new Endpoint(assertionFn, function(e) {})); return this.addEndpoint(new Endpoint(assertionFn, function(e) {}));
}; };
Facet.prototype.addOnEventHandler = function(handler) { Facet.prototype.addOnEventHandler = function(handler, priorityOpt) {
var facet = this; var facet = this;
return this.addEndpoint(new Endpoint(function () { return Patch.emptyPatch; }, function (e) { return this.addEndpoint(new Endpoint(function () { return Patch.emptyPatch; }, function (e) {
facet.actor.pushAction(function () { handler(e); }); facet.actor.pushAction(function () { handler(e); }, priorityOpt);
})); }));
}; };
Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projectionFn, handlerFn) { Facet.prototype.addDataflow = function(subjectFunction) {
var facet = this;
return this.addEndpoint(new Endpoint(function () {
var subjectId = facet.actor.dataflowGraph.currentSubjectId;
facet.actor.pushAction(function () {
facet.actor.dataflowGraph.withSubject(subjectId, function () {
subjectFunction.call(facet.fields);
});
});
return Patch.emptyPatch;
}, function (e) {}));
};
Facet.prototype.onEvent = function(priority,
isTerminal,
eventType,
subscriptionFn,
projectionFn,
handlerFn)
{
var facet = this; var facet = this;
switch (eventType) { switch (eventType) {
case 'message': case 'message':
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) { return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
if (e.type === 'message') { if (e.type === 'message') {
var proj = projectionFn.call(facet.actor.state); var proj = projectionFn.call(facet.fields);
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel); var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
var match = Trie.matchPattern(e.message, spec); var match = Trie.matchPattern(e.message, spec);
// console.log(match); // console.log(match);
if (match) { if (match) {
if (isTerminal) { facet.terminate(); } if (isTerminal) { facet.terminate(); }
facet.actor.pushAction(function () { Util.kwApply(handlerFn, facet.actor.state, match); }); facet.actor.pushAction(function () { Util.kwApply(handlerFn, facet.fields, match); },
priority);
} }
} }
})); }));
@ -147,7 +220,7 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
case 'retracted': case 'retracted':
return this.addEndpoint(new Endpoint(subscriptionFn, function(e) { return this.addEndpoint(new Endpoint(subscriptionFn, function(e) {
if (e.type === 'stateChange') { if (e.type === 'stateChange') {
var proj = projectionFn.call(facet.actor.state); var proj = projectionFn.call(facet.fields);
var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel); var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel);
var objects = Trie.projectObjects(eventType === 'asserted' var objects = Trie.projectObjects(eventType === 'asserted'
? e.patch.added ? e.patch.added
@ -163,8 +236,8 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
facet.terminate(); facet.terminate();
} }
facet.actor.pushAction(function () { facet.actor.pushAction(function () {
Util.kwApply(handlerFn, facet.actor.state, o); Util.kwApply(handlerFn, facet.fields, o);
}); }, priority);
} }
}); });
} }
@ -172,17 +245,17 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec
})); }));
case 'risingEdge': case 'risingEdge':
var endpoint = new Endpoint(function() { return Patch.emptyPatch; }, var endpoint = new Endpoint(function() {
function(e) { var newValue = subscriptionFn.call(facet.fields);
var newValue = subscriptionFn.call(facet.actor.state); if (newValue && !this.currentValue) {
if (newValue && !this.currentValue) { if (isTerminal) { facet.terminate(); }
if (isTerminal) { facet.terminate(); } facet.actor.pushAction(function () {
facet.actor.pushAction(function () { handlerFn.call(facet.fields);
handlerFn.call(facet.actor.state); }, priority);
}); }
} this.currentValue = newValue;
this.currentValue = newValue; return Patch.emptyPatch;
}); }, function(e) {});
endpoint.currentValue = false; endpoint.currentValue = false;
return this.addEndpoint(endpoint); return this.addEndpoint(endpoint);
@ -206,11 +279,15 @@ Facet.prototype.interestWas = function(assertedOrRetracted, pat) {
}; };
Facet.prototype.addEndpoint = function(endpoint) { Facet.prototype.addEndpoint = function(endpoint) {
var patch = endpoint.subscriptionFn.call(this.actor.state); var facet = this;
var r = this.actor.mux.addStream(patch); var patch = facet.actor.dataflowGraph.withSubject([facet, endpoint], function () {
this.endpoints = this.endpoints.set(r.pid, endpoint); return endpoint.subscriptionFn.call(facet.fields);
});
var r = facet.actor.mux.addStream(patch);
endpoint.eid = r.pid;
facet.endpoints = facet.endpoints.set(endpoint.eid, endpoint);
Dataspace.stateChange(r.deltaAggregate); Dataspace.stateChange(r.deltaAggregate);
return this; // for chaining return facet; // for chaining
}; };
Facet.prototype.addInitBlock = function(thunk) { Facet.prototype.addInitBlock = function(thunk) {
@ -223,28 +300,17 @@ Facet.prototype.addDoneBlock = function(thunk) {
return this; return this;
}; };
Facet.prototype.refresh = function() {
var facet = this;
var aggregate = Patch.emptyPatch;
this.endpoints.forEach(function(endpoint, eid) {
var patch = Patch.retract(__).andThen(endpoint.subscriptionFn.call(facet.actor.state));
var r = facet.actor.mux.updateStream(eid, patch);
aggregate = aggregate.andThen(r.deltaAggregate);
});
Dataspace.stateChange(aggregate);
};
Facet.prototype.completeBuild = function() { Facet.prototype.completeBuild = function() {
var facet = this; var facet = this;
this.actor.addFacet(this); this.actor.addFacet(this);
if (this.parent) { if (this.parent) {
this.parent.children = this.parent.children.add(this); this.parent.children = this.parent.children.add(this);
} }
withCurrentFacet(facet, function () { withCurrentFacet(this.actor, facet, function () {
facet.initBlocks.forEach(function(b) { b.call(facet.actor.state); }); facet.initBlocks.forEach(function(b) { b.call(facet.fields); });
}); });
var initialEvent = _Dataspace.stateChange(new Patch.Patch(facet.actor.knowledge, Trie.emptyTrie)); var initialEvent = _Dataspace.stateChange(new Patch.Patch(facet.actor.knowledge, Trie.emptyTrie));
withCurrentFacet(facet, function () { facet.handleEvent(initialEvent); }); withCurrentFacet(this.actor, facet, function () { facet.handleEvent(initialEvent); });
}; };
Facet.prototype.terminate = function() { Facet.prototype.terminate = function() {
@ -267,13 +333,13 @@ Facet.prototype.terminate = function() {
this.actor.removeFacet(this); this.actor.removeFacet(this);
withCurrentFacet(facet, function () {
facet.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
});
this.children.forEach(function (child) { this.children.forEach(function (child) {
child.terminate(); child.terminate();
}); });
withCurrentFacet(this.actor, facet, function () {
facet.doneBlocks.forEach(function(b) { b.call(facet.fields); });
});
}; };
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -281,9 +347,37 @@ Facet.prototype.terminate = function() {
function Endpoint(subscriptionFn, handlerFn) { function Endpoint(subscriptionFn, handlerFn) {
this.subscriptionFn = subscriptionFn; this.subscriptionFn = subscriptionFn;
this.handlerFn = handlerFn; this.handlerFn = handlerFn;
this.eid = 'uninitialized_eid'; // initialized later
}
//---------------------------------------------------------------------------
function referenceField(obj, prop) {
if (!(prop in obj)) {
Actor.current.dataflowGraph.recordObservation(Immutable.List.of(obj, prop));
}
return obj[prop];
}
function declareField(obj, prop, init) {
if (prop in obj) {
obj[prop] = init;
} else {
Actor.current.dataflowGraph.defineObservableProperty(obj, prop, init, {
objectId: Immutable.List.of(obj, prop)
});
}
}
function deleteField(obj, prop) {
Actor.current.dataflowGraph.recordDamage(Immutable.List.of(obj, prop));
return delete obj[prop];
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
module.exports.spawnActor = spawnActor; module.exports.spawnActor = spawnActor;
module.exports.createFacet = createFacet; module.exports.createFacet = createFacet;
module.exports.referenceField = referenceField;
module.exports.declareField = declareField;
module.exports.deleteField = deleteField;

View File

@ -9,7 +9,6 @@ function Graph() {
this.edgesReverse = Immutable.Map(); this.edgesReverse = Immutable.Map();
this.damagedNodes = Immutable.Set(); this.damagedNodes = Immutable.Set();
this.currentSubjectId = null; this.currentSubjectId = null;
this.enforceSubjectPresence = true;
} }
Graph.prototype.withSubject = function (subjectId, f) { Graph.prototype.withSubject = function (subjectId, f) {
@ -30,8 +29,6 @@ Graph.prototype.recordObservation = function (objectId) {
if (this.currentSubjectId) { if (this.currentSubjectId) {
this.edgesForward = MapSet.add(this.edgesForward, objectId, this.currentSubjectId); this.edgesForward = MapSet.add(this.edgesForward, objectId, this.currentSubjectId);
this.edgesReverse = MapSet.add(this.edgesReverse, this.currentSubjectId, objectId); this.edgesReverse = MapSet.add(this.edgesReverse, this.currentSubjectId, objectId);
} else if (this.enforceSubjectPresence) {
throw new Error('Attempt to observe ' + objectId + ' with no currentSubjectId');
} }
}; };
@ -80,7 +77,7 @@ Graph.prototype.repairDamage = function (repairNode) {
Graph.prototype.defineObservableProperty = function (obj, prop, value, maybeOptions) { Graph.prototype.defineObservableProperty = function (obj, prop, value, maybeOptions) {
var graph = this; var graph = this;
var options = typeof maybeOptions === 'undefined' ? {} : maybeOptions; var options = typeof maybeOptions === 'undefined' ? {} : maybeOptions;
var objectId = '__' + (options.baseId || prop); var objectId = options.objectId || '__' + prop;
Object.defineProperty(obj, prop, { Object.defineProperty(obj, prop, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,

View File

@ -33,6 +33,7 @@ module.exports.Reflect = require("./reflect.js");
module.exports.WakeDetector = require("./wake-detector-driver.js"); module.exports.WakeDetector = require("./wake-detector-driver.js");
module.exports.Codec = require("./codec.js"); module.exports.Codec = require("./codec.js");
module.exports.Broker = require("./broker.js"); module.exports.Broker = require("./broker.js");
module.exports.Dataflow = require("./dataflow.js");
module.exports.Patch = require("./patch.js"); module.exports.Patch = require("./patch.js");
copyKeys(['emptyPatch', copyKeys(['emptyPatch',

View File

@ -54,6 +54,19 @@ function Structure(meta, fields) {
} }
} }
Structure.prototype.clone = function () {
return new Structure(this.meta, this.fields);
};
Structure.prototype.get = function (index) {
return this[index];
};
Structure.prototype.set = function (index, value) {
var s = this.clone();
s[index] = s.fields[index] = value;
};
function reviveStructs(j) { function reviveStructs(j) {
if (Array.isArray(j)) { if (Array.isArray(j)) {
return j.map(reviveStructs); return j.map(reviveStructs);

View File

@ -7,7 +7,7 @@ var Dataflow = require('../src/dataflow.js');
function Cell(graph, initialValue, name) { function Cell(graph, initialValue, name) {
this.objectId = graph.defineObservableProperty(this, 'value', initialValue, { this.objectId = graph.defineObservableProperty(this, 'value', initialValue, {
baseId: name, objectId: name,
noopGuard: function (a, b) { noopGuard: function (a, b) {
return a === b; return a === b;
} }
@ -50,7 +50,6 @@ describe('dataflow edges, damage and subjects', function () {
describe('DerivedCell', function () { describe('DerivedCell', function () {
describe('simple case', function () { describe('simple case', function () {
var g = new Dataflow.Graph(); var g = new Dataflow.Graph();
g.enforceSubjectPresence = false;
var c = DerivedCell(g, 'c', function () { return 123; }); var c = DerivedCell(g, 'c', function () { return 123; });
var d = DerivedCell(g, 'd', function () { return c.value * 2; }); var d = DerivedCell(g, 'd', function () { return c.value * 2; });
it('should be properly initialized', function () { it('should be properly initialized', function () {
@ -79,7 +78,6 @@ describe('DerivedCell', function () {
describe('a more complex case', function () { describe('a more complex case', function () {
var g = new Dataflow.Graph(); var g = new Dataflow.Graph();
g.enforceSubjectPresence = false;
function add(a, b) { return a + b; } function add(a, b) { return a + b; }
var xs = new Cell(g, Immutable.List.of(1, 2, 3, 4), 'xs'); var xs = new Cell(g, Immutable.List.of(1, 2, 3, 4), 'xs');
@ -132,7 +130,6 @@ describe('DerivedCell', function () {
describe('scopes', function () { describe('scopes', function () {
var g = new Dataflow.Graph(); var g = new Dataflow.Graph();
g.enforceSubjectPresence = false;
function buildScopes() { function buildScopes() {
var rootScope = {}; var rootScope = {};
@ -147,6 +144,9 @@ describe('scopes', function () {
expect(ss.root.p).to.be(123); expect(ss.root.p).to.be(123);
expect(ss.mid.p).to.be(123); expect(ss.mid.p).to.be(123);
expect(ss.outer.p).to.be(123); expect(ss.outer.p).to.be(123);
expect('p' in ss.root).to.be(true);
expect('p' in ss.mid).to.be(true);
expect('p' in ss.outer).to.be(true);
}); });
it('should make changes at root visible at leaves', function () { it('should make changes at root visible at leaves', function () {
@ -173,6 +173,9 @@ describe('scopes', function () {
expect(ss.outer.p).to.be(123); expect(ss.outer.p).to.be(123);
expect(ss.mid.p).to.be(undefined); expect(ss.mid.p).to.be(undefined);
expect(ss.root.p).to.be(undefined); expect(ss.root.p).to.be(undefined);
expect('p' in ss.root).to.be(false);
expect('p' in ss.mid).to.be(false);
expect('p' in ss.outer).to.be(true);
}); });
it('should hide middle definitions from roots but show to leaves', function () { it('should hide middle definitions from roots but show to leaves', function () {
@ -181,5 +184,8 @@ describe('scopes', function () {
expect(ss.outer.p).to.be(123); expect(ss.outer.p).to.be(123);
expect(ss.mid.p).to.be(123); expect(ss.mid.p).to.be(123);
expect(ss.root.p).to.be(undefined); expect(ss.root.p).to.be(undefined);
expect('p' in ss.root).to.be(false);
expect('p' in ss.mid).to.be(true);
expect('p' in ss.outer).to.be(true);
}); });
}); });