2024-05-17 10:00:00 +02:00

140 lines
4.3 KiB
JavaScript

import { parse } from "./parser.mjs";
function runApply(node, scope, ops) {
if (node.children.length !== 2)
throw new Error(
`expected 2 children for node type apply, got ${node.children.length}`
);
const [fn, arg] = node.children.map((child) => run(child, scope, ops));
if (typeof fn !== "function") {
throw new Error(`${node.children[0].text} is not a function`);
}
// only works if fn is curried!
return fn(arg);
}
function runInfix(left, symbol, right, ops) {
const customOp = ops[symbol];
if (customOp) {
return customOp(left, right);
}
switch (symbol) {
case "+":
return left + right;
case "-":
return left - right;
case "*":
return left * right;
case "/":
return left / right;
case "$":
return left(right);
case "&":
console.log("right", right);
return right(left);
case ".":
return (x) => left(right(x));
default:
throw new Error("unexpected infix operator " + symbol);
}
}
function curry(patterns, body, scope, ops) {
const [variable, ...rest] = patterns;
return (arg) => {
let _scope = { ...scope, [variable.text]: arg };
if (patterns.length === 1) {
const result = run(body, _scope, ops);
return result;
}
return curry(rest, body, _scope, ops);
};
}
export function run(node, scope, ops = {}) {
let runInScope = (node, scp = scope) => run(node, scp, ops);
//console.log("node", node.type, node.text);
switch (node.type) {
case "ERROR":
throw new Error(`invalid syntax: "${node.text}"`);
case "declarations":
let result;
node.children.forEach((declaration) => {
result = runInScope(declaration);
});
return result;
case "integer":
return Number(node.text);
case "float":
return Number(node.text);
case "string":
const str = node.text.slice(1, -1);
return String(str);
case "lambda":
const [_, lpatterns, __, lbody] = node.children;
return curry(lpatterns.children, lbody, scope, ops);
case "function":
const [fvariable, fpatterns, fbody] = node.children;
scope[fvariable.text] = curry(fpatterns.children, fbody, scope, ops);
return scope[fvariable.text];
case "list": {
return node.children
.filter((_, i) => i % 2 === 1) // elements are at odd indices
.map((node) => runInScope(node));
}
case "match":
if (node.children[0].text !== "=" || node.children.length !== 2) {
throw new Error("match node so far only support simple assignments");
}
return runInScope(node.children[1]);
case "bind":
if (node.children.length !== 2)
throw new Error("expected 2 children for node type bind");
if (node.children[0].type !== "variable")
throw new Error("expected variable as first child of bind node");
if (node.children[1].type !== "match")
throw new Error("expected match as first child of bind node");
const [bvariable, bmatch] = node.children;
const value = runInScope(bmatch);
scope[bvariable.text] = value;
return value;
case "variable":
return scope[node.text];
case "infix": {
const [a, op, b] = node.children;
const symbol = op.text;
const [left, right] = [runInScope(a), runInScope(b)];
return runInfix(left, symbol, right, ops);
}
case "apply":
return runApply(node, scope, ops);
case "left_section": {
const [_, b, op] = node.children;
const right = runInScope(b);
return (left) => runInfix(left, op.text, right, ops);
}
case "right_section": {
const [_, op, b] = node.children;
const right = runInScope(b);
return (left) => runInfix(left, op.text, right, ops);
}
case "parens":
if (node.children.length !== 3)
throw new Error("expected 3 children for node type parens");
return runInScope(node.children[1]);
default:
if (node.children.length === 0) {
throw new Error("unhandled leaf type " + node.type);
}
if (node.children.length > 1) {
throw new Error("unhandled branch type " + node.type);
}
return runInScope(node.children[0]);
}
}
export async function evaluate(haskellCode, scope = globalThis, ops) {
const ast = await parse(haskellCode);
return run(ast.rootNode, scope, ops);
}