diff --git a/demo/matchbox.html b/demo/matchbox.html index 028c2b3..0d61e2c 100644 --- a/demo/matchbox.html +++ b/demo/matchbox.html @@ -7,39 +7,10 @@ <h1>Matchbox</h1> -<textarea id="prog1"> -MOV M0, R0 -INC R0 -MOV R0, M0 -</textarea> - -<textarea id="prog2"> -MOV M0, R0 -INC R0 -INC R0 -MOV R0, M0 -</textarea> - -<button onclick="interleave()">Interleave</button> - -<pre id="output"> -</pre> +<div id="container"></div> </body> <script src="../src/matchbox.js"></script> <script> -"use strict"; - -var output = document.getElementById('output'); - -var x = parseCode(document.getElementById('prog1').value); -var y = parseCode(document.getElementById('prog2').value); - -var interleave = function() { - output.innerHTML = ''; - var interleavings = findAllInterleavings(x, y); - for (var i = 0; i < interleavings.length; i++) { - output.innerHTML += '[' + interleavings[i] + ']\n'; - } -}; +launch('../src/yoob/', 'container', {}); </script> diff --git a/src/matchbox.js b/src/matchbox.js index 77cd1e2..1efa709 100644 --- a/src/matchbox.js +++ b/src/matchbox.js @@ -1,108 +1,61 @@ "use strict"; -/* - * A lexical analyzer. - * Create a new yoob.Scanner object, then call init, passing it an - * array of two-element arrays; first element of each of these is the - * type of token, the second element is a regular expression (in a - * String) which matches that token at the start of the string. The - * regular expression should have exactly one capturing group. - * Then call reset, passing it the string to be scanned. - * - */ -var Scanner = function() { - this.text = undefined; - this.token = undefined; - this.type = undefined; - this.error = undefined; - this.table = undefined; - this.whitespacePattern = "^[ \\t\\n\\r]*"; - - this.init = function(table) { - this.table = table; - return this; - }; - - this.reset = function(text) { - this.text = text; - this.token = undefined; - this.type = undefined; - this.error = undefined; - this.scan(); - }; - - this.scanPattern = function(pattern, type) { - var re = new RegExp(pattern); - var match = re.exec(this.text); - if (match === null) return false; - this.type = type; - this.token = match[1]; - this.text = this.text.substr(match[0].length); - //console.log(this.type, this.token); - return true; - }; - - this.scan = function() { - this.scanPattern(this.whitespacePattern, "whitespace"); - if (this.text.length === 0) { - this.token = null; - this.type = "EOF"; - return; - } - for (var i = 0; i < this.table.length; i++) { - var type = this.table[i][0]; - var pattern = this.table[i][1]; - if (this.scanPattern(pattern, type)) return; - } - if (this.scanPattern("^([\\s\\S])", "unknown character")) return; - // should never get here - }; - - this.expect = function(token) { - if (this.token === token) { - this.scan(); - } else { - this.error = "expected '" + token + "' but found '" + this.token + "'"; - } - }; - - this.on = function(token) { - return this.token === token; - }; - - this.onType = function(type) { - return this.type === type; - }; - - this.checkType = function(type) { - if (this.type !== type) { - this.error = "expected " + type + " but found " + this.type + " (" + this.token + ")" - } - }; - - this.expectType = function(type) { - this.checkType(type); - this.scan(); - }; - - this.consume = function(token) { - if (this.on(token)) { - this.scan(); - return true; - } else { - return false; - } - }; - -}; - -var matchboxScanner = (new Scanner()).init([ - ['immediate', "^(\\d+)"], - ['register', "^([rR]\\d+)"], - ['memory', "^([mM]\\d+)"], - ['opcode', "^([a-zA-Z]+)"], - ['comma', "^(,)"] -]); +function launch(prefix, container, config) { + if (typeof container === 'string') { + container = document.getElementById(container); + } + config = config || {}; + var deps = [ + "scanner.js", + "element-factory.js" + ]; + var loaded = 0; + for (var i = 0; i < deps.length; i++) { + var elem = document.createElement('script'); + elem.src = prefix + deps[i]; + elem.onload = function() { + if (++loaded < deps.length) return; + + var prog1ta = yoob.makeTextArea(container, 20, 10); + var prog2ta = yoob.makeTextArea(container, 20, 10); + + prog1ta.value = "MOV M0, R0\nINC R0\nMOV R0, M0"; + prog2ta.value = "MOV M0, R0\nINC R0\nMOV R0, M0"; + + var interleaveBtn = yoob.makeButton(container, "Interleave"); + + var output = yoob.makePre(container); + + initScanner(); + + interleaveBtn.onclick = function() { + var x = parseCode(prog1ta.value); + var y = parseCode(prog2ta.value); + + output.innerHTML = ''; + var interleavings = findAllInterleavings(x, y); + for (var i = 0; i < interleavings.length; i++) { + output.innerHTML += '[' + interleavings[i] + ']\n'; + } + }; + + }; + document.body.appendChild(elem); + } +} + +var matchboxScanner; + +function initScanner() { + matchboxScanner = (new yoob.Scanner()); + matchboxScanner.init([ + ['immediate', "^(\\d+)"], + ['register', "^([rR]\\d+)"], + ['memory', "^([mM]\\d+)"], + ['opcode', "^([a-zA-Z]+)"], + ['comma', "^(,)"] + ]); +} /* * Each instruction is an object with some fields: diff --git a/src/yoob/element-factory.js b/src/yoob/element-factory.js new file mode 100644 index 0000000..3045bae --- /dev/null +++ b/src/yoob/element-factory.js @@ -0,0 +1,203 @@ +/* + * This file is part of yoob.js version 0.8 + * Available from https://github.com/catseye/yoob.js/ + * This file is in the public domain. See http://unlicense.org/ for details. + */ +if (window.yoob === undefined) yoob = {}; + +/* + * Functions for creating elements. + */ + +yoob.makeCanvas = function(container, width, height) { + var canvas = document.createElement('canvas'); + if (width) { + canvas.width = width; + } + if (height) { + canvas.height = height; + } + container.appendChild(canvas); + return canvas; +}; + +yoob.makeButton = function(container, labelText, fun) { + var button = document.createElement('button'); + button.innerHTML = labelText; + container.appendChild(button); + if (fun) { + button.onclick = fun; + } + return button; +}; + +yoob.checkBoxNumber = 0; +yoob.makeCheckbox = function(container, checked, labelText, fun) { + var checkbox = document.createElement('input'); + checkbox.type = "checkbox"; + checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; + checkbox.checked = checked; + var label = document.createElement('label'); + label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; + yoob.checkBoxNumber += 1; + label.appendChild(document.createTextNode(labelText)); + + container.appendChild(checkbox); + container.appendChild(label); + + if (fun) { + checkbox.onchange = function(e) { + fun(checkbox.checked); + }; + } + return checkbox; +}; + +yoob.makeTextInput = function(container, size, value) { + var input = document.createElement('input'); + input.size = "" + (size || 12); + input.value = value || ""; + container.appendChild(input); + return input; +}; + +yoob.makeSlider = function(container, min, max, value, fun) { + var slider = document.createElement('input'); + slider.type = "range"; + slider.min = min; + slider.max = max; + slider.value = value || 0; + if (fun) { + slider.onchange = function(e) { + fun(parseInt(slider.value, 10)); + }; + } + container.appendChild(slider); + return slider; +}; + +yoob.makeParagraph = function(container, innerHTML) { + var p = document.createElement('p'); + p.innerHTML = innerHTML || ''; + container.appendChild(p); + return p; +}; + +yoob.makeSpan = function(container, innerHTML) { + var span = document.createElement('span'); + span.innerHTML = innerHTML || ''; + container.appendChild(span); + return span; +}; + +yoob.makeDiv = function(container, innerHTML) { + var div = document.createElement('div'); + div.innerHTML = innerHTML || ''; + container.appendChild(div); + return div; +}; + +yoob.makePre = function(container, innerHTML) { + var elem = document.createElement('pre'); + elem.innerHTML = innerHTML || ''; + container.appendChild(elem); + return elem; +}; + +yoob.makePanel = function(container, title, isOpen) { + isOpen = !!isOpen; + var panelContainer = document.createElement('div'); + var button = document.createElement('button'); + var innerContainer = document.createElement('div'); + innerContainer.style.display = isOpen ? "block" : "none"; + + button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; + button.onclick = function(e) { + isOpen = !isOpen; + button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; + innerContainer.style.display = isOpen ? "block" : "none"; + }; + + panelContainer.appendChild(button); + panelContainer.appendChild(innerContainer); + container.appendChild(panelContainer); + return innerContainer; +}; + +yoob.makeTextArea = function(container, cols, rows, initial) { + var textarea = document.createElement('textarea'); + textarea.rows = "" + rows; + textarea.cols = "" + cols; + if (initial) { + container.value = initial; + } + container.appendChild(textarea); + return textarea; +}; + +yoob.makeLineBreak = function(container) { + var br = document.createElement('br'); + container.appendChild(br); + return br; +}; + +yoob.makeSelect = function(container, labelText, optionsArray) { + var label = document.createElement('label'); + label.innerHTML = labelText; + container.appendChild(label); + + var select = document.createElement("select"); + + for (var i = 0; i < optionsArray.length; i++) { + var op = document.createElement("option"); + op.value = optionsArray[i][0]; + op.text = optionsArray[i][1]; + if (optionsArray[i].length > 2) { + op.selected = optionsArray[i][2]; + } else { + op.selected = false; + } + select.options.add(op); + } + + container.appendChild(select); + return select; +}; + +SliderPlusTextInput = function() { + this.init = function(cfg) { + this.slider = cfg.slider; + this.textInput = cfg.textInput; + this.callback = cfg.callback; + return this; + }; + + this.set = function(value) { + this.slider.value = "" + value; + this.textInput.value = "" + value; + this.callback(value); + }; +}; + +yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { + yoob.makeSpan(container, label); + var slider = yoob.makeSlider(container, min_, max_, value); + var s = "" + value; + var textInput = yoob.makeTextInput(container, size, s); + slider.onchange = function(e) { + textInput.value = slider.value; + fun(parseInt(slider.value, 10)); + }; + textInput.onchange = function(e) { + var v = parseInt(textInput.value, 10); + if (v !== NaN) { + slider.value = "" + v; + fun(v); + } + }; + return new SliderPlusTextInput().init({ + 'slider': slider, + 'textInput': textInput, + 'callback': fun + }); +}; diff --git a/src/yoob/scanner.js b/src/yoob/scanner.js new file mode 100644 index 0000000..f755d46 --- /dev/null +++ b/src/yoob/scanner.js @@ -0,0 +1,100 @@ +/* + * This file is part of yoob.js version 0.3 + * Available from https://github.com/catseye/yoob.js/ + * This file is in the public domain. See http://unlicense.org/ for details. + */ +if (window.yoob === undefined) yoob = {}; + +/* + * A lexical analyzer. + * Create a new yoob.Scanner object, then call init, passing it an + * array of two-element arrays; first element of each of these is the + * type of token, the second element is a regular expression (in a + * String) which matches that token at the start of the string. The + * regular expression should have exactly one capturing group. + * Then call reset, passing it the string to be scanned. + * + */ +yoob.Scanner = function() { + this.text = undefined; + this.token = undefined; + this.type = undefined; + this.error = undefined; + this.table = undefined; + this.whitespacePattern = "^[ \\t\\n\\r]*"; + + this.init = function(table) { + this.table = table; + }; + + this.reset = function(text) { + this.text = text; + this.token = undefined; + this.type = undefined; + this.error = undefined; + this.scan(); + }; + + this.scanPattern = function(pattern, type) { + var re = new RegExp(pattern); + var match = re.exec(this.text); + if (match === null) return false; + this.type = type; + this.token = match[1]; + this.text = this.text.substr(match[0].length); + return true; + }; + + this.scan = function() { + this.scanPattern(this.whitespacePattern, "whitespace"); + if (this.text.length === 0) { + this.token = null; + this.type = "EOF"; + return; + } + for (var i = 0; i < this.table.length; i++) { + var type = this.table[i][0]; + var pattern = this.table[i][1]; + if (this.scanPattern(pattern, type)) return; + } + if (this.scanPattern("^([\\s\\S])", "unknown character")) return; + // should never get here + }; + + this.expect = function(token) { + if (this.token === token) { + this.scan(); + } else { + this.error = "expected '" + token + "' but found '" + this.token + "'"; + } + }; + + this.on = function(token) { + return this.token === token; + }; + + this.onType = function(type) { + return this.type === type; + }; + + this.checkType = function(type) { + if (this.type !== type) { + this.error = "expected " + type + " but found " + this.type + " (" + this.token + ")" + } + }; + + this.expectType = function(type) { + this.checkType(type); + this.scan(); + }; + + this.consume = function(token) { + if (this.on(token)) { + this.scan(); + return true; + } else { + return false; + } + }; + +};