|
|
|
@ -1,12 +1,15 @@
|
|
|
|
import type { Token } from './lexer'
|
|
|
|
import type { Token } from './lexer'
|
|
|
|
import type { AST, MatchCase, Pattern } from './ast'
|
|
|
|
import type { AST, MatchCase, Pattern } from './ast'
|
|
|
|
|
|
|
|
import { ParseError } from './error'
|
|
|
|
|
|
|
|
|
|
|
|
export class Parser {
|
|
|
|
export class Parser {
|
|
|
|
private tokens: Token[]
|
|
|
|
private tokens: Token[]
|
|
|
|
private pos: number = 0
|
|
|
|
private pos: number = 0
|
|
|
|
|
|
|
|
private source: string
|
|
|
|
|
|
|
|
|
|
|
|
constructor(tokens: Token[]) {
|
|
|
|
constructor(tokens: Token[], source: string) {
|
|
|
|
this.tokens = tokens;
|
|
|
|
this.tokens = tokens;
|
|
|
|
|
|
|
|
this.source = source;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private current(): Token {
|
|
|
|
private current(): Token {
|
|
|
|
@ -33,12 +36,20 @@ export class Parser {
|
|
|
|
const token = this.current();
|
|
|
|
const token = this.current();
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind !== kind) {
|
|
|
|
if (token.kind !== kind) {
|
|
|
|
throw new Error(`Expected ${kind}, got ${token.kind}`);
|
|
|
|
throw ParseError(`Expected ${kind}, got ${token.kind}`, token.line, token.column, this.source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.advance();
|
|
|
|
return this.advance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private getPos(token: Token) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
line: token.line,
|
|
|
|
|
|
|
|
column: token.column,
|
|
|
|
|
|
|
|
start: token.start
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private isInfixOp(): boolean {
|
|
|
|
private isInfixOp(): boolean {
|
|
|
|
const kind = this.current().kind;
|
|
|
|
const kind = this.current().kind;
|
|
|
|
return kind === 'plus' || kind === 'minus' ||
|
|
|
|
return kind === 'plus' || kind === 'minus' ||
|
|
|
|
@ -71,7 +82,7 @@ export class Parser {
|
|
|
|
case 'less-than': return 'lt';
|
|
|
|
case 'less-than': return 'lt';
|
|
|
|
case 'less-equals': return 'lte';
|
|
|
|
case 'less-equals': return 'lte';
|
|
|
|
|
|
|
|
|
|
|
|
default: throw new Error(`Not an operator: ${token.kind}`);
|
|
|
|
default: throw ParseError(`Not an operator: ${token.kind}`, token.line, token.column, this.source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -145,6 +156,7 @@ export class Parser {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private parseMatch(expr: AST): AST {
|
|
|
|
private parseMatch(expr: AST): AST {
|
|
|
|
|
|
|
|
const token = this.current();
|
|
|
|
const cases: MatchCase[] = [];
|
|
|
|
const cases: MatchCase[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
while(this.current().kind === 'pipe') {
|
|
|
|
while(this.current().kind === 'pipe') {
|
|
|
|
@ -152,10 +164,10 @@ export class Parser {
|
|
|
|
const pattern = this.parsePattern();
|
|
|
|
const pattern = this.parsePattern();
|
|
|
|
this.expect('backslash');
|
|
|
|
this.expect('backslash');
|
|
|
|
const result = this.parseExpressionNoMatch();
|
|
|
|
const result = this.parseExpressionNoMatch();
|
|
|
|
cases.push({ pattern, result })
|
|
|
|
cases.push({ pattern, result, ...this.getPos(token) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { kind: 'match', expr, cases };
|
|
|
|
return { kind: 'match', expr, cases, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parsePattern(): Pattern {
|
|
|
|
private parsePattern(): Pattern {
|
|
|
|
@ -217,7 +229,7 @@ export class Parser {
|
|
|
|
this.expect('close-bracket');
|
|
|
|
this.expect('close-bracket');
|
|
|
|
|
|
|
|
|
|
|
|
if (spreadName !== null) {
|
|
|
|
if (spreadName !== null) {
|
|
|
|
return { kind: 'list-spread', head: elements, spread: spreadName };
|
|
|
|
return { kind: 'list-spread', head: elements, spread: spreadName, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { kind: 'list', elements };
|
|
|
|
return { kind: 'list', elements };
|
|
|
|
@ -251,7 +263,7 @@ export class Parser {
|
|
|
|
return pattern;
|
|
|
|
return pattern;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(`Unexpected token in pattern: ${token.kind}`)
|
|
|
|
throw ParseError(`Unexpected token in pattern: ${token.kind}`, token.line, token.column, this.source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private canStartPattern(): boolean {
|
|
|
|
private canStartPattern(): boolean {
|
|
|
|
@ -264,6 +276,7 @@ export class Parser {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parseLambda(): AST {
|
|
|
|
private parseLambda(): AST {
|
|
|
|
|
|
|
|
const token = this.current();
|
|
|
|
const params: string[] = [];
|
|
|
|
const params: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
while (this.current().kind === 'ident') {
|
|
|
|
while (this.current().kind === 'ident') {
|
|
|
|
@ -274,7 +287,7 @@ export class Parser {
|
|
|
|
this.expect('backslash');
|
|
|
|
this.expect('backslash');
|
|
|
|
const body = this.parseExpression();
|
|
|
|
const body = this.parseExpression();
|
|
|
|
|
|
|
|
|
|
|
|
return { kind: 'lambda', params, body };
|
|
|
|
return { kind: 'lambda', params, body, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parseLet(): AST {
|
|
|
|
private parseLet(): AST {
|
|
|
|
@ -288,7 +301,7 @@ export class Parser {
|
|
|
|
name = (nameToken as { value: string }).value;
|
|
|
|
name = (nameToken as { value: string }).value;
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
throw new Error(`Expected ident or underscore, got ${nameToken.kind}`);
|
|
|
|
throw ParseError(`Expected ident or underscore, got ${nameToken.kind}`, nameToken.line, nameToken.column, this.source);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -297,10 +310,11 @@ export class Parser {
|
|
|
|
this.expect('semicolon');
|
|
|
|
this.expect('semicolon');
|
|
|
|
const body = this.parseExpressionNoMatch();
|
|
|
|
const body = this.parseExpressionNoMatch();
|
|
|
|
|
|
|
|
|
|
|
|
return { kind: 'let', name, value, body };
|
|
|
|
return { kind: 'let', name, value, body, ...this.getPos(nameToken) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parseInfix(): AST {
|
|
|
|
private parseInfix(): AST {
|
|
|
|
|
|
|
|
const token = this.current();
|
|
|
|
let left = this.parseApplication();
|
|
|
|
let left = this.parseApplication();
|
|
|
|
|
|
|
|
|
|
|
|
while (this.isInfixOp()) {
|
|
|
|
while (this.isInfixOp()) {
|
|
|
|
@ -313,7 +327,8 @@ export class Parser {
|
|
|
|
left = {
|
|
|
|
left = {
|
|
|
|
kind: 'apply',
|
|
|
|
kind: 'apply',
|
|
|
|
func: right,
|
|
|
|
func: right,
|
|
|
|
args: [left]
|
|
|
|
args: [left],
|
|
|
|
|
|
|
|
...this.getPos(token)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// operators desugar to function calls
|
|
|
|
// operators desugar to function calls
|
|
|
|
@ -322,8 +337,9 @@ export class Parser {
|
|
|
|
|
|
|
|
|
|
|
|
left = {
|
|
|
|
left = {
|
|
|
|
kind: 'apply',
|
|
|
|
kind: 'apply',
|
|
|
|
func: { kind: 'variable', name: opName },
|
|
|
|
func: { kind: 'variable', name: opName, ...this.getPos(token) },
|
|
|
|
args: [left, right]
|
|
|
|
args: [left, right],
|
|
|
|
|
|
|
|
...this.getPos(token)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -332,17 +348,19 @@ export class Parser {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parseApplication(): AST {
|
|
|
|
private parseApplication(): AST {
|
|
|
|
|
|
|
|
const token = this.current();
|
|
|
|
let func = this.parsePostfix();
|
|
|
|
let func = this.parsePostfix();
|
|
|
|
|
|
|
|
|
|
|
|
while (this.canStartPrimary()) {
|
|
|
|
while (this.canStartPrimary()) {
|
|
|
|
const arg = this.parsePostfix();
|
|
|
|
const arg = this.parsePostfix();
|
|
|
|
func = { kind: 'apply', func, args: [arg] };
|
|
|
|
func = { kind: 'apply', func, args: [arg], ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return func;
|
|
|
|
return func;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private parsePostfix(): AST {
|
|
|
|
private parsePostfix(): AST {
|
|
|
|
|
|
|
|
const token = this.current();
|
|
|
|
let expr = this.parsePrimary();
|
|
|
|
let expr = this.parsePrimary();
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
while (true) {
|
|
|
|
@ -368,13 +386,13 @@ export class Parser {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.expect('close-brace');
|
|
|
|
this.expect('close-brace');
|
|
|
|
expr = { kind: 'record-update', record: expr, updates }
|
|
|
|
expr = { kind: 'record-update', record: expr, updates, ...this.getPos(token) }
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// Record access
|
|
|
|
// Record access
|
|
|
|
const fieldToken = this.expect('ident');
|
|
|
|
const fieldToken = this.expect('ident');
|
|
|
|
const field = (fieldToken as { value: string }).value;
|
|
|
|
const field = (fieldToken as { value: string }).value;
|
|
|
|
expr = { kind: 'record-access', record: expr, field };
|
|
|
|
expr = { kind: 'record-access', record: expr, field, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
@ -397,7 +415,7 @@ export class Parser {
|
|
|
|
if (token.kind === 'open-bracket') {
|
|
|
|
if (token.kind === 'open-bracket') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
|
|
|
|
|
|
|
|
const items: (AST | { spread: AST })[] = [];
|
|
|
|
const items: AST[] = [];
|
|
|
|
let first = true;
|
|
|
|
let first = true;
|
|
|
|
|
|
|
|
|
|
|
|
while (this.current().kind !== 'close-bracket') {
|
|
|
|
while (this.current().kind !== 'close-bracket') {
|
|
|
|
@ -408,16 +426,17 @@ export class Parser {
|
|
|
|
|
|
|
|
|
|
|
|
// Spread
|
|
|
|
// Spread
|
|
|
|
if (this.current().kind === 'dot-dot-dot') {
|
|
|
|
if (this.current().kind === 'dot-dot-dot') {
|
|
|
|
|
|
|
|
const spreadToken = this.current();
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
const expr = this.parseExpression();
|
|
|
|
const expr = this.parseExpression();
|
|
|
|
items.push({ spread: expr })
|
|
|
|
items.push({ kind: 'list-spread', spread: expr, ...this.getPos(spreadToken) })
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
items.push(this.parseExpression());
|
|
|
|
items.push(this.parseExpression());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.expect('close-bracket');
|
|
|
|
this.expect('close-bracket');
|
|
|
|
return { kind: 'list', elements: items };
|
|
|
|
return { kind: 'list', elements: items, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'open-brace') {
|
|
|
|
if (token.kind === 'open-brace') {
|
|
|
|
@ -439,34 +458,34 @@ export class Parser {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.expect('close-brace');
|
|
|
|
this.expect('close-brace');
|
|
|
|
return { kind: 'record', fields };
|
|
|
|
return { kind: 'record', fields, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'int') {
|
|
|
|
if (token.kind === 'int') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
return { kind: 'literal', value: { kind: 'int', value: token.value } };
|
|
|
|
return { kind: 'literal', value: { kind: 'int', value: token.value }, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'float') {
|
|
|
|
if (token.kind === 'float') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
return { kind: 'literal', value: { kind: 'float', value: token.value } };
|
|
|
|
return { kind: 'literal', value: { kind: 'float', value: token.value }, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'string') {
|
|
|
|
if (token.kind === 'string') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
return { kind: 'literal', value: { kind: 'string', value: token.value } };
|
|
|
|
return { kind: 'literal', value: { kind: 'string', value: token.value }, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'ident') {
|
|
|
|
if (token.kind === 'ident') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
return { kind: 'variable', name: token.value };
|
|
|
|
return { kind: 'variable', name: token.value, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (token.kind === 'type-ident') {
|
|
|
|
if (token.kind === 'type-ident') {
|
|
|
|
this.advance();
|
|
|
|
this.advance();
|
|
|
|
return { kind: 'constructor', name: token.value };
|
|
|
|
return { kind: 'constructor', name: token.value, ...this.getPos(token) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(`Unexpected token: ${token.kind}`);
|
|
|
|
throw ParseError(`Unexpected token: ${token.kind}`, token.line, token.column, this.source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|