This commit is contained in:
sfja 2025-12-11 01:24:44 +01:00
parent 181f237f4d
commit 9a7e263bed
6 changed files with 286 additions and 34 deletions

View File

@ -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);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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",

View File

@ -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<string, Def>();
@ -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<number, Res>,
) {}
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<number, Res>();
pushRib<Tag extends RibKind["tag"]>(
tag: Tag,
kind: Omit<RibKind & { tag: Tag }, "tag" | "parent">,
) {
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 extends RibKind["tag"]>(
tag: Tag,
kind: Omit<RibKind & { tag: Tag }, "tag" | "parent">,
) {
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",
"",
);
}
}

View File

@ -5,10 +5,14 @@ export type Tok = {
};
const keywords = new Set([
"fn",
"let",
"true",
"false",
"bool",
"int",
"char",
"str",
"fn",
"let",
]);
type OpTree = Map<string, OpTree | null>;