Skip to content

Instantly share code, notes, and snippets.

@EduardoRFS
Created April 20, 2020 03:11
Show Gist options
  • Save EduardoRFS/85203eb0221cc112ba98d4c924c9f0c8 to your computer and use it in GitHub Desktop.
Save EduardoRFS/85203eb0221cc112ba98d4c924c9f0c8 to your computer and use it in GitHub Desktop.
/*
features:
- auto currying
- closures
- recursion
- basic branching
- ffi with JS
missing:
- tail call recursion
to use just get the AST from some ESTree
*/
type Tree = any;
type Value = boolean | number | Closure | Function;
type Scope = { [key: string]: Value };
type Closure = readonly [Scope, [{ name: string }], Tree];
function evaluate_declaration(scope, node): Scope {
const declaration = (scope, node) => {
[node] = node.declarations;
const id = node.id.name;
const innerScope = { ...scope, [id]: undefined };
innerScope[id] = evaluate_expression(innerScope, node.init);
// TODO: yeah ... mutation, sorry about that, no proper lazy evaluation
return innerScope;
};
switch (node.type) {
case 'VariableDeclaration':
return declaration(scope, node);
}
}
function evaluate_expression(scope, node): Value {
const literal = (scope, node) => node.value;
const identifier = (scope, node) => {
if (!(node.name in scope)) {
throw new Error(`unknown identifier ${node.name}`);
}
return scope[node.name];
};
const call_expression = (scope, node) => {
const closure = evaluate_expression(scope, node.callee);
const args = node.arguments.map((arg) => evaluate_expression(scope, arg));
if (typeof closure === 'function') {
return closure(...args);
}
if (!Array.isArray(closure)) {
throw new Error(`${closure} is not a function`);
}
const [closureScope, params, body] = closure;
const callScope = params
.slice(0, node.arguments.length)
.map((param, i) => [param, args[i]] as const)
.reduce(
(scope, [param, argument]) => ({
...scope,
[param.name]: argument,
}),
closureScope
);
return params.length === node.arguments.length
? evaluate_expression(callScope, body)
: ([callScope, params.slice(node.arguments.length), body] as const);
};
const conditional_expression = (scope, node) =>
evaluate_expression(scope, node.test)
? evaluate_expression(scope, node.consequent)
: evaluate_expression(scope, node.alternate);
const binary_expression = (scope, node) =>
call_expression(scope, {
callee: { type: 'Identifier', name: node.operator },
arguments: [node.left, node.right],
});
const arrow_function_expression = (scope, node) =>
[scope, node.params, node.body] as const;
const is_declaration = (node) => node.type === 'VariableDeclaration';
switch (node.type) {
case 'Program':
return node.body.reduce(
([scope], node) =>
is_declaration(node)
? [evaluate_declaration(scope, node), null]
: [scope, evaluate_expression(scope, node)],
[scope, null]
)[1];
case 'ArrowFunctionExpression':
return arrow_function_expression(scope, node);
case 'ConditionalExpression':
return conditional_expression(scope, node);
case 'CallExpression':
return call_expression(scope, node);
case 'ExpressionStatement':
return evaluate_expression(scope, node.expression);
case 'Identifier':
return identifier(scope, node);
case 'Literal':
return literal(scope, node);
case 'BinaryExpression':
return binary_expression(scope, node);
}
}
function evaluate(node, scope = {}) {
scope = {
...scope,
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
'==': (a, b) => a === b,
'!=': (a, b) => a !== b,
log: (x) => {
console.log(x);
return x;
},
};
return evaluate_expression(scope, node);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment