Created
April 13, 2019 00:49
-
-
Save zerobias/98c8929ee256a5f0df47b04f304215f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//@flow | |
'use strict' | |
/* eslint-disable no-self-compare,max-len */ | |
//========================================================================= | |
// Data | |
let jg | |
let stack = new Array(0) | |
const hist = new Array(0) | |
const words = {} | |
const help = {} | |
const debug = false | |
let curx = 100 | |
let cury = 100 | |
let heading = 0 | |
//========================================================================= | |
// Misc. functions | |
function clone(o) { | |
// cloning arrays is funky | |
if (typeof o.slice == 'function') return o.slice(0) | |
return o | |
} | |
function push(x) { | |
stack.push(x) | |
} | |
//function peek() { | |
// return stack[stack.length - 1]; | |
//} | |
// function push_fxn_string(s) { | |
// trace('pushing function') | |
// const f = new Function(`${s}()`) | |
// } | |
// function push_fxn(f) { | |
// const a = new Array(0) | |
// a.push(f) | |
// push(a) | |
// } | |
//========================================================================= | |
// New Prototype Extensions | |
function asStackString(o) { | |
if (Array.isArray(o)) { | |
let s = ')' | |
for (let i = 0; i < o.length; ++i) { | |
if (i > 0) s = ` ${s}` | |
if (i >= 5) return `(... ${s}` | |
const x = o[o.length - i - 1] | |
s = asStackString(x) + s | |
} | |
return `(${s}` | |
} | |
if (typeof o === 'string') return `"${o}"` | |
if (typeof o === 'function') return '[_function_]' | |
return o.toString() | |
} | |
//========================================================================= | |
// Cat Primitive Instructions | |
function pop() { | |
return stack.pop() | |
} | |
function swap() { | |
const x = pop() | |
const y = pop() | |
push(x) | |
push(y) | |
} | |
function dup() { | |
const x = pop() | |
push(x) | |
push(clone(x)) | |
} | |
function peek() { | |
const x = pop() | |
push(x) | |
return x | |
} | |
function compose() { | |
const x = pop() | |
const y = pop() | |
const f = () => { | |
x() | |
y() | |
} | |
push(f) | |
} | |
function quote() { | |
const x = pop() | |
const f = () => { | |
push(x) | |
} | |
push(f) | |
} | |
function cat_if() { | |
const f = pop() | |
const t = pop() | |
const c = pop() | |
if (c) { | |
t() | |
} else { | |
f() | |
} | |
} | |
function cat_while() { | |
const b = pop() | |
const f = pop() | |
while ((b(), pop())) { | |
f() | |
} | |
} | |
function list() { | |
const f = pop() | |
const old = stack | |
stack = new Array(0) | |
f() | |
old.push(stack) | |
stack = old | |
} | |
function fold() { | |
const f = pop() | |
let init = pop() | |
const a = pop() | |
for (let i = a.length - 1; i >= 0; --i) { | |
push(init) | |
const x = a[i] | |
push(x) | |
f() | |
init = pop() | |
} | |
push(init) | |
} | |
function foreach() { | |
const f = pop() | |
const a = pop() | |
for (let i = a.length - 1; i >= 0; --i) { | |
const x = a[i] | |
push(x) | |
f() | |
} | |
} | |
// Cat primitives | |
words.add = () => { | |
push(pop() + pop()) | |
} | |
words.compose = compose | |
words.cons = () => { | |
const x = pop() | |
const xs = peek() | |
xs.push(x) | |
} | |
words.div = () => { | |
swap() | |
push(pop() / pop()) | |
} | |
words.dup = dup | |
words.empty = () => { | |
const x = peek() | |
push(x.length == 0) | |
} | |
words.eq = () => { | |
push(pop() == pop()) | |
} | |
words.false = () => { | |
push(false) | |
} | |
words.if = cat_if | |
words.mod = () => { | |
swap() | |
push(pop() % pop()) | |
} | |
words.mul = () => { | |
push(pop() * pop()) | |
} | |
words.neg = () => { | |
push(-pop()) | |
} | |
words.pop = pop | |
words.quote = quote | |
words.swap = swap | |
words.true = () => { | |
push(true) | |
} | |
words.uncons = () => { | |
const x = pop() | |
y = x.pop() | |
push(x) | |
push(y) | |
} | |
words.while = cat_while | |
words.list = list | |
words.nil = () => { | |
push(new Array(0)) | |
} | |
words.count = () => { | |
push(peek().length) | |
} | |
words.apply = () => { | |
const f = pop() | |
f() | |
} | |
words.inc = () => { | |
push(pop() + 1) | |
} | |
words.dip = () => { | |
const f = pop() | |
const x = pop() | |
f.call() | |
push(x) | |
} | |
words.popd = () => { | |
swap() | |
pop() | |
} | |
words.not = () => { | |
push(false) | |
words.eq() | |
} | |
words.pair = () => { | |
const x = pop() | |
const y = pop() | |
const xs = new Array(0) | |
xs.push(y) | |
xs.push(x) | |
push(xs) | |
} | |
words.and = () => { | |
const b = pop() | |
const a = pop() | |
if (a) { | |
push(b) | |
} else { | |
push(false) | |
} | |
} | |
words.unit = () => { | |
const xs = new Array(0) | |
const x = pop() | |
xs.push(x) | |
push(xs) | |
} | |
words.fold = fold | |
words.foreach = foreach | |
words.repeat = () => { | |
let n = pop() | |
const f = pop() | |
while (n-- > 0) { | |
f() | |
} | |
} | |
words.sub = () => { | |
swap() | |
push(pop() - pop()) | |
} | |
words.neq = () => { | |
push(pop() !== pop()) | |
} | |
words.lt = () => { | |
push(pop() > pop()) | |
} | |
words.lteq = () => { | |
push(pop() >= pop()) | |
} | |
words.gt = () => { | |
push(pop() < pop()) | |
} | |
words.gteq = () => { | |
push(pop() <= pop()) | |
} | |
words.head = () => { | |
words.uncons() | |
swap() | |
pop() | |
} | |
words.first = () => { | |
dup() | |
words.head() | |
} | |
words.rest = () => { | |
words.uncons() | |
pop() | |
} | |
words.tail = () => { | |
dup() | |
words.rest() | |
} | |
// Graphics: non-standard | |
words.set_color = () => { | |
jg.setColor(pop()) | |
} | |
words.line = () => { | |
const y1 = pop() | |
const x1 = pop() | |
const y0 = pop() | |
const x0 = pop() | |
jg.drawLine(x0, y0, x1, y1) | |
jg.paint() | |
} | |
words.rect = () => { | |
const y1 = pop() | |
const x1 = pop() | |
const y0 = pop() | |
const x0 = pop() | |
jg.drawRect(x0, y0, x1, y1) | |
jg.paint() | |
} | |
words.fill_rect = () => { | |
const y1 = pop() | |
const x1 = pop() | |
const y0 = pop() | |
const x0 = pop() | |
jg.fillRect(x0, y0, x1, y1) | |
jg.paint() | |
} | |
words.ellipse = () => { | |
const y1 = pop() | |
const x1 = pop() | |
const y0 = pop() | |
const x0 = pop() | |
jg.drawEllipse(x0, y0, x1, y1) | |
jg.paint() | |
} | |
words.fill_ellipse = () => { | |
const y1 = pop() | |
const x1 = pop() | |
const y0 = pop() | |
const x0 = pop() | |
jg.fillEllipse(x0, y0, x1, y1) | |
jg.paint() | |
} | |
words.move_to = () => { | |
cury = pop() | |
curx = pop() | |
} | |
words.line_to = () => { | |
const y = pop() | |
const x = pop() | |
jg.drawLine(curx, cury, x, y) | |
curx = x | |
cury = y | |
jg.paint() | |
} | |
words.turn_to = () => { | |
heading = pop() | |
} | |
words.tr = () => { | |
heading += (pop() * 2 * 3.1416) / 360 | |
} | |
words.tl = () => { | |
heading -= (pop() * 2 * 3.1416) / 360 | |
} | |
words.fd = () => { | |
const hyp = pop() | |
const x = curx + Math.cos(heading) * hyp | |
const y = cury + Math.sin(heading) * hyp | |
jg.drawLine(curx, cury, x, y) | |
curx = x | |
cury = y | |
jg.paint() | |
} | |
words.mv = () => { | |
const hyp = pop() | |
const x = curx + Math.cos(heading) * hyp | |
const y = cury + Math.sin(heading) * hyp | |
curx = x | |
cury = y | |
} | |
// Symbolic primitives | |
words['+'] = words.add | |
words['-'] = words.sub | |
words['*'] = words.mul | |
words['/'] = words.div | |
words['%'] = words.mod | |
words['<'] = words.lt | |
words['<='] = words.lteq | |
words['>'] = words.gt | |
words['>='] = words.gteq | |
words['=='] = words.eq | |
words['!='] = words.neq | |
words['++'] = words.inc | |
words['--'] = words.dec | |
// Others | |
words.to_value = () => { | |
push(peek().valueOf()) | |
} | |
words.to_string = () => { | |
push(peek().toString()) | |
} | |
words.write = () => { | |
write(pop().toString()) | |
} | |
words.writeln = () => { | |
writeln(pop().toString()) | |
} | |
words.clear_output = () => { | |
clear_output() | |
jg.clear() | |
} | |
words.clear_stack = () => { | |
clear_stack() | |
} | |
words.clear = () => { | |
clear_output() | |
clear_stack() | |
} | |
//========================================================================= | |
// Help definitions | |
help.add = 'adds top two values on the stack, e.g. 2 5 + == 7' | |
help.compose = | |
'composes two functions on the stack, e.g. [1] [+] compose == [1 +]' | |
help.cons = | |
'adds an item to the front of a list, e.g. [1 2 3] list 4 cons == [1 2 3 4] list' | |
help.div = 'divides top value from second value, e.g. 12 6 div == 3' | |
help.dup = | |
'duplicates top value on stack, making a copy of lists, e.g. 5 dup == 5 5' | |
help.empty = | |
'pushes true or false on the stack if a list is empty or not, e.g. nil empty == true' | |
help.eq = 'compares top two values for equality, e.g. 5 3 eq == false' | |
help.false = 'pushes the boolean value of false on the stack' | |
help.if = | |
'executes one function or another depending on whether the item below it is true, e.g. true [12] [13] == 12' | |
help.mod = | |
'computes the remainder of dividing the top-value from the second value, e.g. 5 3 mod == 2' | |
help.mul = 'multiplies top two values on the stack, e.g. 5 3 mul == 15' | |
help.neg = 'negates the top value on the stack, e.g. 3 neg == -3' | |
help.pop = 'removes top value from the stack permanently, e.g. 3 5 pop == 3' | |
help.quote = | |
'replaces top value with a function that pushes that value on the stack, e.g. 42 quote == [42]' | |
help.swap = 'swaps the top two items on the stack, e.g. 3 5 swap == 5 3' | |
help.true = 'pushes the boolean value of true on the stack' | |
help.uncons = | |
'removes top value from the list and places it on the stack, e.g. [1 2 3] list uncons == [1 2] list 3' | |
help.while = | |
'executes a function while a predicate function returns true, e.g. 1 [2 mul] [dup 100 lteq] while == 128' | |
help.list = | |
'constructs a list from a function, e.g. [1 2 3] lists creates a list containing 3 in the first position.' | |
help.nil = 'pushes an empty function on the stack, e.g. nil == [] list' | |
help.count = | |
'pushes the number of items in a list on to the stack, e.g. [1 2 3] list == 3' | |
help.apply = 'executes a function on the stack, e.g. 1 [2 +] apply == 3' | |
help.inc = 'adds one to the top value on the stack, e.g. 3 inc == 4' | |
help.dip = | |
'executes a function, first removing the next value on the stack, and replacing it afterwards' | |
help.popd = 'removes the second value from the stack' | |
help.not = | |
'performs a logical negation of the top value on the stack, e.g. true not == false' | |
help.pair = | |
'creates a list of two items from the top two values on the stack, e.g. 1 2 3 pair == 1 [2 3] list' | |
help.and = | |
'pushes true if the top two values are both true, e.g. false true and == false' | |
help.unit = | |
'creates a list of one item from the top value on the stack, e.g. 1 unit == nil 1 cons' | |
help.fold = | |
'takes a binary function, an accumulator value, and a list from the stack. It updates the accumulator by applying the function to each item in the list combined with the accumulator, e.g. [1 2 3 4] list 0 [add] fold == 10' | |
help.foreach = | |
'calls a function with each item in the list, removing the list afterwards, e.g. 0 [1 2 3] list [add] fold == 6' | |
help.sub = 'subtracts top value from second value, e.g. 5 3 sub == 2' | |
help.neq = | |
'returns true if the top two values are not the same, or false otherwise, e.g. 5 3 neq == true' | |
help.lt = | |
'returns true if the top value is less than the first item, or false otherwise, e.g. 5 3 lt == true' | |
help.lteq = | |
'returns true if the top value is less than, or equal to, the first item, or false otherwise, e.g. 5 3 lteq == true' | |
help.gt = | |
'returns true if the top value is greater than the first item, or false otherwise, e.g. 5 3 gt == false' | |
help.gteq = | |
'returns true if the top value is greater than, or equal to, or false otherwise, e.g. the first item, 5 3 gteq == false' | |
help.head = | |
'replaces a list, with the first in the list, or false otherwise, e.g. e.g. [1 2 3] list head == 3' | |
help.first = | |
'pushes a copy of the first item in a list, or false otherwise, e.g. e.g. [1 2 3] list first == [1 2 3] list 3' | |
help.rest = | |
'removes first item from a list, e.g. [1 2 3] list rest == [1 2] list' | |
help.tail = | |
'pushes a copy of a list without the first item, e.g. [1 2 3] list tail == [1 2 3] list [1 2] list' | |
help.set_color = 'updates the current drawing color, usage: "red" set_color' | |
help.line = 'draws a line using the current color, usage: x1 y1 x2 y2 line' | |
help.rect = | |
'draws a filled rectangle using the current color, usage: x y width height rect' | |
help.fill_rect = | |
'draws a filled rectangle using the current color, usage: x y width height fill_rect' | |
help.ellipse = | |
'draws an outline of an ellipse using the current color, usage: x y width height ellipse' | |
help.fill_ellipse = | |
'draws a filled ellipse using the current color, usage: x y width height fill_ellipse' | |
help['+'] = 'same as add' | |
help['-'] = 'same as sub' | |
help['*'] = 'same as mul' | |
help['/'] = 'same as div' | |
help['%'] = 'same as mod' | |
help['<'] = 'same as lt' | |
help['<='] = 'same as lteq' | |
help['>'] = 'same as gt' | |
help['>='] = 'same as gteq' | |
help['=='] = 'same as eq' | |
help['!='] = 'same as neq' | |
help['++'] = 'same as inc' | |
help['--'] = 'same as dec' | |
help.to_value = | |
'converts a string to a value representation, e.g. 1 "2" to_value add == 3' | |
help.to_string = | |
'converts a value to a string representation, e.g. 1 to_string == "1"' | |
help.write = | |
'outputs a string representation of the top value on the stack, removing it in the process' | |
help.writeln = | |
'outputs a string representation of the top value on the stack followed by a newline, removing it in the process' | |
help.clear_output = 'clears the output area' | |
help.clear_stack = 'removes all items from the stack' | |
help.clear = 'clears both the output area, and the stack' | |
help.move_to = | |
'moves the pen position to the given coordinates, usage: x y move_to' | |
help.line_to = | |
'draws a line to the the given coordinates updateing the pen position, usage: x y line_to' | |
help.turn_to = | |
'updates the heading to the given number degrees, usage: pixels turn_to' | |
help.tr = | |
'updates the current heading by turning right the given number of degrees, usage: degrees tr' | |
help.tl = | |
'updates the current heading by turning left the given number of degrees, usage: degrees tl' | |
help.fd = | |
'moves forward (according to the current heading) the number of pixels, while drawing a line, usage: pixels fd' | |
help.mv = | |
'moves forward (according to the current heading) the number of pixels, without drawing a line, usage: pixels mv' | |
//========================================================================= | |
// Unit tests | |
function run_test_case(string) { | |
stack = new Array(0) | |
parse_input(string) | |
if (stack.length == 1 && stack[0] === true) { | |
stack = new Array(0) | |
return true | |
} | |
throw new Error(0, `Test failed: ${string}`) | |
return false | |
} | |
//============================================================================= | |
// Parsing functions | |
function process_int(s) { | |
const x = parseInt(s, 10) | |
if (isNaN(x)) return false | |
stack.push(x) | |
return true | |
} | |
function process_float(s) { | |
const x = parseFloat(s) | |
if (isNaN(x)) return false | |
stack.push(x) | |
return true | |
} | |
function process_string(s) { | |
if (!(s.length >= 2)) return false | |
if (s.charAt(0) != '"') return false | |
if (s.charAt(s.length - 1) != '"') return false | |
stack.push(s.slice(1, s.length - 1)) | |
return true | |
} | |
function process_quote(s) { | |
trace('processing quote') | |
if (!(s.length >= 2)) return false | |
if (s.charAt(0) != '[') return false | |
if (s.charAt(s.length - 1) != ']') return false | |
const tmp = s.substring(1, s.length - 1) | |
trace('creating function') | |
const f = () => { | |
parse_input(tmp) | |
} | |
trace('created function') | |
push(f) | |
return true | |
} | |
function add_word(name, body) { | |
words[name] = () => { | |
parse_input(body) | |
} | |
display_words() | |
} | |
function trace(s) { | |
if (debug) writeln(`trace: ${s}`) | |
} | |
function fail(s) { | |
writeln(`error: ${s}`) | |
return '' | |
} | |
function process(s) { | |
const intRe = /^[+-]?[\d]+/ | |
const floatRe = /^[+-]?[\d]+\.[\d]+/ | |
const wsRe = /^\s+/ | |
const fxnRe = /^\S+/ | |
const defineRe = /^define\s+(\S+)\s+\{(.*)\}/ | |
if (wsRe.test(s)) { | |
trace('removed leading white space') | |
const n = wsRe.exec(s)[0].length | |
return s.slice(n) | |
} else if (defineRe.test(s)) { | |
const def = defineRe.exec(s) | |
add_word(def[1], def[2]) | |
return '' | |
} else if (s.charAt(0) == '[') { | |
trace('at a quote') | |
let n = 1 | |
let i = 1 | |
while (n > 0 && i < s.length) { | |
if (s.charAt(i) == ']') --n | |
if (s.charAt(i) == '[') ++n | |
++i | |
} | |
if (n > 0) return fail('unmatched [ ') | |
const tmp = s.slice(0, i) | |
if (!process_quote(tmp)) | |
return fail(`unable to process quotation from "${tmp}"`) | |
return s.slice(i) | |
} else if (s.charAt(0) == '"') { | |
trace('at a string') | |
let i = 1 | |
while (s.charAt(i) !== '"' && i < s.length) { | |
// advance over \" strings | |
if (s.charAt(i++) == '\\') i++ | |
} | |
if (!process_string(s.slice(0, i + 1))) | |
return fail(`could not process string from "${s}"`) | |
return s.slice(i + 1) | |
} else if (floatRe.test(s)) { | |
trace('at a float') | |
const n = floatRe.exec(s)[0].length | |
if (!process_float(s.slice(0, n))) | |
return fail(`could not process a float from "${s}"`) | |
return s.slice(n) | |
} else if (intRe.test(s)) { | |
trace('matched an integer') | |
const n = intRe.exec(s)[0].length | |
if (!process_int(s.slice(0, n))) | |
return fail(`could not process an integer from "${s}"`) | |
return s.slice(n) | |
} else if (fxnRe.test(s)) { | |
trace('matched a function') | |
const n = fxnRe.exec(s)[0].length | |
const word = s.slice(0, n) | |
const f = words[word] | |
if (f == null) return fail(`${word} is not a defined function`) | |
f.call() | |
return s.slice(n) | |
} else { | |
return fail(`could not process : ${s}`) | |
} | |
} | |
function parse_input(s) { | |
let rest = s | |
while (rest.length > 0) { | |
trace('processing rest') | |
rest = process(rest) | |
} | |
} | |
function stack_as_string() { | |
let result = '' | |
for (const x of stack) { | |
result = `${asStackString(x)}\n${result}` | |
} | |
return result | |
} | |
function write(s) { | |
element = document.getElementById('out') | |
if (element != null) element.value = element.value + s | |
} | |
function writeln(s) { | |
write(`${s}\n`) | |
} | |
function clear_output() { | |
element = document.getElementById('out') | |
element.value = '' | |
} | |
function clear_stack() { | |
stack = new Array(0) | |
} | |
function update_stack() { | |
stack_display = document.getElementById('stack') | |
stack_display.value = stack_as_string() | |
} | |
// function shortcut(s) { | |
// input = document.getElementById('in') | |
// input.value = s | |
// } | |
function display_words() { | |
const table = document.getElementById('words') | |
while (table.rows.length > 0) table.deleteRow(0) | |
let n = 0 | |
let tr | |
let td | |
const a = new Array() | |
for (const word in words) { | |
a.push(word) | |
} | |
a.sort() | |
for (const word of a) { | |
if (n % 5 == 0) tr = table.insertRow(table.rows.length) | |
td = tr.insertCell(tr.cells.length) | |
const helpText = help[word] != undefined ? help[word] : 'no help available' | |
td.innerHTML = `<a title='${helpText}' href="javascript:shortcut('${word}')">${word}</a>` | |
++n | |
} | |
} | |
//========================================================================= | |
// Main execution function | |
function exec() { | |
trace('executing instruction') | |
input = document.getElementById('in') | |
try { | |
trace('parsing text') | |
hist.push(input.value) | |
parse_input(input.value) | |
} catch (e) { | |
writeln(`exception caught: ${e.name}, ${e.message}`) | |
} | |
update_stack() | |
input.value = '' | |
return false | |
} | |
// TODO: revive the test suites | |
//if (debug) | |
// run_test_suite(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment