This commit is contained in:
Tony Garnock-Jones 2016-07-11 12:06:07 -04:00
parent 53b243ad8a
commit 4ea5b79f3f
9 changed files with 1553 additions and 483 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -65,26 +65,26 @@ var G = new Syndicate.Ground(function () {
(function () { var currentLocation = null; var selectedMarker = null;
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(brokerConnection(wsurl), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return (currentLocation) ? Syndicate.Patch.assert(toBroker(wsurl,currentLocation), 0) : Syndicate.Patch.emptyPatch; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#my_email','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#my_email','change',_), metalevel: 0 }; }), (function() {
.addAssertion((function() { var _ = Syndicate.__; return (currentLocation) ? Syndicate.Patch.assert(toBroker(wsurl, currentLocation), 0) : Syndicate.Patch.emptyPatch; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#my_email', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#my_email', 'change', _), metalevel: 0 }; }), (function() {
var v = email_element.value.trim();
if (currentLocation) currentLocation[1] = v;
localStorage.my_email = v;
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#group','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#group','change',_), metalevel: 0 }; }), (function() {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#group', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#group', 'change', _), metalevel: 0 }; }), (function() {
localStorage.group = group_element.value.trim();
wsurl = wsurl_base + group_element.value.trim();
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#findMarker','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#findMarker','click',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#findMarker', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#findMarker', 'click', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#markerList','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#markerList','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#markerList', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#markerList', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
Syndicate.Dataspace.send(findMarker(document.getElementById('markerList').value));
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub((locationRecord(_,_,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: ((Syndicate._$("loc",locationRecord(_,_,_,_,_)))), metalevel: 0 }; }), (function(loc) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub((locationRecord(_, _, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: ((Syndicate._$("loc",locationRecord(_, _, _, _, _)))), metalevel: 0 }; }), (function(loc) {
currentLocation = loc;
}))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl,locationRecord(_,_,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl,locationRecord((Syndicate._$("id")),(Syndicate._$("email")),_,_,_)), metalevel: 0 }; }), (function(id, email) { var ui = new Syndicate.UI.Anchor(); var marker = new google.maps.Marker({
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl, locationRecord(_, _, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl, locationRecord((Syndicate._$("id")), (Syndicate._$("email")), _, _, _)), metalevel: 0 }; }), (function(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'
@ -105,19 +105,23 @@ Syndicate.Actor.createFacet()
});
}
}
var _cachedAssertion1467054033339_0 = (function() { var _ = Syndicate.__; return fromBroker(wsurl,locationRecord(id,email,_,_,_)); })();
var _cachedAssertion1468253137502_0 = (function() { var _ = Syndicate.__; return fromBroker(wsurl, locationRecord(id, email, _, _, _)); })();
Syndicate.Actor.createFacet()
.addInitBlock((function() {
marker.addListener('click', Syndicate.Dataspace.wrap(function () {
selectMarker();
}));
}))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#markerList',Mustache.render(document.getElementById('markerList-option').innerHTML,{id:id,email:email})), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#markerList',
Mustache.render(document.getElementById('markerList-option').innerHTML, {
id: id,
email: email
})), 0); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(findMarker(id), 0); }), (function() { var _ = Syndicate.__; return { assertion: findMarker(id), metalevel: 0 }; }), (function() {
selectMarker();
if (latestPosition) map.panTo(latestPosition);
}))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl,locationRecord(id,email,_,_,_)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl,locationRecord(id,email,(Syndicate._$("timestamp")),(Syndicate._$("lat")),(Syndicate._$("lng")))), metalevel: 0 }; }), (function(timestamp, lat, lng) {
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(fromBroker(wsurl, locationRecord(id, email, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: fromBroker(wsurl, locationRecord(id, email, (Syndicate._$("timestamp")), (Syndicate._$("lat")), (Syndicate._$("lng")))), metalevel: 0 }; }), (function(timestamp, lat, lng) {
latestTimestamp = new Date(timestamp);
latestPosition = {lat: lat, lng: lng};
marker.setPosition(latestPosition);
@ -128,6 +132,6 @@ Syndicate.Actor.createFacet()
marker.setMap(null);
if (selectedMarker === marker) selectedMarker = null;
}))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054033339_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054033339_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253137502_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253137502_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
});
}).startStepping();

View File

