mirror of
https://github.com/osmarks/random-stuff
synced 2024-11-09 13:59:55 +00:00
189 lines
4.1 KiB
JavaScript
189 lines
4.1 KiB
JavaScript
|
const { regex, sequenceOf, char, choice, many1, many, str, coroutine, possibly } = require("arcsecond")
|
||
|
const fs = require("fs").promises
|
||
|
|
||
|
const spaces = regex(/^ */)
|
||
|
const whitespace = regex(/^[ \n\t]*/)
|
||
|
const name = regex(/^[^ \n\t():]+/)
|
||
|
const code = many(coroutine(function*() {
|
||
|
yield spaces
|
||
|
return yield choice([
|
||
|
coroutine(function*() {
|
||
|
yield char("(")
|
||
|
const x = yield code
|
||
|
yield spaces
|
||
|
yield char(")")
|
||
|
return x
|
||
|
}),
|
||
|
name
|
||
|
])
|
||
|
}))
|
||
|
const program = sequenceOf([
|
||
|
many1(coroutine(function*() {
|
||
|
yield whitespace
|
||
|
const n = yield name
|
||
|
yield whitespace
|
||
|
yield str(":=")
|
||
|
const c = yield code
|
||
|
return { code: c, name: n }
|
||
|
})),
|
||
|
possibly(whitespace)
|
||
|
]).map(([x, _]) => x)
|
||
|
|
||
|
// ---------
|
||
|
|
||
|
let stackL = {depth: 0, children: []}, stackR = {depth: 0, children: []};
|
||
|
let currentIsLeft = true;
|
||
|
|
||
|
function depthDelta(x) {
|
||
|
if(currentIsLeft)
|
||
|
stackL["depth"]+=x;
|
||
|
else
|
||
|
stackR["depth"]+=x;
|
||
|
}
|
||
|
|
||
|
function currentStack() {
|
||
|
let depth, currentStack;
|
||
|
|
||
|
if(currentIsLeft) {
|
||
|
depth = stackL["depth"];
|
||
|
currentStack = stackL["children"];
|
||
|
} else {
|
||
|
depth = stackR["depth"];
|
||
|
currentStack = stackR["children"];
|
||
|
}
|
||
|
|
||
|
for(let i = 0; i < depth; i++) {
|
||
|
const s = currentStack.length - 1
|
||
|
currentStack = currentStack[s];
|
||
|
}
|
||
|
|
||
|
return currentStack;
|
||
|
}
|
||
|
|
||
|
// ---------
|
||
|
|
||
|
function wrap_new() {
|
||
|
currentStack().push([]);
|
||
|
}
|
||
|
|
||
|
function wrap_pop() {
|
||
|
currentStack().pop();
|
||
|
}
|
||
|
|
||
|
function wrap_enter() {
|
||
|
depthDelta(1)
|
||
|
}
|
||
|
|
||
|
function wrap_exit() {
|
||
|
depthDelta(-1)
|
||
|
}
|
||
|
|
||
|
function wrap_warp() {
|
||
|
currentIsLeft = !currentIsLeft;
|
||
|
}
|
||
|
|
||
|
function wrap_send() {
|
||
|
let toMove = currentStack().pop();
|
||
|
|
||
|
wrap_warp();
|
||
|
|
||
|
currentStack().push(toMove);
|
||
|
|
||
|
wrap_warp()
|
||
|
}
|
||
|
|
||
|
function wrap_read(n) {
|
||
|
let thisStack = [];
|
||
|
|
||
|
for(let i = 0; i < n; i++) {
|
||
|
thisStack.push([]);
|
||
|
}
|
||
|
|
||
|
currentStack().push(thisStack);
|
||
|
}
|
||
|
|
||
|
function wrap_write() {
|
||
|
return currentStack().pop().length;
|
||
|
}
|
||
|
|
||
|
// ---------
|
||
|
|
||
|
const stdin = process.stdin
|
||
|
stdin.setRawMode(true)
|
||
|
stdin.resume()
|
||
|
let resolveNext = null
|
||
|
stdin.on("data", key => {
|
||
|
// ctrl+C and ctrl+D
|
||
|
if (key[0] === 3 || key[0] === 4) {
|
||
|
return process.exit()
|
||
|
}
|
||
|
if (resolveNext) { resolveNext(key[0]) }
|
||
|
})
|
||
|
stdin.on("end", () => process.exit())
|
||
|
const awaitKeypress = () => new Promise((resolve, reject) => { resolveNext = resolve })
|
||
|
|
||
|
const DEBUG = false
|
||
|
|
||
|
const execute = async (code, env) => {
|
||
|
let fn
|
||
|
let queue = code
|
||
|
while (fn = queue.shift()) {
|
||
|
// is bracketed, run if stack not empty
|
||
|
if (Array.isArray(fn)) {
|
||
|
if (currentStack().length !== 0) {
|
||
|
if (DEBUG) console.log("brackets", fn)
|
||
|
queue = fn.concat(queue)
|
||
|
}
|
||
|
// is a regular function call or whatever, run it
|
||
|
} else {
|
||
|
const v = env[fn]
|
||
|
if (!v) {
|
||
|
throw new Error(fn + " is undefined")
|
||
|
}
|
||
|
if (typeof v === "function") {
|
||
|
if (DEBUG) console.log("builtin", fn)
|
||
|
await v()
|
||
|
} else {
|
||
|
if (DEBUG) console.log("normal", fn)
|
||
|
queue = v.concat(queue)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const run = async () => {
|
||
|
const code = await fs.readFile(process.argv[2], { encoding: "utf8" })
|
||
|
const parseResult = program.run(code)
|
||
|
if (parseResult.isError) {
|
||
|
console.log(parseResult.error)
|
||
|
process.exit(1)
|
||
|
}
|
||
|
const parsed = parseResult.result
|
||
|
|
||
|
const env = {
|
||
|
warp: wrap_warp,
|
||
|
read: async () => {
|
||
|
wrap_read(await awaitKeypress())
|
||
|
},
|
||
|
write: () => {
|
||
|
//console.log(wrap_write())
|
||
|
process.stdout.write(Buffer.from([wrap_write()]))
|
||
|
},
|
||
|
send: wrap_send,
|
||
|
exit: wrap_exit,
|
||
|
enter: wrap_enter,
|
||
|
pop: wrap_pop,
|
||
|
new: wrap_new,
|
||
|
}
|
||
|
|
||
|
for (const def of parsed) {
|
||
|
env[def.name] = def.code
|
||
|
}
|
||
|
|
||
|
await execute(env.main, env)
|
||
|
}
|
||
|
|
||
|
const cleanup = () => {
|
||
|
process.exit(0)
|
||
|
}
|
||
|
run().then(cleanup, cleanup)
|