diff --git a/compiler/ast.ts b/compiler/ast.ts index e86b9a7..1b98fe1 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -10,6 +10,7 @@ export type Mod = { export type Stmt = { kind: StmtKind; pos: Pos; + details?: StmtDetails; id: number; }; @@ -27,7 +28,6 @@ export type StmtKind = params: Param[]; returnType?: EType; body: Expr; - anno?: Anno; vtype?: VType; } | { type: "let"; param: Param; value: Expr } @@ -36,6 +36,11 @@ export type StmtKind = export type AssignType = "=" | "+=" | "-="; +export type StmtDetails = { + pub: boolean; + annos: Anno[]; +}; + export type Expr = { kind: ExprKind; pos: Pos; @@ -147,17 +152,17 @@ export type GenericParam = { export type Anno = { ident: string; - values: Expr[]; + args: Expr[]; pos: Pos; }; export class AstCreator { private nextNodeId = 0; - public stmt(kind: StmtKind, pos: Pos): Stmt { + public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt { const id = this.nextNodeId; this.nextNodeId += 1; - return { kind, pos, id }; + return { kind, pos, details, id }; } public expr(kind: ExprKind, pos: Pos): Expr { @@ -172,3 +177,21 @@ export class AstCreator { return { kind, pos, id }; } } + +export class AnnoView { + public constructor(private details?: StmtDetails) {} + + public has(...idents: string[]): boolean { + return this.details?.annos.some((anno) => + idents.some((ident) => anno.ident === ident) + ) ?? false; + } + + public get(ident: string): Anno { + const anno = this.details?.annos.find((anno) => anno.ident === ident); + if (!anno) { + throw new Error(); + } + return anno; + } +} diff --git a/compiler/checker.ts b/compiler/checker.ts index 02eb85f..d8f92da 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -1,4 +1,4 @@ -import { EType, Expr, Stmt, Sym } from "./ast.ts"; +import { AnnoView, EType, Expr, Stmt, Sym } from "./ast.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { Pos } from "./token.ts"; import { @@ -162,12 +162,12 @@ export class Checker { throw new Error(); } - if ( - stmt.kind.anno?.ident === "remainder" || - stmt.kind.anno?.ident === "builtin" - ) { + const annos = new AnnoView(stmt.details); + if (annos.has("builtin", "remainder")) { + // NOTE: handled in lowerer return; } + const { returnType } = stmt.kind.vtype!; if (returnType.type === "error") return returnType; this.fnReturnStack.push(returnType); diff --git a/compiler/lexer.ts b/compiler/lexer.ts index dea631a..b7f34f1 100644 --- a/compiler/lexer.ts +++ b/compiler/lexer.ts @@ -48,6 +48,8 @@ export class Lexer { "for", "in", "mod", + "pub", + "use", ]; if (keywords.includes(value)) { return this.token(value, pos); diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index 67fc3e0..4adc504 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -1,6 +1,6 @@ import { Builtins, Ops } from "./arch.ts"; import { Assembler, Label } from "./assembler.ts"; -import { Expr, Stmt } from "./ast.ts"; +import { AnnoView, Expr, Stmt } from "./ast.ts"; import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts"; import { Pos } from "./token.ts"; @@ -111,9 +111,15 @@ class MonoFnLowerer { for (const { ident } of stmt.kind.params) { this.locals.allocSym(ident); } - if (stmt.kind.anno?.ident === "builtin") { - this.lowerFnBuiltinBody(stmt.kind.anno.values); - } else if (stmt.kind.anno?.ident === "remainder") { + + const annos = new AnnoView(stmt.details); + if (annos.has("builtin")) { + const anno = annos.get("builtin"); + if (!anno) { + throw new Error(); + } + this.lowerFnBuiltinBody(anno.args); + } else if (annos.has("remainder")) { this.program.add(Ops.Remainder); } else { this.lowerExpr(stmt.kind.body); diff --git a/compiler/parser.ts b/compiler/parser.ts index 67c1004..5bbf616 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -10,6 +10,7 @@ import { GenericParam, Param, Stmt, + StmtDetails, StmtKind, UnaryType, } from "./ast.ts"; @@ -37,18 +38,18 @@ export class Parser { private parseStmts(): Stmt[] { const stmts: Stmt[] = []; while (!this.done()) { - stmts.push(this.parseModStmt()); + stmts.push(this.parseStmt()); } return stmts; } - private parseModStmt(): Stmt { - if (this.test("mod")) { - return (this.parseMod()); - } else if (this.test("fn")) { - return (this.parseFn()); + private parseStmt(): Stmt { + if ( + ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ) { + return this.parseItemStmt(); } else if ( - this.test("let") || this.test("return") || this.test("break") + ["let", "return", "break"].some((tt) => this.test(tt)) ) { const expr = this.parseSingleLineBlockStmt(); this.eatSemicolon(); @@ -65,42 +66,6 @@ export class Parser { } } - private parseMod(): Stmt { - const pos = this.pos(); - this.step(); - if (!this.test("ident")) { - this.report("expected 'ident'"); - return this.stmt({ type: "error" }, pos); - } - const ident = this.current().identValue!; - this.step(); - if (this.test("string")) { - const filePath = this.current().stringValue!; - this.step(); - this.eatSemicolon(); - return this.stmt({ type: "mod_file", ident, filePath }, pos); - } - - if (!this.test("{")) { - this.report("expected '{' or 'string'"); - return this.stmt({ type: "error" }, pos); - } - this.step(); - - const stmts: Stmt[] = []; - while (!this.done() && !this.test("}")) { - stmts.push(this.parseModStmt()); - } - - if (!this.test("}")) { - this.report("expected '}'"); - return this.stmt({ type: "error" }, pos); - } - this.step(); - - return this.stmt({ type: "mod_block", ident, stmts }, pos); - } - private parseMultiLineBlockExpr(): Expr { const pos = this.pos(); if (this.test("{")) { @@ -160,13 +125,14 @@ export class Parser { this.step(); return this.expr({ type: "block", stmts }, pos); } else if ( - this.test("return") || this.test("break") || this.test("let") + ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ) { + stmts.push(this.parseItemStmt()); + } else if ( + ["let", "return", "break"].some((tt) => this.test(tt)) ) { stmts.push(this.parseSingleLineBlockStmt()); this.eatSemicolon(); - } else if (this.test("fn")) { - stmts.push(this.parseSingleLineBlockStmt()); - stmts.push(this.parseFn()); } else if ( ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt)) ) { @@ -210,7 +176,103 @@ export class Parser { return this.expr({ type: "error" }, pos); } - private parseFn(): Stmt { + private parseItemStmt( + pos = this.pos(), + details: StmtDetails = { + pub: false, + annos: [], + }, + ): Stmt { + const spos = this.pos(); + if (this.test("#") && !details.pub) { + this.step(); + if (!this.test("[")) { + this.report("expected '['"); + return this.stmt({ type: "error" }, spos); + } + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return this.stmt({ type: "error" }, spos); + } + const ident = this.current().identValue!; + this.step(); + const args: Expr[] = []; + if (this.test("(")) { + this.step(); + if (!this.done() && !this.test(")")) { + args.push(this.parseExpr()); + while (this.test(",")) { + this.step(); + args.push(this.parseExpr()); + } + } + if (!this.test(")")) { + this.report("expected ')'"); + return this.stmt({ type: "error" }, spos); + } + this.step(); + } + if (!this.test("]")) { + this.report("expected ']'"); + return this.stmt({ type: "error" }, spos); + } + this.step(); + const anno = { ident, args, pos: spos }; + 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({ type: "error" }, pos); + } + } + + private parseMod(details: StmtDetails): Stmt { + const pos = this.pos(); + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return this.stmt({ type: "error" }, pos); + } + const ident = this.current().identValue!; + this.step(); + if (this.test("string")) { + const filePath = this.current().stringValue!; + this.step(); + this.eatSemicolon(); + return this.stmt({ type: "mod_file", ident, filePath }, pos); + } + + if (!this.test("{")) { + this.report("expected '{' or 'string'"); + return this.stmt({ type: "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({ type: "error" }, pos); + } + this.step(); + + return this.stmt({ type: "mod_block", ident, stmts }, pos, details); + } + + private parseFn(details: StmtDetails): Stmt { const pos = this.pos(); this.step(); if (!this.test("ident")) { @@ -234,14 +296,6 @@ export class Parser { returnType = this.parseEType(); } - let anno: Anno | undefined; - if (this.test("#")) { - const result = this.parseAnno(); - if (!result.ok) { - return this.stmt({ type: "error" }, pos); - } - anno = result.value; - } if (!this.test("{")) { this.report("expected block"); return this.stmt({ type: "error" }, pos); @@ -255,60 +309,12 @@ export class Parser { params, returnType, body, - anno, }, pos, + details, ); } - private parseAnnoArgs(): Expr[] { - this.step(); - if (!this.test("(")) { - this.report("expected '('"); - return []; - } - this.step(); - const annoArgs: Expr[] = []; - if (!this.test(")")) { - annoArgs.push(this.parseExpr()); - while (this.test(",")) { - this.step(); - if (this.test(")")) { - break; - } - annoArgs.push(this.parseExpr()); - } - } - if (!this.test(")")) { - this.report("expected ')'"); - return []; - } - this.step(); - return annoArgs; - } - - private parseAnno(): Res { - const pos = this.pos(); - this.step(); - if (!this.test("[")) { - this.report("expected '['"); - return { ok: false }; - } - this.step(); - if (!this.test("ident")) { - this.report("expected identifier"); - return { ok: false }; - } - const ident = this.current().identValue!; - const values = this.parseAnnoArgs(); - if (!this.test("]")) { - this.report("expected ']'"); - return { ok: false }; - } - this.step(); - return { ok: true, value: { ident, pos, values } }; - } - private parseFnETypeParams(): GenericParam[] { return this.parseDelimitedList(this.parseETypeParam, ">", ","); } @@ -920,8 +926,8 @@ export class Parser { printStackTrace(); } - private stmt(kind: StmtKind, pos: Pos): Stmt { - return this.astCreator.stmt(kind, pos); + private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt { + return this.astCreator.stmt(kind, pos, details); } private expr(kind: ExprKind, pos: Pos): Expr { diff --git a/examples/annos.slg b/examples/annos.slg index 3f1bb76..e470713 100644 --- a/examples/annos.slg +++ b/examples/annos.slg @@ -1,4 +1,7 @@ -fn print(msg: string) #[builtin(print)] { +// + +#[builtin(Print)] +fn print(msg: string) { "hello" + 0 }