@thi.ng/pointfree-lang
Version:
Forth style syntax layer/compiler & CLI for the @thi.ng/pointfree DSL
256 lines (255 loc) • 6.74 kB
JavaScript
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
};