diff --git a/src/ast.ts b/src/ast.ts index 4f3cfb6..77d74f4 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -126,8 +126,18 @@ export function prettyPrint(ast: AST, indent = 0): string { const i = ' '.repeat(indent); switch (ast.kind) { - case 'literal': - return `${i}${ast.value}`; + case 'literal': { + const val = ast.value; + switch (val.kind) { + case 'int': + case 'float': + return `${i}${val.value}`; + case 'string': + return `${i}"${val.value}"`; + default: + return `${i}${val.kind}`; + } + } case 'variable': return `${i}${ast.name}`; @@ -171,7 +181,6 @@ export function prettyPrint(ast: AST, indent = 0): string { .join('\n'); return `${i}match ${expr}\n${cases}`; - default: return `${i}${ast.kind}` } diff --git a/src/interpreter.ts b/src/interpreter.ts index 86116d9..c2b4d9d 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -1,4 +1,4 @@ -import type { AST } from './ast'; +import type { AST, Pattern } from './ast'; import type { Env } from './env'; import type { Value } from './types'; @@ -136,6 +136,24 @@ export function evaluate(ast: AST, env: Env): Value { return evaluate(func.body, callEnv); } + case 'match': { + const value = evaluate(ast.expr, env); + + for (const matchCase of ast.cases) { + const bindings = matchPattern(value, matchCase.pattern); + + if (bindings !== null) { + const newEnv = new Map(env); + for (const [name, val] of Object.entries(bindings)) { + newEnv.set(name, val); + } + return evaluate(matchCase.result, newEnv); + } + } + + throw new Error('Non-exhaustive pattern match'); + } + default: throw new Error('Syntax Error'); } @@ -206,3 +224,66 @@ function evaluateBuiltIn(op: string, left: Value, right: Value): Value { throw new Error(`Unknown built-in: ${op}`); } + +type Bindings = { [key: string]: Value }; + +function matchPattern(value: Value, pattern: Pattern): Bindings | null { + switch (pattern.kind) { + case 'wildcard': + return {}; + + case 'var': + return { [pattern.name]: value }; + + case 'literal': + if (value.kind === 'int' || value.kind === 'float' || value.kind === 'string') { + if (value.value === pattern.value) { + return {}; + } + } + return null; + + case 'constructor': { + if (value.kind !== 'constructor') return null; + if (value.name !== pattern.name) return null; + if (value.args.length !== pattern.args.length) return null; + + const bindings: Bindings = {}; + for (let i = 0; i < pattern.args.length; i++) { + const argBindings = matchPattern(value.args[i], pattern.args[i]); + if (argBindings === null) return null; + Object.assign(bindings, argBindings); + } + return bindings; + } + + case 'list': { + if (value.kind !== 'list') return null; + if (value.elements.length !== pattern.elements.length) return null; + + const bindings: Bindings = {}; + for (let i = 0; i < pattern.elements.length; i++) { + const elemBindings = matchPattern(value.elements[i], pattern.elements[i]); + if (elemBindings === null) return null; + Object.assign(bindings, elemBindings); + } + return bindings; + } + + case 'record': { + if (value.kind !== 'record') return null; + + const bindings: Bindings = {}; + for (const [fieldName, fieldPattern] of Object.entries(pattern.fields)) { + const fieldValue = value.fields[fieldName]; + if (fieldValue === undefined) return null; + + const fieldBindings = matchPattern(fieldValue, fieldPattern); + if (fieldBindings === null) return null; + Object.assign(bindings, fieldBindings); + } + return bindings; + } + } + +} diff --git a/src/main.ts b/src/main.ts index 6e53a64..0703c19 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,7 +13,7 @@ function e(str: string) { console.log(ast); console.log(prettyPrint(ast)); const env: Env = new Map(); - // console.log(evaluate(ast, env)); + console.log(evaluate(ast, env)); } e('add1 = (x \\ x + 1); add1 3'); @@ -26,5 +26,8 @@ e('add1 = (x \\ x + 1); 3 > add1'); e('[1, 2] & [3, 4]'); e('"abc" & "def"'); // e('n | 0 \\ 1 | _ \\ 99'); -e('m | Some x \\ 1 | None \\ 0'); -e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head'); +// e('m | Some x \\ 1 | None \\ 0'); +// e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head'); +e('n = 5; n | 5 \\ "five" | _ \\ "other"'); +e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z'); +e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py'); diff --git a/src/parser.ts b/src/parser.ts index 6493355..50d8542 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -179,7 +179,7 @@ export class Parser { // Record if (token.kind === 'open-brace') { this.advance(); - const fields: { [key: string]: Pattern }= []; + const fields: { [key: string]: Pattern } = {}; let first = true; while (this.current().kind !== 'close-brace') { @@ -193,7 +193,7 @@ export class Parser { } this.expect('close-brace'); - return { kind: 'list', fields }; + return { kind: 'record', fields }; } // Parens