@ -27,10 +27,10 @@ function todoListItemModel(initialId, initialTitle, initialCompleted) {
(function () {
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(todo(this.id,this.title,this.completed), 0); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setCompleted(this.id,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setCompleted(this.id,(Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(todo(this.id, this.title, this.completed), 0); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setCompleted(this.id, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: setCompleted(this.id, (Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setAllCompleted(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setAllCompleted((Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.completed = v; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setTitle(this.id,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: setTitle(this.id,(Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.title = v; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(setTitle(this.id, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: setTitle(this.id, (Syndicate._$("v"))), metalevel: 0 }; }), (function(v) { this.title = v; }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(clearCompletedTodos(), 0); }), (function() { var _ = Syndicate.__; return { assertion: clearCompletedTodos(), metalevel: 0 }; }), (function() {
if (this.completed) Syndicate.Dataspace.send(deleteTodo(this.id));
}))
@ -53,38 +53,48 @@ function todoListItemView(id) {
this.editing = false;
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,(Syndicate._$("title")),(Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
var _cachedAssertion1467054034537_0 = (function() { var _ = Syndicate.__; return todo(id,title,completed); })();
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, (Syndicate._$("title")), (Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
var _cachedAssertion1468253138670_0 = (function() { var _ = Syndicate.__; return todo(id, title, completed); })();
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(show(completed), 0); }), (function() { var _ = Syndicate.__; return { assertion: show(completed), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_1 = (function() { var _ = Syndicate.__; return show(completed); })();
var _cachedAssertion1468253138670_1 = (function() { var _ = Syndicate.__; return show(completed); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.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), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_1, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_1, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_0, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.toggle','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.toggle','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.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), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_1, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_1, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_0, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.toggle', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.toggle', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
Syndicate.Dataspace.send(setCompleted(id, e.target.checked));
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.destroy','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.destroy','click',_), metalevel: 0 }; }), (function() {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('.destroy', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('.destroy', 'click', _), metalevel: 0 }; }), (function() {
Syndicate.Dataspace.send(deleteTodo(id));
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('label','dblclick',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('label','dblclick',_), metalevel: 0 }; }), (function() {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('label', 'dblclick', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('label', 'dblclick', _), metalevel: 0 }; }), (function() {
this.editing = true;
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','keyup',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','keyup',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'keyup', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'keyup', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
if (e.keyCode === ESCAPE_KEY_CODE || e.keyCode === ENTER_KEY_CODE) {
this.editing = false;
}
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','blur',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','blur',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'blur', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'blur', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
this.editing = false;
}))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(this.ui.event('input.edit', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: this.ui.event('input.edit', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
var newTitle = e.target.value.trim();
Syndicate.Dataspace.send((newTitle ? setTitle(id, newTitle) : deleteTodo(id)));
this.editing = false;
}))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,_,_), metalevel: 0 }; }), (function() {})).completeBuild(); })();
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, _, _), metalevel: 0 }; }), (function() {})).completeBuild(); })();
});
}
@ -96,7 +106,7 @@ var G = new Syndicate.Ground(function () {
Syndicate.Actor.spawnActor(new Object(), function() {
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.new-todo','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.new-todo','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.new-todo', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.new-todo', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
var newTitle = e.target.value.trim();
if (newTitle) Syndicate.Dataspace.send(createTodo(newTitle));
e.target.value = "";
@ -109,35 +119,35 @@ Syndicate.Actor.createFacet()
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(activeTodoCount(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: activeTodoCount((Syndicate._$("count"))), metalevel: 0 }; }), (function(count) {
var _cachedAssertion1467054034537_2 = (function() { var _ = Syndicate.__; return activeTodoCount(count); })();
var _cachedAssertion1468253138670_2 = (function() { var _ = Syndicate.__; return activeTodoCount(count); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.context('count').html('.todo-count strong',''+count), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return (count !== 1) ? Syndicate.Patch.assert(this.ui.context('plural').html('.todo-count span.s','s'), 0) : Syndicate.Patch.emptyPatch; }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_2, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_2, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(this.ui.context('count').html('.todo-count strong', '' + count), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return (count !== 1) ? Syndicate.Patch.assert(, 0) : Syndicate.Patch.emptyPatch; }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_2, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_2, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(totalTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: totalTodoCount(0), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_3 = (function() { var _ = Syndicate.__; return totalTodoCount(0); })();
var _cachedAssertion1468253138670_3 = (function() { var _ = Syndicate.__; return totalTodoCount(0); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('section.main','class','hidden'), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('footer.footer','class','hidden'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_3, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_3, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('section.main', 'class', 'hidden'), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('footer.footer', 'class', 'hidden'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_3, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_3, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(completedTodoCount(0), 0); }), (function() { var _ = Syndicate.__; return { assertion: completedTodoCount(0), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_4 = (function() { var _ = Syndicate.__; return completedTodoCount(0); })();
var _cachedAssertion1468253138670_4 = (function() { var _ = Syndicate.__; return completedTodoCount(0); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('button.clear-completed','class','hidden'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_4, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_4, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('button.clear-completed','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('button.clear-completed','click',_), metalevel: 0 }; }), (function() {
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('button.clear-completed', 'class', 'hidden'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_4, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_4, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('button.clear-completed', 'click', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('button.clear-completed', 'click', _), metalevel: 0 }; }), (function() {
Syndicate.Dataspace.send(clearCompletedTodos());
}))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(allCompleted(), 0); }), (function() { var _ = Syndicate.__; return { assertion: allCompleted(), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_5 = (function() { var _ = Syndicate.__; return allCompleted(); })();
var _cachedAssertion1468253138670_5 = (function() { var _ = Syndicate.__; return allCompleted(); })();
Syndicate.Actor.createFacet()
.addInitBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', true)); }))
.addDoneBlock((function() { Syndicate.Dataspace.send(Syndicate.UI.setProperty('.toggle-all', 'checked', false)); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_5, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_5, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.toggle-all','change',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.toggle-all','change',(Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_5, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_5, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('.toggle-all', 'change', _), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('.toggle-all', 'change', (Syndicate._$("e"))), metalevel: 0 }; }), (function(e) {
Syndicate.Dataspace.send(setAllCompleted(e.target.checked));
}))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,_), metalevel: 0 }; }), (function(id) {
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, _), metalevel: 0 }; }), (function(id) {
todoListItemView(id);
})).completeBuild(); })();
});
@ -147,11 +157,11 @@ Syndicate.Actor.createFacet()
var activeCount = 0;
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,(Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount++; else activeCount++; }))
.onEvent(false, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,(Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount--; else activeCount--; }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, (Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount++; else activeCount++; }))
.onEvent(false, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, (Syndicate._$("completed"))), metalevel: 0 }; }), (function(id, completed) { if (completed) completedCount--; else activeCount--; }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(activeTodoCount(activeCount), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(completedTodoCount(completedCount), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(totalTodoCount(activeCount+completedCount), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(totalTodoCount(activeCount + completedCount), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return (completedCount > 0 && activeCount === 0) ? Syndicate.Patch.assert(allCompleted(), 0) : Syndicate.Patch.emptyPatch; })).completeBuild(); })();
});
@ -159,26 +169,27 @@ Syndicate.Actor.createFacet()
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash((Syndicate._$("hash"))), metalevel: 0 }; }), (function(hash) {
var _cachedAssertion1467054034537_6 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash(hash); })();
var _cachedAssertion1468253138670_6 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash(hash); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]','class','selected'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_6, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_6, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(Syndicate.UI.uiAttribute('ul.filters > li > a[href="#'+hash+'"]',
'class', 'selected'), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_6, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_6, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/'), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_7 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/'); })();
var _cachedAssertion1468253138670_7 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/'); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_7, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_7, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_7, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_7, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/active'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/active'), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_8 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/active'); })();
var _cachedAssertion1468253138670_8 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/active'); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(false), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_8, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_8, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_8, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_8, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.locationHash('/completed'), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.locationHash('/completed'), metalevel: 0 }; }), (function() {
var _cachedAssertion1467054034537_9 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/completed'); })();
var _cachedAssertion1468253138670_9 = (function() { var _ = Syndicate.__; return Syndicate.UI.locationHash('/completed'); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(show(true), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_9, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_9, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_9, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_9, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
});
Syndicate.Actor.spawnActor(new Object(), function() {
@ -206,21 +217,21 @@ Syndicate.Actor.createFacet()
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(createTodo(_), 0); }), (function() { var _ = Syndicate.__; return { assertion: createTodo((Syndicate._$("title"))), metalevel: 0 }; }), (function(title) {
todoListItemModel(db.nextId++, title, false);
}))
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")),_,_), metalevel: 0 }; }), (function(id) {
var _cachedAssertion1467054034537_10 = (function() { var _ = Syndicate.__; return todo(id,_,_); })();
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(_, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo((Syndicate._$("id")), _, _), metalevel: 0 }; }), (function(id) {
var _cachedAssertion1468253138670_10 = (function() { var _ = Syndicate.__; return todo(id, _, _); })();
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id,_,_), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id,(Syndicate._$("title")),(Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
var _cachedAssertion1467054034537_11 = (function() { var _ = Syndicate.__; return todo(id,title,completed); })();
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(todo(id, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: todo(id, (Syndicate._$("title")), (Syndicate._$("completed"))), metalevel: 0 }; }), (function(title, completed) {
var _cachedAssertion1468253138670_11 = (function() { var _ = Syndicate.__; return todo(id, title, completed); })();
Syndicate.Actor.createFacet()
.addInitBlock((function() {
db.todos[id] = {id: id, title: title, completed: completed};
localStorage['todos-syndicate'] = JSON.stringify(db);
}))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_11, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_11, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_11, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_11, metalevel: 0 }; }), (function() {})).completeBuild(); }))
.addDoneBlock((function() {
delete db.todos[id];
localStorage['todos-syndicate'] = JSON.stringify(db);
}))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1467054034537_10, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1467054034537_10, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253138670_10, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253138670_10, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
});
}).startStepping();

