add typechecker

This commit is contained in:
SimonFJ20 2024-12-06 14:17:52 +01:00
parent aa888c9368
commit d4ea73de1d
5 changed files with 593 additions and 111 deletions

229
compiler/Checker.ts Normal file
View File

@ -0,0 +1,229 @@
import { EType, Expr } from "./ast.ts";
import { Pos } from "./Token.ts";
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtypes.ts";
export class Checker {
public report(msg: string, pos: Pos) {
console.error(`${msg} at ${pos.line}:${pos.col}`);
}
public checkEType(etype: EType): VType {
const pos = etype.pos;
if (etype.kind.type === "ident") {
if (etype.kind.value === "null") {
return { type: "null" };
}
if (etype.kind.value === "int") {
return { type: "int" };
}
if (etype.kind.value === "bool") {
return { type: "bool" };
}
if (etype.kind.value === "string") {
return { type: "string" };
}
this.report(`undefined type '${etype.kind.value}'`, pos);
return { type: "error" };
}
if (etype.kind.type === "array") {
const inner = this.checkEType(etype.kind.inner);
return { type: "array", inner };
}
if (etype.kind.type === "struct") {
const noTypeTest = etype.kind.fields.reduce(
(acc, param) => [acc[0] || !param.etype, param.ident],
[false, ""],
);
if (noTypeTest[0]) {
this.report(
`field '${noTypeTest[1]}' declared without type`,
pos,
);
return { type: "error" };
}
const declaredTwiceTest = etype.kind.fields.reduce<
[boolean, string[], string]
>(
(acc, curr) => {
if (acc[0]) {
return acc;
}
if (acc[1].includes(curr.ident)) {
return [true, acc[1], curr.ident];
}
return [false, [...acc[1], curr.ident], ""];
},
[false, [], ""],
);
if (
declaredTwiceTest[0]
) {
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
return { type: "error" };
}
const fields = etype.kind.fields.map((param): VTypeParam => ({
ident: param.ident,
vtype: this.checkEType(param.etype!),
}));
return { type: "struct", fields };
}
throw new Error(`unknown explicit type ${etype.kind.type}`);
}
public checkExpr(expr: Expr): VType {
const pos = expr.pos;
const vtype = ((): VType => {
switch (expr.kind.type) {
case "error":
throw new Error("error in AST");
case "ident":
throw new Error("ident expr in AST");
case "sym":
return this.checkSymExpr(expr);
case "null":
return { type: "null" };
case "int":
return { type: "int" };
case "bool":
return { type: "bool" };
case "string":
return { type: "string" };
case "binary":
return this.checkBinaryExpr(expr);
case "group":
return this.checkExpr(expr.kind.expr);
case "field": {
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "struct") {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const value = expr.kind.value;
const found = subject.fields.find((param) =>
param.ident === value
);
if (!found) {
this.report(
`no field named '${expr.kind.value}' on struct`,
pos,
);
return { type: "error" };
}
return found.vtype;
}
case "index": {
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "array") {
this.report("cannot index on non-array", pos);
return { type: "error" };
}
return subject.inner;
}
case "call": {
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "fn") {
this.report("cannot call non-fn", pos);
return { type: "error" };
}
const args = expr.kind.args.map((arg) =>
this.checkExpr(arg)
);
if (args.length !== subject.params.length) {
this.report(
`incorrect number of arguments` +
`, expected ${subject.params.length}`,
pos,
);
}
for (let i = 0; i < args.length; ++i) {
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
this.report(
`incorrect argument ${i} '${
subject.params[i].ident
}'` +
`, expected ${
vtypeToString(subject.params[i].vtype)
}` +
`, got ${vtypeToString(args[i])}`,
pos,
);
break;
}
}
return subject.returnType;
}
case "unary":
case "if":
case "loop":
case "block":
}
throw new Error(`unhandled type ${expr.kind.type}`);
})();
expr.vtype = vtype;
throw new Error(`unknown expression ${expr.kind.type}`);
}
public checkSymExpr(expr: Expr): VType {
const pos = expr.pos;
if (expr.kind.type !== "sym") {
throw new Error();
}
}
public checkBinaryExpr(expr: Expr): VType {
const pos = expr.pos;
if (expr.kind.type !== "binary") {
throw new Error();
}
const left = this.checkExpr(expr.kind.left);
const right = this.checkExpr(expr.kind.right);
for (const operation of simpleBinaryOperations) {
if (operation.binaryType !== expr.kind.binaryType) {
continue;
}
if (!vtypesEqual(operation.operand, left)) {
continue;
}
if (!vtypesEqual(left, right)) {
continue;
}
return operation.result ?? operation.operand;
}
this.report(
`cannot apply binary operation '${expr.kind.binaryType}' ` +
`on types '${vtypeToString(left)}' and '${
vtypeToString(right)
}'`,
pos,
);
return { type: "error" };
}
}
const simpleBinaryOperations: {
binaryType: string;
operand: VType;
result?: VType;
}[] = [
// arithmetic
{ binaryType: "+", operand: { type: "int" } },
{ binaryType: "-", operand: { type: "int" } },
{ binaryType: "*", operand: { type: "int" } },
{ binaryType: "/", operand: { type: "int" } },
// logical
{ binaryType: "and", operand: { type: "bool" } },
{ binaryType: "or", operand: { type: "bool" } },
// equality
{ binaryType: "==", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "bool" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "bool" }, result: { type: "bool" } },
// comparison
{ binaryType: "<", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "<=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">=", operand: { type: "int" }, result: { type: "bool" } },
];

