From f2f81b5ed6d96782fb5d700d181bd3a05e99ecd1 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 1 Feb 2026 16:06:06 -0700 Subject: [PATCH] cleaning up interpreter. adding more types. fixing function application, handling infix binary ops, added currying --- src/ast.ts | 1 - src/interpreter.ts | 84 +++++++++++++++++++++++++++++++--------------- src/main.ts | 13 +++---- src/types.ts | 23 +++++++++---- 4 files changed, 81 insertions(+), 40 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index f755a11..416c069 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,6 +1,5 @@ import type { Value } from './types'; - // Literals and Variables export type Literal = { diff --git a/src/interpreter.ts b/src/interpreter.ts index 2ffe6ab..849ea47 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -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 callEnv = new Map(func.env); - const args = ast.args; + const argValues = ast.args.map(arg => evaluate(arg, env)); - if (args.length !== params.length) - throw new Error('Parameter argument mismatch'); + // 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 < 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++) { + 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); + 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}`); +} diff --git a/src/main.ts b/src/main.ts index e696ddc..eb3e294 100644 --- a/src/main.ts +++ b/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)); diff --git a/src/types.ts b/src/types.ts index c2e8fe5..e22fabb 100644 --- a/src/types.ts +++ b/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;