View File

@ -0,0 +1,2 @@
index.expanded.js
index.md

View File

@ -0,0 +1,12 @@
TARGETS=index.expanded.js index.md
all: $(TARGETS)
%.expanded.js: %.js
../../bin/syndicatec $< > $@ || (rm -f $@; false)
%.md: %.js
sed -E -e 's:^: :g' -e 's:^ /// ?::g' -e 's:^ $$::g' < $< > $@
clean:
rm -f $(TARGETS)

View File

@ -0,0 +1,306 @@
"use strict";
var Syndicate = require('../../src/main.js');
/// -->
/// This is an extended two-buyer book-purchase protocol, based
/// loosely on an example given in:
///
/// > K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
/// > session types,” POPL 2008.
/// # The Scenario
///
/// A book-seller responds to requests for book prices when asked. A
/// pair of prospective buyers run through a shopping list. For each
/// book, the first buyer offers to split the cost of the book with
/// the second. If the second has enough money left, it accepts;
/// otherwise, it rejects the offer, and the first buyer tries a
/// different split. If the second buyer agrees to a split, it then
/// negotiates the purchase of the book with the book-seller.
/// # The Protocol
/// ## Role: SELLER
///
/// - when interest in `bookQuote($title, _)` appears,
/// asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
/// and otherwise an asking-price.
/// - when interest in `order($title, $offer-price, _, _)` appears,
/// asserts `order(title, offer-price, false, false)` for "no sale", otherwise
/// `order(title, offer-price, PositiveInteger, String)`, an accepted sale.
/// ## Role: BUYER
///
/// - observes `bookQuote(title, $price)` to learn prices.
/// - observes `order(title, offer-price, $id, $delivery-date)` to make orders.
/// ## Role: SPLIT-PROPOSER
///
/// - observes `splitProposal(title, asking-price, contribution, $accepted)`
/// to make a split-proposal and learn whether it was accepted or not.
/// ## Role: SPLIT-DISPOSER
///
/// - when interest in `splitProposal($title, $asking-price, $contribution, _)`
/// appears, asserts `splitProposal(title, askingPrice, contribution, true)`
/// to indicate they are willing to go through with the deal, in which case
/// they then perform the role of BUYER for title/asking-price, or asserts
/// `splitProposal(title, asking-price, contribution, false)` to indicate they
/// are unwilling to go through with the deal.
/// # A Sample Run
///
/// Run the program with `../../bin/syndicatec index.js | node` from a
/// checkout of the Syndicate repository.
///
/// A learns that the price of Catch 22 is 2.22
/// A makes an offer to split the price of Catch 22 contributing 1.11
/// B is being asked to contribute 1.11 toward Catch 22 at price 2.22
/// B accepts the offer, leaving them with 3.8899999999999997 remaining funds
/// A learns that the split-proposal for Catch 22 was accepted
/// A learns that Encyclopaedia Brittannica is out-of-stock.
/// The order for Catch 22 has id 10001483, and will be delivered on March 9th
/// A learns that the price of Candide is 34.95
/// A makes an offer to split the price of Candide contributing 17.475
/// B is being asked to contribute 17.475 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 26.212500000000002
/// B is being asked to contribute 8.7375 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 30.581250000000004
/// B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 32.765625
/// B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
/// B accepts the offer, leaving them with 1.7056249999999968 remaining funds
/// A learns that the split-proposal for Candide was accepted
/// A learns that the price of The Wind in the Willows is 3.95
/// A makes an offer to split the price of The Wind in the Willows contributing 1.975
/// The order for Candide has id 10001484, and will be delivered on March 9th
/// B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
/// B hasn't enough funds (1.7056249999999968 remaining)
/// A learns that the split-proposal for The Wind in the Willows was rejected
/// A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
/// B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
/// B accepts the offer, leaving them with 0.718124999999997 remaining funds
/// A learns that the split-proposal for The Wind in the Willows was accepted
/// A has bought everything they wanted!
/// The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
/// # The Code
/// ## Type Declarations
/// First, we declare *assertion types* for our protocol.
var bookQuote = Syndicate.Struct.makeConstructor("bookQuote", ["title","price"]);
var order = Syndicate.Struct.makeConstructor("order", ["title","price","id","deliveryDate"]);
var splitProposal = Syndicate.Struct.makeConstructor("splitProposal", ["title","price","contribution","accepted"]);
/// ## Utilities
/// This routine is under consideration for possible addition to the
/// core library.
///
function whileRelevantAssert(P) {
(function () {
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(P, 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(P), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(P), metalevel: 0 }; }), (function() {})).completeBuild(); })();
}
/// ## Implementation: SELLER
function seller() {
Syndicate.Actor.spawnActor(new Object(), function() {
/// We give our actor two state variables: a dictionary recording our
/// inventory of books (mapping title to price), and a counter
/// tracking the next order ID to be allocated.
this.books = {
"The Wind in the Willows": 3.95,
"Catch 22": 2.22,
"Candide": 34.95
};
this.nextOrderId = 10001483;
/// The seller responds to interest in bookQuotes by asserting a
/// responsive record, if one exists.
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(bookQuote(_, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(bookQuote((Syndicate._$("title")), _)), metalevel: 0 }; }), (function(title) {
var _cachedAssertion1468253139911_0 = (function() { var _ = Syndicate.__; return Syndicate.observe(bookQuote(title, _)); })();
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(bookQuote(title, title in this.books ? this.books[title] : false), 0); }))
.onEvent(true, "retracted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(_cachedAssertion1468253139911_0, 0); }), (function() { var _ = Syndicate.__; return { assertion: _cachedAssertion1468253139911_0, metalevel: 0 }; }), (function() {})).completeBuild(); })).completeBuild(); })();
/// It also responds to order requests.
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(order(_, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(order((Syndicate._$("title")), (Syndicate._$("offerPrice")), _, _)), metalevel: 0 }; }), (function(title, offerPrice) {
/// We cannot sell a book we do not have, and we will not sell for
/// less than our asking price.
var askingPrice = title in this.books ? this.books[title] : false;
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 this.books[title];
Syndicate.Actor.spawnActor(new Object(), function() {
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
});
}
})).completeBuild(); })();
});
}
/// ## Implementation: SPLIT-PROPOSER and book-quote-requestor
function buyerA() {
Syndicate.Actor.spawnActor(new Object(), function() {
var self = this;
/// Our actor remembers which books remain on its shopping list, and
/// tries to buy them one at a time, sharing costs with `buyerB`.
self.titles = ["Catch 22",
"Encyclopaedia Brittannica",
"Candide",
"The Wind in the Willows"];
/// JavaScript's callback-oriented blocking means that we express our
/// loop in almost a tail-recursive style, using helper functions
/// `buyBooks` and `trySplit`.
buyBooks();
function buyBooks() {
if (self.titles.length === 0) {
console.log("A has bought everything they wanted!");
return;
}
var title = self.titles.shift();
/// First, retrieve a quote for the title, and analyze the result.
(function () {
Syndicate.Actor.createFacet()
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(bookQuote(title, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: bookQuote(title, (Syndicate._$("price"))), metalevel: 0 }; }), (function(price) {
if (price === false) {
console.log("A learns that "+title+" is out-of-stock.");
buyBooks();
} else {
console.log("A learns that the price of "+title+" is "+price);
/// Next, repeatedly make split offers to a SPLIT-DISPOSER until
/// either one is accepted, or the contribution from the
/// SPLIT-DISPOSER becomes pointlessly small. We start the process by
/// offering to split the price of the book evenly.
trySplit(title, price, price / 2);
}
})).completeBuild(); })();
}
function trySplit(title, price, contribution) {
console.log("A makes an offer to split the price of "+title+
" contributing "+contribution);
/// If we are about to offer to split the price, but the other buyer
/// would contribute less than 10c, then it's not worth bothering; we
/// may as well buy it ourselves. Another version of the program could
/// perform the BUYER role here.
if (contribution > (price - 0.10)) {
console.log("A gives up on "+title+".");
buyBooks();
} else {
/// Make our proposal, and wait for a response.
(function () {
Syndicate.Actor.createFacet()
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(splitProposal(title, price, contribution, true), 0); }), (function() { var _ = Syndicate.__; return { assertion: splitProposal(title, price, contribution, true), metalevel: 0 }; }), (function() {
console.log("A learns that the split-proposal for "+title+" was accepted");
buyBooks();
}))
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(splitProposal(title, price, contribution, false), 0); }), (function() { var _ = Syndicate.__; return { assertion: splitProposal(title, price, contribution, false), metalevel: 0 }; }), (function() {
console.log("A learns that the split-proposal for "+title+" was rejected");
trySplit(title, price, contribution + ((price - contribution) / 2));
})).completeBuild(); })();
}
}
});
}
/// ## Implementation: SPLIT-DISPOSER and BUYER
function buyerB() {
Syndicate.Actor.spawnActor(new Object(), function() {
/// This actor maintains a record of the amount of money it has left
/// to spend.
this.funds = 5.00;
/// It spends its time waiting for a SPLIT-PROPOSER to offer a
/// `splitProposal`.
(function () {
Syndicate.Actor.createFacet()
.onEvent(false, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.observe(splitProposal(_, _, _, _)), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.observe(splitProposal((Syndicate._$("title")), (Syndicate._$("price")), (Syndicate._$("theirContribution")), _)), metalevel: 0 }; }), (function(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 {
/// 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;
Syndicate.Actor.spawnActor(new Object(), function() {
(function () {
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(splitProposal(title, price, theirContribution, true), 0); }))
.onEvent(true, "asserted", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(order(title, price, _, _), 0); }), (function() { var _ = Syndicate.__; return { assertion: order(title, price, (Syndicate._$("id")), (Syndicate._$("date"))), metalevel: 0 }; }), (function(id, date) {
console.log("The order for "+title+" has id "+id+
", and will be delivered on "+date);
})).completeBuild(); })();
});
}
})).completeBuild(); })();
});
}
/// ## Starting Configuration
new Syndicate.Ground(function () {
seller();
buyerA();
buyerB();
}).startStepping();

