diff --git a/compiler/ast/ast.ts b/compiler/ast/ast.ts index 5bc6367..e78bddc 100644 --- a/compiler/ast/ast.ts +++ b/compiler/ast/ast.ts @@ -22,7 +22,8 @@ export type StmtKind = export type ItemStmt = { item: Item }; export type LetStmt = { - subject: Pat; + pat: Pat; + ty: Ty; expr?: Expr; }; @@ -56,11 +57,18 @@ export type ItemKind = | { tag: "use" } & UseItem | { tag: "type_alias" } & TypeAliasItem; -export type ModBlockItem = { ident: Ident; stmts: Stmt[] }; -export type ModFileItem = { ident: Ident; filePath: string }; +export type ModBlockItem = { stmts: Stmt[] }; +export type ModFileItem = { filePath: string }; export type EnumItem = { variants: Variant[] }; export type StructItem = { data: VariantData }; -export type FnItem = { _: 0 }; + +export type FnItem = { + generics?: Generics; + params: Param[]; + returnTy?: Ty; + body: Block; +}; + export type UseItem = { _: 0 }; export type TypeAliasItem = { ty: Ty }; @@ -92,6 +100,21 @@ export type FieldDef = { span: Span; }; +export type Param = { + pat: Pat; + ty: Ty; + span: Span; +}; + +export type Generics = { + params: GenericParam[]; +}; + +export type GenericParam = { + ident: Ident; + span: Span; +}; + export type Expr = { kind: ExprKind; span: Span; @@ -129,7 +152,7 @@ export type StringExpr = { value: string }; export type GroupExpr = { expr: Expr }; export type ArrayExpr = { exprs: Expr[] }; export type RepeatExpr = { expr: Expr; length: Expr }; -export type StructExpr = { path?: Path; field: ExprField[] }; +export type StructExpr = { path?: Path; fields: ExprField[] }; export type RefExpr = { expr: Expr; refType: RefType; mut: boolean }; export type DerefExpr = { expr: Expr }; export type ElemExpr = { expr: Expr; elem: number }; @@ -137,8 +160,8 @@ export type FieldExpr = { expr: Expr; ident: Ident }; export type IndexExpr = { expr: Expr; index: Expr }; export type CallExpr = { expr: Expr; args: Expr[] }; export type UnaryExpr = { unaryType: UnaryType; expr: Expr }; -export type BinaryExpr = { unaryType: UnaryType; left: Expr; right: Expr }; -export type IfExpr = { cond: Expr; truthy: Block; falsy?: Block }; +export type BinaryExpr = { binaryType: BinaryType; left: Expr; right: Expr }; +export type IfExpr = { cond: Expr; truthy: Block; falsy?: Expr }; export type LoopExpr = { body: Block }; export type WhileExpr = { cond: Expr; body: Block }; export type ForExpr = { pat: Pat; expr: Expr; body: Block }; diff --git a/compiler/diagnostics.ts b/compiler/diagnostics.ts index 8c7f9d7..35d7e6a 100644 --- a/compiler/diagnostics.ts +++ b/compiler/diagnostics.ts @@ -12,6 +12,10 @@ export type Pos = { col: number; }; +export const Span = { + fromto: ({ begin }: Span, { end }: Span): Span => ({ begin, end }), +} as const; + export type Report = { severity: "fatal" | "error" | "warning" | "info"; origin?: string; @@ -31,43 +35,82 @@ function severityColor(severity: "fatal" | "error" | "warning" | "info") { return "\x1b[1m\x1b[33m"; case "info": return "\x1b[1m\x1b[34m"; - } - exhausted(severity) + } + exhausted(severity); } export function prettyPrintReport(ctx: Ctx, rep: Report) { const { severity, msg } = rep; const origin = rep.origin ? `\x1b[1m${rep.origin}:\x1b[0m ` : ""; - console.error(`${origin}${severityColor(severity)}${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`); + console.error( + `${origin}${ + severityColor(severity) + }${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`, + ); if (rep.file && (rep.span || rep.pos)) { - const errorLineOffset = 2 + const errorLineOffset = 2; const { absPath: path } = ctx.fileInfo(rep.file); const { line, col } = rep.span?.begin ?? rep.pos!; console.error(` --> ./${path}:${line}:${col}`); if (rep.span) { const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n"); - spanLines.pop() + spanLines.pop(); if (spanLines.length == 1) { - console.error(`${rep.span.begin.line.toString().padStart(4, ' ')}| ${spanLines[0]}`); - console.error(` | ${severityColor(severity)}${" ".repeat(rep.span.begin.col)}${"~".repeat(rep.span.end.col-rep.span.begin.col)}\x1b[0m`) - return + console.error( + `${rep.span.begin.line.toString().padStart(4, " ")}| ${ + spanLines[0] + }`, + ); + console.error( + ` | ${severityColor(severity)}${ + " ".repeat(rep.span.begin.col) + }${ + "~".repeat(rep.span.end.col - rep.span.begin.col) + }\x1b[0m`, + ); + return; } for (let i = 0; i < spanLines.length; i++) { - console.error(`${(rep.span.begin.line+i).toString().padStart(4, ' ')}| ${spanLines[i]}`); + console.error( + `${ + (rep.span.begin.line + i).toString().padStart(4, " ") + }| ${spanLines[i]}`, + ); if (i == 0) { - console.error(` | ${" ".repeat(rep.span.begin.col-1)}${severityColor(severity)}${"~".repeat(spanLines[i].length-(rep.span.begin.col-1))}\x1b[0m`) - } - else if (i == spanLines.length-1) { - console.error(` | ${severityColor(severity)}${"~".repeat(rep.span.end.col)}\x1b[0m`) - } - else { - console.error(` | ${severityColor(severity)}${"~".repeat(spanLines[i].length)}\x1b[0m`) + console.error( + ` | ${" ".repeat(rep.span.begin.col - 1)}${ + severityColor(severity) + }${ + "~".repeat( + spanLines[i].length - (rep.span.begin.col - 1), + ) + }\x1b[0m`, + ); + } else if (i == spanLines.length - 1) { + console.error( + ` | ${severityColor(severity)}${ + "~".repeat(rep.span.end.col) + }\x1b[0m`, + ); + } else { + console.error( + ` | ${severityColor(severity)}${ + "~".repeat(spanLines[i].length) + }\x1b[0m`, + ); } } - } - else if (rep.pos) { - console.error(`${rep.pos.line.toString().padStart(4, ' ')}| ${ctx.filePosLineText(rep.file, rep.pos)}`); - console.error(` | ${severityColor(severity)}${" ".repeat(rep.pos.col)}^\x1b[0m`) + } else if (rep.pos) { + console.error( + `${rep.pos.line.toString().padStart(4, " ")}| ${ + ctx.filePosLineText(rep.file, rep.pos) + }`, + ); + console.error( + ` | ${severityColor(severity)}${ + " ".repeat(rep.pos.col) + }^\x1b[0m`, + ); } } } diff --git a/compiler/parser/parser.ts b/compiler/parser/parser.ts index aa66507..5b67df6 100644 --- a/compiler/parser/parser.ts +++ b/compiler/parser/parser.ts @@ -1,11 +1,23 @@ import { + BinaryType, + ExprField, + PathSegment, + RefType, + UnaryType, +} from "../ast/ast.ts"; +import { + AssignType, + Block, Expr, ExprKind, File, + GenericParam, Ident, Item, ItemKind, + Param, Pat, + Path, PatKind, Stmt, StmtKind, @@ -13,11 +25,18 @@ import { TyKind, } from "../ast/ast.ts"; import { Ctx, File as CtxFile } from "../ctx.ts"; -import { Span } from "../diagnostics.ts"; -import { todo } from "../util.ts"; +import { Pos, Span } from "../diagnostics.ts"; +import { Res, todo } from "../util.ts"; import { Lexer } from "./lexer.ts"; import { Token } from "./token.ts"; +type ParseRes = Res; + +type StmtDetails = { + pub: boolean; + annos: { ident: Ident; args: Expr[]; pos: Span }[]; +}; + export class Parser { private lexer: Lexer; private currentToken: Token | null; @@ -31,7 +50,7 @@ export class Parser { } public parse(): File { - return this.parseStmts(); + return { stmts: this.parseStmts() }; } private parseStmts(): Stmt[] { @@ -42,33 +61,1071 @@ export class Parser { return stmts; } + private parseStmt(): Stmt { + if ( + ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ) { + return this.parseItemStmt(); + } else if ( + ["let", "type_alias", "return", "break"].some((tt) => this.test(tt)) + ) { + const expr = this.parseSingleLineBlockStmt(); + this.eatSemicolon(); + return expr; + } else if ( + ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt)) + ) { + const expr = this.parseMultiLineBlockExpr(); + return (this.stmt({ tag: "expr", expr }, expr.span)); + } else { + const expr = this.parseAssign(); + this.eatSemicolon(); + return expr; + } + } + + private parseMultiLineBlockExpr(): Expr { + const begin = this.span(); + if (this.test("{")) { + return this.parseBlockExpr(); + } + if (this.test("if")) { + return this.parseIf(); + } + if (this.test("loop")) { + return this.parseLoop(); + } + if (this.test("while")) { + return this.parseWhile(); + } + if (this.test("for")) { + return this.parseFor(); + } + this.report("expected expr"); + return this.expr({ tag: "error" }, begin); + } + + private parseSingleLineBlockStmt(): Stmt { + const begin = this.span(); + if (this.test("let")) { + return this.parseLet(); + } + if (this.test("type_alias")) { + return this.parseTypeAlias(); + } + if (this.test("return")) { + return this.parseReturn(); + } + if (this.test("break")) { + return this.parseBreak(); + } + this.report("expected stmt"); + return this.stmt({ tag: "error" }, begin); + } + + private eatSemicolon() { + if (!this.test(";")) { + this.report( + `expected ';', got '${this.currentToken?.type ?? "eof"}'`, + ); + return; + } + this.step(); + } + + private parseExpr(): Expr { + return this.parseBinary(); + } + + private parseBlock(): ParseRes { + const begin = this.span(); + this.step(); + const stmts: Stmt[] = []; + while (!this.done()) { + if (this.test("}")) { + const span = Span.fromto(begin, this.span()); + this.step(); + return Res.Ok({ stmts, span }); + } else if ( + ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ) { + stmts.push(this.parseItemStmt()); + } else if ( + ["let", "type_alias", "return", "break"] + .some((tt) => this.test(tt)) + ) { + stmts.push(this.parseSingleLineBlockStmt()); + this.eatSemicolon(); + } else if ( + ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt)) + ) { + const expr = this.parseMultiLineBlockExpr(); + const span = expr.span; + if (this.test("}")) { + this.step(); + return Res.Ok({ stmts, expr, span }); + } + stmts.push(this.stmt({ tag: "expr", expr }, span)); + } else { + const expr = this.parseExpr(); + if (this.test("=") || this.test("+=") || this.test("-=")) { + const assignType = this.current().type as AssignType; + this.step(); + const value = this.parseExpr(); + this.eatSemicolon(); + stmts.push( + this.stmt( + { + tag: "assign", + assignType, + subject: expr, + value, + }, + Span.fromto(expr.span, value.span), + ), + ); + } else if (this.test(";")) { + this.step(); + stmts.push(this.stmt({ tag: "expr", expr }, expr.span)); + } else if (this.test("}")) { + this.step(); + return Res.Ok({ stmts, expr, span: expr.span }); + } else { + this.report("expected ';' or '}'"); + return Res.Err(undefined); + } + } + } + this.report("expected '}'"); + return Res.Err(undefined); + } + + private parseBlockExpr(): Expr { + const block = this.parseBlock(); + return block.ok + ? this.expr({ tag: "block", ...block.val }, this.span()) + : this.expr({ tag: "error" }, this.span()); + } + + private parseItemStmt( + pos = this.span(), + details: StmtDetails = { + pub: false, + annos: [], + }, + ): Stmt { + const sbegin = this.span(); + if (this.test("#") && !details.pub) { + this.step(); + if (!this.test("[")) { + this.report("expected '['"); + return this.stmt({ tag: "error" }, sbegin); + } + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return this.stmt({ tag: "error" }, sbegin); + } + const ident = this.parseIdent(); + const args: Expr[] = []; + if (this.test("(")) { + this.step(); + if (!this.done() && !this.test(")")) { + args.push(this.parseExpr()); + while (this.test(",")) { + this.step(); + if (this.done() || this.test(")")) { + break; + } + args.push(this.parseExpr()); + } + } + if (!this.test(")")) { + this.report("expected ')'"); + return this.stmt({ tag: "error" }, sbegin); + } + this.step(); + } + if (!this.test("]")) { + this.report("expected ']'"); + return this.stmt({ tag: "error" }, sbegin); + } + this.step(); + const anno = { ident, args, pos: sbegin }; + return this.parseItemStmt(pos, { + ...details, + annos: [...details.annos, anno], + }); + } else if (this.test("pub") && !details.pub) { + this.step(); + return this.parseItemStmt(pos, { ...details, pub: true }); + } else if (this.test("mod")) { + return this.parseMod(details); + } else if (this.test("fn")) { + return this.parseFn(details); + } else { + this.report("expected item statement"); + return this.stmt({ tag: "error" }, pos); + } + } + + private parseMod(details: StmtDetails): Stmt { + const pos = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return this.stmt({ tag: "error" }, pos); + } + const ident = this.parseIdent(); + if (this.test("string")) { + const filePath = this.current().stringValue!; + this.step(); + this.eatSemicolon(); + return this.stmt({ + tag: "item", + item: this.item( + { tag: "mod_file", filePath }, + pos, + ident, + details.pub, + ), + }, pos); + } + + if (!this.test("{")) { + this.report("expected '{' or 'string'"); + return this.stmt({ tag: "error" }, pos); + } + this.step(); + + const stmts: Stmt[] = []; + while (!this.done() && !this.test("}")) { + stmts.push(this.parseStmt()); + } + + if (!this.test("}")) { + this.report("expected '}'"); + return this.stmt({ tag: "error" }, pos); + } + this.step(); + + return this.stmt({ + tag: "item", + item: this.item( + { tag: "mod_block", stmts }, + pos, + ident, + details.pub, + ), + }, pos); + } + + private parseFn(details: StmtDetails): Stmt { + const pos = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.stmt({ tag: "error" }, pos); + } + const ident = this.parseIdent(); + let genericParams: GenericParam[] | undefined; + if (this.test("<")) { + genericParams = this.parseFnTyParams(); + } + if (!this.test("(")) { + this.report("expected '('"); + return this.stmt({ tag: "error" }, pos); + } + const params = this.parseFnParams(); + let returnTy: Ty | undefined; + if (this.test("->")) { + this.step(); + returnTy = this.parseTy(); + } + + if (!this.test("{")) { + this.report("expected block"); + return this.stmt({ tag: "error" }, pos); + } + const blockRes = this.parseBlock(); + if (!blockRes.ok) { + return this.stmt({ tag: "error" }, this.span()); + } + const body = blockRes.val; + return this.stmt({ + tag: "item", + item: this.item( + { + tag: "fn", + params, + returnTy, + body, + ...(genericParams + ? { generics: { params: genericParams } } + : {}), + }, + pos, + ident, + details.pub, + ), + }, pos); + } + + private parseFnTyParams(): GenericParam[] { + return this.parseDelimitedList(this.parseTyParam, ">", ","); + } + + private parseTyParam(_index: number): ParseRes { + const span = this.span(); + if (this.test("ident")) { + const ident = this.parseIdent(); + return Res.Ok({ ident, span }); + } + this.report("expected generic parameter"); + return Res.Err(undefined); + } + + private parseFnParams(): Param[] { + return this.parseDelimitedList(this.parseParam, ")", ","); + } + + private parseDelimitedList( + parseElem: (this: Parser, index: number) => ParseRes, + endToken: string, + delimiter: string, + ): T[] { + this.step(); + if (this.test(endToken)) { + this.step(); + return []; + } + let i = 0; + const elems: T[] = []; + const elemRes = parseElem.call(this, i); + if (!elemRes.ok) { + return []; + } + elems.push(elemRes.val); + i += 1; + while (this.test(delimiter)) { + this.step(); + if (this.test(endToken)) { + break; + } + const elemRes = parseElem.call(this, i); + if (!elemRes.ok) { + return []; + } + elems.push(elemRes.val); + i += 1; + } + if (!this.test(endToken)) { + this.report(`expected '${endToken}'`); + return elems; + } + this.step(); + return elems; + } + + private parseParam(): ParseRes { + const begin = this.span(); + const pat = this.parsePat(); + if (!this.test(":")) { + this.report("expected ':'"); + return Res.Err(undefined); + } + this.step(); + const ty = this.parseTy(); + return Res.Ok({ + pat, + ty, + span: Span.fromto(begin, ty.span), + }); + } + + private parsePat(): Pat { + return todo(); + } + + private parseLet(): Stmt { + const pos = this.span(); + this.step(); + const paramResult = this.parseParam(); + if (!paramResult.ok) { + return this.stmt({ tag: "error" }, pos); + } + const { pat, ty } = paramResult.val; + if (!this.test("=")) { + this.report("expected '='"); + return this.stmt({ tag: "error" }, pos); + } + this.step(); + const expr = this.parseExpr(); + return this.stmt({ tag: "let", pat, ty, expr }, pos); + } + + private parseTypeAlias(): Stmt { + const begin = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.stmt({ tag: "error" }, begin); + } + const ident = this.parseIdent(); + if (!this.test("=")) { + this.report("expected '='"); + return this.stmt({ tag: "error" }, begin); + } + this.step(); + const ty = this.parseTy(); + return this.stmt({ + tag: "item", + item: this.item( + { + tag: "type_alias", + ty, + }, + Span.fromto(begin, ty.span), + ident, + false, + ), + }, begin); + } + + private parseAssign(): Stmt { + const pos = this.span(); + const subject = this.parseExpr(); + if (this.test("=") || this.test("+=") || this.test("-=")) { + const assignType = this.current().type as AssignType; + this.step(); + const value = this.parseExpr(); + return this.stmt({ + tag: "assign", + assignType, + subject, + value, + }, pos); + } + return this.stmt({ tag: "expr", expr: subject }, pos); + } + + private parseReturn(): Stmt { + const pos = this.span(); + this.step(); + if (this.test(";")) { + return this.stmt({ tag: "return" }, pos); + } + const expr = this.parseExpr(); + return this.stmt({ tag: "return", expr }, pos); + } + + private parseBreak(): Stmt { + const pos = this.span(); + this.step(); + if (this.test(";")) { + return this.stmt({ tag: "break" }, pos); + } + const expr = this.parseExpr(); + return this.stmt({ tag: "break", expr }, pos); + } + + private parseLoop(): Expr { + const pos = this.span(); + this.step(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ tag: "error" }, pos); + } + const bodyRes = this.parseBlock(); + if (!bodyRes.ok) { + return this.expr({ tag: "error" }, pos); + } + const body = bodyRes.val; + return this.expr({ tag: "loop", body }, pos); + } + + private parseWhile(): Expr { + const pos = this.span(); + this.step(); + const cond = this.parseExpr(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ tag: "error" }, pos); + } + const bodyRes = this.parseBlock(); + if (!bodyRes.ok) { + return this.expr({ tag: "error" }, pos); + } + const body = bodyRes.val; + return this.expr({ tag: "while", cond, body }, pos); + } + + private parseFor(): Expr { + const pos = this.span(); + this.step(); + + if (this.test("(")) { + return this.parseForClassicTail(pos); + } + + const pat = this.parsePat(); + + if (!this.test("in")) { + this.report("expected 'in'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + const expr = this.parseExpr(); + + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ tag: "error" }, pos); + } + const bodyRes = this.parseBlock(); + if (!bodyRes.ok) { + return this.expr({ tag: "error" }, pos); + } + const body = bodyRes.val; + return this.expr({ tag: "for", pat, expr, body }, pos); + } + + private parseForClassicTail(begin: Span): Expr { + this.step(); + let decl: Stmt | undefined; + if (!this.test(";")) { + decl = this.parseLet(); + } + if (!this.test(";")) { + this.report("expected ';'"); + return this.expr({ tag: "error" }, begin); + } + this.step(); + let cond: Expr | undefined; + if (!this.test(";")) { + cond = this.parseExpr(); + } + if (!this.test(";")) { + this.report("expected ';'"); + return this.expr({ tag: "error" }, begin); + } + this.step(); + let incr: Stmt | undefined; + if (!this.test(")")) { + incr = this.parseAssign(); + } + if (!this.test(")")) { + this.report("expected '}'"); + return this.expr({ tag: "error" }, begin); + } + this.step(); + + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ tag: "error" }, begin); + } + const bodyRes = this.parseBlock(); + if (!bodyRes.ok) { + return this.expr({ tag: "error" }, begin); + } + const body = bodyRes.val; + return this.expr( + { tag: "c_for", decl, cond, incr, body }, + Span.fromto(begin, body.span), + ); + } + + private parseArray(): Expr { + const pos = this.span(); + this.step(); + const exprs: Expr[] = []; + if (!this.test("]")) { + exprs.push(this.parseExpr()); + while (this.test(",")) { + this.step(); + if (this.done() || this.test("]")) { + break; + } + exprs.push(this.parseExpr()); + } + } + if (!this.test("]")) { + this.report("expected ']'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + return this.expr({ tag: "array", exprs }, pos); + } + + private parseStruct(): Expr { + const pos = this.span(); + this.step(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + const fields: ExprField[] = []; + if (!this.test("}")) { + const res = this.parseStructField(); + if (!res.ok) { + return this.expr({ tag: "error" }, this.span()); + } + fields.push(res.val); + while (this.test(",")) { + this.step(); + if (this.done() || this.test("}")) { + break; + } + const res = this.parseStructField(); + if (!res.ok) { + return this.expr({ tag: "error" }, this.span()); + } + fields.push(res.val); + } + } + if (!this.test("}")) { + this.report("expected '}'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + return this.expr({ tag: "struct", fields }, pos); + } + + private parseStructField(): ParseRes { + const span = this.span(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return Res.Err(undefined); + } + const ident = this.parseIdent(); + if (!this.test(":")) { + this.report("expected ':'"); + return Res.Err(undefined); + } + this.step(); + const expr = this.parseExpr(); + return Res.Ok({ ident, expr, span }); + } + + private parseIf(): Expr { + const pos = this.span(); + this.step(); + const cond = this.parseExpr(); + if (!this.test("{")) { + this.report("expected block"); + return this.expr({ tag: "error" }, pos); + } + const truthyRes = this.parseBlock(); + if (!truthyRes.ok) { + return this.expr({ tag: "error" }, pos); + } + const truthy = truthyRes.val; + if (!this.test("else")) { + return this.expr({ tag: "if", cond, truthy }, pos); + } + //const elsePos = this.span(); + this.step(); + if (this.test("if")) { + const falsy = this.parseIf(); + return this.expr({ tag: "if", cond, truthy, falsy }, pos); + } + if (!this.test("{")) { + this.report("expected block"); + return this.expr({ tag: "error" }, pos); + } + const falsy = this.parseBlockExpr(); + return this.expr({ tag: "if", cond, truthy, falsy }, pos); + } + + private parseBinary(): Expr { + return this.parseOr(); + } + + private parseOr(): Expr { + const pos = this.span(); + let left = this.parseAnd(); + while (true) { + if (this.test("or")) { + left = this.parBinTail(left, pos, this.parseAnd, "or"); + } else { + break; + } + } + return left; + } + + private parseAnd(): Expr { + const pos = this.span(); + let left = this.parseEquality(); + while (true) { + if (this.test("and")) { + left = this.parBinTail(left, pos, this.parseEquality, "and"); + } else { + break; + } + } + return left; + } + + private parseEquality(): Expr { + const pos = this.span(); + const left = this.parseComparison(); + if (this.test("==")) { + return this.parBinTail(left, pos, this.parseComparison, "=="); + } + if (this.test("!=")) { + return this.parBinTail(left, pos, this.parseComparison, "!="); + } + return left; + } + + private parseComparison(): Expr { + const pos = this.span(); + const left = this.parseAddSub(); + if (this.test("<")) { + return this.parBinTail(left, pos, this.parseAddSub, "<"); + } + if (this.test(">")) { + return this.parBinTail(left, pos, this.parseAddSub, ">"); + } + if (this.test("<=")) { + return this.parBinTail(left, pos, this.parseAddSub, "<="); + } + if (this.test(">=")) { + return this.parBinTail(left, pos, this.parseAddSub, ">="); + } + return left; + } + + private parseAddSub(): Expr { + const pos = this.span(); + let left = this.parseMulDiv(); + while (true) { + if (this.test("+")) { + left = this.parBinTail(left, pos, this.parseMulDiv, "+"); + continue; + } + if (this.test("-")) { + left = this.parBinTail(left, pos, this.parseMulDiv, "-"); + continue; + } + break; + } + return left; + } + + private parseMulDiv(): Expr { + const pos = this.span(); + let left = this.parsePrefix(); + while (true) { + if (this.test("*")) { + left = this.parBinTail(left, pos, this.parsePrefix, "*"); + continue; + } + if (this.test("/")) { + left = this.parBinTail(left, pos, this.parsePrefix, "/"); + continue; + } + break; + } + return left; + } + + private parBinTail( + left: Expr, + span: Span, + parseRight: (this: Parser) => Expr, + binaryType: BinaryType, + ): Expr { + this.step(); + const right = parseRight.call(this); + return this.expr( + { tag: "binary", binaryType, left, right }, + span, + ); + } + + private parsePrefix(): Expr { + const pos = this.span(); + if (this.test("not") || this.test("-")) { + const unaryType = this.current().type as UnaryType; + this.step(); + const expr = this.parsePrefix(); + return this.expr({ tag: "unary", unaryType, expr }, pos); + } + if (this.test("&")) { + this.step(); + let refType: RefType = "ref"; + if (this.test("ptr")) { + this.step(); + refType = "ptr"; + } + let mut = false; + if (this.test("mut")) { + this.step(); + mut = true; + } + const expr = this.parsePrefix(); + return this.expr({ tag: "ref", expr, mut, refType }, pos); + } + if (this.test("*")) { + this.step(); + const expr = this.parsePrefix(); + return this.expr({ tag: "deref", expr }, pos); + } + return this.parsePostfix(); + } + + private parsePostfix(): Expr { + let subject = this.parseOperand(); + while (true) { + if (this.test(".")) { + subject = this.parseFieldTail(subject); + continue; + } + if (this.test("[")) { + subject = this.parseIndexTail(subject); + continue; + } + if (this.test("(")) { + subject = this.parseCallTail(subject); + continue; + } + break; + } + return subject; + } + + private parseFieldTail(expr: Expr): Expr { + const pos = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.expr({ tag: "error" }, pos); + } + const ident = this.parseIdent(); + return this.expr({ tag: "field", expr, ident }, pos); + } + + private parseIndexTail(expr: Expr): Expr { + const pos = this.span(); + this.step(); + const index = this.parseExpr(); + if (!this.test("]")) { + this.report("expected ']'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + return this.expr({ tag: "index", expr, index }, pos); + } + + private parseCallTail(expr: Expr): Expr { + const pos = this.span(); + const args = this.parseDelimitedList( + this.parseExprArg, + ")", + ",", + ); + return this.expr({ tag: "call", expr, args }, pos); + } + + private parseExprArg(): ParseRes { + return Res.Ok(this.parseExpr()); + } + + private parseOperand(): Expr { + const pos = this.span(); + if (this.test("ident")) { + const ident = this.current().identValue!; + this.step(); + return this.expr({ tag: "ident", ident }, pos); + } + if (this.test("int")) { + const value = this.current().intValue!; + this.step(); + return this.expr({ tag: "int", value }, pos); + } + if (this.test("string")) { + const value = this.current().stringValue!; + this.step(); + return this.expr({ tag: "string", value }, pos); + } + if (this.test("false")) { + this.step(); + return this.expr({ tag: "bool", value: false }, pos); + } + if (this.test("true")) { + this.step(); + return this.expr({ tag: "bool", value: true }, pos); + } + if (this.test("null")) { + this.step(); + return this.expr({ tag: "null" }, pos); + } + if (this.test("(")) { + this.step(); + const expr = this.parseExpr(); + if (!this.test(")")) { + this.report("expected ')'"); + return this.expr({ tag: "error" }, pos); + } + this.step(); + return this.expr({ tag: "group", expr }, pos); + } + if (this.test("[")) { + return this.parseArray(); + } + if (this.test("struct")) { + return this.parseStruct(); + } + if (this.test("{")) { + return this.parseBlockExpr(); + } + if (this.test("if")) { + return this.parseIf(); + } + if (this.test("loop")) { + return this.parseLoop(); + } + + this.report(`expected expr, got '${this.current().type}'`, pos); + this.step(); + return this.expr({ tag: "error" }, pos); + } + + private parseTy(): Ty { + const pos = this.span(); + if (["null", "int", "bool", "string"].includes(this.current().type)) { + const tag = this.current().type as + | "null" + | "int" + | "bool" + | "string"; + this.step(); + return this.ty({ tag }, pos); + } + if (this.test("ident")) { + const ident = this.current().identValue!; + this.step(); + return this.ty({ tag: "ident", ident: ident }, pos); + } + if (this.test("[")) { + this.step(); + const subject = this.parseTy(); + if (!this.test("]")) { + this.report("expected ']'", pos); + return this.ty({ tag: "error" }, pos); + } + this.step(); + return this.ty({ tag: "array", subject }, pos); + } + if (this.test("struct")) { + this.step(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.ty({ tag: "error" }, pos); + } + const fields = this.parseTyStructFields(); + return this.ty({ tag: "struct", fields }, pos); + } + if (this.test("&")) { + this.step(); + let tag: "ref" | "ref_mut" = "ref"; + if (this.test("mut")) { + this.step(); + tag = "ref_mut"; + } + const subject = this.parseTy(); + return this.ty({ type, subject }, pos); + } + if (this.test("*")) { + this.step(); + let tag: "ptr" | "ptr_mut" = "ptr"; + if (this.test("mut")) { + this.step(); + tag = "ptr_mut"; + } + const subject = this.parseTy(); + return this.ty({ type, subject }, pos); + } + this.report("expected type"); + return this.ty({ tag: "error" }, pos); + } + + private parseTyStructFields(): Param[] { + this.step(); + if (this.test("}")) { + this.step(); + return []; + } + const params: Param[] = []; + const paramResult = this.parseParam(); + if (!paramResult.ok) { + return []; + } + params.push(paramResult.value); + while (this.test(",")) { + this.step(); + if (this.test("}")) { + break; + } + const paramResult = this.parseParam(); + if (!paramResult.ok) { + return []; + } + params.push(paramResult.value); + } + if (!this.test("}")) { + this.report("expected '}'"); + return params; + } + this.step(); + return params; + } + + private parsePath(): Path { + const begin = this.span(); + let end = begin; + const segments: PathSegment[] = []; + } + + private parseIdent(): Ident { + const tok = this.current(); + this.step(); + return { text: tok.identValue!, span: tok.span }; + } + private step() { this.currentToken = this.lexer.next(); } + private done(): boolean { return this.currentToken == null; } + private current(): Token { return this.currentToken!; } - private pos(): Pos { + + private span(): Span { if (this.done()) { - return this.lexer.currentPos(); + throw new Error(); } - return this.current().pos; + return this.current().span; } private test(type: string): boolean { return !this.done() && this.current().type === type; } - private report(msg: string, pos = this.pos()) { - this.reporter.reportError({ + private report(msg: string, span = this.span()) { + this.ctx.report({ + severity: "error", + origin: "parser", msg, - pos, - reporter: "Parser", + file: this.file, + span, }); - printStackTrace(); } private stmt(kind: StmtKind, span: Span): Stmt { diff --git a/compiler/parser/token.ts b/compiler/parser/token.ts index ecfdb6a..7f9a82e 100644 --- a/compiler/parser/token.ts +++ b/compiler/parser/token.ts @@ -1,16 +1,14 @@ import { Span } from "../diagnostics.ts"; -export type Token = TokenData & { +export type Token = { + type: string; span: Span; length: number; + identValue?: string; + intValue?: number; + stringValue?: string; }; -export type TokenData = - | { type: "ident"; identValue: string } - | { type: "int"; intValue: number } - | { type: "string"; stringValue: string } - | { type: string }; - export interface TokenIter { next(): Token | null; } @@ -29,4 +27,3 @@ export class SigFilter implements TokenIter { return token; } } - diff --git a/compiler/util.ts b/compiler/util.ts index fba49e0..bd4d661 100644 --- a/compiler/util.ts +++ b/compiler/util.ts @@ -15,6 +15,8 @@ export type Err = { ok: false; val: E }; export const Ok = (val: V): Ok => ({ ok: true, val }); export const Err = (val: E): Err => ({ ok: false, val }); +export const Res = { Ok, Err } as const; + export type ControlFlow< R = undefined, V = undefined,