diff --git a/js/examples/smoketest-dsl/index.html b/js/examples/smoketest-dsl/index.html
new file mode 100644
index 0000000..b8208b7
--- /dev/null
+++ b/js/examples/smoketest-dsl/index.html
@@ -0,0 +1,14 @@
+
+
+
+ Syndicate: Smoketest with DSL
+
+
+
+
+
+
+
+ Smoketest
+
+
diff --git a/js/examples/smoketest-dsl/index.js b/js/examples/smoketest-dsl/index.js
new file mode 100644
index 0000000..3f16981
--- /dev/null
+++ b/js/examples/smoketest-dsl/index.js
@@ -0,0 +1,31 @@
+assertion type beep(counter);
+
+ground network {
+ console.log('starting ground boot');
+
+ actor {
+ until {
+ case asserted Syndicate.observe(beep(_)) {
+ var counter = 0;
+ state {
+ init {
+ :: beep(counter++);
+ }
+ on message beep(_) {
+ :: beep(counter++);
+ }
+ } until {
+ case (counter >= 10);
+ }
+ }
+ }
+ }
+
+ actor {
+ forever {
+ on message $m {
+ console.log("Got message:", m);
+ }
+ }
+ }
+}
diff --git a/js/examples/textfield-dsl/index.html b/js/examples/textfield-dsl/index.html
new file mode 100644
index 0000000..9684bd4
--- /dev/null
+++ b/js/examples/textfield-dsl/index.html
@@ -0,0 +1,23 @@
+
+
+
+ Syndicate: Textfield Example (DSL variation)
+
+
+
+
+
+
+
+
+ Textfield Example (DSL variation)
+
+ After Hesam
+ Samimi's paper.
+
+ Field contents:
+ Search
+
+
+
+
diff --git a/js/examples/textfield-dsl/index.js b/js/examples/textfield-dsl/index.js
new file mode 100644
index 0000000..5f33832
--- /dev/null
+++ b/js/examples/textfield-dsl/index.js
@@ -0,0 +1,169 @@
+///////////////////////////////////////////////////////////////////////////
+// GUI
+
+assertion type jQuery(selector, eventType, event);
+assertion type fieldCommand(detail);
+assertion type fieldContents(text, pos);
+assertion type highlight(state);
+
+function escapeText(text) {
+ text = text.replace(/&/g, '&');
+ text = text.replace(//g, '>');
+ text = text.replace(/ /g, ' ');
+ return text;
+}
+
+function piece(text, pos, lo, hi, cls) {
+ return ""+
+ ((pos >= lo && pos < hi)
+ ? (escapeText(text.substring(lo, pos)) +
+ "" +
+ escapeText(text.substring(pos, hi)))
+ : escapeText(text.substring(lo, hi)))
+ + "";
+}
+
+function spawnGui() {
+ actor {
+ this.text = '';
+ this.pos = 0;
+ this.highlightState = false;
+
+ this.updateDisplay = function () {
+ var text = this.text;
+ var pos = this.pos;
+ var highlight = this.highlightState;
+ var hLeft = highlight ? highlight.get(0) : 0;
+ var hRight = highlight ? highlight.get(1) : 0;
+ $("#fieldContents")[0].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");
+ };
+
+ forever {
+ on message jQuery("#inputRow", "+keypress", $event) {
+ var keycode = event.keyCode;
+ var character = String.fromCharCode(event.charCode);
+ if (keycode === 37 /* left */) {
+ :: fieldCommand("cursorLeft");
+ } else if (keycode === 39 /* right */) {
+ :: fieldCommand("cursorRight");
+ } else if (keycode === 9 /* tab */) {
+ // ignore
+ } else if (keycode === 8 /* backspace */) {
+ :: fieldCommand("backspace");
+ } else if (character) {
+ :: fieldCommand(["insert", character]);
+ }
+ }
+
+ on asserted fieldContents($text, $pos) {
+ this.text = text;
+ this.pos = pos;
+ this.updateDisplay();
+ }
+
+ on asserted highlight($state) {
+ this.highlightState = state;
+ this.updateDisplay();
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Textfield Model
+
+function spawnModel() {
+ actor {
+ this.fieldContents = "initial";
+ this.cursorPos = this.fieldContents.length; /* positions address gaps between characters */
+
+ forever {
+ assert fieldContents(this.fieldContents, this.cursorPos);
+
+ on message fieldCommand("cursorLeft") {
+ this.cursorPos--;
+ if (this.cursorPos < 0)
+ this.cursorPos = 0;
+ }
+
+ on message fieldCommand("cursorRight") {
+ this.cursorPos++;
+ if (this.cursorPos > this.fieldContents.length)
+ this.cursorPos = this.fieldContents.length;
+ }
+
+ on message fieldCommand("backspace") {
+ if (this.cursorPos > 0) {
+ this.fieldContents =
+ this.fieldContents.substring(0, this.cursorPos - 1) +
+ this.fieldContents.substring(this.cursorPos);
+ this.cursorPos--;
+ }
+ }
+
+ on message fieldCommand(["insert", $newText]) {
+ this.fieldContents =
+ this.fieldContents.substring(0, this.cursorPos) +
+ newText +
+ this.fieldContents.substring(this.cursorPos);
+ this.cursorPos += newText.length;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Search engine
+
+function spawnSearch() {
+ actor {
+ this.fieldContents = "";
+ this.highlight = false;
+
+ this.search = function () {
+ var searchtext = $("#searchBox")[0].value;
+ if (searchtext) {
+ var pos = this.fieldContents.indexOf(searchtext);
+ this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
+ } else {
+ this.highlight = false;
+ }
+ };
+
+ forever {
+ assert highlight(this.highlight);
+
+ on message jQuery("#searchBox", "input", $event) {
+ this.search();
+ }
+
+ on asserted fieldContents($text, _) {
+ this.fieldContents = text;
+ this.search();
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Main
+
+$(document).ready(function () {
+ ground network G {
+ Syndicate.JQuery.spawnJQueryDriver();
+ Syndicate.DOM.spawnDOMDriver();
+
+ spawnGui();
+ spawnModel();
+ spawnSearch();
+ }
+
+ G.network.onStateChange = function (mux, patch) {
+ $("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
+ };
+});
diff --git a/js/examples/textfield-dsl/style.css b/js/examples/textfield-dsl/style.css
new file mode 100644
index 0000000..d612510
--- /dev/null
+++ b/js/examples/textfield-dsl/style.css
@@ -0,0 +1,12 @@
+#fieldContents {
+ font-family: monospace;
+}
+
+.cursor {
+ border-left: solid red 1px;
+ border-right: solid red 1px;
+}
+
+.highlight {
+ background-color: yellow;
+}