View File

@ -0,0 +1,318 @@
/// ---
/// title: Two-Party Buyer Protocol
/// ---
/// <!--
var Syndicate = require('../../src/main.js');
/// -->
/// This is an extended two-buyer book-purchase protocol, based
/// loosely on an example given in:
///
/// > K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
/// > session types,” POPL 2008.
/// # The Scenario
///
/// A book-seller responds to requests for book prices when asked. A
/// pair of prospective buyers run through a shopping list. For each
/// book, the first buyer offers to split the cost of the book with
/// the second. If the second has enough money left, it accepts;
/// otherwise, it rejects the offer, and the first buyer tries a
/// different split. If the second buyer agrees to a split, it then
/// negotiates the purchase of the book with the book-seller.
/// # The Protocol
/// ## Role: SELLER
///
/// - when interest in `bookQuote($title, _)` appears,
/// asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
/// and otherwise an asking-price.
/// - when interest in `order($title, $offer-price, _, _)` appears,
/// asserts `order(title, offer-price, false, false)` for "no sale", otherwise
/// `order(title, offer-price, PositiveInteger, String)`, an accepted sale.
/// ## Role: BUYER
///
/// - observes `bookQuote(title, $price)` to learn prices.
/// - observes `order(title, offer-price, $id, $delivery-date)` to make orders.
/// ## Role: SPLIT-PROPOSER
///
/// - observes `splitProposal(title, asking-price, contribution, $accepted)`
/// to make a split-proposal and learn whether it was accepted or not.
/// ## Role: SPLIT-DISPOSER
///
/// - when interest in `splitProposal($title, $asking-price, $contribution, _)`
/// appears, asserts `splitProposal(title, askingPrice, contribution, true)`
/// to indicate they are willing to go through with the deal, in which case
/// they then perform the role of BUYER for title/asking-price, or asserts
/// `splitProposal(title, asking-price, contribution, false)` to indicate they
/// are unwilling to go through with the deal.
/// # A Sample Run
///
/// Run the program with `../../bin/syndicatec index.js | node` from a
/// checkout of the Syndicate repository.
///
/// A learns that the price of Catch 22 is 2.22
/// A makes an offer to split the price of Catch 22 contributing 1.11
/// B is being asked to contribute 1.11 toward Catch 22 at price 2.22
/// B accepts the offer, leaving them with 3.8899999999999997 remaining funds
/// A learns that the split-proposal for Catch 22 was accepted
/// A learns that Encyclopaedia Brittannica is out-of-stock.
/// The order for Catch 22 has id 10001483, and will be delivered on March 9th
/// A learns that the price of Candide is 34.95
/// A makes an offer to split the price of Candide contributing 17.475
/// B is being asked to contribute 17.475 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 26.212500000000002
/// B is being asked to contribute 8.7375 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 30.581250000000004
/// B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
/// B hasn't enough funds (3.8899999999999997 remaining)
/// A learns that the split-proposal for Candide was rejected
/// A makes an offer to split the price of Candide contributing 32.765625
/// B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
/// B accepts the offer, leaving them with 1.7056249999999968 remaining funds
/// A learns that the split-proposal for Candide was accepted
/// A learns that the price of The Wind in the Willows is 3.95
/// A makes an offer to split the price of The Wind in the Willows contributing 1.975
/// The order for Candide has id 10001484, and will be delivered on March 9th
/// B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
/// B hasn't enough funds (1.7056249999999968 remaining)
/// A learns that the split-proposal for The Wind in the Willows was rejected
/// A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
/// B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
/// B accepts the offer, leaving them with 0.718124999999997 remaining funds
/// A learns that the split-proposal for The Wind in the Willows was accepted
/// A has bought everything they wanted!
/// The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
/// # The Code
/// ## Type Declarations
/// First, we declare *assertion types* for our protocol.
assertion type bookQuote(title, price);
assertion type order(title, price, id, deliveryDate);
assertion type splitProposal(title, price, contribution, accepted);
/// ## Utilities
/// This routine is under consideration for possible addition to the
/// core library.
///
function whileRelevantAssert(P) {
react {
assert P;
} until {
case retracted Syndicate.observe(P);
}
}
/// ## Implementation: SELLER
function seller() {
actor {
/// We give our actor two state variables: a dictionary recording our
/// inventory of books (mapping title to price), and a counter
/// tracking the next order ID to be allocated.
this.books = {
"The Wind in the Willows": 3.95,
"Catch 22": 2.22,
"Candide": 34.95
};
this.nextOrderId = 10001483;
/// The seller responds to interest in bookQuotes by asserting a
/// responsive record, if one exists.
react {
during Syndicate.observe(bookQuote($title, _)) {
assert bookQuote(title, title in this.books ? this.books[title] : false);
}
}
/// It also responds to order requests.
react {
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 = title in this.books ? this.books[title] : false;
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 this.books[title];
actor {
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
}
}
}
}
}
}
/// ## Implementation: SPLIT-PROPOSER and book-quote-requestor
function buyerA() {
actor {
var self = this;
/// Our actor remembers which books remain on its shopping list, and
/// tries to buy them one at a time, sharing costs with `buyerB`.
self.titles = ["Catch 22",
"Encyclopaedia Brittannica",
"Candide",
"The Wind in the Willows"];
/// JavaScript's callback-oriented blocking means that we express our
/// loop in almost a tail-recursive style, using helper functions
/// `buyBooks` and `trySplit`.
buyBooks();
function buyBooks() {
if (self.titles.length === 0) {
console.log("A has bought everything they wanted!");
return;
}
var title = self.titles.shift();
/// First, retrieve a quote for the title, and analyze the result.
react until {
case asserted bookQuote(title, $price) {
if (price === false) {
console.log("A learns that "+title+" is out-of-stock.");
buyBooks();
} else {
console.log("A learns that the price of "+title+" is "+price);
/// Next, repeatedly make split offers to a SPLIT-DISPOSER until
/// either one is accepted, or the contribution from the
/// SPLIT-DISPOSER becomes pointlessly small. We start the process by
/// offering to split the price of the book evenly.
trySplit(title, price, price / 2);
}
}
}
}
function trySplit(title, price, contribution) {
console.log("A makes an offer to split the price of "+title+
" contributing "+contribution);
/// If we are about to offer to split the price, but the other buyer
/// would contribute less than 10c, then it's not worth bothering; we
/// may as well buy it ourselves. Another version of the program could
/// perform the BUYER role here.
if (contribution > (price - 0.10)) {
console.log("A gives up on "+title+".");
buyBooks();
} else {
/// Make our proposal, and wait for a response.
react until {
case asserted splitProposal(title, price, contribution, true) {
console.log("A learns that the split-proposal for "+title+" was accepted");
buyBooks();
}
case asserted splitProposal(title, price, contribution, false) {
console.log("A learns that the split-proposal for "+title+" was rejected");
trySplit(title, price, contribution + ((price - contribution) / 2));
}
}
}
}
}
}
/// ## Implementation: SPLIT-DISPOSER and BUYER
function buyerB() {
actor {
/// This actor maintains a record of the amount of money it has left
/// to spend.
this.funds = 5.00;
/// 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);
/// 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 {
/// 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;
actor {
react {
/// 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);
/// 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);
}
}
}
}
}
}
}
}
/// ## Starting Configuration
ground dataspace {
seller();
buyerA();
buyerB();
}

