Hook dataflow.js into Syndicate/js; add "during ... actor { ... }"
This commit is contained in:
parent
e2575c3ea1
commit
41693b897c
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*.expanded.js
|
|
@ -0,0 +1,7 @@
|
||||||
|
all: index.expanded.js
|
||||||
|
|
||||||
|
%.expanded.js: %.js
|
||||||
|
../../bin/syndicatec $< > $@ || (rm -f $@; false)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.expanded.js
|
|
@ -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();
|
|
|
@ -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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, _));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">'+
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
220
js/src/actor.js
220
js/src/actor.js
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue