Compare commits
No commits in common. "697e928802d797b6cd3364bda3bf32f3c0582f56" and "3f1408376b6a9ffade263b470e6159bf9189c4d0" have entirely different histories.
697e928802
...
3f1408376b
38
src/ast.ts
38
src/ast.ts
@ -1,5 +1,3 @@
|
||||
import { Loc } from "./diagnostics.ts";
|
||||
|
||||
export class File {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
@ -15,7 +13,7 @@ export class File {
|
||||
export class Block {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly stmts: Stmt[],
|
||||
public readonly expr?: Expr,
|
||||
) {}
|
||||
@ -32,7 +30,7 @@ export class Block {
|
||||
export class Stmt {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly kind: StmtKind,
|
||||
) {}
|
||||
|
||||
@ -74,7 +72,7 @@ export type StmtKind =
|
||||
export class Expr {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly kind: ExprKind,
|
||||
) {}
|
||||
|
||||
@ -113,7 +111,7 @@ export type ExprKind =
|
||||
export class Param {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly pat: Pat,
|
||||
public readonly ty?: Ty,
|
||||
) {}
|
||||
@ -130,7 +128,7 @@ export class Param {
|
||||
export class Pat {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly kind: PatKind,
|
||||
) {}
|
||||
|
||||
@ -155,7 +153,7 @@ export type PatKind =
|
||||
export class Ty {
|
||||
constructor(
|
||||
public readonly id: number,
|
||||
public readonly loc: Loc,
|
||||
public readonly line: number,
|
||||
public readonly kind: TyKind,
|
||||
) {}
|
||||
|
||||
@ -207,70 +205,70 @@ export class AstBuilder {
|
||||
}
|
||||
|
||||
block(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
stmts: Stmt[],
|
||||
expr?: Expr,
|
||||
): Block {
|
||||
return new Block(
|
||||
this.id++,
|
||||
loc,
|
||||
line,
|
||||
stmts,
|
||||
expr,
|
||||
);
|
||||
}
|
||||
|
||||
stmt<Tag extends StmtKind["tag"]>(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
tag: Tag,
|
||||
kind: Omit<StmtKind & { tag: Tag }, "tag">,
|
||||
): Stmt {
|
||||
return new Stmt(
|
||||
this.id++,
|
||||
loc,
|
||||
line,
|
||||
{ tag, ...kind } as StmtKind,
|
||||
);
|
||||
}
|
||||
|
||||
expr<Tag extends ExprKind["tag"]>(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
tag: Tag,
|
||||
kind: Omit<StmtKind & { tag: Tag }, "tag">,
|
||||
): Expr {
|
||||
return new Expr(
|
||||
this.id++,
|
||||
loc,
|
||||
line,
|
||||
{ tag, ...kind } as ExprKind,
|
||||
);
|
||||
}
|
||||
|
||||
param(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
pat: Pat,
|
||||
ty?: Ty,
|
||||
): Param {
|
||||
return new Param(this.id++, loc, pat, ty);
|
||||
return new Param(this.id++, line, pat, ty);
|
||||
}
|
||||
|
||||
pat<Tag extends PatKind["tag"]>(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
tag: Tag,
|
||||
kind: Omit<PatKind & { tag: Tag }, "tag">,
|
||||
): Pat {
|
||||
return new Pat(
|
||||
this.id++,
|
||||
loc,
|
||||
line,
|
||||
{ tag, ...kind } as PatKind,
|
||||
);
|
||||
}
|
||||
|
||||
ty<Tag extends TyKind["tag"]>(
|
||||
loc: Loc,
|
||||
line: number,
|
||||
tag: Tag,
|
||||
kind: Omit<TyKind & { tag: Tag }, "tag">,
|
||||
): Ty {
|
||||
return new Ty(
|
||||
this.id++,
|
||||
loc,
|
||||
line,
|
||||
{ tag, ...kind } as TyKind,
|
||||
);
|
||||
}
|
||||
|
||||
40
src/cx.ts
40
src/cx.ts
@ -1,40 +0,0 @@
|
||||
import type { Tok } from "./tok.ts";
|
||||
import type * as ast from "./ast.ts";
|
||||
import type { Syms } from "./resolve.ts";
|
||||
|
||||
export type FileInfo = {
|
||||
id: number;
|
||||
filename: string;
|
||||
text: string;
|
||||
toks?: Tok[];
|
||||
ast?: ast.File;
|
||||
syms?: Syms;
|
||||
};
|
||||
|
||||
export class Cx {
|
||||
private fileIdCounter = 0;
|
||||
private fileInfoMap = new Map<number, FileInfo>();
|
||||
|
||||
async readFile(filename: string): Promise<number> {
|
||||
const id = this.fileIdCounter++;
|
||||
const text = await Deno.readTextFile(filename);
|
||||
this.fileInfoMap.set(id, { id, filename, text });
|
||||
return id;
|
||||
}
|
||||
|
||||
file(id: number): Readonly<FileInfo> {
|
||||
return this.fileInfoMap.get(id)!;
|
||||
}
|
||||
|
||||
setFileToks(id: number, toks: Tok[]) {
|
||||
this.fileInfoMap.get(id)!.toks = toks;
|
||||
}
|
||||
|
||||
setFileAst(id: number, ast: ast.File) {
|
||||
this.fileInfoMap.get(id)!.ast = ast;
|
||||
}
|
||||
|
||||
setFileSyms(id: number, syms: Syms) {
|
||||
this.fileInfoMap.get(id)!.syms = syms;
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import { Cx } from "./cx.ts";
|
||||
|
||||
export type Loc = {
|
||||
fileId: number;
|
||||
idx: number;
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
|
||||
export class Reporter {
|
||||
constructor(
|
||||
private cx: Cx,
|
||||
) {}
|
||||
|
||||
error(loc: Loc, message: string) {
|
||||
this.printDiagnostic(loc, message, "error", "red");
|
||||
}
|
||||
|
||||
info(loc: Loc, message: string) {
|
||||
this.printDiagnostic(loc, message, "info", "blue");
|
||||
}
|
||||
|
||||
private printDiagnostic(
|
||||
{ fileId, idx, line, col }: Loc,
|
||||
message: string,
|
||||
type: string,
|
||||
color: string,
|
||||
) {
|
||||
const { filename, text } = this.cx.file(fileId);
|
||||
const lineNum = line.toString();
|
||||
|
||||
const start = text.lastIndexOf("\n", idx) + 1;
|
||||
const end = text.includes("\n", idx)
|
||||
? text.indexOf("\n", idx)
|
||||
: text.length;
|
||||
const section = text.slice(start, end);
|
||||
|
||||
console.error(
|
||||
"" +
|
||||
`%c${type}%c: ${message}\n` +
|
||||
` %c--> ${filename}:${line}:${col}\n` +
|
||||
` ${" ".repeat(lineNum.length)}%c|\n` +
|
||||
` %c${lineNum}%c|%c${section}\n` +
|
||||
` ${" ".repeat(lineNum.length)}` +
|
||||
`%c|${" ".repeat(col - 1)}%c^ %c${message}%c`,
|
||||
`font-weight: bold; color: ${color}`,
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"color: gray",
|
||||
"color: light-gray",
|
||||
"color: gray",
|
||||
"color: light-gray",
|
||||
"color: gray",
|
||||
`font-weight: bold; color: ${color}`,
|
||||
"font-weight: bold; color: while",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
20
src/main.ts
20
src/main.ts
@ -2,16 +2,16 @@ import * as yaml from "jsr:@std/yaml";
|
||||
import { tokenize } from "./tok.ts";
|
||||
import { Parser } from "./parse.ts";
|
||||
import { Resolver } from "./resolve.ts";
|
||||
import { Cx } from "./cx.ts";
|
||||
|
||||
async function main() {
|
||||
const cx = new Cx();
|
||||
const text = await Deno.readTextFile(Deno.args[0]);
|
||||
const toks = tokenize(text);
|
||||
console.log({ toks });
|
||||
return;
|
||||
|
||||
const fileId = await cx.readFile(Deno.args[0]);
|
||||
tokenize(cx, fileId);
|
||||
|
||||
const parseResult = Parser.parseFile(cx, fileId);
|
||||
if (!parseResult.ok) {
|
||||
const parser = new Parser(toks);
|
||||
const file = parser.parseFile();
|
||||
if (parser.errorOccured) {
|
||||
console.error("parsing failed");
|
||||
Deno.exit(1);
|
||||
}
|
||||
@ -19,13 +19,13 @@ async function main() {
|
||||
// console.log(yaml.stringify({ file }, { skipInvalid: true, indent: 2 }));
|
||||
// console.log(JSON.stringify({ file }, null, 2));
|
||||
|
||||
const resolveResult = Resolver.resolveFile(cx, fileId);
|
||||
if (!resolveResult.ok) {
|
||||
const resolver = new Resolver();
|
||||
const syms = resolver.resolveFile(file);
|
||||
if (resolver.errorOccured) {
|
||||
console.error("resolving failed");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const syms = cx.file(fileId).syms!;
|
||||
console.log(syms);
|
||||
|
||||
// console.log(cx);
|
||||
|
||||
113
src/parse.ts
113
src/parse.ts
@ -1,32 +1,16 @@
|
||||
import { AstBuilder, Block, Expr, File, Param, Pat, Stmt, Ty } from "./ast.ts";
|
||||
import { Cx } from "./cx.ts";
|
||||
import { Loc, Reporter } from "./diagnostics.ts";
|
||||
import { Tok } from "./tok.ts";
|
||||
|
||||
const t = new AstBuilder();
|
||||
|
||||
export type ParseResult = { ok: true } | { ok: false } & object;
|
||||
|
||||
export class Parser {
|
||||
private i = 0;
|
||||
private eaten?: Tok;
|
||||
private errorOccured = false;
|
||||
|
||||
private constructor(
|
||||
private reporter: Reporter,
|
||||
constructor(
|
||||
private toks: Tok[],
|
||||
) {}
|
||||
|
||||
static parseFile(cx: Cx, fileId: number): ParseResult {
|
||||
const parser = new Parser(
|
||||
new Reporter(cx),
|
||||
cx.file(fileId).toks!,
|
||||
);
|
||||
const ast = parser.parseFile();
|
||||
cx.setFileAst(fileId, ast);
|
||||
return { ok: !parser.errorOccured };
|
||||
}
|
||||
|
||||
parseFile(): File {
|
||||
const stmts: Stmt[] = [];
|
||||
while (!this.done) {
|
||||
@ -36,7 +20,7 @@ export class Parser {
|
||||
}
|
||||
|
||||
parseItem(): Stmt {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.test("fn")) {
|
||||
return this.parseFn();
|
||||
} else if (this.test("let")) {
|
||||
@ -44,19 +28,19 @@ export class Parser {
|
||||
} else {
|
||||
this.expect("item");
|
||||
this.step();
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
}
|
||||
|
||||
parseBlock(): Block {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
this.step();
|
||||
|
||||
const stmts: Stmt[] = [];
|
||||
let expr: Expr | undefined = undefined;
|
||||
|
||||
while (!this.done && !this.test("}")) {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.test("fn")) {
|
||||
stmts.push(this.parseFn());
|
||||
} else if (this.test("let")) {
|
||||
@ -68,10 +52,10 @@ export class Parser {
|
||||
const rhs = this.parseExpr();
|
||||
this.expect(";");
|
||||
stmts.push(
|
||||
t.stmt(loc, "assign", { place: lhs, expr: rhs }),
|
||||
t.stmt(line, "assign", { place: lhs, expr: rhs }),
|
||||
);
|
||||
} else if (this.eat(";")) {
|
||||
stmts.push(t.stmt(loc, "expr", { expr: lhs }));
|
||||
stmts.push(t.stmt(line, "expr", { expr: lhs }));
|
||||
} else if (this.test("}")) {
|
||||
expr = lhs;
|
||||
break;
|
||||
@ -82,19 +66,19 @@ export class Parser {
|
||||
}
|
||||
|
||||
this.expect("}");
|
||||
return t.block(loc, stmts, expr);
|
||||
return t.block(line, stmts, expr);
|
||||
}
|
||||
|
||||
parseFn(): Stmt {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
this.step();
|
||||
if (!this.expect("ident")) {
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
const ident = this.eaten!.value!;
|
||||
const params: Param[] = [];
|
||||
if (!this.expect("(")) {
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
if (!this.done && !this.test(")")) {
|
||||
params.push(this.parseParam());
|
||||
@ -106,7 +90,7 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
if (!this.expect(")")) {
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
let retTy: Ty | undefined = undefined;
|
||||
if (this.eat("->")) {
|
||||
@ -114,21 +98,21 @@ export class Parser {
|
||||
}
|
||||
if (!this.test("{")) {
|
||||
this.expect("{");
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
const body = this.parseBlock();
|
||||
return t.stmt(loc, "fn", { ident, params, retTy, body });
|
||||
return t.stmt(line, "fn", { ident, params, retTy, body });
|
||||
}
|
||||
|
||||
parseLet(): Stmt {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
this.step();
|
||||
const param = this.parseParam();
|
||||
if (!this.expect("=")) {
|
||||
return t.stmt(loc, "error", {});
|
||||
return t.stmt(line, "error", {});
|
||||
}
|
||||
const init = this.parseExpr();
|
||||
return t.stmt(loc, "let", { param, init });
|
||||
return t.stmt(line, "let", { param, init });
|
||||
}
|
||||
|
||||
parseExpr(): Expr {
|
||||
@ -138,7 +122,7 @@ export class Parser {
|
||||
parsePostfix(): Expr {
|
||||
let expr = this.parseOp();
|
||||
while (true) {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.eat("(")) {
|
||||
const args: Expr[] = [];
|
||||
if (!this.done && !this.test(")")) {
|
||||
@ -151,9 +135,9 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
if (!this.expect(")")) {
|
||||
return t.expr(loc, "error", {});
|
||||
return t.expr(line, "error", {});
|
||||
}
|
||||
expr = t.expr(loc, "call", { expr, args });
|
||||
expr = t.expr(line, "call", { expr, args });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -162,75 +146,75 @@ export class Parser {
|
||||
}
|
||||
|
||||
parseOp(): Expr {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.eat("ident")) {
|
||||
const ident = this.eaten!.value!;
|
||||
return t.expr(loc, "ident", { ident });
|
||||
return t.expr(line, "ident", { ident });
|
||||
} else if (this.eat("int")) {
|
||||
const value = this.eaten!.value!;
|
||||
return t.expr(loc, "int", { value });
|
||||
return t.expr(line, "int", { value });
|
||||
} else if (this.eat("char")) {
|
||||
const value = this.eaten!.value!;
|
||||
return t.expr(loc, "int", { value });
|
||||
return t.expr(line, "int", { value });
|
||||
} else if (this.eat("str")) {
|
||||
const value = this.eaten!.value!;
|
||||
return t.expr(loc, "int", { value });
|
||||
return t.expr(line, "int", { value });
|
||||
} else {
|
||||
this.expect("expr");
|
||||
this.step();
|
||||
return t.expr(loc, "error", {});
|
||||
return t.expr(line, "error", {});
|
||||
}
|
||||
}
|
||||
|
||||
parseParam(): Param {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
const pat = this.parsePat();
|
||||
let ty: Ty | undefined = undefined;
|
||||
if (this.eat(":")) {
|
||||
ty = this.parseTy();
|
||||
}
|
||||
return t.param(loc, pat, ty);
|
||||
return t.param(line, pat, ty);
|
||||
}
|
||||
|
||||
parsePat(): Pat {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.eat("ident")) {
|
||||
const ident = this.eaten!.value!;
|
||||
return t.pat(loc, "ident", { ident });
|
||||
return t.pat(line, "ident", { ident });
|
||||
} else {
|
||||
this.expect("pat");
|
||||
this.step();
|
||||
return t.pat(loc, "error", {});
|
||||
return t.pat(line, "error", {});
|
||||
}
|
||||
}
|
||||
|
||||
parseTy(): Ty {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (this.eat("int")) {
|
||||
return t.ty(loc, "int", {});
|
||||
return t.ty(line, "int", {});
|
||||
} else if (this.eat("bool")) {
|
||||
return t.ty(loc, "bool", {});
|
||||
return t.ty(line, "bool", {});
|
||||
} else if (this.eat("char")) {
|
||||
return t.ty(loc, "char", {});
|
||||
return t.ty(line, "char", {});
|
||||
} else if (this.eat("str")) {
|
||||
return t.ty(loc, "str", {});
|
||||
return t.ty(line, "str", {});
|
||||
} else if (this.eat("ident")) {
|
||||
const ident = this.eaten!.value!;
|
||||
return t.ty(loc, "ident", { ident });
|
||||
return t.ty(line, "ident", { ident });
|
||||
} else {
|
||||
this.expect("ty");
|
||||
this.step();
|
||||
return t.ty(loc, "error", {});
|
||||
return t.ty(line, "error", {});
|
||||
}
|
||||
}
|
||||
|
||||
private expect(type: string): boolean {
|
||||
const loc = this.loc();
|
||||
const line = this.line();
|
||||
if (!this.eat(type)) {
|
||||
if (this.done) {
|
||||
this.error(loc, `expected '${type}', got 'eof'`);
|
||||
this.error(line, `expected '${type}', got 'eof'`);
|
||||
} else {
|
||||
this.error(loc, `expected '${type}', got '${this.tok.type}'`);
|
||||
this.error(line, `expected '${type}', got '${this.tok.type}'`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -254,8 +238,8 @@ export class Parser {
|
||||
this.i += 1;
|
||||
}
|
||||
|
||||
private loc(): Loc {
|
||||
return this.tok.loc;
|
||||
private line(): number {
|
||||
return this.tok.line;
|
||||
}
|
||||
|
||||
private get tok(): Tok {
|
||||
@ -266,8 +250,15 @@ export class Parser {
|
||||
return this.i >= this.toks.length;
|
||||
}
|
||||
|
||||
private error(loc: Loc, message: string) {
|
||||
public errorOccured = false;
|
||||
private error(line: number, message: string) {
|
||||
this.errorOccured = true;
|
||||
this.reporter.error(loc, message);
|
||||
console.error(
|
||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: red",
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
119
src/resolve.ts
119
src/resolve.ts
@ -1,6 +1,13 @@
|
||||
import * as ast from "./ast.ts";
|
||||
import { Cx } from "./cx.ts";
|
||||
import { Loc, Reporter } from "./diagnostics.ts";
|
||||
import {
|
||||
Block,
|
||||
Expr,
|
||||
File,
|
||||
Pat,
|
||||
Stmt,
|
||||
Ty,
|
||||
Visitor,
|
||||
VisitorBreak,
|
||||
} from "./ast.ts";
|
||||
|
||||
class Res {
|
||||
constructor(
|
||||
@ -33,23 +40,23 @@ export class Def {
|
||||
}
|
||||
}
|
||||
|
||||
loc(): Loc {
|
||||
line(): number {
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
case "fn":
|
||||
return k.stmt.loc;
|
||||
return k.stmt.line;
|
||||
case "param":
|
||||
return k.pat.loc;
|
||||
return k.pat.line;
|
||||
case "let":
|
||||
return k.pat.loc;
|
||||
return k.pat.line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type DefKind =
|
||||
| { tag: "fn"; stmt: ast.Stmt }
|
||||
| { tag: "param"; stmt: ast.Stmt; pat: ast.Pat }
|
||||
| { tag: "let"; stmt: ast.Stmt; pat: ast.Pat };
|
||||
| { tag: "fn"; stmt: Stmt }
|
||||
| { tag: "param"; stmt: Stmt; pat: Pat }
|
||||
| { tag: "let"; stmt: Stmt; pat: Pat };
|
||||
|
||||
type DefineResult =
|
||||
| { ok: true }
|
||||
@ -103,57 +110,42 @@ export class Syms {
|
||||
private astNodeDefs: Map<number, Res>,
|
||||
) {}
|
||||
|
||||
exprRes(expr: ast.Expr): Res {
|
||||
exprRes(expr: Expr): Res {
|
||||
const res = this.astNodeDefs.get(expr.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
|
||||
patRes(pat: ast.Pat): Res {
|
||||
patRes(pat: Pat): Res {
|
||||
const res = this.astNodeDefs.get(pat.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
|
||||
tyRes(ty: ast.Ty): Res {
|
||||
tyRes(ty: Ty): Res {
|
||||
const res = this.astNodeDefs.get(ty.id);
|
||||
if (!res) throw new Error();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export type ResolveResult = { ok: true } | { ok: false };
|
||||
|
||||
export class Resolver implements ast.Visitor {
|
||||
export class Resolver implements Visitor {
|
||||
private rib = new Rib({ tag: "root" });
|
||||
private scopeStack: Rib[] = [];
|
||||
private astNodeDefs = new Map<number, Res>();
|
||||
private errorOccured = false;
|
||||
|
||||
private constructor(
|
||||
private reporter: Reporter,
|
||||
) {}
|
||||
|
||||
static resolveFile(cx: Cx, fileId: number): ResolveResult {
|
||||
const file = cx.file(fileId);
|
||||
const resolver = new Resolver(new Reporter(cx));
|
||||
const syms = resolver.resolveFile(file.ast!);
|
||||
cx.setFileSyms(fileId, syms);
|
||||
return { ok: !resolver.errorOccured };
|
||||
}
|
||||
|
||||
resolveFile(file: ast.File): Syms {
|
||||
resolveFile(file: File): Syms {
|
||||
file.visit({
|
||||
visitStmt: (stmt): void | ast.VisitorBreak => {
|
||||
visitStmt: (stmt): void | VisitorBreak => {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.tryDefine(
|
||||
stmt.loc,
|
||||
stmt.line,
|
||||
k.ident,
|
||||
new Def({ tag: "fn", stmt }),
|
||||
);
|
||||
}
|
||||
return ast.VisitorBreak;
|
||||
return VisitorBreak;
|
||||
},
|
||||
});
|
||||
|
||||
@ -161,36 +153,36 @@ export class Resolver implements ast.Visitor {
|
||||
return new Syms(this.astNodeDefs);
|
||||
}
|
||||
|
||||
visitStmt(stmt: ast.Stmt): void | ast.VisitorBreak {
|
||||
visitStmt(stmt: Stmt): void | VisitorBreak {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.saveScope();
|
||||
this.pushRib("fn", {});
|
||||
|
||||
k.body.visit({
|
||||
visitStmt: (stmt): void | ast.VisitorBreak => {
|
||||
visitStmt: (stmt): void | VisitorBreak => {
|
||||
const k = stmt.kind;
|
||||
if (k.tag === "fn") {
|
||||
this.tryDefine(
|
||||
stmt.loc,
|
||||
stmt.line,
|
||||
k.ident,
|
||||
new Def({ tag: "fn", stmt }),
|
||||
);
|
||||
}
|
||||
return ast.VisitorBreak;
|
||||
return VisitorBreak;
|
||||
},
|
||||
});
|
||||
|
||||
this.pushRib("param", {});
|
||||
for (const param of k.params) {
|
||||
param.pat.visit({
|
||||
visitPat: (pat: ast.Pat) => {
|
||||
visitPat: (pat: Pat) => {
|
||||
const k = pat.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
this.tryDefine(
|
||||
pat.loc,
|
||||
pat.line,
|
||||
k.ident,
|
||||
new Def({ tag: "param", stmt, pat }),
|
||||
);
|
||||
@ -202,17 +194,17 @@ export class Resolver implements ast.Visitor {
|
||||
k.body.visit(this);
|
||||
|
||||
this.restoreScope();
|
||||
return ast.VisitorBreak;
|
||||
return VisitorBreak;
|
||||
} else if (k.tag === "let") {
|
||||
this.pushRib("let", {});
|
||||
k.param.pat.visit({
|
||||
visitPat: (pat: ast.Pat) => {
|
||||
visitPat: (pat: Pat) => {
|
||||
const k = pat.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
this.tryDefine(
|
||||
pat.loc,
|
||||
pat.line,
|
||||
k.ident,
|
||||
new Def({ tag: "let", stmt, pat }),
|
||||
);
|
||||
@ -221,30 +213,30 @@ export class Resolver implements ast.Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
visitBlock(block: ast.Block): void | ast.VisitorBreak {
|
||||
visitBlock(block: Block): void | VisitorBreak {
|
||||
this.saveScope();
|
||||
this.pushRib("block", {});
|
||||
block.stmts.forEach((stmt) => stmt.visit(this));
|
||||
block.expr?.visit(this);
|
||||
this.restoreScope();
|
||||
return ast.VisitorBreak;
|
||||
return VisitorBreak;
|
||||
}
|
||||
|
||||
visitExpr(expr: ast.Expr): void | ast.VisitorBreak {
|
||||
visitExpr(expr: Expr): void | VisitorBreak {
|
||||
const k = expr.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
const res = this.resolve(expr.loc, k.ident);
|
||||
const res = this.resolve(expr.line, k.ident);
|
||||
this.astNodeDefs.set(expr.id, res);
|
||||
}
|
||||
|
||||
visitTy(ty: ast.Ty): void | ast.VisitorBreak {
|
||||
visitTy(ty: Ty): void | VisitorBreak {
|
||||
const k = ty.kind;
|
||||
if (k.tag !== "ident") {
|
||||
return;
|
||||
}
|
||||
const res = this.resolve(ty.loc, k.ident);
|
||||
const res = this.resolve(ty.line, k.ident);
|
||||
this.astNodeDefs.set(ty.id, res);
|
||||
}
|
||||
|
||||
@ -263,11 +255,11 @@ export class Resolver implements ast.Visitor {
|
||||
this.rib = this.scopeStack.pop()!;
|
||||
}
|
||||
|
||||
private tryDefine(loc: Loc, ident: string, def: Def) {
|
||||
private tryDefine(line: number, ident: string, def: Def) {
|
||||
const result = this.define(ident, def);
|
||||
if (!result.ok) {
|
||||
this.error(loc, `redefinition of '${ident}'`);
|
||||
this.info(result.originalDef.loc(), `'${ident}' defined here`);
|
||||
this.error(line, `redefinition of '${ident}'`);
|
||||
this.info(result.originalDef.line(), `'${ident}' defined here`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,20 +267,33 @@ export class Resolver implements ast.Visitor {
|
||||
return this.rib.define(ident, def);
|
||||
}
|
||||
|
||||
private resolve(loc: Loc, ident: string): Res {
|
||||
private resolve(line: number, ident: string): Res {
|
||||
const res = this.rib.resolve(ident);
|
||||
if (res.is("unresolved")) {
|
||||
this.error(loc, `unresolved ident '${ident}'`);
|
||||
this.error(line, `unresolved ident '${ident}'`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private error(loc: Loc, message: string) {
|
||||
public errorOccured = false;
|
||||
private error(line: number, message: string) {
|
||||
this.errorOccured = true;
|
||||
this.reporter.error(loc, message);
|
||||
console.error(
|
||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: red",
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
private info(loc: Loc, message: string) {
|
||||
this.reporter.info(loc, message);
|
||||
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",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
58
src/tok.ts
58
src/tok.ts
@ -1,28 +1,24 @@
|
||||
import { Cx } from "./cx.ts";
|
||||
import { Loc, Reporter } from "./diagnostics.ts";
|
||||
|
||||
export type Tok = {
|
||||
type: string;
|
||||
loc: Loc;
|
||||
value: string;
|
||||
idx: number;
|
||||
line: number;
|
||||
col: number;
|
||||
length: number;
|
||||
value?: string;
|
||||
};
|
||||
const keywords = ["true", "false", "bool", "int", "char", "str", "fn", "let"];
|
||||
|
||||
const keywords = new Set([
|
||||
"true",
|
||||
"false",
|
||||
"bool",
|
||||
"int",
|
||||
"char",
|
||||
"str",
|
||||
"fn",
|
||||
"let",
|
||||
]);
|
||||
|
||||
export function tokenize(text: string): Tok[] {
|
||||
const rules: Record<string, { match: RegExp; ignore?: boolean }> = {
|
||||
"whitespace": { match: /^[ \t\r]+/, ignore: true },
|
||||
"newline": { match: /^\n/s, ignore: true },
|
||||
"linecomment": { match: /^\/\/[^\n]*/, ignore: true },
|
||||
"blockcomment": { match: /^\/\*.*?\*\//s, ignore: true },
|
||||
_keywords: {
|
||||
match: new RegExp(
|
||||
`^(${keywords.map((s) => `(?:${s})`).join("|")})`,
|
||||
),
|
||||
},
|
||||
_identity: {
|
||||
match: new RegExp(
|
||||
`^(?:(?:\-\>)|[${RegExp.escape("()[]{}+-*/,.;:!=<>&|?")}])`,
|
||||
@ -34,16 +30,11 @@ const rules: Record<string, { match: RegExp; ignore?: boolean }> = {
|
||||
"str": { match: /^"(?:(?:\\.)|[^"])*"/s },
|
||||
};
|
||||
|
||||
export function tokenize(cx: Cx, fileId: number) {
|
||||
const { text } = cx.file(fileId);
|
||||
const rep = new Reporter(cx);
|
||||
|
||||
const toks: Tok[] = [];
|
||||
let idx = 0;
|
||||
let line = 1;
|
||||
let col = 1;
|
||||
while (idx < text.length) {
|
||||
const loc: Loc = { fileId, idx, line, col };
|
||||
let found = false;
|
||||
for (const [id, rule] of Object.entries(rules)) {
|
||||
const match = text.slice(idx).match(rule.match);
|
||||
@ -62,15 +53,10 @@ export function tokenize(cx: Cx, fileId: number) {
|
||||
|
||||
if (rule.ignore) continue;
|
||||
|
||||
const tok: Tok = {
|
||||
type: id,
|
||||
loc,
|
||||
value: match[0],
|
||||
};
|
||||
if (id === "_identity") {
|
||||
const length = match[0].length;
|
||||
const tok: Tok = { type: id, idx, line, col, length };
|
||||
if (id === "_keywords" || id === "_identity") {
|
||||
tok.type = match[0];
|
||||
} else if (id === "ident") {
|
||||
tok.type = keywords.has(match[0]) ? match[0] : id;
|
||||
} else {
|
||||
tok.value = match[0];
|
||||
}
|
||||
@ -78,10 +64,20 @@ export function tokenize(cx: Cx, fileId: number) {
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
rep.error(loc, `invalid character '${text[idx]}'`);
|
||||
printError(line, `invalid character '${text[idx]}'`);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
cx.setFileToks(fileId, toks);
|
||||
return toks;
|
||||
}
|
||||
|
||||
function printError(line: number, message: string) {
|
||||
console.error(
|
||||
`%cerror%c: ${message}\n %c--> line ${line}%c`,
|
||||
"font-weight: bold; color: red",
|
||||
"font-weight: bold; color: while",
|
||||
"color: cyan",
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user