Skip to content

Instantly share code, notes, and snippets.

@zerobias
Created April 13, 2019 00:49
Show Gist options
  • Save zerobias/98c8929ee256a5f0df47b04f304215f0 to your computer and use it in GitHub Desktop.
Save zerobias/98c8929ee256a5f0df47b04f304215f0 to your computer and use it in GitHub Desktop.
//@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