1import { parse } from './parser.mjs'; 2 3function runApply(node, scope, ops) { 4 if (node.children.length !== 2) 5 throw new Error(`expected 2 children for node type apply, got ${node.children.length}`); 6 const [fn, arg] = node.children.map((child) => run(child, scope, ops)); 7 if (typeof fn !== 'function') { 8 throw new Error(`${node.children[0].text} is not a function`); 9 } 10 // only works if fn is curried! 11 return fn(arg); 12} 13 14function runInfix(left, symbol, right, ops) { 15 const customOp = ops[symbol]; 16 if (customOp) { 17 return customOp(left, right); 18 } 19 switch (symbol) { 20 case '+': 21 return left + right; 22 case '-': 23 return left - right; 24 case '*': 25 return left * right; 26 case '/': 27 return left / right; 28 case '$': 29 return left(right); 30 case '&': 31 console.log('right', right); 32 return right(left); 33 case '.': 34 return (x) => left(right(x)); 35 default: 36 throw new Error('unexpected infix operator ' + symbol); 37 } 38} 39 40function curry(patterns, body, scope, ops) { 41 const [variable, ...rest] = patterns; 42 return (arg) => { 43 let _scope = { ...scope, [variable.text]: arg }; 44 if (patterns.length === 1) { 45 const result = run(body, _scope, ops); 46 return result; 47 } 48 return curry(rest, body, _scope, ops); 49 }; 50} 51 52export function run(node, scope, ops = {}) { 53 let runInScope = (node, scp = scope) => run(node, scp, ops); 54 //console.log("node", node.type, node.text); 55 if (ops[node.type]) { 56 return ops[node.type](node); 57 } 58 switch (node.type) { 59 case 'ERROR': 60 throw new Error(`invalid syntax: "${node.text}"`); 61 case 'declarations': 62 let result; 63 node.children.forEach((declaration) => { 64 result = runInScope(declaration); 65 }); 66 return result; 67 case 'integer': 68 return Number(node.text); 69 case 'float': 70 return Number(node.text); 71 case 'string': 72 const str = node.text.slice(1, -1); 73 return String(str); 74 case 'lambda': 75 const [_, lpatterns, __, lbody] = node.children; 76 return curry(lpatterns.children, lbody, scope, ops); 77 case 'function': 78 const [fvariable, fpatterns, fbody] = node.children; 79 scope[fvariable.text] = curry(fpatterns.children, fbody, scope, ops); 80 return scope[fvariable.text]; 81 case 'list': { 82 return node.children 83 .filter((_, i) => i % 2 === 1) // elements are at odd indices 84 .map((node) => runInScope(node)); 85 } 86 case 'match': 87 if (node.children[0].text !== '=' || node.children.length !== 2) { 88 throw new Error('match node so far only support simple assignments'); 89 } 90 return runInScope(node.children[1]); 91 case 'bind': 92 if (node.children.length !== 2) throw new Error('expected 2 children for node type bind'); 93 if (node.children[0].type !== 'variable') throw new Error('expected variable as first child of bind node'); 94 if (node.children[1].type !== 'match') throw new Error('expected match as first child of bind node'); 95 const [bvariable, bmatch] = node.children; 96 const value = runInScope(bmatch); 97 scope[bvariable.text] = value; 98 return value; 99 case 'variable': 100 return scope[node.text]; 101 case 'infix': { 102 const [a, op, b] = node.children; 103 const symbol = op.text; 104 const [left, right] = [runInScope(a), runInScope(b)]; 105 return runInfix(left, symbol, right, ops); 106 } 107 case 'apply': 108 return runApply(node, scope, ops); 109 case 'left_section': { 110 const [_, b, op] = node.children; 111 const right = runInScope(b); 112 return (left) => runInfix(left, op.text, right, ops); 113 } 114 case 'right_section': { 115 const [_, op, b] = node.children; 116 const right = runInScope(b); 117 return (left) => runInfix(left, op.text, right, ops); 118 } 119 case 'parens': 120 if (node.children.length !== 3) throw new Error('expected 3 children for node type parens'); 121 return runInScope(node.children[1]); 122 default: 123 if (node.children.length === 0) { 124 throw new Error('unhandled leaf type ' + node.type); 125 } 126 if (node.children.length > 1) { 127 throw new Error('unhandled branch type ' + node.type); 128 } 129 return runInScope(node.children[0]); 130 } 131} 132 133export function evaluate(haskellCode, scope = globalThis, ops) { 134 const ast = parse(haskellCode); 135 return run(ast.rootNode, scope, ops); 136}