View File

@ -0,0 +1,318 @@
---
title: Two-Party Buyer Protocol
---
<!--
var Syndicate = require('../../src/main.js');
-->
This is an extended two-buyer book-purchase protocol, based
loosely on an example given in:
> K. Honda, N. Yoshida, and M. Carbone, “Multiparty asynchronous
> session types,” POPL 2008.
# The Scenario
A book-seller responds to requests for book prices when asked. A
pair of prospective buyers run through a shopping list. For each
book, the first buyer offers to split the cost of the book with
the second. If the second has enough money left, it accepts;
otherwise, it rejects the offer, and the first buyer tries a
different split. If the second buyer agrees to a split, it then
negotiates the purchase of the book with the book-seller.
# The Protocol
## Role: SELLER
- when interest in `bookQuote($title, _)` appears,
asserts `bookQuote(title, Maybe Float)`, `false` meaning not available,
and otherwise an asking-price.
- when interest in `order($title, $offer-price, _, _)` appears,
asserts `order(title, offer-price, false, false)` for "no sale", otherwise
`order(title, offer-price, PositiveInteger, String)`, an accepted sale.
## Role: BUYER
- observes `bookQuote(title, $price)` to learn prices.
- observes `order(title, offer-price, $id, $delivery-date)` to make orders.
## Role: SPLIT-PROPOSER
- observes `splitProposal(title, asking-price, contribution, $accepted)`
to make a split-proposal and learn whether it was accepted or not.
## Role: SPLIT-DISPOSER
- when interest in `splitProposal($title, $asking-price, $contribution, _)`
appears, asserts `splitProposal(title, askingPrice, contribution, true)`
to indicate they are willing to go through with the deal, in which case
they then perform the role of BUYER for title/asking-price, or asserts
`splitProposal(title, asking-price, contribution, false)` to indicate they
are unwilling to go through with the deal.
# A Sample Run
Run the program with `../../bin/syndicatec index.js | node` from a
checkout of the Syndicate repository.
A learns that the price of Catch 22 is 2.22
A makes an offer to split the price of Catch 22 contributing 1.11
B is being asked to contribute 1.11 toward Catch 22 at price 2.22
B accepts the offer, leaving them with 3.8899999999999997 remaining funds
A learns that the split-proposal for Catch 22 was accepted
A learns that Encyclopaedia Brittannica is out-of-stock.
The order for Catch 22 has id 10001483, and will be delivered on March 9th
A learns that the price of Candide is 34.95
A makes an offer to split the price of Candide contributing 17.475
B is being asked to contribute 17.475 toward Candide at price 34.95
B hasn't enough funds (3.8899999999999997 remaining)
A learns that the split-proposal for Candide was rejected
A makes an offer to split the price of Candide contributing 26.212500000000002
B is being asked to contribute 8.7375 toward Candide at price 34.95
B hasn't enough funds (3.8899999999999997 remaining)
A learns that the split-proposal for Candide was rejected
A makes an offer to split the price of Candide contributing 30.581250000000004
B is being asked to contribute 4.368749999999999 toward Candide at price 34.95
B hasn't enough funds (3.8899999999999997 remaining)
A learns that the split-proposal for Candide was rejected
A makes an offer to split the price of Candide contributing 32.765625
B is being asked to contribute 2.184375000000003 toward Candide at price 34.95
B accepts the offer, leaving them with 1.7056249999999968 remaining funds
A learns that the split-proposal for Candide was accepted
A learns that the price of The Wind in the Willows is 3.95
A makes an offer to split the price of The Wind in the Willows contributing 1.975
The order for Candide has id 10001484, and will be delivered on March 9th
B is being asked to contribute 1.975 toward The Wind in the Willows at price 3.95
B hasn't enough funds (1.7056249999999968 remaining)
A learns that the split-proposal for The Wind in the Willows was rejected
A makes an offer to split the price of The Wind in the Willows contributing 2.9625000000000004
B is being asked to contribute 0.9874999999999998 toward The Wind in the Willows at price 3.95
B accepts the offer, leaving them with 0.718124999999997 remaining funds
A learns that the split-proposal for The Wind in the Willows was accepted
A has bought everything they wanted!
The order for The Wind in the Willows has id 10001485, and will be delivered on March 9th
# The Code
## Type Declarations
First, we declare *assertion types* for our protocol.
assertion type bookQuote(title, price);
assertion type order(title, price, id, deliveryDate);
assertion type splitProposal(title, price, contribution, accepted);
## Utilities
This routine is under consideration for possible addition to the
core library.
function whileRelevantAssert(P) {
react {
assert P;
} until {
case retracted Syndicate.observe(P);
}
}
## Implementation: SELLER
function seller() {
actor {
We give our actor two state variables: a dictionary recording our
inventory of books (mapping title to price), and a counter
tracking the next order ID to be allocated.
this.books = {
"The Wind in the Willows": 3.95,
"Catch 22": 2.22,
"Candide": 34.95
};
this.nextOrderId = 10001483;
The seller responds to interest in bookQuotes by asserting a
responsive record, if one exists.
react {
during Syndicate.observe(bookQuote($title, _)) {
assert bookQuote(title, title in this.books ? this.books[title] : false);
}
}
It also responds to order requests.
react {
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 = title in this.books ? this.books[title] : false;
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 this.books[title];
actor {
whileRelevantAssert(order(title, offerPrice, orderId, "March 9th"));
}
}
}
}
}
}
## Implementation: SPLIT-PROPOSER and book-quote-requestor
function buyerA() {
actor {
var self = this;
Our actor remembers which books remain on its shopping list, and
tries to buy them one at a time, sharing costs with `buyerB`.
self.titles = ["Catch 22",
"Encyclopaedia Brittannica",
"Candide",
"The Wind in the Willows"];
JavaScript's callback-oriented blocking means that we express our
loop in almost a tail-recursive style, using helper functions
`buyBooks` and `trySplit`.
buyBooks();
function buyBooks() {
if (self.titles.length === 0) {
console.log("A has bought everything they wanted!");
return;
}
var title = self.titles.shift();
First, retrieve a quote for the title, and analyze the result.
react until {
case asserted bookQuote(title, $price) {
if (price === false) {
console.log("A learns that "+title+" is out-of-stock.");
buyBooks();
} else {
console.log("A learns that the price of "+title+" is "+price);
Next, repeatedly make split offers to a SPLIT-DISPOSER until
either one is accepted, or the contribution from the
SPLIT-DISPOSER becomes pointlessly small. We start the process by
offering to split the price of the book evenly.
trySplit(title, price, price / 2);
}
}
}
}
function trySplit(title, price, contribution) {
console.log("A makes an offer to split the price of "+title+
" contributing "+contribution);
If we are about to offer to split the price, but the other buyer
would contribute less than 10c, then it's not worth bothering; we
may as well buy it ourselves. Another version of the program could
perform the BUYER role here.
if (contribution > (price - 0.10)) {
console.log("A gives up on "+title+".");
buyBooks();
} else {
Make our proposal, and wait for a response.
react until {
case asserted splitProposal(title, price, contribution, true) {
console.log("A learns that the split-proposal for "+title+" was accepted");
buyBooks();
}
case asserted splitProposal(title, price, contribution, false) {
console.log("A learns that the split-proposal for "+title+" was rejected");
trySplit(title, price, contribution + ((price - contribution) / 2));
}
}
}
}
}
}
## Implementation: SPLIT-DISPOSER and BUYER
function buyerB() {
actor {
This actor maintains a record of the amount of money it has left
to spend.
this.funds = 5.00;
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);
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 {
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;
actor {
react {
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);
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);
}
}
}
}
}
}
}
}
## Starting Configuration
ground dataspace {
seller();
buyerA();
buyerB();
}