more CG syntax. parsing the easy stuff

This commit is contained in:
Dustin Swan 2026-02-01 00:03:32 -07:00
parent 71237d0307
commit 1ed325e98b
No known key found for this signature in database
GPG key ID: 30D46587E2100467
3 changed files with 204 additions and 82 deletions

View file

@ -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();
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();
const expr = this.parseExpression();
this.expect('close-paren');
return expr;
}
if (token.kind === 'number') {
if (token.kind === 'open-bracket') {
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 } };
}
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') {
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();
}
}