|
|
|
|
@ -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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|