add typechecker
This commit is contained in:
parent
aa888c9368
commit
d4ea73de1d
229
compiler/Checker.ts
Normal file
229
compiler/Checker.ts
Normal 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" } },
|
||||
];
|
@ -5,16 +5,17 @@ export class Lexer {
|
||||
private line = 1;
|
||||
private col = 1;
|
||||
|
||||
|
||||
public constructor (private text: string) {}
|
||||
public constructor(private text: string) {}
|
||||
|
||||
public next(): Token | null {
|
||||
if (this.done())
|
||||
if (this.done()) {
|
||||
return null;
|
||||
}
|
||||
const pos = this.pos();
|
||||
if (this.test(/[ \t\n\r]/)) {
|
||||
while (!this.done() && this.test(/[ \t\n\r]/))
|
||||
while (!this.done() && this.test(/[ \t\n\r]/)) {
|
||||
this.step();
|
||||
}
|
||||
return this.next();
|
||||
}
|
||||
|
||||
@ -39,6 +40,8 @@ export class Lexer {
|
||||
return this.token("if", pos);
|
||||
case "else":
|
||||
return this.token("else", pos);
|
||||
case "struct":
|
||||
return this.token("struct", pos);
|
||||
default:
|
||||
return { ...this.token("ident", pos), identValue: value };
|
||||
}
|
||||
@ -53,25 +56,26 @@ export class Lexer {
|
||||
}
|
||||
|
||||
if (this.test("0")) {
|
||||
this.step()
|
||||
this.step();
|
||||
if (!this.done() && this.test(/[0-9]/)) {
|
||||
console.error(
|
||||
`Lexer: invalid number`
|
||||
+ ` at ${pos.line}:${pos.col}`,
|
||||
`Lexer: invalid number` +
|
||||
` at ${pos.line}:${pos.col}`,
|
||||
);
|
||||
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();
|
||||
let value = "";
|
||||
while (!this.done() && !this.test("\"")) {
|
||||
while (!this.done() && !this.test('"')) {
|
||||
if (this.test("\\")) {
|
||||
this.step();
|
||||
if (this.done())
|
||||
if (this.done()) {
|
||||
break;
|
||||
}
|
||||
value += {
|
||||
"n": "\n",
|
||||
"t": "\t",
|
||||
@ -82,10 +86,10 @@ export class Lexer {
|
||||
}
|
||||
this.step();
|
||||
}
|
||||
if (this.done() || !this.test("\"")) {
|
||||
if (this.done() || !this.test('"')) {
|
||||
console.error(
|
||||
`Lexer: unclosed/malformed string`
|
||||
+ ` at ${pos.line}:${pos.col}`,
|
||||
`Lexer: unclosed/malformed string` +
|
||||
` at ${pos.line}:${pos.col}`,
|
||||
);
|
||||
return this.token("error", pos);
|
||||
}
|
||||
@ -113,7 +117,7 @@ export class Lexer {
|
||||
return this.token("->", pos);
|
||||
}
|
||||
if (this.test("=")) {
|
||||
this.step()
|
||||
this.step();
|
||||
return this.token("-=", pos);
|
||||
}
|
||||
}
|
||||
@ -125,16 +129,21 @@ export class Lexer {
|
||||
this.step();
|
||||
return this.token("+=", pos);
|
||||
}
|
||||
if (first === "-" && !this.done() && this.test(">")) {
|
||||
this.step();
|
||||
return this.token("->", pos);
|
||||
}
|
||||
return this.token(first, pos);
|
||||
}
|
||||
if (this.test("/")) {
|
||||
this.step()
|
||||
this.step();
|
||||
if (this.test("/")) {
|
||||
while (!this.done() && !this.test("\n"))
|
||||
while (!this.done() && !this.test("\n")) {
|
||||
this.step();
|
||||
return this.next()
|
||||
}
|
||||
return this.next();
|
||||
}
|
||||
return this.token("/", pos)
|
||||
return this.token("/", pos);
|
||||
}
|
||||
if (this.test("false")) {
|
||||
this.step();
|
||||
@ -180,20 +189,29 @@ export class Lexer {
|
||||
this.step();
|
||||
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();
|
||||
return this.next();
|
||||
return this.next();
|
||||
}
|
||||
|
||||
private done(): boolean { return this.index >= this.text.length; }
|
||||
|
||||
private current(): string { return this.text[this.index]; }
|
||||
|
||||
public currentPos(): Pos { return this.pos(); }
|
||||
private done(): boolean {
|
||||
return this.index >= this.text.length;
|
||||
}
|
||||
|
||||
private current(): string {
|
||||
return this.text[this.index];
|
||||
}
|
||||
|
||||
public currentPos(): Pos {
|
||||
return this.pos();
|
||||
}
|
||||
|
||||
private step() {
|
||||
if (this.done())
|
||||
if (this.done()) {
|
||||
return;
|
||||
}
|
||||
if (this.current() === "\n") {
|
||||
this.line += 1;
|
||||
this.col = 1;
|
||||
@ -207,8 +225,8 @@ export class Lexer {
|
||||
return {
|
||||
index: this.index,
|
||||
line: this.line,
|
||||
col: this.col
|
||||
}
|
||||
col: this.col,
|
||||
};
|
||||
}
|
||||
|
||||
private token(type: string, pos: Pos): Token {
|
||||
@ -217,13 +235,10 @@ export class Lexer {
|
||||
}
|
||||
|
||||
private test(pattern: RegExp | string): boolean {
|
||||
if (typeof pattern === "string")
|
||||
if (typeof pattern === "string") {
|
||||
return this.current() === pattern;
|
||||
else
|
||||
} else {
|
||||
return pattern.test(this.current());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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 { Pos, Token } from "./Token.ts";
|
||||
|
||||
@ -10,12 +19,19 @@ export class Parser {
|
||||
this.currentToken = lexer.next();
|
||||
}
|
||||
|
||||
private step() { this.currentToken = this.lexer.next() }
|
||||
public done(): boolean { return this.currentToken == null; }
|
||||
private current(): Token { return this.currentToken!; }
|
||||
private step() {
|
||||
this.currentToken = this.lexer.next();
|
||||
}
|
||||
public done(): boolean {
|
||||
return this.currentToken == null;
|
||||
}
|
||||
private current(): Token {
|
||||
return this.currentToken!;
|
||||
}
|
||||
private pos(): Pos {
|
||||
if (this.done())
|
||||
if (this.done()) {
|
||||
return this.lexer.currentPos();
|
||||
}
|
||||
return this.current().pos;
|
||||
}
|
||||
|
||||
@ -25,13 +41,18 @@ export class Parser {
|
||||
|
||||
private report(msg: string, pos = this.pos()) {
|
||||
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
|
||||
class ReportNotAnError extends Error { constructor() { super("ReportNotAnError"); } }
|
||||
class ReportNotAnError extends Error {
|
||||
constructor() {
|
||||
super("ReportNotAnError");
|
||||
}
|
||||
}
|
||||
try {
|
||||
throw new ReportNotAnError();
|
||||
} catch (error) {
|
||||
if (!(error instanceof ReportNotAnError))
|
||||
if (!(error instanceof ReportNotAnError)) {
|
||||
throw error;
|
||||
console.log(error)
|
||||
}
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,33 +68,47 @@ export class Parser {
|
||||
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 {
|
||||
const pos = this.pos();
|
||||
if (this.test("{"))
|
||||
if (this.test("{")) {
|
||||
return this.parseBlock();
|
||||
if (this.test("if"))
|
||||
}
|
||||
if (this.test("if")) {
|
||||
return this.parseIf();
|
||||
if (this.test("loop"))
|
||||
}
|
||||
if (this.test("loop")) {
|
||||
return this.parseLoop();
|
||||
}
|
||||
this.report("expected expr");
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
|
||||
private parseSingleLineBlockStmt(): Stmt {
|
||||
const pos = this.pos();
|
||||
if (this.test("let"))
|
||||
if (this.test("let")) {
|
||||
return this.parseLet();
|
||||
if (this.test("return"))
|
||||
}
|
||||
if (this.test("return")) {
|
||||
return this.parseReturn();
|
||||
if (this.test("break"))
|
||||
}
|
||||
if (this.test("break")) {
|
||||
return this.parseBreak();
|
||||
}
|
||||
this.report("expected stmt");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
|
||||
private eatSemicolon() {
|
||||
if (!this.test(";")) {
|
||||
this.report(`expected ';', got '${this.currentToken?.type ?? "eof"}'`);
|
||||
this.report(
|
||||
`expected ';', got '${this.currentToken?.type ?? "eof"}'`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.step();
|
||||
@ -91,11 +126,12 @@ export class Parser {
|
||||
if (this.test("}")) {
|
||||
this.step();
|
||||
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());
|
||||
this.eatSemicolon();
|
||||
}
|
||||
else if (this.test("fn")) {
|
||||
} else if (this.test("fn")) {
|
||||
stmts.push(this.parseSingleLineBlockStmt());
|
||||
stmts.push(this.parseFn());
|
||||
} else if (this.test("{") || this.test("if") || this.test("loop")) {
|
||||
@ -111,7 +147,12 @@ export class Parser {
|
||||
this.step();
|
||||
const value = this.parseExpr();
|
||||
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(";")) {
|
||||
this.step();
|
||||
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
||||
@ -133,13 +174,15 @@ export class Parser {
|
||||
while (!this.done()) {
|
||||
if (this.test("fn")) {
|
||||
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());
|
||||
this.eatSemicolon();
|
||||
} else if (this.test("{") || this.test("if") || this.test("loop")) {
|
||||
const expr = this.parseMultiLineBlockExpr();
|
||||
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
||||
} else {
|
||||
} else {
|
||||
stmts.push(this.parseAssign());
|
||||
this.eatSemicolon();
|
||||
}
|
||||
@ -161,12 +204,20 @@ export class Parser {
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
const params = this.parseFnParams();
|
||||
let returnType: EType | null = null;
|
||||
if (this.test("->")) {
|
||||
this.step();
|
||||
returnType = this.parseEType();
|
||||
}
|
||||
if (!this.test("{")) {
|
||||
this.report("expected block");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
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[] {
|
||||
@ -175,18 +226,21 @@ export class Parser {
|
||||
this.step();
|
||||
return [];
|
||||
}
|
||||
let params: Param[] = [];
|
||||
const params: Param[] = [];
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok)
|
||||
if (!paramResult.ok) {
|
||||
return [];
|
||||
}
|
||||
params.push(paramResult.value);
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.test(")"))
|
||||
if (this.test(")")) {
|
||||
break;
|
||||
}
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok)
|
||||
if (!paramResult.ok) {
|
||||
return [];
|
||||
}
|
||||
params.push(paramResult.value);
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
@ -197,11 +251,15 @@ export class Parser {
|
||||
return params;
|
||||
}
|
||||
|
||||
public parseParam(): { ok: true, value: Param } | { ok: false } {
|
||||
public parseParam(): { ok: true; value: Param } | { ok: false } {
|
||||
const pos = this.pos();
|
||||
if (this.test("ident")) {
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
if (this.test(":")) {
|
||||
const etype = this.parseEType();
|
||||
return { ok: true, value: { ident, etype, pos } };
|
||||
}
|
||||
return { ok: true, value: { ident, pos } };
|
||||
}
|
||||
this.report("expected param");
|
||||
@ -212,8 +270,9 @@ export class Parser {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok)
|
||||
if (!paramResult.ok) {
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
const param = paramResult.value;
|
||||
if (!this.test("=")) {
|
||||
this.report("expected '='");
|
||||
@ -297,10 +356,25 @@ export class Parser {
|
||||
const subject = this.parsePrefix();
|
||||
return this.expr({ type: "unary", unaryType: "not", subject }, pos);
|
||||
}
|
||||
for (const binaryType of ["+", "*", "==", "-", "/", "!=", "<", ">", "<=", ">=", "or", "and"]) {
|
||||
const subject = this.parseBinary(binaryType as BinaryType, pos)
|
||||
for (
|
||||
const binaryType of [
|
||||
"+",
|
||||
"*",
|
||||
"==",
|
||||
"-",
|
||||
"/",
|
||||
"!=",
|
||||
"<",
|
||||
">",
|
||||
"<=",
|
||||
">=",
|
||||
"or",
|
||||
"and",
|
||||
]
|
||||
) {
|
||||
const subject = this.parseBinary(binaryType as BinaryType, pos);
|
||||
if (subject !== null) {
|
||||
return subject
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
return this.parsePostfix();
|
||||
@ -312,8 +386,8 @@ export class Parser {
|
||||
const left = this.parsePrefix();
|
||||
const right = this.parsePrefix();
|
||||
return this.expr({ type: "binary", binaryType, left, right }, pos);
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public parsePostfix(): Expr {
|
||||
@ -349,8 +423,9 @@ export class Parser {
|
||||
args.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.test(")"))
|
||||
if (this.test(")")) {
|
||||
break;
|
||||
}
|
||||
args.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
@ -394,7 +469,7 @@ export class Parser {
|
||||
}
|
||||
if (this.test("null")) {
|
||||
this.step();
|
||||
return this.expr({ type: "null"}, pos);
|
||||
return this.expr({ type: "null" }, pos);
|
||||
}
|
||||
if (this.test("(")) {
|
||||
this.step();
|
||||
@ -406,16 +481,78 @@ export class Parser {
|
||||
this.step();
|
||||
return this.expr({ type: "group", expr }, pos);
|
||||
}
|
||||
if (this.test("{"))
|
||||
if (this.test("{")) {
|
||||
return this.parseBlock();
|
||||
if (this.test("if"))
|
||||
}
|
||||
if (this.test("if")) {
|
||||
return this.parseIf();
|
||||
if (this.test("loop"))
|
||||
}
|
||||
if (this.test("loop")) {
|
||||
return this.parseLoop();
|
||||
}
|
||||
|
||||
this.report("expected expr", pos);
|
||||
this.step();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
114
compiler/ast.ts
114
compiler/ast.ts
@ -1,59 +1,97 @@
|
||||
import { Pos } from "./Token.ts";
|
||||
import { VType } from "./vtypes.ts";
|
||||
|
||||
export type UnaryType = "not";
|
||||
export type BinaryType = "+" | "*" | "==" | "-" | "/" | "!=" | "<" | ">" | "<=" | ">=" | "or" | "and";
|
||||
export type BinaryType =
|
||||
| "+"
|
||||
| "*"
|
||||
| "=="
|
||||
| "-"
|
||||
| "/"
|
||||
| "!="
|
||||
| "<"
|
||||
| ">"
|
||||
| "<="
|
||||
| ">="
|
||||
| "or"
|
||||
| "and";
|
||||
|
||||
export type Param = {
|
||||
ident: string,
|
||||
pos: Pos,
|
||||
ident: string;
|
||||
etype?: EType;
|
||||
pos: Pos;
|
||||
vtype?: VType;
|
||||
};
|
||||
|
||||
export type Stmt = {
|
||||
kind: StmtKind,
|
||||
pos: Pos,
|
||||
id: number,
|
||||
kind: StmtKind;
|
||||
pos: Pos;
|
||||
vtype?: VType;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type StmtKind =
|
||||
| { type: "error" }
|
||||
| { type: "break", expr?: Expr }
|
||||
| { type: "return", expr?: Expr }
|
||||
| { type: "fn", ident: string, params: Param[], body: Expr }
|
||||
| { type: "let", param: Param, value: Expr }
|
||||
| { type: "assign", subject: Expr, value: Expr }
|
||||
| { type: "expr", expr: Expr }
|
||||
;
|
||||
| { type: "break"; expr?: Expr }
|
||||
| { type: "return"; expr?: Expr }
|
||||
| {
|
||||
type: "fn";
|
||||
ident: string;
|
||||
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 = {
|
||||
kind: ExprKind,
|
||||
pos: Pos,
|
||||
id: number,
|
||||
kind: ExprKind;
|
||||
pos: Pos;
|
||||
vtype?: VType;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type ExprKind =
|
||||
| { type: "error" }
|
||||
| { type: "int", value: number }
|
||||
| { type: "string", value: string }
|
||||
| { type: "ident", value: string }
|
||||
| { type: "group", expr: Expr }
|
||||
| { type: "field", subject: Expr, value: string }
|
||||
| { type: "index", subject: Expr, value: Expr }
|
||||
| { type: "call", subject: Expr, args: Expr[] }
|
||||
| { type: "unary", unaryType: UnaryType, subject: Expr }
|
||||
| { type: "binary", binaryType: BinaryType, left: Expr, right: Expr }
|
||||
| { type: "if", cond: Expr, truthy: Expr, falsy?: Expr }
|
||||
| { type: "bool", value: boolean}
|
||||
| { type: "null"}
|
||||
| { type: "loop", body: Expr }
|
||||
| { type: "block", stmts: Stmt[], expr?: Expr }
|
||||
| { type: "sym", ident: string, defType: "let" | "fn" | "fn_param" | "builtin", stmt?: Stmt, param?: Param }
|
||||
;
|
||||
| { type: "int"; value: number }
|
||||
| { type: "string"; value: string }
|
||||
| { type: "ident"; value: string }
|
||||
| { type: "group"; expr: Expr }
|
||||
| { type: "field"; subject: Expr; value: string }
|
||||
| { type: "index"; subject: Expr; value: Expr }
|
||||
| { type: "call"; subject: Expr; args: Expr[] }
|
||||
| { type: "unary"; unaryType: UnaryType; subject: Expr }
|
||||
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
|
||||
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr }
|
||||
| { type: "bool"; value: boolean }
|
||||
| { type: "null" }
|
||||
| { type: "loop"; body: Expr }
|
||||
| { type: "block"; stmts: Stmt[]; expr?: Expr }
|
||||
| {
|
||||
type: "sym";
|
||||
ident: string;
|
||||
defType: "let" | "fn" | "fn_param" | "builtin";
|
||||
stmt?: Stmt;
|
||||
param?: Param;
|
||||
};
|
||||
|
||||
export type Sym = {
|
||||
ident: string,
|
||||
type: "let" | "fn" | "fn_param" | "builtin",
|
||||
pos?: Pos,
|
||||
stmt?: Stmt,
|
||||
param?: Param,
|
||||
}
|
||||
ident: string;
|
||||
type: "let" | "fn" | "fn_param" | "builtin";
|
||||
pos?: Pos;
|
||||
stmt?: Stmt;
|
||||
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
63
compiler/vtypes.ts
Normal 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}'`);
|
||||
}
|
Loading…
Reference in New Issue
Block a user