From f3523e486b4cf6ecd58be8f608005b46dfcd139c Mon Sep 17 00:00:00 2001 From: sfja Date: Wed, 11 Dec 2024 03:11:00 +0100 Subject: [PATCH] fix compilter --- .gitignore | 1 + compiler/assembler.ts | 43 ++++++++++ compiler/checker.ts | 172 +++++++++++++++++++------------------ compiler/info.ts | 55 ++++++++++++ compiler/lexer.ts | 25 +++--- compiler/lowerer.ts | 67 ++++++++++++--- compiler/lowerer_locals.ts | 34 ++++++-- compiler/main.ts | 22 +++-- compiler/parser.ts | 22 ++--- compiler/resolver.ts | 28 +++--- examples/add_fn.slg | 10 ++- examples/add_int.slg | 2 +- examples/add_let.slg | 2 +- runtime/main.cpp | 43 +++++++++- runtime/vm.cpp | 7 +- slige-run.sh | 12 +++ 16 files changed, 390 insertions(+), 155 deletions(-) create mode 100644 .gitignore create mode 100644 compiler/info.ts create mode 100755 slige-run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e65e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out.slgbc diff --git a/compiler/assembler.ts b/compiler/assembler.ts index c33af98..b4336cd 100644 --- a/compiler/assembler.ts +++ b/compiler/assembler.ts @@ -43,14 +43,23 @@ export class Assembler { } public assemble(): number[] { + console.log("Assembling..."); let ip = 0; const output: number[] = []; const locs: { [key: string]: number } = {}; const refs: { [key: number]: string } = {}; + + const debugLines: { + startIp: number; + endIp: number; + insString: string; + }[] = []; + for (const line of this.lines) { for (const label of line.labels ?? []) { locs[label] = ip; } + const startIp = ip; for (const lit of line.ins as Lit[]) { if (typeof lit === "number") { output.push(lit); @@ -69,6 +78,11 @@ export class Assembler { ip += 1; } } + debugLines.push({ + startIp, + endIp: ip, + insString: this.insToString(line.ins), + }); } for (let i = 0; i < output.length; ++i) { if (!(i in refs)) { @@ -82,6 +96,14 @@ export class Assembler { } output[i] = locs[refs[i]]; } + + for (const line of debugLines) { + console.log( + line.startIp.toString().padStart(3, " ") + " " + + output.slice(line.startIp, line.endIp).join(", ") + "\n" + + line.insString + "\n", + ); + } return output; } @@ -110,4 +132,25 @@ export class Assembler { console.log(` ${op} ${args}`); } } + + private insToString(ins: Ins): string { + const op = opToString(ins[0] as number) + .padEnd(13, " "); + const args = (ins.slice(1) as Lit[]).map((lit) => { + if (typeof lit === "number") { + return lit; + } else if (typeof lit === "boolean") { + return lit.toString(); + } else if (typeof lit === "string") { + return '"' + + lit.replaceAll("\\", "\\\\").replaceAll("\0", "\\0") + .replaceAll("\n", "\\n").replaceAll("\t", "\\t") + .replaceAll("\r", "\\r") + + '"'; + } else { + return lit.label; + } + }).join(", "); + return ` ${op} ${args}`; + } } diff --git a/compiler/checker.ts b/compiler/checker.ts index efd45e4..6fc1522 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -1,5 +1,6 @@ import { StmtKind } from "./ast.ts"; import { EType, Expr, Stmt } from "./ast.ts"; +import { printStackTrace, Reporter } from "./info.ts"; import { Pos } from "./token.ts"; import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts"; @@ -7,12 +8,37 @@ export class Checker { private fnReturnStack: VType[] = []; private loopBreakStack: VType[][] = []; + public constructor(private reporter: Reporter) {} + public check(stmts: Stmt[]) { + this.checkFnHeaders(stmts); for (const stmt of stmts) { this.checkStmt(stmt); } } + private checkFnHeaders(stmts: Stmt[]) { + for (const stmt of stmts) { + if (stmt.kind.type !== "fn") { + continue; + } + const returnType: VType = stmt.kind.returnType + ? this.checkEType(stmt.kind.returnType) + : { type: "null" }; + const params: VTypeParam[] = []; + for (const param of stmt.kind.params) { + if (param.etype === undefined) { + this.report("parameter types must be defined", param.pos); + stmt.kind.vtype = { type: "error" }; + } + const vtype = this.checkEType(param.etype!); + param.vtype = vtype; + params.push({ ident: param.ident, vtype }); + } + stmt.kind.vtype = { type: "fn", params, returnType }; + } + } + public checkStmt(stmt: Stmt) { switch (stmt.kind.type) { case "error": @@ -53,8 +79,8 @@ export class Checker { if (!vtypesEqual(prevBreakType, exprType)) { this.report( `incompatible types for break` + - `, got ${exprType}` + - ` incompatible with ${prevBreakType}`, + `, got ${exprType}` + + ` incompatible with ${prevBreakType}`, pos, ); return; @@ -78,8 +104,8 @@ export class Checker { if (!vtypesEqual(exprType, returnType)) { this.report( `incompatible return type` + - `, got ${exprType}` + - `, expected ${returnType}`, + `, got ${exprType}` + + `, expected ${returnType}`, pos, ); } @@ -90,28 +116,18 @@ export class Checker { throw new Error(); } const pos = stmt.pos; - const returnType: VType = stmt.kind.returnType - ? this.checkEType(stmt.kind.returnType) - : { type: "null" }; - const params: VTypeParam[] = []; - for (const param of stmt.kind.params) { - if (param.etype === undefined) { - this.report("parameter types must be defined", param.pos); - stmt.kind.vtype = { type: "error" }; - } - const vtype = this.checkEType(param.etype!); - param.vtype = vtype; - params.push({ ident: param.ident, vtype }); + if (stmt.kind.vtype!.type !== "fn") { + throw new Error(); } - stmt.kind.vtype = { type: "fn", params, returnType }; + const { returnType } = stmt.kind.vtype!; this.fnReturnStack.push(returnType); const body = this.checkExpr(stmt.kind.body); this.fnReturnStack.pop(); if (!vtypesEqual(returnType, body)) { this.report( `incompatible return type` + - `, got ${body}` + - `, expected ${returnType}`, + `, expected '${vtypeToString(returnType)}'` + + `, got '${vtypeToString(body)}'`, pos, ); } @@ -128,8 +144,8 @@ export class Checker { if (!vtypesEqual(value, paramVtype)) { this.report( `incompatible value type` + - `, got '${vtypeToString(value)}'` + - `, expected '${vtypeToString(paramVtype)}'`, + `, got '${vtypeToString(value)}'` + + `, expected '${vtypeToString(paramVtype)}'`, pos, ); return; @@ -165,8 +181,8 @@ export class Checker { if (!vtypesEqual(found.vtype, value)) { this.report( `cannot assign incompatible type to field '${found.ident}'` + - `, got '${vtypeToString(value)}'` + - `, expected '${vtypeToString(found.vtype)}'`, + `, got '${vtypeToString(value)}'` + + `, expected '${vtypeToString(found.vtype)}'`, pos, ); return; @@ -187,8 +203,8 @@ export class Checker { if (!vtypesEqual(subject.inner, value)) { this.report( `cannot assign incompatible type to array ` + - `'${vtypeToString(subject)}'` + - `, got '${vtypeToString(value)}'`, + `'${vtypeToString(subject)}'` + + `, got '${vtypeToString(value)}'`, pos, ); return; @@ -205,11 +221,12 @@ export class Checker { ) { this.report( `cannot assign to incompatible type` + - `, got '${vtypeToString(value)}'` + - `, expected '${vtypeToString( - stmt.kind.subject.kind.sym.param.vtype!, - ) - }'`, + `, got '${vtypeToString(value)}'` + + `, expected '${ + vtypeToString( + stmt.kind.subject.kind.sym.param.vtype!, + ) + }'`, pos, ); return; @@ -347,7 +364,7 @@ export class Checker { if (args.length !== subject.params.length) { this.report( `incorrect number of arguments` + - `, expected ${subject.params.length}`, + `, expected ${subject.params.length}`, pos, ); } @@ -355,8 +372,8 @@ export class Checker { 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])}`, + `, expected ${vtypeToString(subject.params[i].vtype)}` + + `, got ${vtypeToString(args[i])}`, pos, ); break; @@ -382,7 +399,7 @@ export class Checker { } this.report( `cannot apply unary operation '${expr.kind.unaryType}' ` + - `on type '${vtypeToString(subject)}'`, + `on type '${vtypeToString(subject)}'`, pos, ); return { type: "error" }; @@ -409,8 +426,9 @@ export class Checker { } this.report( `cannot apply binary operation '${expr.kind.binaryType}' ` + - `on types '${vtypeToString(left)}' and '${vtypeToString(right) - }'`, + `on types '${vtypeToString(left)}' and '${ + vtypeToString(right) + }'`, pos, ); return { type: "error" }; @@ -436,7 +454,7 @@ export class Checker { if (falsy === undefined && truthy.type !== "null") { this.report( `if expressions without false-case must result in type 'null'` + - `, got '${vtypeToString(truthy)}'`, + `, got '${vtypeToString(truthy)}'`, pos, ); return { type: "error" }; @@ -444,8 +462,8 @@ export class Checker { if (falsy !== undefined && !vtypesEqual(truthy, falsy)) { this.report( `if cases must be compatible, got incompatible types` + - ` '${vtypeToString(truthy)}'` + - ` and '${vtypeToString(falsy)}'`, + ` '${vtypeToString(truthy)}'` + + ` and '${vtypeToString(falsy)}'`, pos, ); return { type: "error" }; @@ -463,7 +481,7 @@ export class Checker { if (body.type !== "null") { this.report( `loop body must result in type 'null'` + - `, got '${vtypeToString(body)}'`, + `, got '${vtypeToString(body)}'`, pos, ); return { type: "error" }; @@ -488,8 +506,8 @@ export class Checker { if (breakType[1]) { this.report( `incompatible types in break statements` + - `, got '${vtypeToString(breakType[2])}'` + - ` incompatible with ${vtypeToString(breakType[0])}`, + `, got '${vtypeToString(breakType[2])}'` + + ` incompatible with ${vtypeToString(breakType[0])}`, pos, ); return { type: "error" }; @@ -501,7 +519,7 @@ export class Checker { if (expr.kind.type !== "block") { throw new Error(); } - const pos = expr.pos; + this.checkFnHeaders(expr.kind.stmts); for (const stmt of expr.kind.stmts) { this.checkStmt(stmt); } @@ -574,20 +592,8 @@ export class Checker { } private report(msg: string, pos: Pos) { - console.error(`Checker: ${msg} at ${pos.line}:${pos.col}`); - class ReportNotAnError extends Error { - constructor() { - super("ReportNotAnError"); - } - } - try { - throw new ReportNotAnError(); - } catch (error) { - if (!(error instanceof ReportNotAnError)) { - throw error; - } - console.log(error); - } + this.reporter.reportError({ reporter: "Checker", msg, pos }); + printStackTrace(); } } @@ -596,35 +602,35 @@ const simpleUnaryOperations: { operand: VType; result?: VType; }[] = [ - { unaryType: "not", operand: { type: "bool" } }, - ]; + { unaryType: "not", operand: { type: "bool" } }, +]; const simpleBinaryOperations: { binaryType: string; operand: VType; result?: VType; }[] = [ - // arithmetic - { binaryType: "+", operand: { type: "int" } }, - { binaryType: "+", operand: { type: "string" } }, - { 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" } }, - ]; + // arithmetic + { binaryType: "+", operand: { type: "int" } }, + { binaryType: "+", operand: { type: "string" } }, + { 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" } }, +]; diff --git a/compiler/info.ts b/compiler/info.ts new file mode 100644 index 0000000..07b2ca8 --- /dev/null +++ b/compiler/info.ts @@ -0,0 +1,55 @@ +import { Pos } from "./token.ts"; + +export type Report = { + type: "error" | "note"; + reporter: string; + pos?: Pos; + msg: string; +}; + +export class Reporter { + private reports: Report[] = []; + private errorSet = false; + + public reportError(report: Omit) { + this.reports.push({ ...report, type: "error" }); + this.printReport({ ...report, type: "error" }); + this.errorSet = true; + } + + private printReport({ reporter, type, pos, msg }: Report) { + console.error( + `${reporter}: ${type}: ${msg}${ + pos ? ` at ${pos.line}:${pos.col}` : "" + }`, + ); + } + + public addNote(report: Omit) { + this.reports.push({ ...report, type: "note" }); + this.printReport({ ...report, type: "note" }); + } + + public errorOccured(): boolean { + return this.errorSet; + } +} + +export function printStackTrace() { + class ReportNotAnError extends Error { + constructor() { + super("ReportNotAnError"); + } + } + try { + throw new ReportNotAnError(); + } catch (error) { + if (!(error instanceof ReportNotAnError)) { + throw error; + } + console.log( + error.stack?.replace("Error: ReportNotAnError", "Stack trace:") ?? + error, + ); + } +} diff --git a/compiler/lexer.ts b/compiler/lexer.ts index 55f71bf..fabd11c 100644 --- a/compiler/lexer.ts +++ b/compiler/lexer.ts @@ -1,3 +1,4 @@ +import { Reporter } from "./info.ts"; import { Pos, Token } from "./token.ts"; export class Lexer { @@ -5,7 +6,7 @@ export class Lexer { private line = 1; private col = 1; - public constructor(private text: string) {} + public constructor(private text: string, private reporter: Reporter) {} public next(): Token | null { if (this.done()) { @@ -58,10 +59,7 @@ export class Lexer { if (this.test("0")) { this.step(); if (!this.done() && this.test(/[0-9]/)) { - console.error( - `Lexer: invalid number` + - ` at ${pos.line}:${pos.col}`, - ); + this.report("invalid number", pos); return this.token("error", pos); } return { ...this.token("int", pos), intValue: 0 }; @@ -87,10 +85,7 @@ export class Lexer { this.step(); } if (this.done() || !this.test('"')) { - console.error( - `Lexer: unclosed/malformed string` + - ` at ${pos.line}:${pos.col}`, - ); + this.report("unclosed/malformed string", pos); return this.token("error", pos); } this.step(); @@ -189,9 +184,7 @@ export class Lexer { this.step(); return this.token("return", pos); } - console.error( - `Lexer: illegal character '${this.current()}' at ${pos.line}:${pos.col}`, - ); + this.report(`illegal character '${this.current()}'`, pos); this.step(); return this.next(); } @@ -241,4 +234,12 @@ export class Lexer { return pattern.test(this.current()); } } + + private report(msg: string, pos: Pos) { + this.reporter.reportError({ + msg, + pos, + reporter: "Lexer", + }); + } } diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index 7381a06..19dee22 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -12,15 +12,34 @@ export class Lowerer { private breakStack: Label[] = []; public lower(stmts: Stmt[]) { + this.program.add(Ops.PushPtr, { label: "_start" }); + this.program.add(Ops.Jump); + this.scoutFnHeaders(stmts); for (const stmt of stmts) { this.lowerStaticStmt(stmt); } + this.program.setLabel({ label: "_start" }); + this.program.add(Ops.PushPtr, { label: "main" }); + this.program.add(Ops.Call, 0); + this.program.add(Ops.Pop); } public finish(): number[] { return this.program.assemble(); } + private scoutFnHeaders(stmts: Stmt[]) { + for (const stmt of stmts) { + if (stmt.kind.type !== "fn") { + continue; + } + const label = stmt.kind.ident === "main" + ? "main" + : `${stmt.kind.ident}_${stmt.id}`; + this.fnStmtIdLabelMap[stmt.id] = label; + } + } + private lowerStaticStmt(stmt: Stmt) { switch (stmt.kind.type) { case "fn": @@ -101,26 +120,43 @@ export class Lowerer { if (stmt.kind.type !== "fn") { throw new Error(); } - const label = `${stmt.kind.ident}_${stmt.id}`; - this.fnStmtIdLabelMap[stmt.id] = label; + const label = stmt.kind.ident === "main" + ? "main" + : `${stmt.kind.ident}_${stmt.id}`; this.program.setLabel({ label }); const outerLocals = this.locals; - this.locals = new LocalsFnRoot(outerLocals); + const fnRoot = new LocalsFnRoot(outerLocals); const outerProgram = this.program; - this.program = new Assembler(); + this.program = new Assembler(); + this.locals = fnRoot; for (const { ident } of stmt.kind.params) { this.locals.allocSym(ident); - this.program.add( - Ops.StoreLocal, - this.locals.symId(ident), - ); } + this.locals.allocSym("::return"); + this.program.add(Ops.PushNull); this.lowerExpr(stmt.kind.body); + this.program.add(Ops.StoreLocal, this.locals.symId("::return")); + this.locals = outerLocals; + + for ( + let i = 0; + i < fnRoot.stackReserved() - 1 - stmt.kind.params.length; + ++i + ) { + outerProgram.add(Ops.PushNull); + this.program.add(Ops.Pop); + } + if (stmt.kind.params.length >= 1) { + this.program.add(Ops.StoreLocal, 0); + } + for (let i = 0; i < stmt.kind.params.length - 1; ++i) { + this.program.add(Ops.Pop); + } + this.program.add(Ops.Return); - this.locals = outerLocals; outerProgram.concat(this.program); this.program = outerProgram; } @@ -195,7 +231,7 @@ export class Lowerer { } if (expr.kind.sym.type === "fn") { const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id]; - this.program.add(Ops.PushPtr, label); + this.program.add(Ops.PushPtr, { label }); return; } throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); @@ -272,6 +308,7 @@ export class Lowerer { this.lowerExpr(arg); } this.lowerExpr(expr.kind.subject); + this.program.add(Ops.Call, expr.kind.args.length); } private lowerIfExpr(expr: Expr) { @@ -285,11 +322,13 @@ export class Lowerer { this.lowerExpr(expr.kind.cond); this.program.add(Ops.Not); - this.program.add(Ops.JumpIfTrue, falseLabel); + this.program.add(Ops.PushPtr, falseLabel); + this.program.add(Ops.JumpIfTrue); this.lowerExpr(expr.kind.truthy); - this.program.add(Ops.Jump, doneLabel); + this.program.add(Ops.PushPtr, doneLabel); + this.program.add(Ops.Jump); this.program.setLabel(falseLabel); @@ -311,7 +350,8 @@ export class Lowerer { this.program.setLabel(contineLabel); this.lowerExpr(expr.kind.body); - this.program.add(Ops.Jump, breakLabel); + this.program.add(Ops.PushPtr, breakLabel); + this.program.add(Ops.Jump); this.program.setLabel(breakLabel); if (expr.vtype!.type === "null") { this.program.add(Ops.PushNull); @@ -326,6 +366,7 @@ export class Lowerer { } const outerLocals = this.locals; this.locals = new LocalLeaf(this.locals); + this.scoutFnHeaders(expr.kind.stmts); for (const stmt of expr.kind.stmts) { this.lowerStmt(stmt); } diff --git a/compiler/lowerer_locals.ts b/compiler/lowerer_locals.ts index 2d43d5c..5142117 100644 --- a/compiler/lowerer_locals.ts +++ b/compiler/lowerer_locals.ts @@ -1,25 +1,28 @@ export interface Locals { - reserveId(id: number): void; + reserveAmount(id: number): void; allocSym(ident: string): void; symId(ident: string): number; + + currentLocalIdCounter(): number; } export class LocalsFnRoot implements Locals { private localsAmount = 0; - private localIdCounter = 0; private symLocalMap: { [key: string]: number } = {}; + private localIdCounter: number; constructor(private parent?: Locals) { + this.localIdCounter = parent?.currentLocalIdCounter() ?? 0; } - public reserveId(id: number): void { - this.localsAmount = Math.max(id + 1, this.localsAmount); + public reserveAmount(amount: number): void { + this.localsAmount = Math.max(amount, this.localsAmount); } public allocSym(ident: string) { this.symLocalMap[ident] = this.localIdCounter; this.localIdCounter++; - this.reserveId(this.localIdCounter); + this.reserveAmount(this.localIdCounter); } public symId(ident: string): number { @@ -31,23 +34,32 @@ export class LocalsFnRoot implements Locals { } throw new Error(`undefined symbol '${ident}'`); } + + public stackReserved(): number { + return this.localsAmount; + } + + public currentLocalIdCounter(): number { + return this.localIdCounter; + } } export class LocalLeaf implements Locals { - private localIdCounter = 0; private symLocalMap: { [key: string]: number } = {}; + private localIdCounter: number; constructor(private parent: Locals) { + this.localIdCounter = parent.currentLocalIdCounter(); } - public reserveId(id: number): void { - this.parent.reserveId(id); + public reserveAmount(amount: number): void { + this.parent.reserveAmount(amount); } public allocSym(ident: string) { this.symLocalMap[ident] = this.localIdCounter; this.localIdCounter++; - this.reserveId(this.localIdCounter); + this.reserveAmount(this.localIdCounter); } public symId(ident: string): number { @@ -56,4 +68,8 @@ export class LocalLeaf implements Locals { } return this.parent.symId(ident); } + + public currentLocalIdCounter(): number { + return this.localIdCounter; + } } diff --git a/compiler/main.ts b/compiler/main.ts index 4f1968e..7ee322e 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -1,21 +1,33 @@ import { Checker } from "./checker.ts"; +import { Reporter } from "./info.ts"; import { Lexer } from "./lexer.ts"; import { Lowerer } from "./lowerer.ts"; import { Parser } from "./parser.ts"; import { Resolver } from "./resolver.ts"; -const text = await Deno.readTextFile("example.slg"); +//const text = await Deno.readTextFile("example.slg"); +const text = await Deno.readTextFile(Deno.args[0]); -const lexer = new Lexer(text); +const reporter = new Reporter(); -const parser = new Parser(lexer); +const lexer = new Lexer(text, reporter); + +const parser = new Parser(lexer, reporter); const ast = parser.parseStmts(); -new Resolver().resolve(ast); -new Checker().check(ast); +new Resolver(reporter).resolve(ast); +new Checker(reporter).check(ast); // console.log(JSON.stringify(ast, null, 4)) + +if (reporter.errorOccured()) { + console.error("Errors occurred, stopping compilation."); + Deno.exit(1); +} + const lowerer = new Lowerer(); lowerer.lower(ast); lowerer.printProgram(); const program = lowerer.finish(); //console.log(JSON.stringify(program, null, 4)); console.log(JSON.stringify(program)); + +await Deno.writeTextFile("out.slgbc", JSON.stringify(program)); diff --git a/compiler/parser.ts b/compiler/parser.ts index cce3303..ebb7d8b 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -8,6 +8,7 @@ import { Stmt, StmtKind, } from "./ast.ts"; +import { printStackTrace, Reporter } from "./info.ts"; import { Lexer } from "./lexer.ts"; import { Pos, Token } from "./token.ts"; @@ -15,7 +16,7 @@ export class Parser { private currentToken: Token | null; private nextNodeId = 0; - public constructor(private lexer: Lexer) { + public constructor(private lexer: Lexer, private reporter: Reporter) { this.currentToken = lexer.next(); } @@ -41,19 +42,12 @@ 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"); - } - } - try { - throw new ReportNotAnError(); - } catch (error) { - if (!(error instanceof ReportNotAnError)) { - throw error; - } - console.log(error); - } + this.reporter.reportError({ + msg, + pos, + reporter: "Parser", + }); + printStackTrace(); } private stmt(kind: StmtKind, pos: Pos): Stmt { diff --git a/compiler/resolver.ts b/compiler/resolver.ts index 1842806..fa106cd 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -1,4 +1,5 @@ import { Expr, Stmt } from "./ast.ts"; +import { Reporter } from "./info.ts"; import { FnSyms, GlobalSyms, @@ -11,6 +12,8 @@ import { Pos } from "./token.ts"; export class Resolver { private root = new GlobalSyms(); + public constructor(private reporter: Reporter) {} + public resolve(stmts: Stmt[]) { const scopeSyms = new StaticSyms(this.root); this.scoutFnStmts(stmts, scopeSyms); @@ -200,15 +203,19 @@ export class Resolver { } private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) { - console.error( - `use of undefined symbol '${ident}' at ${pos.line}${pos.col}`, - ); + this.reporter.reportError({ + reporter: "Resolver", + msg: `use of undefined symbol '${ident}'`, + pos, + }); } private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) { - console.error( - `symbol already defined '${ident}', at ${pos.line}${pos.col}`, - ); + this.reporter.reportError({ + reporter: "Resolver", + msg: `symbol already defined '${ident}'`, + pos, + }); const prev = syms.get(ident); if (!prev.ok) { throw new Error("expected to be defined"); @@ -216,9 +223,10 @@ export class Resolver { if (!prev.sym.pos) { return; } - const { line: prevLine, col: prevCol } = prev.sym.pos; - console.error( - `previous definition of '${ident}' at ${prevLine}:${prevCol}`, - ); + this.reporter.addNote({ + reporter: "Resolver", + msg: `previous definition of '${ident}'`, + pos: prev.sym.pos, + }); } } diff --git a/examples/add_fn.slg b/examples/add_fn.slg index 1107d55..38e55b0 100644 --- a/examples/add_fn.slg +++ b/examples/add_fn.slg @@ -1,9 +1,15 @@ -fn main() { +fn main() -> int { add(1, 2) } fn add(a: int, b: int) -> int { - + a b + let c = a; + let d = b; + { + let a = c; + + a d + } + //+ a b } diff --git a/examples/add_int.slg b/examples/add_int.slg index 24cf8d9..b45070e 100644 --- a/examples/add_int.slg +++ b/examples/add_int.slg @@ -1,5 +1,5 @@ -fn main() { +fn main() -> int { + 1 2 } diff --git a/examples/add_let.slg b/examples/add_let.slg index d1a564b..41976cc 100644 --- a/examples/add_let.slg +++ b/examples/add_let.slg @@ -1,5 +1,5 @@ -fn main() { +fn main() -> int { let a = 5; let b = 3; + a b diff --git a/runtime/main.cpp b/runtime/main.cpp index 22bbcc7..34ffb52 100644 --- a/runtime/main.cpp +++ b/runtime/main.cpp @@ -1,17 +1,56 @@ #include "actions.hpp" #include "json.hpp" #include "rpc_server.hpp" +#include "vm.hpp" #include "vm_provider.hpp" +#include +#include +#include +#include +#include -int main() +int execute_file_and_exit(std::string filename) { + auto file = std::ifstream(); + file.open(filename.c_str()); + if (!file) { + std::cout << std::format("error: could not open file '{}'\n", filename); + return 1; + } + auto text = std::string(std::istreambuf_iterator { file }, {}); + auto parsed = sliger::json::parse_json(text); + if (not parsed.ok()) { + std::cout << std::format("error: {} at {}:{}\n", parsed.err().msg, + parsed.err().pos.line, parsed.err().pos.col); + return 1; + } + auto program = std::vector(); + for (auto& v : parsed.val()->as().values) { + program.push_back( + static_cast(v->as().value)); + } + auto vm = sliger::VM(program, + { + .flame_graph = false, + .code_coverage = false, + }); + vm.run_until_done(); + return 0; +} + +int main(int argc, char** argv) +{ + if (argc >= 3 && std::string(argv[1]) == "run") { + return execute_file_and_exit(argv[2]); + } + auto state = sliger::rpc::vm_provider::VmProvider(); auto rpc = sliger::rpc::RpcServer( [&](std::unique_ptr req, std::unique_ptr writer) { auto action = sliger::rpc::action::action_from_json(std::move(req)); - action->perform_action(std::move(writer)); + action->perform_action(std::move(writer), state); }); rpc.listen(); } diff --git a/runtime/vm.cpp b/runtime/vm.cpp index 6d903dc..2b433e5 100644 --- a/runtime/vm.cpp +++ b/runtime/vm.cpp @@ -87,8 +87,8 @@ void VM::run_n_instructions(size_t amount) void VM::run_instruction() { - /*std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,*/ - /* maybe_op_to_string(this->program[this->pc]), stack_repr_string(4));*/ + std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc, + maybe_op_to_string(this->program[this->pc]), stack_repr_string(6)); auto op = eat_op(); switch (op) { case Op::Nop: @@ -183,7 +183,8 @@ void VM::run_instruction() stack_push(std::move(arguments.at(i - 1))); } this->pc = fn_ptr.as_ptr().value; - this->bp = static_cast(this->stack.size()); + this->bp + = static_cast(this->stack.size() - arguments.size()); if (this->opts.flame_graph) { this->flame_graph.report_call( fn_ptr.as_ptr().value, this->instruction_counter); diff --git a/slige-run.sh b/slige-run.sh new file mode 100755 index 0000000..6bc5c26 --- /dev/null +++ b/slige-run.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +echo Compiling $1... + +deno run --allow-read --allow-write compiler/main.ts $1 + +echo "Running out.slgbc..." + +./runtime/build/sliger run out.slgbc +