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