UNPKG

@thi.ng/pointfree-lang

Version:

Forth style syntax layer/compiler & CLI for the @thi.ng/pointfree DSL

256 lines (255 loc) 6.74 kB
import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import { illegalState } from "@thi.ng/errors/illegal-state"; import * as pf from "@thi.ng/pointfree"; import { ALIASES } from "./api.js"; import { LOGGER } from "./logger.js"; import { SyntaxError, parse } from "./parser.js"; const __nodeLoc = (node) => node.loc ? `line ${node.loc.join(":")} -` : ""; const __resolveSym = (node, ctx) => { const id = node.id; let w = ctx[2].__words[id] || ALIASES[id] || pf[id]; if (!w) { illegalArgs(`${__nodeLoc(node)} unknown symbol: ${id}`); } return w; }; const __resolveVar = (node, ctx) => { const id = node.id; const v = ctx[2].__vars[id]; if (!v) { illegalArgs(`${__nodeLoc(node)} unknown var: ${id}`); } if (!v.length) { illegalState(`${__nodeLoc(node)} missing bindings for var: ${id}`); } return v[0]; }; const __resolveNode = (node, ctx) => { switch (node.type) { case "sym": return __resolveSym(node, ctx); case "var_deref": return __resolveVar(node, ctx); case "var_store": return __storeVar(node.id); case "array": return __resolveArray(node, ctx); case "obj": return __resolveObject(node, ctx); default: return node.body; } }; const __resolveArray = (node, ctx) => { const res = []; for (let n of node.body) { res.push(__resolveNode(n, ctx)); } return res; }; const __resolveObject = (node, ctx) => { const res = {}; for (let [k, v] of node.body) { res[k.type === "sym" ? k.id : __resolveNode(k, ctx)] = __resolveNode( v, ctx ); } return res; }; const __loadVar = (node) => (ctx) => { ctx[0].push(__resolveVar(node, ctx)); return ctx; }; const __storeVar = (id) => (ctx) => { pf.ensureStack(ctx[0], 1); const v = ctx[2].__vars[id]; if (v === void 0) { ctx[2].__vars[id] = [ctx[0].pop()]; } else { v[0] = ctx[0].pop(); } return ctx; }; const __beginvar = (id) => (ctx) => { pf.ensureStack(ctx[0], 1); const v = ctx[2].__vars[id]; if (v === void 0) { ctx[2].__vars[id] = [ctx[0].pop()]; } else { v.unshift(ctx[0].pop()); } return ctx; }; const __endvar = (id) => (ctx) => { const v = ctx[2].__vars[id]; if (v === void 0 || v.length === 0) { illegalState(`can't end scope for var: ${id}`); } v.shift(); if (!v.length) { delete ctx[2].__vars[id]; } return ctx; }; const __visit = (node, ctx, state) => { LOGGER.fine("visit", node.type, node, ctx[0].toString()); switch (node.type) { case "sym": return __visitSym(node, ctx, state); case "number": case "boolean": case "string": case "nil": ctx[0].push(node.body); return ctx; case "array": return __visitArray(node, ctx, state); case "obj": return __visitObject(node, ctx, state); case "var_deref": return __visitDeref(node, ctx, state); case "var_store": return __visitStore(node, ctx, state); case "word": return __visitWord(node, ctx, state); case "stack_comment": __visitStackComment(node, state); default: LOGGER.fine("skipping node..."); } return ctx; }; const __visitSym = (node, ctx, state) => { const w = __resolveSym(node, ctx); if (state.word) { ctx[0].push(w); return ctx; } else { return w(ctx); } }; const __visitDeref = (node, ctx, state) => (ctx[0].push(state.word ? __loadVar(node) : __resolveVar(node, ctx)), ctx); const __visitStore = (node, ctx, state) => { const store = __storeVar(node.id); return state.word ? (ctx[0].push(store), ctx) : store(ctx); }; const __pushLocals = (fn, wctx, locals) => { if (locals) { for (let stack = wctx[0], i = locals.length; i-- > 0; ) { stack.push(fn(locals[i])); } } }; const __visitWord = (node, ctx, state) => { const id = node.id; if (state.word) { illegalState( `${__nodeLoc(node)}: can't define words inside quotations (${id})` ); } let wctx = pf.ctx([], ctx[2]); state.word = { name: id, loc: node.loc }; const locals = node.locals; __pushLocals(__beginvar, wctx, locals); for (let n of node.body) { wctx = __visit(n, wctx, state); } __pushLocals(__endvar, wctx, locals); const w = pf.defWord(wctx[0]); w.__meta = state.word; ctx[2].__words[id] = w; state.word = void 0; return ctx; }; const __visitStackComment = (node, state) => { const word = state.word; if (word && !word.stack) { word.stack = node.body.join(" -- "); word.arities = node.body.map((x) => { const args = x.split(" "); return args[0] === "" ? 0 : x.indexOf("?") >= 0 ? -1 : args.length; }); } }; const __visitWithResolver = (resolve) => (node, ctx, state) => { ctx[0].push( state.word ? (_ctx) => (_ctx[0].push(resolve(node, _ctx)), _ctx) : resolve(node, ctx) ); return ctx; }; const __visitArray = __visitWithResolver(__resolveArray); const __visitObject = __visitWithResolver(__resolveObject); const __ensureEnv = (env) => { env = env || {}; if (!env.__words) { env.__words = {}; } if (!env.__vars) { env.__vars = {}; } const vars = env.__vars; for (let k in env) { if (k !== "__words" && k !== "__vars") { vars[k] = [env[k]]; } } return env; }; const __finalizeEnv = (ctx) => { const env = ctx[2]; const vars = env.__vars; delete env.__vars; for (let k in vars) { const v = vars[k]; if (v.length !== 1) { illegalState(`dangling or missing scopes for var: ${k}`); } env[k] = v[0]; } return ctx; }; const run = (src, env, stack = []) => { let ctx = pf.ctx(stack, __ensureEnv(env)); const state = {}; try { for (let node of parse(src)) { ctx = __visit(node, ctx, state); } return __finalizeEnv(ctx); } catch (e) { if (e instanceof SyntaxError) { throw new Error( `line ${e.location.start.line}:${e.location.start.column}: ${e.message}` ); } else { throw e; } } }; const runU = (src, env, stack, n = 1) => pf.unwrap(run(src, env, stack), n); const runE = (src, env, stack) => run(src, env, stack)[2]; const runWord = (id, env, stack = []) => __finalizeEnv(env.__words[id](pf.ctx(stack, __ensureEnv(env)))); const runWordU = (id, env, stack = [], n = 1) => pf.unwrap( __finalizeEnv(env.__words[id](pf.ctx(stack, __ensureEnv(env)))), n ); const runWordE = (id, env, stack = []) => __finalizeEnv(env.__words[id](pf.ctx(stack, __ensureEnv(env))))[2]; const ffi = (env, words) => { env = __ensureEnv(env); env.__words = { ...env.__words, ...words }; return env; }; import { ensureStack, ensureStackN, unwrap } from "@thi.ng/pointfree"; export { ensureStack, ensureStackN, ffi, run, runE, runU, runWord, runWordE, runWordU, unwrap };