more CG syntax. parsing the easy stuff
This commit is contained in:
parent
71237d0307
commit
1ed325e98b
3 changed files with 204 additions and 82 deletions
114
src/ast.ts
114
src/ast.ts
|
|
@ -1,35 +1,24 @@
|
||||||
import type { Value } from './types';
|
import type { Value } from './types';
|
||||||
|
|
||||||
|
|
||||||
|
// Literals and Variables
|
||||||
|
|
||||||
export type Literal = {
|
export type Literal = {
|
||||||
kind: 'literal'
|
kind: 'literal'
|
||||||
value: Value
|
value: Value
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BinaryOp = {
|
|
||||||
kind: 'binaryop'
|
|
||||||
operator: string
|
|
||||||
left: AST
|
|
||||||
right: AST
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Variable = {
|
export type Variable = {
|
||||||
kind: 'variable'
|
kind: 'variable'
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Let = {
|
export type Constructor = {
|
||||||
kind: 'let'
|
kind: 'constructor'
|
||||||
name: string
|
name: string
|
||||||
value: AST
|
|
||||||
body: AST
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type If = {
|
// Functions
|
||||||
kind: 'if'
|
|
||||||
condition: AST
|
|
||||||
then: AST
|
|
||||||
else: AST
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Lambda = {
|
export type Lambda = {
|
||||||
kind: 'lambda'
|
kind: 'lambda'
|
||||||
|
|
@ -43,4 +32,93 @@ export type Apply = {
|
||||||
args: AST[]
|
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
|
||||||
|
|
|
||||||
39
src/main.ts
39
src/main.ts
|
|
@ -1,31 +1,10 @@
|
||||||
import { evaluate } from './interpreter'
|
// import { evaluate } from './interpreter'
|
||||||
import type { AST } from './ast'
|
// import type { AST } from './ast'
|
||||||
import type { Env } from './env'
|
// import type { Env } from './env'
|
||||||
import { tokenize } from './lexer'
|
import { tokenize } from './lexer'
|
||||||
import { Parser } from './parser'
|
import { Parser } from './parser'
|
||||||
|
|
||||||
const ast: AST = {
|
// const env: Env = new Map();
|
||||||
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 res = evaluate(ast, env);
|
// const res = evaluate(ast, env);
|
||||||
|
|
||||||
|
|
@ -42,13 +21,13 @@ console.log(tokenize(str));
|
||||||
// const p = new Parser(tokens);
|
// const p = new Parser(tokens);
|
||||||
// console.log(p.parse());
|
// 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("let x = 5 in x * 4");
|
||||||
// const tokens2 = tokenize("(x, y) => x + y");
|
// const tokens2 = tokenize("(x, y) => x + y");
|
||||||
|
const tokens2 = tokenize('[1, 2, 3]');
|
||||||
const p2 = new Parser(tokens2);
|
const p2 = new Parser(tokens2);
|
||||||
const ast3 = p2.parse();
|
const ast3 = p2.parse();
|
||||||
const env3: Env = new Map();
|
|
||||||
console.log(ast3);
|
console.log(ast3);
|
||||||
console.log(evaluate(ast3, env3));
|
// const env3: Env = new Map();
|
||||||
*/
|
// console.log(ast3);
|
||||||
|
// console.log(evaluate(ast3, env3));
|
||||||
|
|
|
||||||
133
src/parser.ts
133
src/parser.ts
|
|
@ -14,6 +14,7 @@ export class Parser {
|
||||||
if (this.pos >= this.tokens.length) {
|
if (this.pos >= this.tokens.length) {
|
||||||
return { kind: 'eof' } as Token;
|
return { kind: 'eof' } as Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.tokens[this.pos];
|
return this.tokens[this.pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,63 +22,134 @@ export class Parser {
|
||||||
return this.tokens[this.pos++];
|
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 {
|
parse(): AST {
|
||||||
return this.parseExpression();
|
return this.parseExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseExpression(): AST {
|
private parseExpression(): AST {
|
||||||
if (this.current().kind === 'let') {
|
let left = this.parsePrimary();
|
||||||
return this.parseLet();
|
|
||||||
|
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 {
|
private parsePrimary(): AST {
|
||||||
const token = this.current();
|
const token = this.current();
|
||||||
|
|
||||||
if (token.kind === 'open-paren') {
|
if (token.kind === 'open-paren') {
|
||||||
const savedPos = this.pos;
|
|
||||||
this.advance();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
this.pos = tempPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pos = savedPos;
|
|
||||||
|
|
||||||
if (isLambda) {
|
|
||||||
return this.parseLambda();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.advance();
|
this.advance();
|
||||||
const expr = this.parseExpression();
|
const expr = this.parseExpression();
|
||||||
this.expect('close-paren');
|
this.expect('close-paren');
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.kind === 'number') {
|
if (token.kind === 'open-bracket') {
|
||||||
this.advance();
|
this.advance();
|
||||||
|
|
||||||
|
const items: AST[] = [];
|
||||||
|
let first = true;
|
||||||
|
|
||||||
|
while (this.current().kind !== 'close-bracket') {
|
||||||
|
if (!first) {
|
||||||
|
this.expect('comma');
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
items.push(this.parseExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.expect('close-bracket');
|
||||||
|
return { kind: 'list', elements: items };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind === 'open-brace') {
|
||||||
|
this.advance();
|
||||||
|
|
||||||
|
const fields: { [key: string]: AST } = {};
|
||||||
|
let first = true;
|
||||||
|
|
||||||
|
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();
|
||||||
return { kind: 'literal', value: { kind: 'int', value: token.value } };
|
return { kind: 'literal', value: { kind: 'int', value: token.value } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token.kind === 'float') {
|
||||||
|
this.advance();
|
||||||
|
return { kind: 'literal', value: { kind: 'float', value: token.value } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind === 'string') {
|
||||||
|
this.advance();
|
||||||
|
return { kind: 'literal', value: { kind: 'string', value: token.value } };
|
||||||
|
}
|
||||||
|
|
||||||
if (token.kind === 'ident') {
|
if (token.kind === 'ident') {
|
||||||
this.advance();
|
this.advance();
|
||||||
|
|
||||||
return { kind: 'variable', name: token.value };
|
return { kind: 'variable', name: token.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected token: ${token.kind}`);
|
throw new Error(`Unexpected token: ${token.kind}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
private parseMultiplicative(): AST {
|
private parseMultiplicative(): AST {
|
||||||
let left = this.parsePostfix();
|
let left = this.parsePostfix();
|
||||||
|
|
||||||
|
|
@ -165,13 +237,6 @@ export class Parser {
|
||||||
|
|
||||||
return expr;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue