diff --git a/src/ast.ts b/src/ast.ts index 3ff31a0..0031515 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,35 +1,24 @@ import type { Value } from './types'; + +// Literals and Variables + export type Literal = { kind: 'literal' value: Value } -export type BinaryOp = { - kind: 'binaryop' - operator: string - left: AST - right: AST -} - export type Variable = { kind: 'variable' name: string } -export type Let = { - kind: 'let' +export type Constructor = { + kind: 'constructor' name: string - value: AST - body: AST } -export type If = { - kind: 'if' - condition: AST - then: AST - else: AST -} +// Functions export type Lambda = { kind: 'lambda' @@ -43,4 +32,93 @@ export type Apply = { args: AST[] } -export type AST = Literal | BinaryOp | Variable | Let | If | Lambda | Apply +// Bindings + +export type Let = { + kind: 'let' + name: string + value: AST + body: AST +} + +// Matching + +export type Match = { + kind: 'match' + expr: AST + cases: MatchCase[] +} + +export type MatchCase = { + pattern: Pattern + result: AST +} + +export type Pattern = + | { kind: 'wildcard' } + | { kind: 'var', name: string } + | { kind: 'literal', value: number | string } + | { kind: 'constructor', name: string, args: Pattern[] } + | { kind: 'list', elements: Pattern[] } + | { kind: 'record', fields: { [key: string]: Pattern } } + +// Data Structures + +export type Record = { + kind: 'record' + fields: { [key: string]: AST } +} + +export type RecordAccess = { + kind: 'record-access' + record: AST + field: string +} + +export type RecordUpdate = { + kind: 'record-update' + record: AST + updates: { [key: string]: AST } +} + +export type List = { + kind: 'list' + elements: AST[] +} + +// Top-level constructs + +export type Definition = { + kind: 'definition' + name: string + body: AST + // type?: string // TODO +} + +export type TypeDef = { + kind: 'typedef' + name: string + variants: Array<{ name: string, args: string[] }> +} + +export type Import = { + kind: 'import' + module: string + items: string[] | 'all' +} + +export type AST = + | Literal + | Variable + | Constructor + | Lambda + | Apply + | Let + | Match + | Record + | RecordAccess + | RecordUpdate + | List + | Definition + | TypeDef + | Import diff --git a/src/main.ts b/src/main.ts index 08f3329..5b875ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,31 +1,10 @@ -import { evaluate } from './interpreter' -import type { AST } from './ast' -import type { Env } from './env' +// import { evaluate } from './interpreter' +// import type { AST } from './ast' +// import type { Env } from './env' import { tokenize } from './lexer' import { Parser } from './parser' -const ast: AST = { - kind: 'binaryop', - operator: '+', - left: { - kind: 'literal', - value: { kind: 'int', value: 1 } - }, - right: { - kind: 'binaryop', - operator: '*', - left: { - kind: 'literal', - value: { kind: 'int', value: 2 } - }, - right: { - kind: 'literal', - value: { kind: 'int', value: 3 } - } - } -}; - -const env: Env = new Map(); +// const env: Env = new Map(); // const res = evaluate(ast, env); @@ -42,13 +21,13 @@ console.log(tokenize(str)); // const p = new Parser(tokens); // console.log(p.parse()); -/* -const tokens2 = tokenize("let x = (y) => 5 + y in x(3)"); +// 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('[1, 2, 3]'); const p2 = new Parser(tokens2); const ast3 = p2.parse(); -const env3: Env = new Map(); console.log(ast3); -console.log(evaluate(ast3, env3)); -*/ +// const env3: Env = new Map(); +// console.log(ast3); +// console.log(evaluate(ast3, env3)); diff --git a/src/parser.ts b/src/parser.ts index df16e4f..932e756 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -14,6 +14,7 @@ export class Parser { if (this.pos >= this.tokens.length) { return { kind: 'eof' } as Token; } + return this.tokens[this.pos]; } @@ -21,63 +22,134 @@ export class Parser { return this.tokens[this.pos++]; } + private expect(kind: Token['kind']): Token { + const token = this.current(); + + if (token.kind !== kind) { + throw new Error(`Expected ${kind}, got ${token.kind}`); + } + + return this.advance(); + } + + private isInfixOp(): boolean { + const kind = this.current().kind; + return kind === 'plus' || kind === 'minus' || + kind === 'star' || kind === 'slash' || + kind === 'greater-than' || kind === 'ampersand'; + + } + + private tokenToOpName(token: Token): string { + switch (token.kind) { + case 'plus': return '+'; + case 'minus': return '-'; + case 'star': return '*'; + case 'slash': return '/'; + case 'greater-than': return '>'; + case 'ampersand': return '&'; + default: throw new Error(`Not an operator: ${token.kind}`); + } + } + parse(): AST { return this.parseExpression(); } private parseExpression(): AST { - if (this.current().kind === 'let') { - return this.parseLet(); + let left = this.parsePrimary(); + + while (this.isInfixOp()) { + const opToken = this.advance(); + const opName = this.tokenToOpName(opToken); + const right = this.parsePrimary(); + + left = { + kind: 'apply', + func: { kind: 'variable', name: opName }, + args: [left, right] + } } - return this.parseAdditive(); + + return left; } private parsePrimary(): AST { const token = this.current(); if (token.kind === 'open-paren') { - const savedPos = this.pos; this.advance(); + const expr = this.parseExpression(); + this.expect('close-paren'); + return expr; + } - let isLambda = false; - if (this.current().kind === 'close-paren') { - isLambda = true; - } else if (this.current().kind === 'ident') { - let tempPos = this.pos; - this.advance(); - if (this.current().kind === 'comma' || (this.current().kind === 'close-paren' && this.tokens[this.pos + 1]?.kind === 'arrow')) { - isLambda = true; + if (token.kind === 'open-bracket') { + this.advance(); + + const items: AST[] = []; + let first = true; + + while (this.current().kind !== 'close-bracket') { + if (!first) { + this.expect('comma'); } - this.pos = tempPos; + first = false; + + items.push(this.parseExpression()); } - this.pos = savedPos; + this.expect('close-bracket'); + return { kind: 'list', elements: items }; + } + + if (token.kind === 'open-brace') { + this.advance(); + + const fields: { [key: string]: AST } = {}; + let first = true; - if (isLambda) { - return this.parseLambda(); + while (this.current().kind !== 'close-brace') { + if (!first) { + this.expect('comma'); + } + first = false; + + const keyToken = this.expect('ident'); + const key = (keyToken as { value: string }).value; + this.expect('equals'); + fields[key] = this.parseExpression(); } + this.expect('close-brace'); + return { kind: 'record', fields }; + } + + if (token.kind === 'int') { this.advance(); - const expr = this.parseExpression(); - this.expect('close-paren'); - return expr; + return { kind: 'literal', value: { kind: 'int', value: token.value } }; } - if (token.kind === 'number') { + if (token.kind === 'float') { this.advance(); + return { kind: 'literal', value: { kind: 'float', value: token.value } }; + } - return { kind: 'literal', value: { kind: 'int', value: token.value } }; + if (token.kind === 'string') { + this.advance(); + return { kind: 'literal', value: { kind: 'string', value: token.value } }; } if (token.kind === 'ident') { this.advance(); - return { kind: 'variable', name: token.value }; } throw new Error(`Unexpected token: ${token.kind}`); } + /* + private parseMultiplicative(): AST { let left = this.parsePostfix(); @@ -165,13 +237,6 @@ export class Parser { return expr; } + */ - private expect(kind: Token['kind']): Token { - const token = this.current(); - if (token.kind !== kind) { - throw new Error(`Expected ${kind}, got ${token.kind}`); - } - - return this.advance(); - } }