cleaning up interpreter. adding more types. fixing function application, handling infix binary ops, added currying
This commit is contained in:
parent
b6f7e63e49
commit
f2f81b5ed6
4 changed files with 82 additions and 41 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import type { Value } from './types';
|
||||
|
||||
|
||||
// Literals and Variables
|
||||
|
||||
export type Literal = {
|
||||
|
|
|
|||
|
|
@ -13,28 +13,11 @@ export function evaluate(ast: AST, env: Env): Value {
|
|||
throw new Error(`Unknown variable: ${ast.name}`);
|
||||
return val;
|
||||
|
||||
case 'binaryop':
|
||||
const left = evaluate(ast.left, env);
|
||||
const right = evaluate(ast.right, env);
|
||||
return evaluateBinaryOp(ast.operator, left, right);
|
||||
|
||||
case 'let':
|
||||
const val2 = evaluate(ast.value, env);
|
||||
const newEnv = new Map(env).set(ast.name, val2);
|
||||
return evaluate(ast.body, newEnv);
|
||||
|
||||
case 'if':
|
||||
const res = evaluate(ast.condition, env);
|
||||
|
||||
if (res.kind !== 'bool')
|
||||
throw new Error('Condition must be bool');
|
||||
|
||||
if (res.value) {
|
||||
return evaluate(ast.then, env);
|
||||
} else {
|
||||
return evaluate(ast.else, env);
|
||||
}
|
||||
|
||||
case 'lambda':
|
||||
return {
|
||||
kind: 'closure',
|
||||
|
|
@ -44,21 +27,54 @@ export function evaluate(ast: AST, env: Env): Value {
|
|||
}
|
||||
|
||||
case 'apply':
|
||||
// Operators
|
||||
if (ast.func.kind === 'variable') {
|
||||
const name = ast.func.name;
|
||||
const builtIns = ['+', '-', '*', '/', '>', '&'];
|
||||
|
||||
if (builtIns.includes(name)) {
|
||||
const argValues = ast.args.map(arg => evaluate(arg, env));
|
||||
|
||||
if (argValues.length !== 2) {
|
||||
throw new Error(`${name} expects 2 args`);
|
||||
}
|
||||
|
||||
return evaluateBuiltIn(name, argValues[0], argValues[1]);
|
||||
}
|
||||
}
|
||||
|
||||
const func = evaluate(ast.func, env);
|
||||
|
||||
if (func.kind !== 'closure')
|
||||
throw new Error('Not a closure');
|
||||
throw new Error('Not a function');
|
||||
|
||||
const params = func.params;
|
||||
const argValues = ast.args.map(arg => evaluate(arg, env));
|
||||
|
||||
// Too few args (Currying)
|
||||
if (argValues.length < func.params.length) {
|
||||
// Bind the params we have
|
||||
const newEnv = new Map(func.env);
|
||||
|
||||
for (let i = 0; i < argValues.length; i++) {
|
||||
newEnv.set(func.params[i], argValues[i]);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'closure',
|
||||
params: func.params.slice(argValues.length),
|
||||
body: func.body,
|
||||
env: newEnv
|
||||
};
|
||||
}
|
||||
|
||||
// Too many args
|
||||
if (argValues.length > func.params.length)
|
||||
throw new Error('Too many arguments');
|
||||
|
||||
// Exact number of args
|
||||
const callEnv = new Map(func.env);
|
||||
const args = ast.args;
|
||||
|
||||
if (args.length !== params.length)
|
||||
throw new Error('Parameter argument mismatch');
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = evaluate(args[i], env);
|
||||
const param = params[i];
|
||||
callEnv.set(param, arg);
|
||||
for (let i = 0; i < argValues.length; i++) {
|
||||
callEnv.set(func.params[i], argValues[i]);
|
||||
}
|
||||
|
||||
return evaluate(func.body, callEnv);
|
||||
|
|
@ -97,3 +113,17 @@ function evaluateBinaryOp(op: string, left: Value, right: Value): Value {
|
|||
throw new Error(`Unknown operation: ${op}`);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateBuiltIn(op: string, left: Value, right: Value): Value {
|
||||
if (op === '+' || op === '-' || op === '*' || op === '/') {
|
||||
return evaluateBinaryOp(op, left, right);
|
||||
}
|
||||
|
||||
if (op === '>')
|
||||
throw new Error('TODO Not implemented yet');
|
||||
|
||||
if (op === '&')
|
||||
throw new Error('TODO Not implemented yet');
|
||||
|
||||
throw new Error(`Unknown built-in: ${op}`);
|
||||
}
|
||||
|
|
|
|||
13
src/main.ts
13
src/main.ts
|
|
@ -1,6 +1,6 @@
|
|||
// import { evaluate } from './interpreter'
|
||||
import { evaluate } from './interpreter'
|
||||
// import type { AST } from './ast'
|
||||
// import type { Env } from './env'
|
||||
import type { Env } from './env'
|
||||
import { tokenize } from './lexer'
|
||||
import { Parser } from './parser'
|
||||
import { prettyPrint } from './ast';
|
||||
|
|
@ -25,11 +25,12 @@ console.log(tokenize(str));
|
|||
// const tokens2 = tokenize("let x = (y) => 5 + y in x(3)");
|
||||
// const tokens2 = tokenize("let x = 5 in x * 4");
|
||||
// const tokens2 = tokenize("(x, y) => x + y");
|
||||
const tokens2 = tokenize('point { x = 3 }');
|
||||
// const tokens2 = tokenize('point { x = 3 }');
|
||||
// const tokens2 = tokenize('add1 = (x \\ x + 1); add1 3');
|
||||
const tokens2 = tokenize('sum = (x y \\ x + y); sum 5 3');
|
||||
const p2 = new Parser(tokens2);
|
||||
const ast3 = p2.parse();
|
||||
console.log(ast3);
|
||||
console.log(prettyPrint(ast3));
|
||||
// const env3: Env = new Map();
|
||||
// console.log(ast3);
|
||||
// console.log(evaluate(ast3, env3));
|
||||
const env3: Env = new Map();
|
||||
console.log(evaluate(ast3, env3));
|
||||
|
|
|
|||
23
src/types.ts
23
src/types.ts
|
|
@ -16,11 +16,6 @@ export type StringValue = {
|
|||
value: string
|
||||
}
|
||||
|
||||
export type BoolValue = {
|
||||
kind: 'bool'
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export type Closure = {
|
||||
kind: 'closure'
|
||||
params: string[]
|
||||
|
|
@ -28,4 +23,20 @@ export type Closure = {
|
|||
env: Env
|
||||
}
|
||||
|
||||
export type Value = IntValue | FloatValue | StringValue | BoolValue | Closure;
|
||||
export type ListValue = {
|
||||
kind: 'list'
|
||||
elements: Value[]
|
||||
}
|
||||
|
||||
export type RecordValue = {
|
||||
kind: 'record'
|
||||
fields: { [key: string]: Value }
|
||||
}
|
||||
|
||||
export type ConstructorValue = {
|
||||
kind: 'constructor'
|
||||
name: string
|
||||
args: Value[]
|
||||
}
|
||||
|
||||
export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue