diff --git a/js/compiler/compiler.js b/js/compiler/compiler.js index e9edbb0..69b4b8a 100644 --- a/js/compiler/compiler.js +++ b/js/compiler/compiler.js @@ -42,28 +42,29 @@ var forEachChild = (function () { return forEachChild; })(); -function buildActor(nameExpOpt, block) { +function buildActor(nameExpOpt, block, withReact) { var nameExpStr; if (nameExpOpt.numChildren === 1) { nameExpStr = ', ' + nameExpOpt.asES5; } else { nameExpStr = ''; } - return 'Syndicate.Actor.spawnActor(function() ' + block.asES5 + nameExpStr + ');'; + return 'Syndicate.Actor.spawnActor(function() ' + + (withReact ? reactWrap(block.asES5) : block.asES5) + + nameExpStr + ');'; } -function buildFacet(facetBlock, transitionBlock) { - return '(function () { ' + (facetBlock ? facetBlock.facetVarDecls : '') + - '\nSyndicate.Actor.createFacet()' + - (facetBlock ? facetBlock.asES5 : '') + - (transitionBlock ? transitionBlock.asES5 : '') + - '.completeBuild(); }).call(this);'; +function reactWrap(blockCode) { + return '{ Syndicate.Actor.Facet.build((function () { ' + + blockCode + + ' }).bind(this)); }'; } function buildOnEvent(isTerminal, eventType, subscription, projection, bindings, body) { - return '\n.onEvent(Syndicate.Actor.PRIORITY_NORMAL, ' + isTerminal + ', ' + JSON.stringify(eventType) + ', ' + + return 'Syndicate.Actor.Facet.current.onEvent(Syndicate.Actor.PRIORITY_NORMAL, ' + isTerminal + ', ' + + JSON.stringify(eventType) + ', ' + subscription + ', ' + projection + - ', (function(' + bindings.join(', ') + ') ' + body + '))'; + ', (function(' + bindings.join(', ') + ') ' + body + '));'; } function buildCaseEvent(eventPattern, body) { @@ -85,8 +86,11 @@ function buildCaseEvent(eventPattern, body) { } var modifiedSourceActions = { - ActorStatement: function(_actor, _namedOpt, nameExpOpt, block) { - return buildActor(nameExpOpt, block); + ActorStatement_noReact: function(_actorStar, _namedOpt, nameExpOpt, block) { + return buildActor(nameExpOpt, block, false); + }, + ActorStatement_withReact: function(_actor, _namedOpt, nameExpOpt, block) { + return buildActor(nameExpOpt, block, true); }, DataspaceStatement_ground: function(_ground, _dataspace, maybeId, block) { @@ -101,14 +105,8 @@ var modifiedSourceActions = { return 'Syndicate.Dataspace.spawn(new Dataspace(function () ' + block.asES5 + '));'; }, - ActorFacetStatement_state: function(_state, facetBlock, _until, transitionBlock) { - return buildFacet(facetBlock, transitionBlock); - }, - ActorFacetStatement_until: function(_react, _until, transitionBlock) { - return buildFacet(null, transitionBlock); - }, - ActorFacetStatement_forever: function(_forever, facetBlock) { - return buildFacet(facetBlock, null); + ActorFacetStatement: function(_react, block) { + return '(function () ' + reactWrap(block.asES5) + ').call(this);'; }, AssertionTypeDeclarationStatement: function(_assertion, @@ -151,24 +149,17 @@ var modifiedSourceActions = { return 'Syndicate.Dataspace.send(' + expr.asES5 + ')' + sc.interval.contents; }, - FacetBlock: function(_leftParen, _varStmts, init, situations, done, _rightParen) { - return (init ? init.asES5 : '') + situations.asES5.join('') + (done ? done.asES5 : ''); + ActorEndpointStatement_start: function (_on, _start, block) { + return 'Syndicate.Actor.Facet.current.addInitBlock((function() ' + block.asES5 + '));'; }, - FacetStateTransitionBlock: function(_leftParen, transitions, _rightParen) { - return transitions.asES5.join(''); + ActorEndpointStatement_stop: function (_on, _stop, block) { + return 'Syndicate.Actor.Facet.current.addDoneBlock((function() ' + block.asES5 + '));'; }, - - FacetInitBlock: function(_init, block) { - return '\n.addInitBlock((function() ' + block.asES5 + '))'; + ActorEndpointStatement_assert: function(_assert, expr, whenClause, _sc) { + return 'Syndicate.Actor.Facet.current.addAssertion(' + + buildSubscription([expr], 'assert', 'pattern', whenClause, null) + ');'; }, - FacetDoneBlock: function(_done, block) { - return '\n.addDoneBlock((function() ' + block.asES5 + '))'; - }, - - FacetSituation_assert: function(_assert, expr, whenClause, _sc) { - return '\n.addAssertion(' + buildSubscription([expr], 'assert', 'pattern', whenClause, null) + ')'; - }, - FacetSituation_event: function(_on, eventPattern, block) { + ActorEndpointStatement_event: function(_on, eventPattern, block) { return buildOnEvent(false, eventPattern.eventType, eventPattern.subscription, @@ -176,63 +167,59 @@ var modifiedSourceActions = { eventPattern.bindings, block.asES5); }, - FacetSituation_onEvent: function (_on, _event, id, block) { - return '\n.addOnEventHandler((function(' + id.asES5 + ') ' + block.asES5 + '))'; + ActorEndpointStatement_onEvent: function (_on, _event, id, block) { + return 'Syndicate.Actor.Facet.current.addOnEventHandler((function(' + id.asES5 + ') ' + + block.asES5 + '));'; }, - FacetSituation_dataflow: function (_dataflow, block) { - return '\n.addDataflow((function () ' + block.asES5 + '))'; + ActorEndpointStatement_stopOnWithCont: function(_stop, _on, eventPattern, block) { + return buildCaseEvent(eventPattern, block.asES5); }, - FacetSituation_during: function(_during, pattern, facetBlock) { + ActorEndpointStatement_stopOnNoCont: function(_stop, _on, eventPattern, _sc) { + return buildCaseEvent(eventPattern, '{}'); + }, + ActorEndpointStatement_dataflow: function (_dataflow, block) { + return 'Syndicate.Actor.Facet.current.addDataflow((function () ' + block.asES5 + '));'; + }, + ActorEndpointStatement_during: function(_during, pattern, block) { var cachedAssertionVar = gensym('cachedAssertion'); return buildOnEvent(false, 'asserted', pattern.subscription, pattern.projection, pattern.bindings, - '{ ' + facetBlock.facetVarDecls + - '\nvar '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';'+ - '\nSyndicate.Actor.createFacet()' + - facetBlock.asES5 + - buildOnEvent(true, - 'retracted', - pattern.instantiatedSubscription(cachedAssertionVar), - pattern.instantiatedProjection(cachedAssertionVar), - [], - '{}') + - '.completeBuild(); }'); + '{\n' + + 'var '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';\n'+ + reactWrap(block.asES5 + '\n' + + buildOnEvent(true, + 'retracted', + pattern.instantiatedSubscription(cachedAssertionVar), + pattern.instantiatedProjection(cachedAssertionVar), + [], + '{}')) + '}'); }, - FacetSituation_duringActor: function(_during, pattern, _actor, _named, nameExpOpt, facetBlock) { + ActorEndpointStatement_duringActor: function(_during, pattern, _actor, _named, nameExpOpt, block) + { var cachedAssertionVar = gensym('cachedAssertion'); var actorBlock = { - asES5: '{ ' + facetBlock.facetVarDecls + - '\nSyndicate.Actor.createFacet()' + - facetBlock.asES5 + - buildOnEvent(true, - 'retracted', - pattern.instantiatedSubscription(cachedAssertionVar), - pattern.instantiatedProjection(cachedAssertionVar), - [], - '{}') + - '.completeBuild(); }' + asES5: reactWrap(block.asES5 + '\n' + + buildOnEvent(true, + 'retracted', + pattern.instantiatedSubscription(cachedAssertionVar), + pattern.instantiatedProjection(cachedAssertionVar), + [], + '{}')) }; return buildOnEvent(false, 'asserted', pattern.subscription, pattern.projection, pattern.bindings, - '{ var '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';'+ - '\n' + buildActor(nameExpOpt, actorBlock) + ' }'); + '{ var '+cachedAssertionVar+' = '+pattern.instantiatedAssertion+';\n'+ + buildActor(nameExpOpt, actorBlock, true) + ' }'); }, AssertWhenClause: function(_when, _lparen, expr, _rparen) { return expr.asES5; - }, - - FacetStateTransition_withContinuation: function(_case, eventPattern, block) { - return buildCaseEvent(eventPattern, block.asES5); - }, - FacetStateTransition_noContinuation: function(_case, eventPattern, _sc) { - return buildCaseEvent(eventPattern, '{}'); } }; @@ -256,11 +243,7 @@ semantics.addAttribute('memberPropExpr', { } }); -semantics.addAttribute('facetVarDecls', { - FacetBlock: function (_leftParen, varDecls, _init, _situations, _done, _rightParen) { - return varDecls.asES5.join(' '); - } -}); +/////////////////////////////////////////////////////////////////////////// semantics.addAttribute('asSyndicateStructureArguments', { FormalParameterList: function(formals) { diff --git a/js/compiler/demo-bankaccount.js b/js/compiler/demo-bankaccount.js index 67ab2ff..3e71079 100644 --- a/js/compiler/demo-bankaccount.js +++ b/js/compiler/demo-bankaccount.js @@ -8,39 +8,29 @@ message type deposit(amount); ground dataspace { actor { field this.balance = 0; - - react { - assert account(this.balance); - dataflow { - console.log("Balance inside account is", this.balance); - } - on message deposit($amount) { - this.balance += amount; - } + assert account(this.balance); + dataflow { + console.log("Balance inside account is", this.balance); + } + on message deposit($amount) { + this.balance += amount; } } actor { - react { - on asserted account($balance) { - console.log("Balance is now", balance); - } + on asserted account($balance) { + console.log("Balance is now", balance); } } actor { - react { - do { - console.log("Waiting for account."); - } - finally { - console.log("Account became ready."); - } - } until { - case asserted Syndicate.observe(deposit(_)) { - :: deposit(+100); - :: deposit(-30); - } + on start { + console.log("Waiting for account."); + } + stop on asserted Syndicate.observe(deposit(_)) { + console.log("Account became ready."); + :: deposit(+100); + :: deposit(-30); } } } diff --git a/js/compiler/demo-during-criterion-snapshotting.js b/js/compiler/demo-during-criterion-snapshotting.js index f071c60..39cae50 100644 --- a/js/compiler/demo-during-criterion-snapshotting.js +++ b/js/compiler/demo-during-criterion-snapshotting.js @@ -21,20 +21,19 @@ assertion type foo(x, y); ground dataspace { actor { field this.x = 123; - react { - assert foo(this.x, 999); - during foo(this.x, $v) { - do { - console.log('x=', this.x, 'v=', v); - if (this.x === 123) { - this.x = 124; - } - } - finally { - console.log('finally for x=', this.x, 'v=', v); + assert foo(this.x, 999); + + during foo(this.x, $v) { + on start { + console.log('x=', this.x, 'v=', v); + if (this.x === 123) { + this.x = 124; } } + on stop { + console.log('finally for x=', this.x, 'v=', v); + } } } } diff --git a/js/compiler/demo-filesystem.js b/js/compiler/demo-filesystem.js index 6dee48d..94103f0 100644 --- a/js/compiler/demo-filesystem.js +++ b/js/compiler/demo-filesystem.js @@ -24,53 +24,47 @@ ground dataspace { actor { this.files = {}; - react { - during Syndicate.observe(file($name, _)) { - do { - console.log("At least one reader exists for:", name); - } - assert file(name, field this.files[name]); - finally { - console.log("No remaining readers exist for:", name); - } + during Syndicate.observe(file($name, _)) { + on start { + console.log("At least one reader exists for:", name); } - on message saveFile($name, $newcontent) { - field this.files[name] = newcontent; - } - on message deleteFile($name) { - delete field this.files[name]; + assert file(name, field this.files[name]); + on stop { + console.log("No remaining readers exist for:", name); } } + on message saveFile($name, $newcontent) { + field this.files[name] = newcontent; + } + on message deleteFile($name) { + delete field this.files[name]; + } } /////////////////////////////////////////////////////////////////////////// // A simple demo client of the file system actor { - react { - on asserted file("hello.txt", $content) { - console.log("hello.txt has content", JSON.stringify(content)); - } - } until { - case asserted file("hello.txt", "quit demo") { - console.log("The hello.txt file contained 'quit demo', so we will quit"); - } + on asserted file("hello.txt", $content) { + console.log("hello.txt has content", JSON.stringify(content)); } - react until { - case asserted Syndicate.observe(saveFile(_, _)) { - :: saveFile("hello.txt", "a"); - :: deleteFile("hello.txt"); - :: saveFile("hello.txt", "c"); - :: saveFile("hello.txt", "quit demo"); - :: saveFile("hello.txt", "final contents"); - actor { - react until { - case asserted file("hello.txt", $content) { - console.log("second observer sees that hello.txt content is", - JSON.stringify(content)); - } - } + stop on asserted file("hello.txt", "quit demo") { + console.log("The hello.txt file contained 'quit demo', so we will quit"); + } + } + + actor { + stop on asserted Syndicate.observe(saveFile(_, _)) { + :: saveFile("hello.txt", "a"); + :: deleteFile("hello.txt"); + :: saveFile("hello.txt", "c"); + :: saveFile("hello.txt", "quit demo"); + :: saveFile("hello.txt", "final contents"); + actor { + stop on asserted file("hello.txt", $content) { + console.log("second observer sees that hello.txt content is", + JSON.stringify(content)); } } } diff --git a/js/compiler/demo-nested-during.js b/js/compiler/demo-nested-during.js index 819a244..55e4799 100644 --- a/js/compiler/demo-nested-during.js +++ b/js/compiler/demo-nested-during.js @@ -60,69 +60,58 @@ assertion type view(str); ground dataspace { actor { - react { - field this.title = "first"; - assert todo(this.title); - on message 3 { - this.title = "second"; - } + field this.title = "first"; + assert todo(this.title); + on message 3 { + this.title = "second"; } } actor { - react { - assert show(); + assert show(); + } + + actor { + field this.editing = false; + + during todo($title) { + on start { console.log('OUTER++', title); } + during show() { + on start { console.log('++', title); } + assert view((this.editing ? 'EDIT ' : 'VIEW ') + title); + on stop { console.log('--', title); } + } + on stop { console.log('OUTER--', title); } + } + + on message 1 { + this.editing = true; + :: 2; + } + + on message 2 { + :: 3; + this.editing = false; } } actor { - react { - field this.editing = false; - - during todo($title) { - do { console.log('OUTER++', title); } - during show() { - do { console.log('++', title); } - assert view((this.editing ? 'EDIT ' : 'VIEW ') + title); - finally { console.log('--', title); } - } - finally { console.log('OUTER--', title); } - } - - on message 1 { - this.editing = true; - :: 2; - } - - on message 2 { - :: 3; - this.editing = false; - } + on start { :: 0; } + stop on message 0 { + :: 1; } } actor { - react { - do { :: 0; } - } until { - case message 0 { - :: 1; - } - } - } - - actor { - react { - field this.count = 0; - on retracted view($x) { console.log('VIEW--', x); } - on asserted view($x) { - console.log('VIEW++', x); - if (x === 'VIEW second') { - this.count++; - if (this.count === 1) { - console.log("Kicking off second edit cycle"); - :: 1; - } + field this.count = 0; + on retracted view($x) { console.log('VIEW--', x); } + on asserted view($x) { + console.log('VIEW++', x); + if (x === 'VIEW second') { + this.count++; + if (this.count === 1) { + console.log("Kicking off second edit cycle"); + :: 1; } } } diff --git a/js/compiler/demo-proper-interest-tracking.js b/js/compiler/demo-proper-interest-tracking.js index 97855b8..03fb922 100644 --- a/js/compiler/demo-proper-interest-tracking.js +++ b/js/compiler/demo-proper-interest-tracking.js @@ -15,33 +15,28 @@ assertion type entry(key, val); ground dataspace { actor named 'listener' { - react { - assert ready('listener'); - on asserted entry($key, _) { - console.log('key asserted', key); - react { - on asserted entry(key, $value) { console.log('binding', key, '--->', value); } - on retracted entry(key, $value) { console.log('binding', key, '-/->', value); } - } until { - case retracted entry(key, _) { - console.log('key retracted', key); - } + assert ready('listener'); + on asserted entry($key, _) { + console.log('key asserted', key); + react { + on asserted entry(key, $value) { console.log('binding', key, '--->', value); } + on retracted entry(key, $value) { console.log('binding', key, '-/->', value); } + stop on retracted entry(key, _) { + console.log('key retracted', key); } } } } actor named 'other-listener' { - react { - assert ready('other-listener'); - during entry($key, _) { - do { console.log('(other-listener) key asserted', key); } - during entry(key, $value) { - do { console.log('(other-listener) binding', key, '--->', value); } - finally { console.log('(other-listener) binding', key, '-/->', value); } - } - finally { console.log('(other-listener) key retracted', key); } + assert ready('other-listener'); + during entry($key, _) { + on start { console.log('(other-listener) key asserted', key); } + during entry(key, $value) { + on start { console.log('(other-listener) binding', key, '--->', value); } + on stop { console.log('(other-listener) binding', key, '-/->', value); } } + on stop { console.log('(other-listener) key retracted', key); } } } @@ -49,36 +44,33 @@ ground dataspace { console.log('pause'); react { assert ready('pause'); - } until { - case asserted ready('pause') { + on asserted ready('pause') { return k(); } } } actor named 'driver' { - react until { - case asserted ready('listener') { - react until { - case asserted ready('other-listener') { - Dataspace.stateChange(Patch.assert(entry('a', 1))); - Dataspace.stateChange(Patch.assert(entry('a', 2))); - Dataspace.stateChange(Patch.assert(entry('b', 3))); - Dataspace.stateChange(Patch.assert(entry('c', 33))); - Dataspace.stateChange(Patch.assert(entry('a', 4))); - Dataspace.stateChange(Patch.assert(entry('a', 5))); + stop on asserted ready('listener') { + react { + stop on asserted ready('other-listener') { + Dataspace.stateChange(Patch.assert(entry('a', 1))); + Dataspace.stateChange(Patch.assert(entry('a', 2))); + Dataspace.stateChange(Patch.assert(entry('b', 3))); + Dataspace.stateChange(Patch.assert(entry('c', 33))); + Dataspace.stateChange(Patch.assert(entry('a', 4))); + Dataspace.stateChange(Patch.assert(entry('a', 5))); + pause(function () { + Dataspace.stateChange(Patch.retract(entry('a', 2))); + Dataspace.stateChange(Patch.retract(entry('c', 33))); + Dataspace.stateChange(Patch.assert(entry('a', 9))); pause(function () { - Dataspace.stateChange(Patch.retract(entry('a', 2))); - Dataspace.stateChange(Patch.retract(entry('c', 33))); - Dataspace.stateChange(Patch.assert(entry('a', 9))); + Dataspace.stateChange(Patch.retract(entry('a', __))); pause(function () { - Dataspace.stateChange(Patch.retract(entry('a', __))); - pause(function () { - console.log('done'); - }); + console.log('done'); }); }); - } + }); } } } diff --git a/js/compiler/syndicate.ohm b/js/compiler/syndicate.ohm index 66c5899..be3320d 100644 --- a/js/compiler/syndicate.ohm +++ b/js/compiler/syndicate.ohm @@ -10,6 +10,7 @@ Syndicate <: ES5 { += ActorStatement | DataspaceStatement | ActorFacetStatement + | ActorEndpointStatement | AssertionTypeDeclarationStatement | FieldDeclarationStatement | SendMessageStatement @@ -17,16 +18,29 @@ Syndicate <: ES5 { FunctionBodyBlock = "{" FunctionBody "}" // odd that this isn't in es5.ohm somewhere ActorStatement - = actor (named Expression)? FunctionBodyBlock + = actorStar (named Expression)? FunctionBodyBlock -- noReact + | actor (named Expression)? FunctionBodyBlock -- withReact DataspaceStatement = ground dataspace identifier? FunctionBodyBlock -- ground | dataspace FunctionBodyBlock -- normal ActorFacetStatement - = react FacetBlock until FacetStateTransitionBlock -- state - | react until FacetStateTransitionBlock -- until - | react FacetBlock -- forever + = react FunctionBodyBlock + + ActorEndpointStatement + = on start FunctionBodyBlock -- start + | on stop FunctionBodyBlock -- stop + | assert FacetPattern AssertWhenClause? #(sc) -- assert + | on FacetEventPattern FunctionBodyBlock -- event + | on event identifier FunctionBodyBlock -- onEvent + | stop on FacetTransitionEventPattern FunctionBodyBlock -- stopOnWithCont + | stop on FacetTransitionEventPattern #(sc) -- stopOnNoCont + | dataflow FunctionBodyBlock -- dataflow + | during FacetPattern FunctionBodyBlock -- during + | during FacetPattern actor (named Expression)? FunctionBodyBlock -- duringActor + + AssertWhenClause = when "(" Expression ")" AssertionTypeDeclarationStatement = (assertion | message) type identifier "(" FormalParameterList ")" ("=" stringLiteral)? #(sc) @@ -37,30 +51,6 @@ Syndicate <: ES5 { SendMessageStatement = "::" Expression #(sc) - //--------------------------------------------------------------------------- - // Ongoing event handlers. - - FacetBlock = "{" - (VariableStatement | FieldDeclarationStatement | FunctionDeclaration)* - FacetInitBlock? - FacetSituation* - FacetDoneBlock? - "}" - FacetStateTransitionBlock = "{" FacetStateTransition* "}" - - FacetInitBlock = do FunctionBodyBlock - FacetDoneBlock = finally FunctionBodyBlock - - FacetSituation - = assert FacetPattern AssertWhenClause? #(sc) -- assert - | on FacetEventPattern FunctionBodyBlock -- event - | on event identifier FunctionBodyBlock -- onEvent - | dataflow FunctionBodyBlock -- dataflow - | during FacetPattern FacetBlock -- during - | during FacetPattern actor (named Expression)? FacetBlock -- duringActor - - AssertWhenClause = when "(" Expression ")" - FacetEventPattern = message FacetPattern -- messageEvent | asserted FacetPattern -- assertedEvent @@ -70,10 +60,6 @@ Syndicate <: ES5 { = FacetEventPattern -- facetEvent | "(" Expression ")" -- risingEdge - FacetStateTransition - = case FacetTransitionEventPattern FunctionBodyBlock -- withContinuation - | case FacetTransitionEventPattern #(sc) -- noContinuation - FacetPattern = LeftHandSideExpression metalevel decimalIntegerLiteral -- withMetalevel | LeftHandSideExpression -- noMetalevel @@ -83,7 +69,8 @@ Syndicate <: ES5 { // we don't want to make them unavailable to programs as // identifiers. - actor = "actor" ~identifierPart + actorStar = "actor*" ~identifierPart + actor = "actor" ~("*" | identifierPart) assert = "assert" ~identifierPart asserted = "asserted" ~identifierPart assertion = "assertion" ~identifierPart @@ -99,7 +86,8 @@ Syndicate <: ES5 { on = "on" ~identifierPart react = "react" ~identifierPart retracted = "retracted" ~identifierPart + start = "start" ~identifierPart + stop = "stop" ~identifierPart type = "type" ~identifierPart - until = "until" ~identifierPart when = "when" ~identifierPart } diff --git a/js/examples/button/index.js b/js/examples/button/index.js index 4b433d8..5e90a6d 100644 --- a/js/examples/button/index.js +++ b/js/examples/button/index.js @@ -2,13 +2,11 @@ ground dataspace { Syndicate.UI.spawnUIDriver(); actor { - field this.counter = 0; var ui = new Syndicate.UI.Anchor(); - react { - assert ui.html('#button-label', '' + this.counter); - on message Syndicate.UI.globalEvent('#counter', 'click', _) { - this.counter++; - } + field this.counter = 0; + assert ui.html('#button-label', '' + this.counter); + on message Syndicate.UI.globalEvent('#counter', 'click', _) { + this.counter++; } } } diff --git a/js/examples/chat/index.js b/js/examples/chat/index.js index 887515a..8546d36 100644 --- a/js/examples/chat/index.js +++ b/js/examples/chat/index.js @@ -19,37 +19,36 @@ function spawnChatApp() { var ui = new Syndicate.UI.Anchor(); field this.nym; field this.status; - react { - on asserted inputValue('#nym', $v) { this.nym = v; } - on asserted inputValue('#status', $v) { this.status = v; } - on asserted brokerConnected($url) { outputState('connected to ' + url); } - on retracted brokerConnected($url) { outputState('disconnected from ' + url); } + on asserted inputValue('#nym', $v) { this.nym = v; } + on asserted inputValue('#status', $v) { this.status = v; } - during inputValue('#wsurl', $url) { - assert brokerConnection(url); + on asserted brokerConnected($url) { outputState('connected to ' + url); } + on retracted brokerConnected($url) { outputState('disconnected from ' + url); } - on message Syndicate.WakeDetector.wakeEvent() { - :: forceBrokerDisconnect(url); - } + during inputValue('#wsurl', $url) { + assert brokerConnection(url); - assert toBroker(url, present(this.nym, this.status)); - during fromBroker(url, present($who, $status)) { - assert ui.context(who, status) - .html('#nymlist', - Mustache.render($('#nym_template').html(), { who: who, status: status })); - } + on message Syndicate.WakeDetector.wakeEvent() { + :: forceBrokerDisconnect(url); + } - on message Syndicate.UI.globalEvent('#send_chat', 'click', _) { - var inp = $("#chat_input"); - var utterance = inp.val(); - inp.val(""); - if (utterance) :: toBroker(url, says(this.nym, utterance)); - } + assert toBroker(url, present(this.nym, this.status)); + during fromBroker(url, present($who, $status)) { + assert ui.context(who, status) + .html('#nymlist', + Mustache.render($('#nym_template').html(), { who: who, status: status })); + } - on message fromBroker(url, says($who, $what)) { - outputUtterance(who, what); - } + on message Syndicate.UI.globalEvent('#send_chat', 'click', _) { + var inp = $("#chat_input"); + var utterance = inp.val(); + inp.val(""); + if (utterance) :: toBroker(url, says(this.nym, utterance)); + } + + on message fromBroker(url, says($who, $what)) { + outputUtterance(who, what); } } } @@ -84,13 +83,11 @@ assertion type inputValue(selector, value); function spawnInputChangeMonitor() { actor { - react { - during Syndicate.observe(inputValue($selector, _)) actor { - field this.value = $(selector).val(); - assert inputValue(selector, this.value); - on message Syndicate.UI.globalEvent(selector, 'change', $e) { - this.value = e.target.value; - } + during Syndicate.observe(inputValue($selector, _)) actor { + field this.value = $(selector).val(); + assert inputValue(selector, this.value); + on message Syndicate.UI.globalEvent(selector, 'change', $e) { + this.value = e.target.value; } } } diff --git a/js/examples/iot/index.js b/js/examples/iot/index.js index ff466c6..1ba5d79 100644 --- a/js/examples/iot/index.js +++ b/js/examples/iot/index.js @@ -11,10 +11,8 @@ assertion type componentPresent(name); function spawnTV() { actor { var ui = new Syndicate.UI.Anchor(); - react { - during tvAlert($text) { - assert ui.context(text).html('#tv', Mustache.render($('#alert_template').html(), { text: text })); - } + during tvAlert($text) { + assert ui.context(text).html('#tv', Mustache.render($('#alert_template').html(), { text: text })); } } } @@ -24,11 +22,9 @@ function spawnTV() { function spawnRemoteControl() { actor { - react { - assert componentPresent('remote control'); - on message Syndicate.UI.globalEvent('#remote-control', 'click', _) { - :: remoteClick(); - } + assert componentPresent('remote control'); + on message Syndicate.UI.globalEvent('#remote-control', 'click', _) { + :: remoteClick(); } } } @@ -41,15 +37,13 @@ function spawnRemoteListener() { // state, if we've been clicked, turn it off. We don't do this // here, for simplicity. - react { - on asserted powerDraw($watts) { - this.stoveIsOn = watts > 0; - } + on asserted powerDraw($watts) { + this.stoveIsOn = watts > 0; + } - on message remoteClick() { - if (this.stoveIsOn) { - :: switchAction(false); - } + on message remoteClick() { + if (this.stoveIsOn) { + :: switchAction(false); } } } @@ -62,24 +56,23 @@ function spawnStoveSwitch() { actor { field this.powerOn = false; this.ui = new Syndicate.UI.Anchor(); - react { - assert componentPresent('stove switch'); - assert switchState(this.powerOn); - assert this.ui.html('#stove-switch', - Mustache.render($('#stove_element_template').html(), - { imgurl: ("img/stove-coil-element-" + - (this.powerOn ? "hot" : "cold") + ".jpg") })); + assert componentPresent('stove switch'); + assert switchState(this.powerOn); - on message Syndicate.UI.globalEvent('#stove-switch-on', 'click', _) { this.powerOn = true; } - on message Syndicate.UI.globalEvent('#stove-switch-off', 'click', _) { this.powerOn = false; } + assert this.ui.html('#stove-switch', + Mustache.render($('#stove_element_template').html(), + { imgurl: ("img/stove-coil-element-" + + (this.powerOn ? "hot" : "cold") + ".jpg") })); - on message switchAction($newState) { - this.powerOn = newState; - } - } until { - case message Syndicate.UI.globalEvent('#kill-stove-switch', 'click', _); + on message Syndicate.UI.globalEvent('#stove-switch-on', 'click', _) { this.powerOn = true; } + on message Syndicate.UI.globalEvent('#stove-switch-off', 'click', _) { this.powerOn = false; } + + on message switchAction($newState) { + this.powerOn = newState; } + + stop on message Syndicate.UI.globalEvent('#kill-stove-switch', 'click', _); } } @@ -87,19 +80,18 @@ function spawnPowerDrawMonitor() { actor { field this.watts = 0; this.ui = new Syndicate.UI.Anchor(); - react { - assert componentPresent('power draw monitor'); - assert powerDraw(this.watts); - assert this.ui.html('#power-draw-meter', - Mustache.render($('#power_draw_template').html(), { watts: this.watts })); + assert componentPresent('power draw monitor'); + assert powerDraw(this.watts); - on asserted switchState($on) { - this.watts = on ? 1500 : 0; - } - } until { - case message Syndicate.UI.globalEvent('#kill-power-draw-monitor', 'click', _); + assert this.ui.html('#power-draw-meter', + Mustache.render($('#power_draw_template').html(), { watts: this.watts })); + + on asserted switchState($on) { + this.watts = on ? 1500 : 0; } + + stop on message Syndicate.UI.globalEvent('#kill-power-draw-monitor', 'click', _); } } @@ -108,18 +100,16 @@ function spawnPowerDrawMonitor() { function spawnTimeoutListener() { actor { - react { - during powerDraw($watts) { - do { - if (watts > 0) { - var powerOnTime = Date.now(); - react { - on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 3000) { - react { assert tvAlert('Stove on too long?'); } - } - on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 10000) { - $("img.flames").show(); - } + during powerDraw($watts) { + on start { + if (watts > 0) { + var powerOnTime = Date.now(); + react { + on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 3000) { + react { assert tvAlert('Stove on too long?'); } + } + on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 10000) { + $("img.flames").show(); } } } @@ -130,17 +120,14 @@ function spawnTimeoutListener() { // function spawnTimeoutListener() { // actor { -// react { -// on asserted powerDraw($watts) { -// if (watts > 0) { -// var powerOnTime = Date.now(); -// react { -// on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 3000) { -// react { assert tvAlert('Stove on too long?'); } -// } -// } until { -// case asserted powerDraw(0); // alt: on retracted powerDraw(watts); +// on asserted powerDraw($watts) { +// if (watts > 0) { +// var powerOnTime = Date.now(); +// react { +// on asserted Syndicate.Timer.timeLaterThan(powerOnTime + 3000) { +// react { assert tvAlert('Stove on too long?'); } // } +// stop on asserted powerDraw(0); // alt: on retracted powerDraw(watts); // } // } // } @@ -151,16 +138,14 @@ function spawnTimeoutListener() { // actor { // this.mostRecentTime = 0; // this.powerOnTime = null; -// react { -// on asserted powerDraw($watts) { -// this.powerOnTime = (watts > 0) ? this.mostRecentTime : null; -// } -// on message Syndicate.Timer.periodicTick(200) { -// this.mostRecentTime = Date.now(); -// } -// assert tvAlert('Stove on too long?') -// when (this.powerOnTime !== null && this.mostRecentTime - this.powerOnTime > 3000); +// on asserted powerDraw($watts) { +// this.powerOnTime = (watts > 0) ? this.mostRecentTime : null; // } +// on message Syndicate.Timer.periodicTick(200) { +// this.mostRecentTime = Date.now(); +// } +// assert tvAlert('Stove on too long?') +// when (this.powerOnTime !== null && this.mostRecentTime - this.powerOnTime > 3000); // } // } @@ -169,13 +154,10 @@ function spawnTimeoutListener() { function spawnFailureMonitor() { actor { - react { - on retracted componentPresent($who) { - react { - assert tvAlert('FAILURE: ' + who); - } until { - case asserted componentPresent(who); - } + on retracted componentPresent($who) { + react { + assert tvAlert('FAILURE: ' + who); + stop on asserted componentPresent(who); } } } @@ -185,7 +167,7 @@ function spawnFailureMonitor() { // Chaos Monkey function spawnChaosMonkey() { - actor { + actor* { monitorComponent('power draw monitor', '#spawn-power-draw-monitor', '#kill-power-draw-monitor', @@ -201,11 +183,11 @@ function spawnChaosMonkey() { var jKillButtons = $(killButtonSelector); react { during componentPresent(name) { - do { + on start { jSpawnButtons.prop('disabled', true); jKillButtons.prop('disabled', false); } - finally { + on stop { jSpawnButtons.prop('disabled', false); jKillButtons.prop('disabled', true); } diff --git a/js/examples/location/index.js b/js/examples/location/index.js index e10fb10..ec9b60e 100644 --- a/js/examples/location/index.js +++ b/js/examples/location/index.js @@ -61,87 +61,85 @@ ground dataspace G { timeout: 15000 })); - react { - field this.currentLocation = null; - var selectedMarker = null; + field this.currentLocation = null; + var selectedMarker = null; - assert brokerConnection(this.wsurl); - assert toBroker(this.wsurl, this.currentLocation) when (this.currentLocation); + assert brokerConnection(this.wsurl); + assert toBroker(this.wsurl, this.currentLocation) when (this.currentLocation); - on message Syndicate.UI.globalEvent('#my_email', 'change', _) { - var v = email_element.value.trim(); - if (this.currentLocation) this.currentLocation = this.currentLocation.set(1, v); - localStorage.my_email = v; + on message Syndicate.UI.globalEvent('#my_email', 'change', _) { + var v = email_element.value.trim(); + if (this.currentLocation) this.currentLocation = this.currentLocation.set(1, v); + localStorage.my_email = v; + } + + on message Syndicate.UI.globalEvent('#group', 'change', _) { + localStorage.group = group_element.value.trim(); + this.wsurl = wsurl_base + group_element.value.trim(); + } + + on message Syndicate.UI.globalEvent('#findMarker', 'click', $e) { + :: findMarker(document.getElementById('markerList').value); + } + on message Syndicate.UI.globalEvent('#markerList', 'change', $e) { + :: findMarker(document.getElementById('markerList').value); + } + + on message ($loc = locationRecord(_, _, _, _, _)) { + this.currentLocation = loc; + } + + during fromBroker(this.wsurl, locationRecord($id, $email, _, _, _)) { + var ui = new Syndicate.UI.Anchor(); + var marker = new google.maps.Marker({ + map: map, + clickable: true, + icon: 'https://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=32&d=retro' + }); + var latestTimestamp = null; + var latestPosition = null; + function selectMarker() { + selectedMarker = marker; + updateInfoWindow(); + infoWindow.open(map, marker); } - - on message Syndicate.UI.globalEvent('#group', 'change', _) { - localStorage.group = group_element.value.trim(); - this.wsurl = wsurl_base + group_element.value.trim(); - } - - on message Syndicate.UI.globalEvent('#findMarker', 'click', $e) { - :: findMarker(document.getElementById('markerList').value); - } - on message Syndicate.UI.globalEvent('#markerList', 'change', $e) { - :: findMarker(document.getElementById('markerList').value); - } - - on message ($loc = locationRecord(_, _, _, _, _)) { - this.currentLocation = loc; - } - - during fromBroker(this.wsurl, locationRecord($id, $email, _, _, _)) { - var ui = new Syndicate.UI.Anchor(); - var marker = new google.maps.Marker({ - map: map, - clickable: true, - icon: 'https://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=32&d=retro' - }); - var latestTimestamp = null; - var latestPosition = null; - function selectMarker() { - selectedMarker = marker; - updateInfoWindow(); - infoWindow.open(map, marker); + function updateInfoWindow() { + if (selectedMarker === marker && latestPosition && latestTimestamp) { + geocoder.geocode({'location': latestPosition}, function (results, status) { + if (status === google.maps.GeocoderStatus.OK && results[0]) { + infoWindow.setContent(Mustache.render(document.getElementById('info').innerHTML, { + email: email, + timestamp: latestTimestamp ? latestTimestamp.toString() : '', + address: results[0].formatted_address + })); + } + }); } - function updateInfoWindow() { - if (selectedMarker === marker && latestPosition && latestTimestamp) { - geocoder.geocode({'location': latestPosition}, function (results, status) { - if (status === google.maps.GeocoderStatus.OK && results[0]) { - infoWindow.setContent(Mustache.render(document.getElementById('info').innerHTML, { - email: email, - timestamp: latestTimestamp ? latestTimestamp.toString() : '', - address: results[0].formatted_address - })); - } - }); - } - } - do { - marker.addListener('click', Syndicate.Dataspace.wrap(function () { - selectMarker(); - })); - } - assert ui.html('#markerList', - Mustache.render(document.getElementById('markerList-option').innerHTML, { - id: id, - email: email - })); - on message findMarker(id) { + } + on start { + marker.addListener('click', Syndicate.Dataspace.wrap(function () { selectMarker(); - if (latestPosition) map.panTo(latestPosition); - } - on asserted fromBroker(this.wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) { - latestTimestamp = new Date(timestamp); - latestPosition = {lat: lat, lng: lng}; - marker.setPosition(latestPosition); - marker.setTitle(email + ' ' + latestTimestamp.toTimeString()); - updateInfoWindow(); - } - finally { - marker.setMap(null); - if (selectedMarker === marker) selectedMarker = null; - } + })); + } + assert ui.html('#markerList', + Mustache.render(document.getElementById('markerList-option').innerHTML, { + id: id, + email: email + })); + on message findMarker(id) { + selectMarker(); + if (latestPosition) map.panTo(latestPosition); + } + on asserted fromBroker(this.wsurl, locationRecord(id, email, $timestamp, $lat, $lng)) { + latestTimestamp = new Date(timestamp); + latestPosition = {lat: lat, lng: lng}; + marker.setPosition(latestPosition); + marker.setTitle(email + ' ' + latestTimestamp.toTimeString()); + updateInfoWindow(); + } + on stop { + marker.setMap(null); + if (selectedMarker === marker) selectedMarker = null; } } } diff --git a/js/examples/motion/index.js b/js/examples/motion/index.js index 7291ba5..1b488d9 100644 --- a/js/examples/motion/index.js +++ b/js/examples/motion/index.js @@ -23,32 +23,31 @@ ground dataspace G { } var wsurl = 'wss://demo-broker.syndicate-lang.org:8443/'; - react { - assert brokerConnection(wsurl); - assert Syndicate.UI.uiAttribute('rect#my_color', 'fill', color); + assert brokerConnection(wsurl); - assert toBroker(wsurl, point(color, this.publishedX, this.publishedY)); - on message Syndicate.Timer.periodicTick(100) { - this.publishedX = x; - this.publishedY = y; - } + assert Syndicate.UI.uiAttribute('rect#my_color', 'fill', color); - on message Syndicate.UI.windowEvent('deviceorientation', $e) { - var scale = 0.5; - x = clamp(e.gamma * scale); - y = clamp((e.beta - 40) * scale); - } + assert toBroker(wsurl, point(color, this.publishedX, this.publishedY)); + on message Syndicate.Timer.periodicTick(100) { + this.publishedX = x; + this.publishedY = y; + } - during fromBroker(wsurl, point($oc, $ox, $oy)) { - assert ui.context(oc) - .html('#container', - Mustache.render(document.getElementById('circle-template').innerHTML, { - color: oc, - x: ox, - y: oy - })); - } + on message Syndicate.UI.windowEvent('deviceorientation', $e) { + var scale = 0.5; + x = clamp(e.gamma * scale); + y = clamp((e.beta - 40) * scale); + } + + during fromBroker(wsurl, point($oc, $ox, $oy)) { + assert ui.context(oc) + .html('#container', + Mustache.render(document.getElementById('circle-template').innerHTML, { + color: oc, + x: ox, + y: oy + })); } } } diff --git a/js/examples/smoketest-dsl/index.js b/js/examples/smoketest-dsl/index.js index 56f0f72..ad74a34 100644 --- a/js/examples/smoketest-dsl/index.js +++ b/js/examples/smoketest-dsl/index.js @@ -4,28 +4,23 @@ ground dataspace { console.log('starting ground boot'); actor { - react until { - case asserted Syndicate.observe(beep(_)) { - field this.counter = 0; - react { - do { - :: beep(this.counter++); - } - on message beep(_) { - :: beep(this.counter++); - } - } until { - case (this.counter > 10); + stop on asserted Syndicate.observe(beep(_)) { + field this.counter = 0; + react { + on start { + :: beep(this.counter++); } + on message beep(_) { + :: beep(this.counter++); + } + stop on (this.counter > 10); } } } actor { - react { - on message beep($counter) { - console.log("beep!", counter); - } + on message beep($counter) { + console.log("beep!", counter); } } } diff --git a/js/examples/svg/index.js b/js/examples/svg/index.js index cd6faa7..d50c738 100644 --- a/js/examples/svg/index.js +++ b/js/examples/svg/index.js @@ -8,19 +8,17 @@ ground dataspace G { field this.handX; field this.handY; - react { - assert ui.html('#clock', - ''+ - ''+ - ''+ - '') + assert ui.html('#clock', + ''+ + ''+ + ''+ + '') when (typeof this.angle === 'number'); - on message Syndicate.Timer.periodicTick(1000) { - this.angle = ((((Date.now() / 1000) % 60) / 60) - 0.25) * 2 * Math.PI; - this.handX = 50 + 40 * Math.cos(this.angle); - this.handY = 50 + 40 * Math.sin(this.angle); - } + on message Syndicate.Timer.periodicTick(1000) { + this.angle = ((((Date.now() / 1000) % 60) / 60) - 0.25) * 2 * Math.PI; + this.handX = 50 + 40 * Math.cos(this.angle); + this.handY = 50 + 40 * Math.sin(this.angle); } } } diff --git a/js/examples/table/index.js b/js/examples/table/index.js index d5cf9ed..124f7fe 100644 --- a/js/examples/table/index.js +++ b/js/examples/table/index.js @@ -3,9 +3,7 @@ message type setSortColumn(number); function newRow(id, firstName, lastName, address, age) { actor named ('model' + id) { - react { - assert person(id, firstName, lastName, address, age); - } + assert person(id, firstName, lastName, address, age); } } @@ -27,25 +25,21 @@ function spawnView() { return '' + text + ''; } - react { - on message setSortColumn($c) { this.orderColumn = c; } + on message setSortColumn($c) { this.orderColumn = c; } - during person($id, $firstName, $lastName, $address, $age) { - assert ui.context(id) - .html('table#the-table tbody', - '' + [id, firstName, lastName, address, age].map(cell).join('') + '', - [id, firstName, lastName, address, age][this.orderColumn]); - } + during person($id, $firstName, $lastName, $address, $age) { + assert ui.context(id) + .html('table#the-table tbody', + '' + [id, firstName, lastName, address, age].map(cell).join('') + '', + [id, firstName, lastName, address, age][this.orderColumn]); } } } function spawnController() { actor named 'controller' { - react { - on message Syndicate.UI.globalEvent('table#the-table th', 'click', $e) { - :: setSortColumn(JSON.parse(e.target.dataset.column)); - } + on message Syndicate.UI.globalEvent('table#the-table th', 'click', $e) { + :: setSortColumn(JSON.parse(e.target.dataset.column)); } } } diff --git a/js/examples/textfield-dsl/index.js b/js/examples/textfield-dsl/index.js index a080530..18d3d2b 100644 --- a/js/examples/textfield-dsl/index.js +++ b/js/examples/textfield-dsl/index.js @@ -26,52 +26,50 @@ function piece(text, pos, lo, hi, cls) { function spawnGui() { actor { - react { - field this.text = ''; - field this.pos = 0; - field this.highlightState = false; + 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"); - } + 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) { - switch (event.keyCode) { - case 37 /* left */: :: fieldCommand("cursorLeft"); break; - case 39 /* right */: :: fieldCommand("cursorRight"); break; - case 9 /* tab */: /* ignore */ break; - case 8 /* backspace */: - event.preventDefault(); // that this works here is a minor miracle - :: fieldCommand("backspace"); - break; - default: break; - } + on message globalEvent("#inputRow", "+keydown", $event) { + switch (event.keyCode) { + case 37 /* left */: :: fieldCommand("cursorLeft"); break; + case 39 /* right */: :: fieldCommand("cursorRight"); break; + case 9 /* tab */: /* ignore */ break; + case 8 /* backspace */: + event.preventDefault(); // that this works here is a minor miracle + :: fieldCommand("backspace"); + break; + default: break; } + } - on message globalEvent("#inputRow", "keypress", $event) { - var character = String.fromCharCode(event.charCode); - if (event.charCode && character) { - :: fieldCommand(["insert", character]); - } + on message globalEvent("#inputRow", "keypress", $event) { + var character = String.fromCharCode(event.charCode); + if (event.charCode && character) { + :: fieldCommand(["insert", character]); } + } - on asserted fieldContents($text, $pos) { - this.text = text; - this.pos = pos; - } + on asserted fieldContents($text, $pos) { + this.text = text; + this.pos = pos; + } - on asserted highlight($state) { - this.highlightState = state; - } + on asserted highlight($state) { + this.highlightState = state; } } } @@ -81,40 +79,38 @@ function spawnGui() { function spawnModel() { actor { - react { - field this.fieldValue = "initial"; - field this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */ + 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") { - this.cursorPos--; - if (this.cursorPos < 0) - this.cursorPos = 0; + on message fieldCommand("cursorLeft") { + this.cursorPos--; + if (this.cursorPos < 0) + this.cursorPos = 0; + } + + on message fieldCommand("cursorRight") { + this.cursorPos++; + if (this.cursorPos > this.fieldValue.length) + this.cursorPos = this.fieldValue.length; + } + + on message fieldCommand("backspace") { + if (this.cursorPos > 0) { + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos - 1) + + this.fieldValue.substring(this.cursorPos); + this.cursorPos--; } + } - on message fieldCommand("cursorRight") { - this.cursorPos++; - if (this.cursorPos > this.fieldValue.length) - this.cursorPos = this.fieldValue.length; - } - - on message fieldCommand("backspace") { - if (this.cursorPos > 0) { - this.fieldValue = - this.fieldValue.substring(0, this.cursorPos - 1) + - this.fieldValue.substring(this.cursorPos); - this.cursorPos--; - } - } - - on message fieldCommand(["insert", $newText]) { - this.fieldValue = - this.fieldValue.substring(0, this.cursorPos) + - newText + - this.fieldValue.substring(this.cursorPos); - this.cursorPos += newText.length; - } + on message fieldCommand(["insert", $newText]) { + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos) + + newText + + this.fieldValue.substring(this.cursorPos); + this.cursorPos += newText.length; } } } @@ -124,29 +120,27 @@ function spawnModel() { function spawnSearch() { actor { - react { - field this.searchtext = document.getElementById("searchBox").value; - field this.fieldValue = ""; - field this.highlight = false; + 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; - } + 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) { - this.searchtext = document.getElementById("searchBox").value; - } + on message globalEvent("#searchBox", "input", $event) { + this.searchtext = document.getElementById("searchBox").value; + } - on asserted fieldContents($text, _) { - this.fieldValue = text; - } + on asserted fieldContents($text, _) { + this.fieldValue = text; } } } diff --git a/js/examples/todo/index.js b/js/examples/todo/index.js index 69fbfa3..1b1e354 100644 --- a/js/examples/todo/index.js +++ b/js/examples/todo/index.js @@ -25,23 +25,21 @@ assertion type show(completed); function todoListItemModel(initialId, initialTitle, initialCompleted) { actor { - react { - field this.id = initialId; - field this.title = initialTitle; - field this.completed = initialCompleted; + field this.id = initialId; + field this.title = initialTitle; + field this.completed = initialCompleted; - assert todo(this.id, this.title, this.completed); + stop on message deleteTodo(this.id); - on message setCompleted(this.id, $v) { this.completed = v; } - on message setAllCompleted($v) { this.completed = v; } + assert todo(this.id, this.title, this.completed); - on message setTitle(this.id, $v) { this.title = v; } + on message setCompleted(this.id, $v) { this.completed = v; } + on message setAllCompleted($v) { this.completed = v; } - on message clearCompletedTodos() { - if (this.completed) :: deleteTodo(this.id); - } - } until { - case message deleteTodo(this.id); + on message setTitle(this.id, $v) { this.title = v; } + + on message clearCompletedTodos() { + if (this.completed) :: deleteTodo(this.id); } } } @@ -57,53 +55,51 @@ function getTemplate(id) { function todoListItemView(id) { actor { + stop on retracted todo(id, _, _); + this.ui = new Syndicate.UI.Anchor(); - react { - field this.editing = false; + field this.editing = false; - during todo(id, $title, $completed) { - during show(completed) { - assert this.ui.html('.todo-list', - Mustache.render(getTemplate(this.editing - ? 'todo-list-item-edit-template' - : 'todo-list-item-view-template'), - { - id: id, - title: title, - completed_class: completed ? "completed" : "", - checked: completed ? "checked" : "", - }), - id); - } + during todo(id, $title, $completed) { + during show(completed) { + assert this.ui.html('.todo-list', + Mustache.render(getTemplate(this.editing + ? 'todo-list-item-edit-template' + : 'todo-list-item-view-template'), + { + id: id, + title: title, + completed_class: completed ? "completed" : "", + checked: completed ? "checked" : "", + }), + id); } + } - on message this.ui.event('.toggle', 'change', $e) { - :: setCompleted(id, e.target.checked); - } + on message this.ui.event('.toggle', 'change', $e) { + :: setCompleted(id, e.target.checked); + } - on message this.ui.event('.destroy', 'click', _) { - :: deleteTodo(id); - } + on message this.ui.event('.destroy', 'click', _) { + :: deleteTodo(id); + } - on message this.ui.event('label', 'dblclick', _) { - this.editing = true; - } + on message this.ui.event('label', 'dblclick', _) { + this.editing = true; + } - on message this.ui.event('input.edit', 'keyup', $e) { - if (e.keyCode === ESCAPE_KEY_CODE || e.keyCode === ENTER_KEY_CODE) { - this.editing = false; - } - } - on message this.ui.event('input.edit', 'blur', $e) { + on message this.ui.event('input.edit', 'keyup', $e) { + if (e.keyCode === ESCAPE_KEY_CODE || e.keyCode === ENTER_KEY_CODE) { this.editing = false; } - on message this.ui.event('input.edit', 'change', $e) { - var newTitle = e.target.value.trim(); - :: (newTitle ? setTitle(id, newTitle) : deleteTodo(id)); - this.editing = false; - } - } until { - case retracted todo(id, _, _); + } + on message this.ui.event('input.edit', 'blur', $e) { + this.editing = false; + } + on message this.ui.event('input.edit', 'change', $e) { + var newTitle = e.target.value.trim(); + :: (newTitle ? setTitle(id, newTitle) : deleteTodo(id)); + this.editing = false; } } } @@ -114,80 +110,72 @@ ground dataspace G { Syndicate.UI.spawnUIDriver(); actor { - react { - on message Syndicate.UI.globalEvent('.new-todo', 'change', $e) { - var newTitle = e.target.value.trim(); - if (newTitle) :: createTodo(newTitle); - e.target.value = ""; - } + on message Syndicate.UI.globalEvent('.new-todo', 'change', $e) { + var newTitle = e.target.value.trim(); + if (newTitle) :: createTodo(newTitle); + e.target.value = ""; } } actor { this.ui = new Syndicate.UI.Anchor(); - react { - during activeTodoCount($count) { - assert this.ui.context('count').html('.todo-count strong', '' + count); - assert this.ui.context('plural').html('.todo-count span.s', 's') when (count !== 1); - } + during activeTodoCount($count) { + assert this.ui.context('count').html('.todo-count strong', '' + count); + assert this.ui.context('plural').html('.todo-count span.s', 's') when (count !== 1); + } - during totalTodoCount(0) { - assert Syndicate.UI.uiAttribute('section.main', 'class', 'hidden'); - assert Syndicate.UI.uiAttribute('footer.footer', 'class', 'hidden'); - } + during totalTodoCount(0) { + assert Syndicate.UI.uiAttribute('section.main', 'class', 'hidden'); + assert Syndicate.UI.uiAttribute('footer.footer', 'class', 'hidden'); + } - during completedTodoCount(0) { - assert Syndicate.UI.uiAttribute('button.clear-completed', 'class', 'hidden'); - } - on message Syndicate.UI.globalEvent('button.clear-completed', 'click', _) { - :: clearCompletedTodos(); - } + during completedTodoCount(0) { + assert Syndicate.UI.uiAttribute('button.clear-completed', 'class', 'hidden'); + } + on message Syndicate.UI.globalEvent('button.clear-completed', 'click', _) { + :: clearCompletedTodos(); + } - during allCompleted() { - do { :: Syndicate.UI.setProperty('.toggle-all', 'checked', true); } - finally { :: Syndicate.UI.setProperty('.toggle-all', 'checked', false); } - } - on message Syndicate.UI.globalEvent('.toggle-all', 'change', $e) { - :: setAllCompleted(e.target.checked); - } + during allCompleted() { + on start { :: Syndicate.UI.setProperty('.toggle-all', 'checked', true); } + on stop { :: Syndicate.UI.setProperty('.toggle-all', 'checked', false); } + } + on message Syndicate.UI.globalEvent('.toggle-all', 'change', $e) { + :: setAllCompleted(e.target.checked); + } - on asserted todo($id, _, _) { - todoListItemView(id); - } + on asserted todo($id, _, _) { + todoListItemView(id); } } actor { - react { - field this.completedCount = 0; - field this.activeCount = 0; - on asserted todo($id, _, $c) { if (c) this.completedCount++; else this.activeCount++; } - on retracted todo($id, _, $c) { if (c) this.completedCount--; else this.activeCount--; } - assert activeTodoCount(this.activeCount); - assert completedTodoCount(this.completedCount); - assert totalTodoCount(this.activeCount + this.completedCount); - assert allCompleted() when (this.completedCount > 0 && this.activeCount === 0); - } + field this.completedCount = 0; + field this.activeCount = 0; + on asserted todo($id, _, $c) { if (c) this.completedCount++; else this.activeCount++; } + on retracted todo($id, _, $c) { if (c) this.completedCount--; else this.activeCount--; } + assert activeTodoCount(this.activeCount); + assert completedTodoCount(this.completedCount); + assert totalTodoCount(this.activeCount + this.completedCount); + assert allCompleted() when (this.completedCount > 0 && this.activeCount === 0); } actor { - react { - during Syndicate.UI.locationHash($hash) { - assert Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]', - 'class', 'selected'); - } + during Syndicate.UI.locationHash($hash) { + assert Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]', + 'class', 'selected'); + } - during Syndicate.UI.locationHash('/') { - assert show(true); - assert show(false); - } - during Syndicate.UI.locationHash('/active') { - assert show(false); - } - during Syndicate.UI.locationHash('/completed') { - assert show(true); - } + during Syndicate.UI.locationHash('/') { + assert show(true); + assert show(false); + } + during Syndicate.UI.locationHash('/active') { + assert show(false); + } + during Syndicate.UI.locationHash('/completed') { + assert show(true); } } @@ -202,32 +190,32 @@ ground dataspace G { } } else { db = {nextId: 0, todos: {}}; - react until { - case asserted Syndicate.observe(createTodo(_)) { - :: createTodo('Buy milk'); - :: createTodo('Buy bread'); - :: createTodo('Finish PhD'); + on start { + react { + stop on asserted Syndicate.observe(createTodo(_)) { + :: createTodo('Buy milk'); + :: createTodo('Buy bread'); + :: createTodo('Finish PhD'); + } } } } - react { - on message createTodo($title) { - todoListItemModel(db.nextId++, title, false); - } + on message createTodo($title) { + todoListItemModel(db.nextId++, title, false); + } - during todo($id, _, _) { - during todo(id, $title, $completed) { - do { - db.todos[id] = {id: id, title: title, completed: completed}; - localStorage['todos-syndicate'] = JSON.stringify(db); - } - } - finally { - delete db.todos[id]; + during todo($id, _, _) { + during todo(id, $title, $completed) { + on start { + db.todos[id] = {id: id, title: title, completed: completed}; localStorage['todos-syndicate'] = JSON.stringify(db); } } + on stop { + delete db.todos[id]; + localStorage['todos-syndicate'] = JSON.stringify(db); + } } } } diff --git a/js/examples/two-buyer-protocol/index.js b/js/examples/two-buyer-protocol/index.js index 44a2a13..8505604 100644 --- a/js/examples/two-buyer-protocol/index.js +++ b/js/examples/two-buyer-protocol/index.js @@ -76,10 +76,9 @@ assertion type splitProposal(title, price, contribution, accepted); /// core library. /// function whileRelevantAssert(P) { - react { + actor { assert P; - } until { - case retracted Syndicate.observe(P); + stop on retracted Syndicate.observe(P); } } @@ -117,38 +116,32 @@ function seller() { /// The seller responds to interest in bookQuotes by asserting a /// responsive record, if one exists. - react { - during Syndicate.observe(bookQuote($title, _)) { - assert bookQuote(title, this.priceOf(title)); - } + during Syndicate.observe(bookQuote($title, _)) { + assert bookQuote(title, this.priceOf(title)); } /// It also responds to order requests. - react { - on asserted - Syndicate.observe(order($title, $offerPrice, _, _)) { + on asserted + Syndicate.observe(order($title, $offerPrice, _, _)) { /// We cannot sell a book we do not have, and we will not sell for /// less than our asking price. - var askingPrice = this.priceOf(title); - if ((askingPrice === false) || (offerPrice < askingPrice)) { - whileRelevantAssert( - order(title, offerPrice, false, false)); - } else { + var askingPrice = this.priceOf(title); + if ((askingPrice === false) || (offerPrice < askingPrice)) { + whileRelevantAssert( + order(title, offerPrice, false, false)); + } else { /// But if we can sell it, we do so by allocating an order ID and /// replying to the orderer. - var orderId = this.nextOrderId++; - delete field this.books[title]; + var orderId = this.nextOrderId++; + delete field this.books[title]; - actor { - whileRelevantAssert( - order(title, offerPrice, orderId, "March 9th")); - } - } + whileRelevantAssert( + order(title, offerPrice, orderId, "March 9th")); } } } @@ -157,7 +150,7 @@ function seller() { /// ### Implementation: SPLIT-PROPOSER and book-quote-requestor function buyerA() { - actor { + actor* { var self = this; /// Our actor remembers which books remain on its shopping list, and @@ -184,8 +177,8 @@ function buyerA() { /// First, retrieve a quote for the title, and analyze the result. - react until { - case asserted bookQuote(title, $price) { + react { + stop on asserted bookQuote(title, $price) { if (price === false) { console.log("A learns that "+title+" is out-of-stock."); buyBooks(); @@ -220,15 +213,15 @@ function buyerA() { /// Make our proposal, and wait for a response. - react until { - case asserted + react { + stop on asserted splitProposal(title, price, contribution, true) { console.log("A learns that the split-proposal for "+ title+" was accepted"); buyBooks(); } - case asserted + stop on asserted splitProposal(title, price, contribution, false) { console.log("A learns that the split-proposal for "+ title+" was rejected"); @@ -255,55 +248,50 @@ function buyerB() { /// It spends its time waiting for a SPLIT-PROPOSER to offer a /// `splitProposal`. - react { - on asserted - Syndicate.observe(splitProposal($title, - $price, - $theirContribution, - _)) - { - var myContribution = price - theirContribution; - console.log("B is being asked to contribute "+myContribution+ - " toward "+title+" at price "+price); + on asserted + Syndicate.observe(splitProposal($title, + $price, + $theirContribution, + _)) + { + var myContribution = price - theirContribution; + console.log("B is being asked to contribute "+myContribution+ + " toward "+title+" at price "+price); /// We may not be able to afford contributing this much. - if (myContribution > this.funds) { - console.log("B hasn't enough funds ("+this.funds+ - " remaining)"); - whileRelevantAssert( - splitProposal(title, price, theirContribution, false)); - } else { + if (myContribution > this.funds) { + console.log("B hasn't enough funds ("+this.funds+ + " remaining)"); + whileRelevantAssert( + splitProposal(title, price, theirContribution, false)); + } else { /// But if we *can* afford it, update our remaining funds and spawn a /// small actor to handle the actual purchase now that we have agreed /// on a split. - var remainingFunds = this.funds - myContribution; - console.log("B accepts the offer, leaving them with "+ - remainingFunds+" remaining funds"); - this.funds = remainingFunds; + var remainingFunds = this.funds - myContribution; + console.log("B accepts the offer, leaving them with "+ + remainingFunds+" remaining funds"); + this.funds = remainingFunds; - actor { - react { + actor { /// While waiting for order confirmation, take the opportunity to /// signal to our SPLIT-PROPOSER that we accepted their proposal. - assert splitProposal(title, - price, - theirContribution, - true); + assert splitProposal(title, + price, + theirContribution, + true); /// When order confirmation arrives, this purchase is completed. - } until { - case asserted order(title, price, $id, $date) { - console.log("The order for "+title+" has id "+id+ - ", and will be delivered on "+date); - } + stop on asserted order(title, price, $id, $date) { + console.log("The order for "+title+" has id "+id+ + ", and will be delivered on "+date); } - } } } } diff --git a/js/src/actor.js b/js/src/actor.js index 018f9cf..fb65965 100644 --- a/js/src/actor.js +++ b/js/src/actor.js @@ -122,10 +122,6 @@ Actor.prototype.checkForTermination = function() { //--------------------------------------------------------------------------- -function createFacet() { - return new Facet(Dataspace.activeBehavior()); -} - function Facet(actor) { this.actor = actor; this.endpoints = Immutable.Map(); @@ -141,6 +137,12 @@ function Facet(actor) { Facet.nextFid = 0; Facet.current = null; +Facet.build = function(f) { + var facet = new Facet(Dataspace.activeBehavior()); + withCurrentFacet(facet, f); + facet.completeBuild(); +}; + function withCurrentFacet(facet, f) { var previousFacet = Facet.current; Facet.current = facet; @@ -373,7 +375,7 @@ function deleteField(obj, prop) { //--------------------------------------------------------------------------- module.exports.spawnActor = spawnActor; -module.exports.createFacet = createFacet; +module.exports.Facet = Facet; module.exports.referenceField = referenceField; module.exports.declareField = declareField; module.exports.deleteField = deleteField;