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); }