// I thank God for allowing me to program. let stack = [] let env = make_env() const root = env const w = env.word let source = '' let pointer = 0 const special = identity_group('{ [ ( ` " ) ] }') const number_regex = /^-?\d+(\.?\d*)$/ let make_fun = make_sub let catch_code = (x) => { console.error(x) } const default_catch = catch_code let compile_into_list = compile_in_list const escape = { r: "\r", n: "\n", 0: "\0", t: "\t" } function error (...message) { env = root throw Error(message.map(x => typeof x === 'string'? x : JSON.stringify(x)).join(' ')) } function make_env (parent) { return { parent: parent, word: {} } } function identity_group (str_or_list) { if (typeof str_or_list === 'string') { str_or_list = str_or_list.split(' ') } const obj = {} for (let element of str_or_list) { obj[element] = 1 } return obj } function read_word () { let word = '' let chr = source[pointer++] while (chr !== undefined && chr.match(/\s/)) { chr = source[pointer++] } while (chr !== undefined && !chr.match(/\s/)) { if (special[chr]) { if (word === '') { return chr } else { pointer-- return word } } word += chr chr = source[pointer++] } if (word !== '') { return word } } function put (...stuff) { stack.push(...stuff) } function get () { if (stack.length) { return stack.pop() } else { error('stack underflow') } } function getn (num) { if (num) { if (stack.length < num) { error('getn stack underflow', num) } else { return stack.splice(-num) } } else { return [] } } function get2 () { if (stack.length < 2) { error('stack underflow 2') } else { return stack.splice(-2) } } function get3 () { if (stack.length < 3) { error('stack underflow 3') } else { return stack.splice(-3) } } function get4 () { if (stack.length < 4) { error('stack underflow 4') } else { return stack.splice(-4) } } function find (name) { if (env.word[name]) { return env.word[name] } else if (env.parent) { let e = env while (e = e.parent) { if (e.word[name]) { return e.word[name] } } } } function search (name) { if (env.word[name]) { return env } else if (env.parent) { let e = env while (e = e.parent) { if (e.word[name]) { return e } } } } function compile_element (element) { const type = typeof element if (type === 'string') { const word = find(element) if (word) { if (word.hasOwnProperty('immediate')) { word() if (word.immediate) { return get() } } else { return word } } else { return compile_atom(element) } } else if (type === 'function') { return element } else if (Array.isArray(element)) { return compile_list(element) } else { return () => { put(element) } } } function compile_in_list (element, list) { const value = compile_element(element) if (value) { list.push(value) } } function make_sub (list) { const compiled_unit = () => { for (let element of list) { element() } } return compiled_unit } function make_async_sub (list) { const asynchronous_compiled_unit = async () => { for (let element of list) { element(); if (stack[stack.length - 1] instanceof Promise) { const promise = stack.pop() try { const result = await promise if (result !== undefined) { put(result) } } catch (e) { catch_code(e) } } } } return asynchronous_compiled_unit } function compile_list (list) { const code = [] for (let element of list) { if (Array.isArray(element)) { code.push(() => put(element)) } else { compile_into_list(element, code) } } return make_fun(code) } function compile_atom (atom) { if (atom.match(number_regex)) { const number = parseFloat(atom) return () => put(number) } else if (atom.startsWith("'")) { const string = atom.substr(1) return () => put(string) } else if (atom.startsWith(':')) { if (atom.endsWith(':')) { const name = atom.substr(1, atom.length - 2) const delayed_binding_and_value_push = () => { const value = get() const runtime_binding = () => put(runtime_binding.value) runtime_binding.value = value env.word[name] = runtime_binding put(value) } return delayed_binding_and_value_push } else { const name = atom.substr(1) const delayed_binding = () => { const value = get(); const runtime_binding = () => put(runtime_binding.value) runtime_binding.value = value env.word[name] = runtime_binding } return delayed_binding } } else if (atom.endsWith(':')) { const name = atom.substr(0, atom.length - 1) const delayed_lookup = () => { const word = find(name); undef(word, atom, 'does not exist at runtime'); word() } return delayed_lookup } else if (atom.startsWith('.')) { if (atom.endsWith('!')) { return compile_stack_dot_notation(atom.substr(1, atom.length - 2), (slot, property) => slot[property] = get()) } else { return compile_stack_dot_notation(atom.substr(1), (slot, property) => put(slot[property])) } } else if (atom.match(/\./)) { if (atom.endsWith('!')) { return compile_dot_notation(atom.substr(0, atom.length - 1), (slot, property) => slot[property] = get()) } else { return compile_dot_notation(atom, (slot, property) => put(slot[property])) } } else if (atom.startsWith('--')) { const method_name = atom.substr(2) return () => { const [obj, args] = get2(); put(obj[method_name](...args)) } } else if (atom.startsWith('-')) { const method_name = atom.substr(1) return () => { put(get()[method_name]()) } } else if (atom.startsWith('~~')) { const method_name = atom.substr(2) return () => { const [obj, args] = get2(); obj[method_name](...args) } } else if (atom.startsWith('~')) { const method_name = atom.substr(1) return () => { get()[method_name]() } } else { error(atom, 'not recognized') } } //ats function compile_string (string) { const old = source const oldp = pointer source = string pointer = 0 const code = [] let element while ((element = read_word()) !== undefined) { compile_into_list(element, code) } const compiled_unit = make_fun(code) source = old pointer = oldp return compiled_unit } function interpret_element (element) { const value = compile_element(element) if (value) { value() } } function interpret_string (string) { compile_string(string)() } function node () { function repl () { const handler = function (data) { const string = data.toString() if (string === '\n') { process.stdin.pause() process.stdin.removeListener('data', handler) } else { try { interpret_string(string.trim()) console.log(stack) } catch (e) { console.error(e) } } } process.stdin.on('data', handler) } repl() } function browser () { let ctx, animation, canvas const style = document.createElement('style') document.head.appendChild(style) style.sheet.insertRule('oh { display: none }') w.body = () => put(document.body) w.window = () => put(window) w.document = () => put(document) w.element = () => put(document.createElement(get())) w['set-ctx'] = () => ctx = get().getContext('2d') w.canvas = () => put(canvas) w.clear = () => ctx.clearRect(0, 0, canvas.width, canvas.height) w['make-canvas'] = () => { canvas = document.createElement('canvas') ctx = canvas.getContext('2d') canvas.width = innerWidth canvas.height = innerHeight window.addEventListener('resize', () => { canvas.width = innerWidth; canvas.height = innerHeigth }) document.body.style.overflow = 'hidden' document.body.style.margin = 0 document.body.style.padding = 0 document.body.appendChild(canvas) } w.rectangle = () => ctx.fillRect(...get4()) w.color = () => ctx.fillStyle = get() w.animation = () => { const code = get() if (animation) { cancelAnimationFrame(animation) } const fun = () => { code(); animation = requestAnimationFrame(fun) } animation = requestAnimationFrame(fun) } w['to-body'] = () => { const element = get() if (Array.isArray(element)) { for (let el of element) { document.body.appendChild(el) } } else { document.body.appendChild(element) } } w.image = () => { const src = get() put(new Promise((res, rej) => { const img = new Image() img.onload = () => res(img) img.onerror = () => rej('image failed to load: ' + src) img.src = src })) } w.images = () => { const sources = get() let count = 0 const len = sources.length let resolve, reject const images = [] put(new Promise((res, rej) => { resolve = res; reject = rej })) for (let src of sources) { const img = new Image() img.onload = () => { images.push(img) if(++count === len) { resolve(images) } } img.onerror = () => reject('failed to load image: ' + src) img.src = src } } async function load_scripts () { for (let oh of document.getElementsByTagName('oh')) { const src = oh.getAttribute('src') if (src) { const res = await fetch(src) if (res.ok) { interpret_string(await res.text()) } else { console.error('failed to load', src) } } const str = oh.textContent.trim() if (str) { interpret_string(str) } oh.remove() } } window.addEventListener('load', load_scripts) } function init () { if (typeof window === 'undefined') { node() } else { browser() } } function undef (element, ...message) { if (element === undefined) { error(...message) } return element } function block (end = 'end') { let element const code = [] while ((element = read_word()) !== end) { undef(element, 'block did not find a terminating', end) compile_into_list(element, code) } return make_fun(code) } function env_block (e = env, end = 'end') { let element const code = [] const old = env env = e while ((element = read_word()) !== end) { undef(element, 'block did not find a terminating', end) compile_into_list(element, code) } env = old return make_fun(code) } function read_name (caller_name) { const name = read_word() undef(name, caller_name, 'did not read a word') return name } function immediate (flag, str_or_list) { if (typeof str_or_list === 'string') { str_or_list = str_or_list.split(' ') } for (let name of str_or_list) { const word = find(name) undef(word, name, 'is not a word when trying to make it immediate') word.immediate = flag } } function get_chain (object, properties) { const len = properties.length - 1 for (let i = 0; i < len; i++) { if (properties[i] in object) { object = object[properties[i]] } else { error(object, 'does not have a property named', properties[i], properties, len) } } return [object, properties[len]] } function compile_stack_dot_notation (name, code) { const properties = translate_case(name).split('.') const stack_dot_assignment = function () { code(...get_chain(get(), properties)) } return stack_dot_assignment } function compile_dot_notation (name, code) { let [object_expr, ...properties] = name.split('.') properties = properties.map(translate_case) const obj_expr = compile_element(object_expr) undef(obj_expr, 'compile dot assignment did not receive code from', object_expr, name) const dot_assignment = function () { obj_expr() code(...get_chain(get(), properties)) } return dot_assignment } function compile_runtime_mutation (name, code) { const runtime_mutation = function () { const word = find(name) undef(word, name, 'did not find a word named', name) if (word.hasOwnProperty('value')) { code(word, 'value') } else { error(name, 'is not a word with a binding at runtime') } } return runtime_mutation } function compile_mutation (name, code) { const word = find(name) undef(word, name, 'did not find a word named', name) if (word.hasOwnProperty('value')) { const mutation = function () { code(word, 'value') } return mutation } else { error(name, 'is not a word with a binding at compile time') } } function mutator (caller_name, code) { env.word[caller_name] = () => { const name = read_name(caller_name) if (name.startsWith('.')) { put(compile_stack_dot_notation(name.substr(1), code)) } else if (name.match(/\./)) { put(compile_dot_notation(name, code)) } else if (name.endsWith(':')) { put(compile_runtime_mutation(name.substr(0, name.length - 1), code)) } else { put(compile_mutation(name, code)) } } env.word[caller_name].immediate = 1 } function build_list () { const list = [] let word while ((word = read_word()) !== ')') { undef(word, '( did not find a terminating )') if (word.match(number_regex)) { list.push(parseFloat(word)) } else if (word === 'nil') { list.push(null) } else if (word === '(') { list.push(build_list()) } else if (word === '"') { w['"']() list.push(get()) } else if (word === '`') { w['`']() get()() list.push(get()) } else { list.push(word) } } return list } function trace_into_list (element, list) { const code = compile_element(element) if (code) { list.push(() => { code(); console.log('trace:', element, ...stack) }) } } function translate_case (str) { let res = '' const len = str.length for (let i = 0; i < len; i++) { if (str[i] === '-') { if (i + 1 >= len) { error('- in dot notation at the end of a property', str) } res += str[++i].toUpperCase() } else { res += str[i] } } return res } // wds w['('] = () => { const list = build_list(); put(() => put([...list])) } w[':'] = () => { const name = read_name(':') env.word[name] = env_block(make_env(env), ';') } w['wait'] = () => { make_fun = make_async_sub } w['no-wait'] = () => { make_fun = make_sub } w.s = () => { console.log([...stack]) } w.block = () => { const e = env const code = block() put(() => { const old = env; env = make_env(e); code(); env = old }) } w.lambda = () => { const e = env const code = env_block(e) put(() => put(() => { const old = env; env = make_env(e); code(); env = old })) } w.defun = () => { const e = env const name = read_name('defun') const code = env_block(e) env.word[name] = () => { const old = env; env = make_env(e); code(); env = old } } w.eval = () => { interpret_element(get()) } w.compile = () => { put(compile_element(get())) } w.r = () => stack = [] w.declare = () => { const name = read_name('declare') if (name === '(') { for (let word of build_list()) { const fun = () => { put(fun.value) } env.word[word] = fun fun.value = 0 } } else { const fun = () => put(fun.value) fun.value = 0 env.word[name] = fun } } w.bind = () => { const name = read_name('bind') if (name === '(') { const funs = [] const names = build_list() const len = names.length for (let i = 0; i < len; i++) { const fun = () => put(funs[i].value) funs[i] = fun env.word[names[i]] = fun } put(() => { const values = getn(len); for (let i = 0; i < len; i++) { funs[i].value = values[i] } }) } else { const fun = () => put(fun.value) fun.value = 0 put(() => { fun.value = get() }) env.word[name] = fun } } w.if = () => { const test = block('then') let word const true_branch = [] const false_branch = [] let pointer = true_branch while ((word = read_word()) !== 'end') { if (word === 'else') { if (pointer === false_branch) { error('if found else twice') } pointer = false_branch } else { compile_into_list(word, pointer) } } const true_code = make_fun(true_branch) const false_code = make_fun(false_branch) put(() => { test(); if (get()) { true_code() } else { false_code() } }) } w.else = () => { error('else found outside if or in if without then') } w.end = () => { error('end found outside a block or in if without then') } w.nop = () => {} w.find = () => put(find(get())) w.promise = () => { const e = make_env(env) let reject, resolve e.word.reject = () => reject(get()) e.word.resolve = () => resolve(get()) const code = env_block(e) put(() => put(new Promise((res, rej) => { reject = rej; resolve = res; code() }))) } w.interval = () => { const time = read_word() undef(time, 'interval did not read a number for the interval time') if (time.match(number_regex)) { const code = block() put(() => setInterval(code, parseFloat(time))) } else { error('interval did not read a valid number for the interval time') } } w.timeout = () => { const time = read_word() undef(time, 'timeout did not read a number for the timeout time') if (time.match(number_regex)) { const code = block() put(() => setTimeout(code, parseFloat(time))) } else { error('timeout did not read a valid number for the timeout time') } } mutator('increment-by-one', (slot, property) => slot[property]++) mutator('decrement-by-one', (slot, property) => slot[property]--) mutator('increment', (slot, property) => slot[property] += get()) mutator('decrement', (slot, property) => slot[property] -= get()) mutator('set', (slot, property) => slot[property] = get()) mutator('get', (slot, property) => put(slot[property])) w.drop = get w['2drop'] = get2 w['3drop'] = get3 w['4drop'] = get4 w.dup = () => { const element = get(); put(element, element) } w.swap = () => { const [one, two] = get2(); put(two, one) } w['+'] = () => { const [one, two] = get2(); put(one + two) } w['-'] = () => { const [one, two] = get2(); put(one - two) } w['/'] = () => { const [one, two] = get2(); put(one / two) } w['*'] = () => { const [one, two] = get2(); put(one * two) } w['%'] = () => { const [one, two] = get2(); put(one % two) } w['{'] = () => { const code = block('}'); put(() => put(code)) } w.log = () => console.log(get()) w.module = () => { const name = read_name('module') const e = make_env(env) env_block(e) env.word[name] = () => put(e) env.word[name].immediate = 0 } w.import = () => { const e = get() const word = read_name('import') if (word === '(') { for (let name of build_list()) { env.word[name] = e.word[name] } } else { env.word[word] = e.word[word] } } w['import-all'] = () => { const e = get() for (let name of Object.keys(e.word)) { env.word[name] = e.word[name] } } w.object = () => { const obj = {} const list = get() const len = list.length for (let i = 0; i < len; i += 2) { obj[list[i]] = list[i + 1] } put(obj) } w.obj = () => { const obj = {} const list = get() const len = list.length const values = getn(len) for (let i = 0; i < len; i++) { obj[list[i]] = values[i] } put(obj) } w.properties = () => { const [object, list] = get2() const res = [] for (let property of list) { put(object[property]) } } w['"'] = () => { let str = '' let chr while ((chr = source[pointer++]) !== '"') { undef(chr, '" did not find a terminating "') if (chr === '\\') { chr = source[pointer++] undef(chr, '" \\ at the end of a string') if (escape[chr]) { str += escape[chr] } else { str += chr } } else { str += chr } } const string = () => put(str) put(string) } w['`'] = () => { let str = '' let chr while ((chr = source[pointer++]) !== '`') { undef(chr, '` did not find a terminating `') if (chr === '\\') { chr = source[pointer++] undef(chr, '` \\ at the end of a string') if (escape[chr]) { str += escape[chr] } else { str += chr } } else { str += chr } } const alternative_string = () => put(str) put(alternative_string) } w['get-property'] = () => { const [object, property] = get2() put(object[property]) } w['random-key'] = () => { const keys = Object.keys(get()) put(keys[Math.floor(Math.random() * keys.length)]) } w.trace = () => compile_into_list = trace_into_list w['no-trace'] = () => compile_into_list = compile_in_list w.list = () => put(getn(get())) w.fetch = () => { put(fetch(get())) } w.console = () => put(console) immediate(0, ': wait no-wait defun trace no-trace module import import-all declare') immediate(1, 'block lambda bind increment { ( " ` if promise interval timeout') init()