View File

@ -5,16 +5,17 @@ export class Lexer {
private line = 1; private line = 1;
private col = 1; private col = 1;
public constructor(private text: string) {}
public constructor (private text: string) {}
public next(): Token | null { public next(): Token | null {
if (this.done()) if (this.done()) {
return null; return null;
}
const pos = this.pos(); const pos = this.pos();
if (this.test(/[ \t\n\r]/)) { if (this.test(/[ \t\n\r]/)) {
while (!this.done() && this.test(/[ \t\n\r]/)) while (!this.done() && this.test(/[ \t\n\r]/)) {
this.step(); this.step();
}
return this.next(); return this.next();
} }
@ -39,6 +40,8 @@ export class Lexer {
return this.token("if", pos); return this.token("if", pos);
case "else": case "else":
return this.token("else", pos); return this.token("else", pos);
case "struct":
return this.token("struct", pos);
default: default:
return { ...this.token("ident", pos), identValue: value }; return { ...this.token("ident", pos), identValue: value };
} }
@ -53,25 +56,26 @@ export class Lexer {
} }
if (this.test("0")) { if (this.test("0")) {
this.step() this.step();
if (!this.done() && this.test(/[0-9]/)) { if (!this.done() && this.test(/[0-9]/)) {
console.error( console.error(
`Lexer: invalid number` `Lexer: invalid number` +
+ ` at ${pos.line}:${pos.col}`, ` at ${pos.line}:${pos.col}`,
); );
return this.token("error", pos); return this.token("error", pos);
} }
return { ...this.token("int", pos), intValue: 0}; return { ...this.token("int", pos), intValue: 0 };
} }
if (this.test("\"")) { if (this.test('"')) {
this.step(); this.step();
let value = ""; let value = "";
while (!this.done() && !this.test("\"")) { while (!this.done() && !this.test('"')) {
if (this.test("\\")) { if (this.test("\\")) {
this.step(); this.step();
if (this.done()) if (this.done()) {
break; break;
}
value += { value += {
"n": "\n", "n": "\n",
"t": "\t", "t": "\t",
@ -82,10 +86,10 @@ export class Lexer {
} }
this.step(); this.step();
} }
if (this.done() || !this.test("\"")) { if (this.done() || !this.test('"')) {
console.error( console.error(
`Lexer: unclosed/malformed string` `Lexer: unclosed/malformed string` +
+ ` at ${pos.line}:${pos.col}`, ` at ${pos.line}:${pos.col}`,
); );
return this.token("error", pos); return this.token("error", pos);
} }
@ -113,7 +117,7 @@ export class Lexer {
return this.token("->", pos); return this.token("->", pos);
} }
if (this.test("=")) { if (this.test("=")) {
this.step() this.step();
return this.token("-=", pos); return this.token("-=", pos);
} }
} }
@ -125,16 +129,21 @@ export class Lexer {
this.step(); this.step();
return this.token("+=", pos); return this.token("+=", pos);
} }
if (first === "-" && !this.done() && this.test(">")) {
this.step();
return this.token("->", pos);
}
return this.token(first, pos); return this.token(first, pos);
} }
if (this.test("/")) { if (this.test("/")) {
this.step() this.step();
if (this.test("/")) { if (this.test("/")) {
while (!this.done() && !this.test("\n")) while (!this.done() && !this.test("\n")) {
this.step(); this.step();
return this.next() }
return this.next();
} }
return this.token("/", pos) return this.token("/", pos);
} }
if (this.test("false")) { if (this.test("false")) {
this.step(); this.step();
@ -180,20 +189,29 @@ export class Lexer {
this.step(); this.step();
return this.token("return", pos); return this.token("return", pos);
} }
console.error(`Lexer: illegal character '${this.current()}' at ${pos.line}:${pos.col}`); console.error(
`Lexer: illegal character '${this.current()}' at ${pos.line}:${pos.col}`,
);
this.step(); this.step();
return this.next(); return this.next();
} }
private done(): boolean { return this.index >= this.text.length; } private done(): boolean {
return this.index >= this.text.length;
}
private current(): string { return this.text[this.index]; } private current(): string {
return this.text[this.index];
}
public currentPos(): Pos { return this.pos(); } public currentPos(): Pos {
return this.pos();
}
private step() { private step() {
if (this.done()) if (this.done()) {
return; return;
}
if (this.current() === "\n") { if (this.current() === "\n") {
this.line += 1; this.line += 1;
this.col = 1; this.col = 1;
@ -207,8 +225,8 @@ export class Lexer {
return { return {
index: this.index, index: this.index,
line: this.line, line: this.line,
col: this.col col: this.col,
} };
} }
private token(type: string, pos: Pos): Token { private token(type: string, pos: Pos): Token {
@ -217,13 +235,10 @@ export class Lexer {
} }
private test(pattern: RegExp | string): boolean { private test(pattern: RegExp | string): boolean {
if (typeof pattern === "string") if (typeof pattern === "string") {
return this.current() === pattern; return this.current() === pattern;
else } else {
return pattern.test(this.current()); return pattern.test(this.current());
}
} }
} }

View File

@ -1,4 +1,13 @@
import { Expr, ExprKind, Param, Stmt, StmtKind, BinaryType} from "./ast.ts"; import {
BinaryType,
EType,
ETypeKind,
Expr,
ExprKind,
Param,
Stmt,
StmtKind,
} from "./ast.ts";
import { Lexer } from "./Lexer.ts"; import { Lexer } from "./Lexer.ts";
import { Pos, Token } from "./Token.ts"; import { Pos, Token } from "./Token.ts";
@ -10,12 +19,19 @@ export class Parser {
this.currentToken = lexer.next(); this.currentToken = lexer.next();
} }
private step() { this.currentToken = this.lexer.next() } private step() {
public done(): boolean { return this.currentToken == null; } this.currentToken = this.lexer.next();
private current(): Token { return this.currentToken!; } }
public done(): boolean {
return this.currentToken == null;
}
private current(): Token {
return this.currentToken!;
}
private pos(): Pos { private pos(): Pos {
if (this.done()) if (this.done()) {
return this.lexer.currentPos(); return this.lexer.currentPos();
}
return this.current().pos; return this.current().pos;
} }
@ -25,13 +41,18 @@ export class Parser {
private report(msg: string, pos = this.pos()) { private report(msg: string, pos = this.pos()) {
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`); console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
class ReportNotAnError extends Error { constructor() { super("ReportNotAnError"); } } class ReportNotAnError extends Error {
constructor() {
super("ReportNotAnError");
}
}
try { try {
throw new ReportNotAnError(); throw new ReportNotAnError();
} catch (error) { } catch (error) {
if (!(error instanceof ReportNotAnError)) if (!(error instanceof ReportNotAnError)) {
throw error; throw error;
console.log(error) }
console.log(error);
} }
} }
@ -47,33 +68,47 @@ export class Parser {
return { kind, pos, id }; return { kind, pos, id };
} }
private etype(kind: ETypeKind, pos: Pos): EType {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
}
private parseMultiLineBlockExpr(): Expr { private parseMultiLineBlockExpr(): Expr {
const pos = this.pos(); const pos = this.pos();
if (this.test("{")) if (this.test("{")) {
return this.parseBlock(); return this.parseBlock();
if (this.test("if")) }
if (this.test("if")) {
return this.parseIf(); return this.parseIf();
if (this.test("loop")) }
if (this.test("loop")) {
return this.parseLoop(); return this.parseLoop();
}
this.report("expected expr"); this.report("expected expr");
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
private parseSingleLineBlockStmt(): Stmt { private parseSingleLineBlockStmt(): Stmt {
const pos = this.pos(); const pos = this.pos();
if (this.test("let")) if (this.test("let")) {
return this.parseLet(); return this.parseLet();
if (this.test("return")) }
if (this.test("return")) {
return this.parseReturn(); return this.parseReturn();
if (this.test("break")) }
if (this.test("break")) {
return this.parseBreak(); return this.parseBreak();
}
this.report("expected stmt"); this.report("expected stmt");
return this.stmt({ type: "error" }, pos); return this.stmt({ type: "error" }, pos);
} }
private eatSemicolon() { private eatSemicolon() {
if (!this.test(";")) { if (!this.test(";")) {
this.report(`expected ';', got '${this.currentToken?.type ?? "eof"}'`); this.report(
`expected ';', got '${this.currentToken?.type ?? "eof"}'`,
);
return; return;
} }
this.step(); this.step();
@ -91,11 +126,12 @@ export class Parser {
if (this.test("}")) { if (this.test("}")) {
this.step(); this.step();
return this.expr({ type: "block", stmts }, pos); return this.expr({ type: "block", stmts }, pos);
} else if (this.test("return") || this.test("break") || this.test("let")) { } else if (
this.test("return") || this.test("break") || this.test("let")
) {
stmts.push(this.parseSingleLineBlockStmt()); stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon(); this.eatSemicolon();
} } else if (this.test("fn")) {
else if (this.test("fn")) {
stmts.push(this.parseSingleLineBlockStmt()); stmts.push(this.parseSingleLineBlockStmt());
stmts.push(this.parseFn()); stmts.push(this.parseFn());
} else if (this.test("{") || this.test("if") || this.test("loop")) { } else if (this.test("{") || this.test("if") || this.test("loop")) {
@ -111,7 +147,12 @@ export class Parser {
this.step(); this.step();
const value = this.parseExpr(); const value = this.parseExpr();
this.eatSemicolon(); this.eatSemicolon();
stmts.push(this.stmt({ type: "assign", subject: expr, value }, pos)); stmts.push(
this.stmt(
{ type: "assign", subject: expr, value },
pos,
),
);
} else if (this.test(";")) { } else if (this.test(";")) {
this.step(); this.step();
stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
@ -133,13 +174,15 @@ export class Parser {
while (!this.done()) { while (!this.done()) {
if (this.test("fn")) { if (this.test("fn")) {
stmts.push(this.parseFn()); stmts.push(this.parseFn());
} else if (this.test("let") || this.test("return") || this.test("break")) { } else if (
this.test("let") || this.test("return") || this.test("break")
) {
stmts.push(this.parseSingleLineBlockStmt()); stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon(); this.eatSemicolon();
} else if (this.test("{") || this.test("if") || this.test("loop")) { } else if (this.test("{") || this.test("if") || this.test("loop")) {
const expr = this.parseMultiLineBlockExpr(); const expr = this.parseMultiLineBlockExpr();
stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
} else { } else {
stmts.push(this.parseAssign()); stmts.push(this.parseAssign());
this.eatSemicolon(); this.eatSemicolon();
} }
@ -161,12 +204,20 @@ export class Parser {
return this.stmt({ type: "error" }, pos); return this.stmt({ type: "error" }, pos);
} }
const params = this.parseFnParams(); const params = this.parseFnParams();
let returnType: EType | null = null;
if (this.test("->")) {
this.step();
returnType = this.parseEType();
}
if (!this.test("{")) { if (!this.test("{")) {
this.report("expected block"); this.report("expected block");
return this.stmt({ type: "error" }, pos); return this.stmt({ type: "error" }, pos);
} }
const body = this.parseBlock(); const body = this.parseBlock();
return this.stmt({ type: "fn", ident, params, body }, pos); if (returnType === null) {
return this.stmt({ type: "fn", ident, params, body }, pos);
}
return this.stmt({ type: "fn", ident, params, returnType, body }, pos);
} }
public parseFnParams(): Param[] { public parseFnParams(): Param[] {
@ -175,18 +226,21 @@ export class Parser {
this.step(); this.step();
return []; return [];
} }
let params: Param[] = []; const params: Param[] = [];
const paramResult = this.parseParam(); const paramResult = this.parseParam();
if (!paramResult.ok) if (!paramResult.ok) {
return []; return [];
}
params.push(paramResult.value); params.push(paramResult.value);
while (this.test(",")) { while (this.test(",")) {
this.step(); this.step();
if (this.test(")")) if (this.test(")")) {
break; break;
}
const paramResult = this.parseParam(); const paramResult = this.parseParam();
if (!paramResult.ok) if (!paramResult.ok) {
return []; return [];
}
params.push(paramResult.value); params.push(paramResult.value);
} }
if (!this.test(")")) { if (!this.test(")")) {
@ -197,11 +251,15 @@ export class Parser {
return params; return params;
} }
public parseParam(): { ok: true, value: Param } | { ok: false } { public parseParam(): { ok: true; value: Param } | { ok: false } {
const pos = this.pos(); const pos = this.pos();
if (this.test("ident")) { if (this.test("ident")) {
const ident = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
if (this.test(":")) {
const etype = this.parseEType();
return { ok: true, value: { ident, etype, pos } };
}
return { ok: true, value: { ident, pos } }; return { ok: true, value: { ident, pos } };
} }
this.report("expected param"); this.report("expected param");
@ -212,8 +270,9 @@ export class Parser {
const pos = this.pos(); const pos = this.pos();
this.step(); this.step();
const paramResult = this.parseParam(); const paramResult = this.parseParam();
if (!paramResult.ok) if (!paramResult.ok) {
return this.stmt({ type: "error" }, pos); return this.stmt({ type: "error" }, pos);
}
const param = paramResult.value; const param = paramResult.value;
if (!this.test("=")) { if (!this.test("=")) {
this.report("expected '='"); this.report("expected '='");
@ -297,10 +356,25 @@ export class Parser {
const subject = this.parsePrefix(); const subject = this.parsePrefix();
return this.expr({ type: "unary", unaryType: "not", subject }, pos); return this.expr({ type: "unary", unaryType: "not", subject }, pos);
} }
for (const binaryType of ["+", "*", "==", "-", "/", "!=", "<", ">", "<=", ">=", "or", "and"]) { for (
const subject = this.parseBinary(binaryType as BinaryType, pos) const binaryType of [
"+",
"*",
"==",
"-",
"/",
"!=",
"<",
">",
"<=",
">=",
"or",
"and",
]
) {
const subject = this.parseBinary(binaryType as BinaryType, pos);
if (subject !== null) { if (subject !== null) {
return subject return subject;
} }
} }
return this.parsePostfix(); return this.parsePostfix();
@ -313,7 +387,7 @@ export class Parser {
const right = this.parsePrefix(); const right = this.parsePrefix();
return this.expr({ type: "binary", binaryType, left, right }, pos); return this.expr({ type: "binary", binaryType, left, right }, pos);
} }
return null return null;
} }
public parsePostfix(): Expr { public parsePostfix(): Expr {
@ -349,8 +423,9 @@ export class Parser {
args.push(this.parseExpr()); args.push(this.parseExpr());
while (this.test(",")) { while (this.test(",")) {
this.step(); this.step();
if (this.test(")")) if (this.test(")")) {
break; break;
}
args.push(this.parseExpr()); args.push(this.parseExpr());
} }
} }
@ -394,7 +469,7 @@ export class Parser {
} }
if (this.test("null")) { if (this.test("null")) {
this.step(); this.step();
return this.expr({ type: "null"}, pos); return this.expr({ type: "null" }, pos);
} }
if (this.test("(")) { if (this.test("(")) {
this.step(); this.step();
@ -406,16 +481,78 @@ export class Parser {
this.step(); this.step();
return this.expr({ type: "group", expr }, pos); return this.expr({ type: "group", expr }, pos);
} }
if (this.test("{")) if (this.test("{")) {
return this.parseBlock(); return this.parseBlock();
if (this.test("if")) }
if (this.test("if")) {
return this.parseIf(); return this.parseIf();
if (this.test("loop")) }
if (this.test("loop")) {
return this.parseLoop(); return this.parseLoop();
}
this.report("expected expr", pos); this.report("expected expr", pos);
this.step(); this.step();
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
public parseEType(): EType {
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
return this.etype({ type: "ident", value: ident }, pos);
}
if (this.test("[")) {
this.step();
const inner = this.parseEType();
if (!this.test("]")) {
this.report("expected ']'", pos);
return this.etype({ type: "error" }, pos);
}
this.step();
return this.etype({ type: "array", inner }, pos);
}
if (this.test("struct")) {
this.step();
if (!this.test("{")) {
this.report("expected '{'");
return this.etype({ type: "error" }, pos);
}
const fields = this.parseETypeStructFields();
return this.etype({ type: "struct", fields }, pos);
}
this.report("expected type");
return this.etype({ type: "error" }, pos);
}
public parseETypeStructFields(): Param[] {
this.step();
if (this.test("}")) {
this.step();
return [];
}
const params: Param[] = [];
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
while (this.test(",")) {
this.step();
if (this.test("}")) {
break;
}
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
}
if (!this.test("}")) {
this.report("expected '}'");
return params;
}
this.step();
return params;
}
} }

View File

@ -1,59 +1,97 @@
import { Pos } from "./Token.ts"; import { Pos } from "./Token.ts";
import { VType } from "./vtypes.ts";
export type UnaryType = "not"; export type UnaryType = "not";
export type BinaryType = "+" | "*" | "==" | "-" | "/" | "!=" | "<" | ">" | "<=" | ">=" | "or" | "and"; export type BinaryType =
| "+"
| "*"
| "=="
| "-"
| "/"
| "!="
| "<"
| ">"
| "<="
| ">="
| "or"
| "and";
export type Param = { export type Param = {
ident: string, ident: string;
pos: Pos, etype?: EType;
pos: Pos;
vtype?: VType;
}; };
export type Stmt = { export type Stmt = {
kind: StmtKind, kind: StmtKind;
pos: Pos, pos: Pos;
id: number, vtype?: VType;
id: number;
}; };
export type StmtKind = export type StmtKind =
| { type: "error" } | { type: "error" }
| { type: "break", expr?: Expr } | { type: "break"; expr?: Expr }
| { type: "return", expr?: Expr } | { type: "return"; expr?: Expr }
| { type: "fn", ident: string, params: Param[], body: Expr } | {
| { type: "let", param: Param, value: Expr } type: "fn";
| { type: "assign", subject: Expr, value: Expr } ident: string;
| { type: "expr", expr: Expr } params: Param[];
; returnType?: EType;
body: Expr;
}
| { type: "let"; param: Param; value: Expr }
| { type: "assign"; subject: Expr; value: Expr }
| { type: "expr"; expr: Expr };
export type Expr = { export type Expr = {
kind: ExprKind, kind: ExprKind;
pos: Pos, pos: Pos;
id: number, vtype?: VType;
id: number;
}; };
export type ExprKind = export type ExprKind =
| { type: "error" } | { type: "error" }
| { type: "int", value: number } | { type: "int"; value: number }
| { type: "string", value: string } | { type: "string"; value: string }
| { type: "ident", value: string } | { type: "ident"; value: string }
| { type: "group", expr: Expr } | { type: "group"; expr: Expr }
| { type: "field", subject: Expr, value: string } | { type: "field"; subject: Expr; value: string }
| { type: "index", subject: Expr, value: Expr } | { type: "index"; subject: Expr; value: Expr }
| { type: "call", subject: Expr, args: Expr[] } | { type: "call"; subject: Expr; args: Expr[] }
| { type: "unary", unaryType: UnaryType, subject: Expr } | { type: "unary"; unaryType: UnaryType; subject: Expr }
| { type: "binary", binaryType: BinaryType, left: Expr, right: Expr } | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
| { type: "if", cond: Expr, truthy: Expr, falsy?: Expr } | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr }
| { type: "bool", value: boolean} | { type: "bool"; value: boolean }
| { type: "null"} | { type: "null" }
| { type: "loop", body: Expr } | { type: "loop"; body: Expr }
| { type: "block", stmts: Stmt[], expr?: Expr } | { type: "block"; stmts: Stmt[]; expr?: Expr }
| { type: "sym", ident: string, defType: "let" | "fn" | "fn_param" | "builtin", stmt?: Stmt, param?: Param } | {
; type: "sym";
ident: string;
defType: "let" | "fn" | "fn_param" | "builtin";
stmt?: Stmt;
param?: Param;
};
export type Sym = { export type Sym = {
ident: string, ident: string;
type: "let" | "fn" | "fn_param" | "builtin", type: "let" | "fn" | "fn_param" | "builtin";
pos?: Pos, pos?: Pos;
stmt?: Stmt, stmt?: Stmt;
param?: Param, param?: Param;
} };
export type EType = {
kind: ETypeKind;
pos: Pos;
id: number;
};
export type ETypeKind =
| { type: "error" }
| { type: "ident"; value: string }
| { type: "array"; inner: EType }
| { type: "struct"; fields: Param[] };

63
compiler/vtypes.ts Normal file
View File

@ -0,0 +1,63 @@
export type VType =
| { type: "" }
| { type: "error" }
| { type: "unknown" }
| { type: "null" }
| { type: "int" }
| { type: "string" }
| { type: "bool" }
| { type: "array"; inner: VType }
| { type: "struct"; fields: VTypeParam[] }
| { type: "fn"; params: VTypeParam[]; returnType: VType };
export type VTypeParam = {
ident: string;
vtype: VType;
};
export function vtypesEqual(a: VType, b: VType): boolean {
if (a.type !== b.type) {
return false;
}
if (
["error", "unknown", "null", "int", "string", "bool", "struct"]
.includes(a.type)
) {
return true;
}
if (a.type === "array" && b.type === "array") {
return vtypesEqual(a.inner, b.inner);
}
if (a.type === "fn" && b.type === "fn") {
if (a.params.length !== b.params.length) {
return false;
}
for (let i = 0; i < a.params.length; ++i) {
if (!vtypesEqual(a.params[i].vtype, b.params[i].vtype)) {
return false;
}
}
return vtypesEqual(a.returnType, b.returnType);
}
return false;
}
export function vtypeToString(vtype: VType): string {
if (
["error", "unknown", "null", "int", "string", "bool", "struct"]
.includes(vtype.type)
) {
return vtype.type;
}
if (vtype.type === "array") {
return `[${vtypeToString(vtype.inner)}]`;
}
if (vtype.type === "fn") {
const paramString = vtype.params.map((param) =>
`${param.ident}: ${vtypeToString(param.vtype)}`
)
.join(", ");
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
}
throw new Error(`unhandled vtype '${vtype.type}'`);
}