init
This commit is contained in:
commit
f947c22ffd
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vscode/
|
||||||
5
deno.jsonc
Normal file
5
deno.jsonc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"fmt": {
|
||||||
|
"indentWidth": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
9
program.ethlang
Normal file
9
program.ethlang
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
fn add(a: int, b: int) -> int {
|
||||||
|
return __add(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> int {
|
||||||
|
let sum = add(2, 3);
|
||||||
|
print_int(sum);
|
||||||
|
}
|
||||||
105
src/ast.ts
Normal file
105
src/ast.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
export class Node {
|
||||||
|
private static idCounter = 0;
|
||||||
|
|
||||||
|
static create<Tag extends NodeKind["tag"]>(
|
||||||
|
line: number,
|
||||||
|
tag: Tag,
|
||||||
|
kind: Omit<NodeKind & { tag: Tag }, "tag">,
|
||||||
|
): Node {
|
||||||
|
return new Node(
|
||||||
|
Node.idCounter++,
|
||||||
|
line,
|
||||||
|
{ tag, ...kind } as NodeKind & { tag: Tag },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
public id: number,
|
||||||
|
public line: number,
|
||||||
|
public kind: NodeKind,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
visit(v: Visitor) {
|
||||||
|
if (v.visit(this) === "break") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.visitBelow(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitBelow(v: Visitor) {
|
||||||
|
const visit = (...nodes: (Node | null)[]) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
node?.visit(v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const k = this.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "Error":
|
||||||
|
return visit();
|
||||||
|
case "File":
|
||||||
|
return visit(...k.stmts);
|
||||||
|
case "Block":
|
||||||
|
return visit(...k.stmts);
|
||||||
|
case "ExprStmt":
|
||||||
|
return visit(k.expr);
|
||||||
|
case "AssignStmt":
|
||||||
|
return visit(k.place, k.expr);
|
||||||
|
case "FnStmt":
|
||||||
|
return visit(...k.params, k.retTy, k.body);
|
||||||
|
case "ReturnStmt":
|
||||||
|
return visit(k.expr);
|
||||||
|
case "LetStmt":
|
||||||
|
return visit(k.param, k.expr);
|
||||||
|
case "Param":
|
||||||
|
return visit(k.ty);
|
||||||
|
case "IdentExpr":
|
||||||
|
return visit();
|
||||||
|
case "IntExpr":
|
||||||
|
return visit();
|
||||||
|
case "CallExpr":
|
||||||
|
return visit(k.expr, ...k.args);
|
||||||
|
case "IdentTy":
|
||||||
|
return visit();
|
||||||
|
}
|
||||||
|
const _: never = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeKind =
|
||||||
|
| { tag: "Error" }
|
||||||
|
| { tag: "File"; stmts: Node[] }
|
||||||
|
| { tag: "Block"; stmts: Node[] }
|
||||||
|
| { tag: "ExprStmt"; expr: Node }
|
||||||
|
| { tag: "AssignStmt"; place: Node; expr: Node }
|
||||||
|
| {
|
||||||
|
tag: "FnStmt";
|
||||||
|
ident: string;
|
||||||
|
params: Node[];
|
||||||
|
retTy: Node | null;
|
||||||
|
body: Node;
|
||||||
|
}
|
||||||
|
| { tag: "ReturnStmt"; expr: Node | null }
|
||||||
|
| { tag: "LetStmt"; param: Node; expr: Node }
|
||||||
|
| { tag: "Param"; ident: string; ty: Node | null }
|
||||||
|
| { tag: "IdentExpr"; ident: string }
|
||||||
|
| { tag: "IntExpr"; value: number }
|
||||||
|
| { tag: "CallExpr"; expr: Node; args: Node[] }
|
||||||
|
| { tag: "IdentTy"; ident: string };
|
||||||
|
|
||||||
|
export interface Visitor {
|
||||||
|
visit(node: Node): void | "break";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeWithKind<
|
||||||
|
Tag extends NodeKind["tag"],
|
||||||
|
> = Node & { kind: { tag: Tag } };
|
||||||
|
|
||||||
|
export function assertNodeWithKind<Tag extends NodeKind["tag"]>(
|
||||||
|
node: Node,
|
||||||
|
tag: Tag,
|
||||||
|
): asserts node is NodeWithKind<Tag> {
|
||||||
|
if (node.kind.tag !== tag) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
478
src/front.ts
Normal file
478
src/front.ts
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
import * as ast from "./ast.ts";
|
||||||
|
import { rootSyms } from "./root_syms.ts";
|
||||||
|
import { Ty } from "./ty.ts";
|
||||||
|
|
||||||
|
export class Checker {
|
||||||
|
private nodeTys = new Map<number, Ty>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private filename: string,
|
||||||
|
private text: string,
|
||||||
|
private file: ast.Node,
|
||||||
|
private resols: ResolveMap,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
check(node: ast.Node): Ty {
|
||||||
|
if (this.nodeTys.has(node.id)) {
|
||||||
|
return this.nodeTys.get(node.id)!;
|
||||||
|
}
|
||||||
|
const ty = this.checkNode(node);
|
||||||
|
this.nodeTys.set(node.id, ty);
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkNode(node: ast.Node): Ty {
|
||||||
|
const k = node.kind;
|
||||||
|
|
||||||
|
if (k.tag === "FnStmt") {
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("not checked");
|
||||||
|
}
|
||||||
|
|
||||||
|
private error(line: number, message: string): never {
|
||||||
|
printDiagnostics(
|
||||||
|
this.filename,
|
||||||
|
line,
|
||||||
|
"error",
|
||||||
|
message,
|
||||||
|
this.text,
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Sym =
|
||||||
|
| { tag: "Error" }
|
||||||
|
| { tag: "Builtin"; id: string }
|
||||||
|
| { tag: "Fn"; stmt: ast.NodeWithKind<"FnStmt"> }
|
||||||
|
| {
|
||||||
|
tag: "FnParam";
|
||||||
|
stmt: ast.NodeWithKind<"FnStmt">;
|
||||||
|
param: ast.NodeWithKind<"Param">;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
tag: "Let";
|
||||||
|
stmt: ast.NodeWithKind<"LetStmt">;
|
||||||
|
param: ast.NodeWithKind<"Param">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ResolveMap {
|
||||||
|
constructor(
|
||||||
|
private resols: Map<number, Sym>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get(node: ast.Node): Sym {
|
||||||
|
if (!this.resols.has(node.id)) {
|
||||||
|
throw new Error("not resolved");
|
||||||
|
}
|
||||||
|
return this.resols.get(node.id)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResolverSyms {
|
||||||
|
static root(): ResolverSyms {
|
||||||
|
return new ResolverSyms(
|
||||||
|
new Map(
|
||||||
|
rootSyms.map<[string, Sym]>((sym) => [
|
||||||
|
sym.id,
|
||||||
|
{ tag: "Builtin", id: sym.id },
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static forkFrom(parent: ResolverSyms): ResolverSyms {
|
||||||
|
return new ResolverSyms(
|
||||||
|
new Map(),
|
||||||
|
parent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private syms = new Map<string, Sym>(),
|
||||||
|
public parent: ResolverSyms | null,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
define(ident: string, sym: Sym) {
|
||||||
|
this.syms.set(ident, sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(ident: string): Sym | null {
|
||||||
|
if (this.syms.has(ident)) {
|
||||||
|
return this.syms.get(ident)!;
|
||||||
|
}
|
||||||
|
if (this.parent) {
|
||||||
|
return this.parent.resolve(ident);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolve(
|
||||||
|
filename: string,
|
||||||
|
text: string,
|
||||||
|
file: ast.Node,
|
||||||
|
): ResolveMap {
|
||||||
|
let syms = ResolverSyms.root();
|
||||||
|
const resols = new Map<number, Sym>();
|
||||||
|
|
||||||
|
file.visit({
|
||||||
|
visit(node) {
|
||||||
|
const k = node.kind;
|
||||||
|
|
||||||
|
if (k.tag === "File" || k.tag === "Block") {
|
||||||
|
syms = ResolverSyms.forkFrom(syms);
|
||||||
|
for (const stmt of k.stmts) {
|
||||||
|
const k = stmt.kind;
|
||||||
|
if (k.tag === "FnStmt") {
|
||||||
|
ast.assertNodeWithKind(stmt, "FnStmt");
|
||||||
|
syms.define(k.ident, { tag: "Fn", stmt });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.visitBelow(this);
|
||||||
|
syms = syms.parent!;
|
||||||
|
return "break";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.tag === "FnStmt") {
|
||||||
|
ast.assertNodeWithKind(node, "FnStmt");
|
||||||
|
syms = ResolverSyms.forkFrom(syms);
|
||||||
|
for (const param of k.params) {
|
||||||
|
ast.assertNodeWithKind(param, "Param");
|
||||||
|
syms.define(param.kind.ident, {
|
||||||
|
tag: "FnParam",
|
||||||
|
stmt: node,
|
||||||
|
param,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
node.visitBelow(this);
|
||||||
|
syms = syms.parent!;
|
||||||
|
return "break";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.tag === "LetStmt") {
|
||||||
|
const stmt = node as ast.NodeWithKind<"LetStmt">;
|
||||||
|
const param = k.param as ast.NodeWithKind<"Param">;
|
||||||
|
syms.define(param.kind.ident, { tag: "Let", stmt, param });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.tag === "IdentExpr") {
|
||||||
|
const sym = syms.resolve(k.ident);
|
||||||
|
if (sym === null) {
|
||||||
|
printDiagnostics(
|
||||||
|
filename,
|
||||||
|
node.line,
|
||||||
|
"error",
|
||||||
|
`undefined symbol '${k.ident}'`,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
resols.set(node.id, sym);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ResolveMap(resols);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(
|
||||||
|
filename: string,
|
||||||
|
text: string,
|
||||||
|
): ast.Node {
|
||||||
|
return new Parser(filename, text).parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Parser {
|
||||||
|
private toks: Tok[];
|
||||||
|
private idx = 0;
|
||||||
|
private currentLine = 1;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private filename: string,
|
||||||
|
private text: string,
|
||||||
|
) {
|
||||||
|
this.toks = tokenize(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseFile(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
const stmts: ast.Node[] = [];
|
||||||
|
while (!this.done) {
|
||||||
|
stmts.push(this.parseStmt());
|
||||||
|
}
|
||||||
|
return ast.Node.create(loc, "File", { stmts });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseBlock(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
this.mustEat("{");
|
||||||
|
const stmts: ast.Node[] = [];
|
||||||
|
while (!this.done && !this.test("}")) {
|
||||||
|
stmts.push(this.parseStmt());
|
||||||
|
}
|
||||||
|
this.mustEat("}");
|
||||||
|
return ast.Node.create(loc, "Block", { stmts });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseStmt(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (this.test("fn")) {
|
||||||
|
return this.parseFnStmt();
|
||||||
|
} else if (this.test("return")) {
|
||||||
|
return this.parseReturnStmt();
|
||||||
|
} else if (this.test("let")) {
|
||||||
|
return this.parseLetStmt();
|
||||||
|
} else {
|
||||||
|
const place = this.parseExpr();
|
||||||
|
if (this.eat("=")) {
|
||||||
|
const expr = this.parseExpr();
|
||||||
|
this.mustEat(";");
|
||||||
|
return ast.Node.create(loc, "AssignStmt", { place, expr });
|
||||||
|
}
|
||||||
|
this.mustEat(";");
|
||||||
|
return ast.Node.create(loc, "ExprStmt", { expr: place });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseFnStmt(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
this.step();
|
||||||
|
const ident = this.mustEat("ident").value;
|
||||||
|
this.mustEat("(");
|
||||||
|
const params: ast.Node[] = [];
|
||||||
|
if (!this.test(")")) {
|
||||||
|
params.push(this.parseParam());
|
||||||
|
while (this.eat(",")) {
|
||||||
|
if (this.test(")")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
params.push(this.parseParam());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mustEat(")");
|
||||||
|
let retTy: ast.Node | null = null;
|
||||||
|
if (this.eat("->")) {
|
||||||
|
retTy = this.parseTy();
|
||||||
|
}
|
||||||
|
const body = this.parseBlock();
|
||||||
|
return ast.Node.create(loc, "FnStmt", { ident, params, retTy, body });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseReturnStmt(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
this.step();
|
||||||
|
let expr: ast.Node | null = null;
|
||||||
|
if (!this.test(";")) {
|
||||||
|
expr = this.parseExpr();
|
||||||
|
}
|
||||||
|
this.mustEat(";");
|
||||||
|
return ast.Node.create(loc, "ReturnStmt", { expr });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLetStmt(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
this.step();
|
||||||
|
const param = this.parseParam();
|
||||||
|
this.mustEat("=");
|
||||||
|
const expr = this.parseExpr();
|
||||||
|
this.mustEat(";");
|
||||||
|
return ast.Node.create(loc, "LetStmt", { param, expr });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseParam(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
const ident = this.mustEat("ident").value;
|
||||||
|
let ty: ast.Node | null = null;
|
||||||
|
if (this.eat(":")) {
|
||||||
|
ty = this.parseTy();
|
||||||
|
}
|
||||||
|
return ast.Node.create(loc, "Param", { ident, ty });
|
||||||
|
}
|
||||||
|
|
||||||
|
parseExpr(): ast.Node {
|
||||||
|
return this.parsePostfix();
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePostfix(): ast.Node {
|
||||||
|
let expr = this.parseOperand();
|
||||||
|
while (true) {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (this.eat("(")) {
|
||||||
|
const args: ast.Node[] = [];
|
||||||
|
if (!this.test(")")) {
|
||||||
|
args.push(this.parseExpr());
|
||||||
|
while (this.eat(",")) {
|
||||||
|
if (this.done || this.test(")")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
args.push(this.parseExpr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mustEat(")");
|
||||||
|
expr = ast.Node.create(loc, "CallExpr", { expr, args });
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseOperand(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (this.test("ident")) {
|
||||||
|
const ident = this.current.value;
|
||||||
|
this.step();
|
||||||
|
return ast.Node.create(loc, "IdentExpr", { ident });
|
||||||
|
} else if (this.test("int")) {
|
||||||
|
const value = Number(this.current.value);
|
||||||
|
this.step();
|
||||||
|
return ast.Node.create(loc, "IntExpr", { value });
|
||||||
|
} else {
|
||||||
|
this.mustEat("<expression>");
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTy(): ast.Node {
|
||||||
|
const loc = this.loc();
|
||||||
|
if (this.test("ident")) {
|
||||||
|
const ident = this.current.value;
|
||||||
|
this.step();
|
||||||
|
return ast.Node.create(loc, "IdentTy", { ident });
|
||||||
|
} else {
|
||||||
|
this.mustEat("<type>");
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mustEat(type: string, loc: number = this.loc()): Tok {
|
||||||
|
const tok = this.current;
|
||||||
|
if (tok.type !== type) {
|
||||||
|
this.error(
|
||||||
|
`expected '${type}', got '${
|
||||||
|
this.done ? "eof" : this.current.type
|
||||||
|
}'`,
|
||||||
|
loc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private error(message: string, loc: number): never {
|
||||||
|
printDiagnostics(this.filename, loc, "error", message, this.text);
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private eat(type: string): boolean {
|
||||||
|
if (this.test(type)) {
|
||||||
|
this.step();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private step() {
|
||||||
|
this.idx += 1;
|
||||||
|
if (!this.done) {
|
||||||
|
this.currentLine = this.current.line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private test(type: string): boolean {
|
||||||
|
return !this.done && this.current.type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loc(): number {
|
||||||
|
return this.currentLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get current(): Tok {
|
||||||
|
return this.toks[this.idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get done(): boolean {
|
||||||
|
return this.idx >= this.toks.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Tok = { type: string; value: string; line: number };
|
||||||
|
|
||||||
|
const keywordRegex =
|
||||||
|
/^(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)$/;
|
||||||
|
|
||||||
|
export function tokenize(text: string): Tok[] {
|
||||||
|
return text
|
||||||
|
.replace(/\/\/[^\n]*/g, "")
|
||||||
|
.replace(/((?:\-\>)|[\n\(\)\{\},.;:])/g, " $1 ")
|
||||||
|
.split(/[ \t\r]/)
|
||||||
|
.filter((value) => value !== "")
|
||||||
|
.reduce<[[string, number][], number]>(
|
||||||
|
([toks, line], value) => {
|
||||||
|
if (value === "\n") {
|
||||||
|
return [toks, line + 1];
|
||||||
|
} else {
|
||||||
|
return [[...toks, [value, line]], line];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[[], 1],
|
||||||
|
)[0]
|
||||||
|
.map<Tok>(([value, line]) => ({ type: value, value, line }))
|
||||||
|
.map((tok) =>
|
||||||
|
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tok.value)
|
||||||
|
? {
|
||||||
|
...tok,
|
||||||
|
type: keywordRegex.test(tok.value) ? tok.value : "ident",
|
||||||
|
}
|
||||||
|
: tok
|
||||||
|
)
|
||||||
|
.map((tok) =>
|
||||||
|
/^0|(?:[1-9][0-9]*)$/.test(tok.value)
|
||||||
|
? { ...tok, type: "int" }
|
||||||
|
: tok
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printDiagnostics(
|
||||||
|
filename: string,
|
||||||
|
line: number,
|
||||||
|
severity: "error",
|
||||||
|
message: string,
|
||||||
|
text?: string,
|
||||||
|
) {
|
||||||
|
const severityColor = ({
|
||||||
|
"error": "red",
|
||||||
|
} as { [Key in typeof severity]: string })[severity];
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`%c${severity}%c: ${message}\n %c--> ${filename}:${line}%c`,
|
||||||
|
`color: ${severityColor}; font-weight: bold;`,
|
||||||
|
"color: white; font-weight: bold;",
|
||||||
|
"color: gray;",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newlines = text
|
||||||
|
.split("")
|
||||||
|
.map((ch, idx) => ch === "\n" ? idx : null)
|
||||||
|
.filter((v) => v !== null);
|
||||||
|
const lineText = text.slice(newlines[line - 2] + 1, newlines[line - 1]);
|
||||||
|
const lineNumberText = line.toString();
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`${" ".repeat(lineNumberText.length)}%c|\n` +
|
||||||
|
`${lineNumberText}|%c${lineText}\n` +
|
||||||
|
`${" ".repeat(lineNumberText.length)}%c|` +
|
||||||
|
`%c${"~".repeat(lineText.length)}%c`,
|
||||||
|
"color: cyan;",
|
||||||
|
"color: white;",
|
||||||
|
"color: cyan;",
|
||||||
|
`color: ${severityColor};`,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
34
src/main.ts
Normal file
34
src/main.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import * as front from "./front.ts";
|
||||||
|
import * as ast from "./ast.ts";
|
||||||
|
|
||||||
|
const filename = Deno.args[0];
|
||||||
|
const text = await Deno.readTextFile(filename);
|
||||||
|
|
||||||
|
const fileAst = front.parse(filename, text);
|
||||||
|
const resols = front.resolve(filename, text, fileAst);
|
||||||
|
const checker = new front.Checker(filename, text, fileAst, resols);
|
||||||
|
|
||||||
|
let mainFn: ast.NodeWithKind<"FnStmt"> | null = null;
|
||||||
|
|
||||||
|
fileAst.visit({
|
||||||
|
visit(node) {
|
||||||
|
if (node.kind.tag === "FnStmt" && node.kind.ident === "main") {
|
||||||
|
if (mainFn) {
|
||||||
|
console.error("error: multiple 'main' functions");
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.assertNodeWithKind(node, "FnStmt");
|
||||||
|
mainFn = node;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mainFn) {
|
||||||
|
console.error("error: no 'main' function");
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainTy = checker.check(mainFn);
|
||||||
|
|
||||||
|
console.log({ ast: fileAst, resols });
|
||||||
13
src/middle.ts
Normal file
13
src/middle.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class BasicBlock {
|
||||||
|
constructor(
|
||||||
|
public instructions: Inst[],
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Inst {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InsKind =
|
||||||
|
| { tag: "Error" }
|
||||||
|
| { tag: "Call" };
|
||||||
23
src/root_syms.ts
Normal file
23
src/root_syms.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Ty } from "./ty.ts";
|
||||||
|
|
||||||
|
export type RootSym = {
|
||||||
|
id: string;
|
||||||
|
ty: Ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rootSyms: RootSym[] = [
|
||||||
|
{
|
||||||
|
id: "print_int",
|
||||||
|
ty: Ty.create("Fn", {
|
||||||
|
params: [],
|
||||||
|
retTy: Ty.Void,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "__add",
|
||||||
|
ty: Ty.create("Fn", {
|
||||||
|
params: [Ty.Int, Ty.Int],
|
||||||
|
retTy: Ty.Int,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
43
src/ty.ts
Normal file
43
src/ty.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as ast from "./ast.ts";
|
||||||
|
|
||||||
|
export class Ty {
|
||||||
|
private static idCounter = 0;
|
||||||
|
private static internedTys = new Map<string, Ty>();
|
||||||
|
|
||||||
|
static create<Tag extends TyKind["tag"]>(
|
||||||
|
tag: Tag,
|
||||||
|
kind: Omit<TyKind & { tag: Tag }, "tag">,
|
||||||
|
): Ty {
|
||||||
|
const ty = new Ty(
|
||||||
|
this.idCounter,
|
||||||
|
{ tag, ...kind } as TyKind & { tag: Tag },
|
||||||
|
);
|
||||||
|
const hash = ty.internHash();
|
||||||
|
if (this.internedTys.has(hash)) {
|
||||||
|
return this.internedTys.get(hash)!;
|
||||||
|
}
|
||||||
|
this.internedTys.set(hash, ty);
|
||||||
|
this.idCounter += 1;
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Error = Ty.create("Error", {});
|
||||||
|
static Void = Ty.create("Void", {});
|
||||||
|
static Int = Ty.create("Int", {});
|
||||||
|
|
||||||
|
private internHash(): string {
|
||||||
|
return JSON.stringify(this.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
public id: number,
|
||||||
|
public kind: TyKind,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TyKind =
|
||||||
|
| { tag: "Error" }
|
||||||
|
| { tag: "Void" }
|
||||||
|
| { tag: "Int" }
|
||||||
|
| { tag: "Fn"; params: Ty[]; retTy: Ty }
|
||||||
|
| { tag: "FnStmt"; ty: Ty; stmt: ast.NodeWithKind<"FnStmt"> };
|
||||||
Loading…
x
Reference in New Issue
Block a user