diff --git a/example.lang4 b/example.lang4 index a656dd0..7e469d1 100644 --- a/example.lang4 +++ b/example.lang4 @@ -4,8 +4,13 @@ fn main() -> int { let ch = 'c'; let s = "hello\ world"; + + inner(); + + fn inner() { + print_int(v); + } - print_int(v); print_str(v); } diff --git a/src/ast.ts b/src/ast.ts index 79e5ac6..80e3d33 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -173,6 +173,10 @@ export class Ty { export type TyKind = | { tag: "error" } + | { tag: "int" } + | { tag: "bool" } + | { tag: "char" } + | { tag: "str" } | { tag: "ident"; ident: string }; export const VisitorBreak = Symbol(); diff --git a/src/main.ts b/src/main.ts index 3c4e299..5084c0a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,11 +10,22 @@ async function main() { const parser = new Parser(toks); const file = parser.parseFile(); + if (parser.errorOccured) { + console.error("parsing failed"); + Deno.exit(1); + } // console.log(yaml.stringify({ file }, { skipInvalid: true, indent: 2 })); // console.log(JSON.stringify({ file }, null, 2)); - file.visit(new Resolver()); + const resolver = new Resolver(); + const syms = resolver.resolveFile(file); + if (resolver.errorOccured) { + console.error("resolving failed"); + Deno.exit(1); + } + + console.log(syms); // console.log(cx); } diff --git a/src/parse.ts b/src/parse.ts index 3a125e2..c058406 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -190,7 +190,15 @@ export class Parser { parseTy(): Ty { const line = this.line(); - if (this.eat("ident")) { + if (this.eat("int")) { + return t.ty(line, "int", {}); + } else if (this.eat("bool")) { + return t.ty(line, "bool", {}); + } else if (this.eat("char")) { + return t.ty(line, "char", {}); + } else if (this.eat("str")) { + return t.ty(line, "str", {}); + } else if (this.eat("ident")) { const ident = this.eaten!.value!; return t.ty(line, "ident", { ident }); } else { @@ -242,7 +250,9 @@ export class Parser { return this.i >= this.toks.length; } + public errorOccured = false; private error(line: number, message: string) { + this.errorOccured = true; console.error( `%cerror%c: ${message}\n %c--> line ${line}%c`, "font-weight: bold; color: red", diff --git a/src/resolve.ts b/src/resolve.ts index 621858c..a6d8345 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -1,9 +1,56 @@ -import { Pat, Stmt, Visitor, VisitorBreak } from "./ast.ts"; +import { + Block, + Expr, + File, + Pat, + Stmt, + Ty, + Visitor, + VisitorBreak, +} from "./ast.ts"; + +class Res { + constructor( + private kind: ResKind, + ) {} + + is(tag: ResKind["tag"]): boolean { + return this.kind.tag === tag; + } +} + +type ResKind = + | { tag: "unresolved" } + | { tag: "fn"; def: Def } + | { tag: "local"; def: Def }; export class Def { constructor( - kind: DefKind, + private kind: DefKind, ) {} + + resolve(): Res { + const k = this.kind; + switch (k.tag) { + case "fn": + return new Res({ tag: "fn", def: this }); + case "param": + case "let": + return new Res({ tag: "local", def: this }); + } + } + + line(): number { + const k = this.kind; + switch (k.tag) { + case "fn": + return k.stmt.line; + case "param": + return k.pat.line; + case "let": + return k.pat.line; + } + } } export type DefKind = @@ -11,6 +58,10 @@ export type DefKind = | { tag: "param"; stmt: Stmt; pat: Pat } | { tag: "let"; stmt: Stmt; pat: Pat }; +type DefineResult = + | { ok: true } + | { ok: false; error: "redefinition"; originalDef: Def }; + export class Rib { private syms = new Map(); @@ -18,64 +69,231 @@ export class Rib { private kind: RibKind, ) {} - define(ident: string, def: Def) { + define(ident: string, def: Def): DefineResult { + if (this.syms.has(ident)) { + return { + ok: false, + error: "redefinition", + originalDef: this.syms.get(ident)!, + }; + } this.syms.set(ident, def); + return { ok: true }; + } + + resolve(ident: string): Res { + const k = this.kind; + const sym = this.syms.get(ident); + if (!sym) { + if (k.tag === "root") { + return new Res({ tag: "unresolved" }); + } + return k.parent.resolve(ident); + } + const res = sym.resolve(); + if (k.tag === "fn" && res.is("local")) { + return new Res({ tag: "unresolved" }); + } + return res; } } export type RibKind = | { tag: "root" } | { tag: "fn"; parent: Rib } + | { tag: "param"; parent: Rib } | { tag: "block"; parent: Rib } | { tag: "let"; parent: Rib }; +export class Syms { + constructor( + private astNodeDefs: Map, + ) {} + + exprRes(expr: Expr): Res { + const res = this.astNodeDefs.get(expr.id); + if (!res) throw new Error(); + return res; + } + + patRes(pat: Pat): Res { + const res = this.astNodeDefs.get(pat.id); + if (!res) throw new Error(); + return res; + } + + tyRes(ty: Ty): Res { + const res = this.astNodeDefs.get(ty.id); + if (!res) throw new Error(); + return res; + } +} + export class Resolver implements Visitor { private rib = new Rib({ tag: "root" }); - private ribStateStack: Rib[] = []; + private scopeStack: Rib[] = []; + private astNodeDefs = new Map(); - pushRib( - tag: Tag, - kind: Omit, - ) { - this.rib = new Rib({ tag, parent: this.rib, ...kind } as RibKind); - } + resolveFile(file: File): Syms { + file.visit({ + visitStmt: (stmt): void | VisitorBreak => { + const k = stmt.kind; + if (k.tag === "fn") { + this.tryDefine( + stmt.line, + k.ident, + new Def({ tag: "fn", stmt }), + ); + } + return VisitorBreak; + }, + }); - saveRibState() { - this.ribStateStack.push(this.rib); - } - - restoreRibState() { - this.rib = this.ribStateStack.pop()!; - } - - define(ident: string, def: Def) { - this.rib.define(ident, def); + file.visit(this); + return new Syms(this.astNodeDefs); } visitStmt(stmt: Stmt): void | VisitorBreak { const k = stmt.kind; if (k.tag === "fn") { - this.define(k.ident, new Def({ tag: "fn", stmt })); - this.saveRibState(); + this.saveScope(); this.pushRib("fn", {}); - k.params.forEach((param) => param.visit(this)); + + k.body.visit({ + visitStmt: (stmt): void | VisitorBreak => { + const k = stmt.kind; + if (k.tag === "fn") { + this.tryDefine( + stmt.line, + k.ident, + new Def({ tag: "fn", stmt }), + ); + } + return VisitorBreak; + }, + }); + + this.pushRib("param", {}); + for (const param of k.params) { + param.pat.visit({ + visitPat: (pat: Pat) => { + const k = pat.kind; + if (k.tag !== "ident") { + return; + } + this.tryDefine( + pat.line, + k.ident, + new Def({ tag: "param", stmt, pat }), + ); + }, + }); + } + k.retTy?.visit(this); k.body.visit(this); - this.restoreRibState(); + + this.restoreScope(); return VisitorBreak; } else if (k.tag === "let") { this.pushRib("let", {}); k.param.pat.visit({ visitPat: (pat: Pat) => { const k = pat.kind; - if (k.tag === "ident") { - this.define( - k.ident, - new Def({ tag: "let", stmt, pat }), - ); + if (k.tag !== "ident") { + return; } + this.tryDefine( + pat.line, + k.ident, + new Def({ tag: "let", stmt, pat }), + ); }, }); } } + + visitBlock(block: Block): void | VisitorBreak { + this.saveScope(); + this.pushRib("block", {}); + block.stmts.forEach((stmt) => stmt.visit(this)); + block.expr?.visit(this); + this.restoreScope(); + return VisitorBreak; + } + + visitExpr(expr: Expr): void | VisitorBreak { + const k = expr.kind; + if (k.tag !== "ident") { + return; + } + const res = this.resolve(expr.line, k.ident); + this.astNodeDefs.set(expr.id, res); + } + + visitTy(ty: Ty): void | VisitorBreak { + const k = ty.kind; + if (k.tag !== "ident") { + return; + } + const res = this.resolve(ty.line, k.ident); + this.astNodeDefs.set(ty.id, res); + } + + private pushRib( + tag: Tag, + kind: Omit, + ) { + this.rib = new Rib({ tag, parent: this.rib, ...kind } as RibKind); + } + + private saveScope() { + this.scopeStack.push(this.rib); + } + + private restoreScope() { + this.rib = this.scopeStack.pop()!; + } + + private tryDefine(line: number, ident: string, def: Def) { + const result = this.define(ident, def); + if (!result.ok) { + this.error(line, `redefinition of '${ident}'`); + this.info(result.originalDef.line(), `'${ident}' defined here`); + } + } + + private define(ident: string, def: Def): DefineResult { + return this.rib.define(ident, def); + } + + private resolve(line: number, ident: string): Res { + const res = this.rib.resolve(ident); + if (res.is("unresolved")) { + this.error(line, `unresolved ident '${ident}'`); + } + return res; + } + + public errorOccured = false; + private error(line: number, message: string) { + this.errorOccured = true; + console.error( + `%cerror%c: ${message}\n %c--> line ${line}%c`, + "font-weight: bold; color: red", + "font-weight: bold; color: while", + "color: cyan", + "", + ); + } + + private info(line: number, message: string) { + console.error( + `%cinfo%c: ${message}\n %c--> line ${line}%c`, + "font-weight: bold; color: blue", + "font-weight: bold; color: while", + "color: cyan", + "", + ); + } } diff --git a/src/tok.ts b/src/tok.ts index 3baeccf..5d83357 100644 --- a/src/tok.ts +++ b/src/tok.ts @@ -5,10 +5,14 @@ export type Tok = { }; const keywords = new Set([ - "fn", - "let", "true", "false", + "bool", + "int", + "char", + "str", + "fn", + "let", ]); type OpTree = Map;