jevstrudel.git / packages / hs2js / src / hs2js.mjs
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}