bolt/src/parser.ts

297 lines
7.2 KiB
TypeScript
Raw Normal View History

2020-02-25 12:26:21 +01:00
import {
Syntax,
Token,
FuncDecl,
Identifier,
SyntaxKind,
TokenStream,
RetStmt,
VarDecl,
Stmt,
Patt,
Expr,
BindPatt,
Param,
RefExpr,
TypeRef,
TypeDecl,
ConstExpr,
QualName
} from "./ast"
function describeKind(kind: SyntaxKind): string {
switch (kind) {
case SyntaxKind.Identifier:
return "an identifier"
case SyntaxKind.Operator:
return "an operator"
case SyntaxKind.Literal:
return "a constant literal"
case SyntaxKind.Semi:
return "';'"
case SyntaxKind.Colon:
return "':'"
case SyntaxKind.Dot:
return "'.'"
case SyntaxKind.Comma:
return "','"
case SyntaxKind.Braced:
return "'{' .. '}'"
case SyntaxKind.Bracketed:
return "'[' .. ']'"
case SyntaxKind.Parenthesized:
return "'(' .. ')'"
default:
return "a token"
}
}
function enumerate(elements: string[]) {
if (elements.length === 1) {
return elements[0]
} else {
return elements.slice(0, elements.length-2).join(',') + ' or ' + elements[elements.length-1]
}
}
export class ParseError extends Error {
constructor(public actual: Token, public expected: SyntaxKind[]) {
super(`${actual.span.file.path}:${actual.span.start.line}:${actual.span.start.column}: got ${describeKind(actual.kind)} but expected ${enumerate(expected.map(e => describeKind(e)))}`)
}
}
export class Parser {
constructor() {
}
parseQualName(tokens: TokenStream) {
const path: Identifier[] = [];
while (true) {
const t0 = tokens.peek(2);
if (t0.kind !== SyntaxKind.Dot) {
break;
}
path.push(tokens.get() as Identifier)
tokens.get();
}
const name = tokens.get();
if (name.kind !== SyntaxKind.Identifier) {
throw new ParseError(name, [SyntaxKind.Identifier]);
}
const startNode = path.length > 0 ? path[0] : name;
const endNode = name;
return new QualName(name, path, null, [startNode, endNode]);
}
parsePattern(tokens: TokenStream): Patt {
const t0 = tokens.peek(1);
if (t0.kind === SyntaxKind.Identifier) {
tokens.get();
return new BindPatt(t0, null, t0)
} else {
throw new ParseError(t0, [SyntaxKind.Identifier])
}
}
parseTypeDecl(tokens: TokenStream): TypeDecl {
const t0 = tokens.peek();
if (t0.kind === SyntaxKind.Identifier) {
const name = this.parseQualName(tokens)
return new TypeRef(name, [], null, name.origNode)
} else {
throw new ParseError(t0, [SyntaxKind.Identifier]);
}
}
parseExpr(tokens: TokenStream): Expr {
const t0 = tokens.peek();
if (t0.kind === SyntaxKind.Literal) {
return new ConstExpr(t0.value, null, t0);
} else if (t0.kind === SyntaxKind.Identifier) {
const name = this.parseQualName(tokens);
return new RefExpr(name, null, name.origNode);
} else {
throw new ParseError(t0, [SyntaxKind.Literal, SyntaxKind.Identifier]);
}
}
parseParam(tokens: TokenStream) {
let defaultValue = null;
let typeDecl = null;
const pattern = this.parsePattern(tokens)
const t0 = tokens.peek(1);
if (t0.kind === SyntaxKind.Colon) {
tokens.get();
typeDecl = this.parseTypeDecl(tokens);
const t1 = tokens.peek(1);
if (t1.kind === SyntaxKind.EqSign) {
tokens.get();
defaultValue = this.parseExpr(tokens);
}
} else if (t0.kind === SyntaxKind.EqSign) {
tokens.get();
defaultValue = this.parseExpr(tokens);
}
return new Param(pattern, typeDecl, defaultValue)
}
parseVarDecl(tokens: TokenStream): VarDecl {
}
parseRetStmt(tokens: TokenStream): RetStmt {
// Assuming first token is 'return'
const t0 = tokens.get();
let expr = null;
const t1 = tokens.peek();
if (t1.kind !== SyntaxKind.EOS) {
expr = this.parseExpr(tokens)
}
return new RetStmt(expr, null, [t0, expr.getEndNode()]);
}
parseStmts(tokens: TokenStream, origNode: Syntax | null): Stmt[] {
// TODO
return []
}
protected assertEmpty(tokens: TokenStream) {
const t0 = tokens.peek(1);
if (t0.kind !== SyntaxKind.EOS) {
throw new ParseError(t0, [SyntaxKind.EOS]);
}
}
parseFuncDecl(tokens: TokenStream, origNode: Syntax | null) {
// Assuming the first identifier is the 'fn' keyword
tokens.get();
let name: QualName;
let returnType = null;
let body = null;
let params: Param[] = [];
const t0 = tokens.peek(1);
const t1 = tokens.peek(2);
const isParamLike = (token: Token) =>
token.kind === SyntaxKind.Identifier || token.kind === SyntaxKind.Parenthesized;
const parseParamLike = (tokens: TokenStream) => {
const t0 = tokens.peek(1);
if (t0.kind === SyntaxKind.Identifier) {
tokens.get();
return new Param(new BindPatt(t0, null, t0), null, null, null, t0)
} else if (t0.kind === SyntaxKind.Parenthesized) {
tokens.get();
const innerTokens = t0.toStream();
const param = this.parseParam(innerTokens)
this.assertEmpty(innerTokens);
return param
} else {
throw new ParseError(t0, [SyntaxKind.Identifier, SyntaxKind.Parenthesized])
}
}
// Parse parameters
if (t0.kind === SyntaxKind.Operator) {
name = new QualName(t0, [], null, t0);
tokens.get();
params.push(parseParamLike(tokens))
} else if (isParamLike(t0) && t1.kind == SyntaxKind.Operator) {
params.push(parseParamLike(tokens));
name = new QualName(t1, [], null, t1);
while (true) {
const t2 = tokens.peek();
if (t2.kind !== SyntaxKind.Operator) {
break;
}
if (t2.text !== t1.text) {
throw new Error(`Operators have to match when defining or declaring an n-ary operator.`);
}
tokens.get();
params.push(parseParamLike(tokens))
}
} else if (t0.kind === SyntaxKind.Identifier) {
name = this.parseQualName(tokens)
const t2 = tokens.get();
if (t2.kind === SyntaxKind.Parenthesized) {
const innerTokens = t2.toStream();
while (true) {
const t3 = innerTokens.peek();
if (t3.kind === SyntaxKind.EOS) {
break;
}
params.push(this.parseParam(innerTokens))
const t4 = innerTokens.get();
if (t4.kind === SyntaxKind.Comma) {
continue;
} else if (t4.kind === SyntaxKind.EOS) {
break;
} else {
throw new ParseError(t4, [SyntaxKind.Comma, SyntaxKind.EOS])
}
}
}
} else {
throw new ParseError(t0, [SyntaxKind.Identifier, SyntaxKind.Operator, SyntaxKind.Parenthesized])
}
// Parse return type
const t2 = tokens.peek();
if (t2.kind === SyntaxKind.RArrow) {
tokens.get();
returnType = this.parseTypeDecl(tokens, t2);
}
// Parse function body
const t3 = tokens.peek();
if (t3.kind === SyntaxKind.Braced) {
body = this.parseStmts(tokens, t3);
}
return new FuncDecl(name, params, returnType, body, null, origNode)
}
parseDecl(tokens: TokenStream, origNode: Syntax | null) {
const t0 = tokens.peek(1);
if (t0.kind === SyntaxKind.Identifier && t0.text === 'fn') {
this.parseFuncDecl(tokens, origNode)
}
}
}