diff --git a/compiler/Checker.ts b/compiler/Checker.ts index f004723..92525cc 100644 --- a/compiler/Checker.ts +++ b/compiler/Checker.ts @@ -196,17 +196,19 @@ export class Checker { return; } case "sym": { - if (stmt.kind.subject.kind.defType !== "let") { + if (stmt.kind.subject.kind.sym.type !== "let") { this.report("cannot only assign to let-symbol", pos); return { type: "error" }; } - if (!vtypesEqual(stmt.kind.subject.kind.param!.vtype!, value)) { + if ( + !vtypesEqual(stmt.kind.subject.kind.sym.param.vtype!, value) + ) { this.report( `cannot assign to incompatible type` + `, got '${vtypeToString(value)}'` + `, expected '${ vtypeToString( - stmt.kind.subject.kind.param!.vtype!, + stmt.kind.subject.kind.sym.param.vtype!, ) }'`, pos, @@ -266,12 +268,11 @@ export class Checker { if (expr.kind.type !== "sym") { throw new Error(); } - const pos = expr.pos; - switch (expr.kind.defType) { + switch (expr.kind.sym.type) { case "let": - return expr.kind.param?.vtype!; + return expr.kind.sym.param.vtype!; case "fn": { - const fnStmt = expr.kind.stmt!; + const fnStmt = expr.kind.sym.stmt!; if (fnStmt.kind.type !== "fn") { throw new Error(); } @@ -283,9 +284,13 @@ export class Checker { return { type: "fn", params, returnType }; } case "fn_param": - return expr.kind.param!.vtype!; + return expr.kind.sym.param.vtype!; case "builtin": - throw new Error(); + case "let_static": + case "closure": + throw new Error( + `not implemented, sym type '${expr.kind.sym.type}'`, + ); } } diff --git a/compiler/Lowerer.ts b/compiler/Lowerer.ts index 2239a17..c7525a5 100644 --- a/compiler/Lowerer.ts +++ b/compiler/Lowerer.ts @@ -1,93 +1,83 @@ +import { Builtins } from "./arch.ts"; import { BinaryType, Expr, Stmt } from "./ast.ts"; +import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; import { Ops } from "./mod.ts"; -import { VType } from "./vtypes.ts"; - -interface Locals { - reserveId(id: number): void; - allocSym(ident: string): void; - symId(ident: string): number; -} - -class LocalsFnRoot implements Locals { - private localsAmount = 0; - private localIdCounter = 0; - private symLocalMap: { [key: string]: number } = {}; - - constructor(private parent?: Locals) { - } - - reserveId(id: number): void { - this.localsAmount = Math.max(id + 1, this.localsAmount); - } - - allocSym(ident: string) { - this.symLocalMap[ident] = this.localIdCounter; - this.localIdCounter++; - this.reserveId(this.localIdCounter); - } - - symId(ident: string): number { - if (ident in this.symLocalMap) { - return this.symLocalMap[ident]; - } - if (this.parent) { - return this.parent.symId(ident); - } - throw new Error(`undefined symbol '${ident}'`); - } -} - -class LocalLeaf implements Locals { - private localIdCounter = 0; - private symLocalMap: { [key: string]: number } = {}; - - constructor(private parent: Locals) { - } - - reserveId(id: number): void { - this.parent.reserveId(id); - } - - allocSym(ident: string) { - this.symLocalMap[ident] = this.localIdCounter; - this.localIdCounter++; - this.reserveId(this.localIdCounter); - } - - symId(ident: string): number { - if (ident in this.symLocalMap) { - return this.symLocalMap[ident]; - } - return this.parent.symId(ident); - } -} +import { VType, vtypeToString } from "./vtypes.ts"; export class Lowerer { private program: number[] = []; - private locals = new LocalsFnRoot(); + private locals: Locals = new LocalsFnRoot(); + private fnStmtIdAddrMap: { [key: number]: number } = {}; - lower(stmts: Stmt[]) { + public lower(stmts: Stmt[]) { for (const stmt of stmts) { - this.lowerStmt(stmt); + this.lowerStaticStmt(stmt); } } - lowerStmt(stmt: Stmt) { + public finish(): number[] { + return this.program; + } + + private lowerStaticStmt(stmt: Stmt) { + switch (stmt.kind.type) { + case "fn": + return this.lowerFnStmt(stmt); + case "error": + case "break": + case "return": + case "let": + case "assign": + case "expr": + } + throw new Error(`unhandled static statement '${stmt.kind.type}'`); + } + + private lowerStmt(stmt: Stmt) { switch (stmt.kind.type) { case "error": case "break": case "return": - case "fn": break; + case "fn": + return this.lowerFnStmt(stmt); case "let": return this.lowerLetStmt(stmt); case "assign": + break; case "expr": + this.lowerExpr(stmt.kind.expr); + this.program.push(Ops.Pop); + return; } - throw new Error(`Unhandled stmt ${stmt.kind.type}`); + throw new Error(`unhandled stmt '${stmt.kind.type}'`); } - lowerLetStmt(stmt: Stmt) { + private lowerFnStmt(stmt: Stmt) { + if (stmt.kind.type !== "fn") { + throw new Error(); + } + const outerLocals = this.locals; + this.locals = new LocalsFnRoot(outerLocals); + const outerProgram = this.program; + this.program = []; + + for (const { ident } of stmt.kind.params) { + this.locals.allocSym(ident); + this.program.push( + Ops.StoreLocal, + this.locals.symId(ident), + ); + } + this.lowerExpr(stmt.kind.body); + this.program.push(Ops.Return); + + this.locals = outerLocals; + outerProgram.push(...this.program); + this.program = outerProgram; + } + + private lowerLetStmt(stmt: Stmt) { if (stmt.kind.type !== "let") { throw new Error(); } @@ -97,55 +87,88 @@ export class Lowerer { this.program.push(this.locals.symId(stmt.kind.param.ident)); } - lowerExpr(expr: Expr) { + private lowerExpr(expr: Expr) { switch (expr.kind.type) { - case "string": case "error": break; + case "sym": + return this.lowerSymExpr(expr); + case "null": + break; case "int": - return this.lowerInt(expr); + return this.lowerIntExpr(expr); + case "bool": + break; + case "string": + return this.lowerStringExpr(expr); case "ident": + break; case "group": + break; case "field": + break; case "index": + break; case "call": + return this.lowerCallExpr(expr); case "unary": break; case "binary": return this.lowerBinaryExpr(expr); case "if": - case "bool": - case "null": + return this.lowerIfExpr(expr); case "loop": - case "block": break; - case "sym": - return this.lowerSym(expr); + case "block": + return this.lowerBlockExpr(expr); } - throw new Error(`Unhandled expr ${expr.kind.type}`); + throw new Error(`unhandled expr '${expr.kind.type}'`); } - lowerInt(expr: Expr) { - if (expr.kind.type !== "int") { - throw new Error(); - } - this.program.push(Ops.PushInt); - this.program.push(expr.kind.value); - } - - lowerSym(expr: Expr) { + private lowerSymExpr(expr: Expr) { if (expr.kind.type !== "sym") { throw new Error(); } - if (expr.kind.defType == "let") { - this.program.push(Ops.LoadLocal); - this.program.push(this.locals.symId(expr.kind.ident)); + if (expr.kind.sym.type === "let") { + this.program.push( + Ops.LoadLocal, + this.locals.symId(expr.kind.ident), + ); return; } - throw new Error(`Unhandled sym deftype ${expr.kind.defType}`); + if (expr.kind.sym.type === "fn_param") { + this.program.push( + Ops.LoadLocal, + this.locals.symId(expr.kind.ident), + ); + return; + } + if (expr.kind.sym.type === "fn") { + const addr = this.fnStmtIdAddrMap[expr.kind.sym.stmt.id]; + this.program.push(Ops.PushPtr, addr); + return; + } + throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); } - lowerBinaryExpr(expr: Expr) { + private lowerIntExpr(expr: Expr) { + if (expr.kind.type !== "int") { + throw new Error(); + } + this.program.push(Ops.PushInt, expr.kind.value); + } + + private lowerStringExpr(expr: Expr) { + if (expr.kind.type !== "string") { + throw new Error(); + } + this.program.push(Ops.PushString); + for (let i = 0; i < expr.kind.value.length; ++i) { + this.program.push(expr.kind.value.charCodeAt(i)); + } + } + + private lowerBinaryExpr(expr: Expr) { if (expr.kind.type !== "binary") { throw new Error(); } @@ -171,10 +194,57 @@ export class Lowerer { case "and": } } + if (expr.vtype!.type === "string") { + if (expr.kind.binaryType === "+") { + this.program.push(Ops.Builtin, Builtins.StringAdd); + return; + } + } throw new Error( - `Unhandled vtype/binaryType '${ - expr.vtype!.type - }/${expr.kind.binaryType}'`, + `unhandled binaryType` + + ` '${vtypeToString(expr.kind.left.vtype!)}'` + + ` ${expr.kind.binaryType}` + + ` '${vtypeToString(expr.kind.left.vtype!)}'`, ); } + + private lowerCallExpr(expr: Expr) { + if (expr.kind.type !== "call") { + throw new Error(); + } + for (const arg of expr.kind.args) { + this.lowerExpr(arg); + } + this.lowerExpr(expr.kind.subject); + } + + private lowerIfExpr(expr: Expr) { + if (expr.kind.type !== "if") { + throw new Error(); + } + this.lowerExpr(expr.kind.cond); + this.lowerExpr(expr.kind.truthy); + const falsyIndex = this.program.length; + if (expr.kind.falsy) { + this.lowerExpr(expr.kind.falsy); + } + const doneIndex = this.program.length; + } + + private lowerBlockExpr(expr: Expr) { + if (expr.kind.type !== "block") { + throw new Error(); + } + const outerLocals = this.locals; + this.locals = new LocalLeaf(this.locals); + for (const stmt of expr.kind.stmts) { + this.lowerStmt(stmt); + } + if (expr.kind.expr) { + this.lowerExpr(expr.kind.expr); + } else { + this.program.push(Ops.PushNull); + } + this.locals = outerLocals; + } } diff --git a/compiler/Resolver.ts b/compiler/Resolver.ts index 5442f09..106bac5 100644 --- a/compiler/Resolver.ts +++ b/compiler/Resolver.ts @@ -1,42 +1,43 @@ -import { Expr, Stmt, Sym } from "./ast.ts"; +import { Expr, Stmt } from "./ast.ts"; +import { + FnSyms, + GlobalSyms, + LeafSyms, + StaticSyms, + Syms, +} from "./resolver_syms.ts"; import { Pos } from "./Token.ts"; -type SymMap = { [ident: string]: Sym }; - -class Syms { - private syms: SymMap = {}; - - public constructor(private parent?: Syms) {} - - public define(ident: string, sym: Sym) { - this.syms[ident] = sym; - } - - public definedLocally(ident: string): boolean { - return ident in this.syms; - } - - public get(ident: string): { ok: true; sym: Sym } | { ok: false } { - if (ident in this.syms) { - return { ok: true, sym: this.syms[ident] }; - } - if (this.parent) { - return this.parent.get(ident); - } - return { ok: false }; - } -} - export class Resolver { - private root = new Syms(); + private root = new GlobalSyms(); public resolve(stmts: Stmt[]) { - const scopeSyms = new Syms(this.root); + const scopeSyms = new StaticSyms(this.root); + this.scoutFnStmts(stmts, scopeSyms); for (const stmt of stmts) { this.resolveStmt(stmt, scopeSyms); } } + private scoutFnStmts(stmts: Stmt[], syms: Syms) { + for (const stmt of stmts) { + if (stmt.kind.type !== "fn") { + continue; + } + if (syms.definedLocally(stmt.kind.ident)) { + this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms); + return; + } + const ident = stmt.kind.ident; + syms.define(ident, { + ident: stmt.kind.ident, + type: "fn", + pos: stmt.pos, + stmt, + }); + } + } + private resolveExpr(expr: Expr, syms: Syms) { if (expr.kind.type === "error") { return; @@ -51,7 +52,8 @@ export class Resolver { return; } if (expr.kind.type === "block") { - const childSyms = new Syms(syms); + const childSyms = new LeafSyms(syms); + this.scoutFnStmts(expr.kind.stmts, childSyms); for (const stmt of expr.kind.stmts) { this.resolveStmt(stmt, childSyms); } @@ -119,14 +121,8 @@ export class Resolver { expr.kind = { type: "sym", ident: ident.value, - defType: sym.type, + sym, }; - if (sym.stmt) { - expr.kind.stmt = sym.stmt; - } - if (sym.param) { - expr.kind.param = sym.param; - } } private resolveStmt(stmt: Stmt, syms: Syms) { @@ -187,18 +183,7 @@ export class Resolver { if (stmt.kind.type !== "fn") { throw new Error("expected fn statement"); } - if (syms.definedLocally(stmt.kind.ident)) { - this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms); - return; - } - const ident = stmt.kind.ident; - syms.define(ident, { - ident: stmt.kind.ident, - type: "fn", - pos: stmt.pos, - stmt, - }); - const fnScopeSyms = new Syms(syms); + const fnScopeSyms = new FnSyms(syms); for (const param of stmt.kind.params) { if (fnScopeSyms.definedLocally(param.ident)) { this.reportAlreadyDefined(param.ident, param.pos, syms); @@ -214,7 +199,7 @@ export class Resolver { this.resolveExpr(stmt.kind.body, fnScopeSyms); } - private reportUseOfUndefined(ident: string, pos: Pos, syms: Syms) { + private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) { console.error( `use of undefined symbol '${ident}' at ${pos.line}${pos.col}`, ); diff --git a/compiler/arch.ts b/compiler/arch.ts index 851e67a..ba900a9 100644 --- a/compiler/arch.ts +++ b/compiler/arch.ts @@ -30,4 +30,13 @@ export const Ops = { Xor: 22, Not: 23, SourceMap: 24, + Builtin: 25, + ReserveStatics: 26, + LoadStatic: 27, + StoreStatic: 28, +} as const; + +export type Builtins = typeof Builtins; +export const Builtins = { + StringAdd: 0, } as const; diff --git a/compiler/ast.ts b/compiler/ast.ts index 05255f9..89b1ef8 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -71,18 +71,21 @@ export type ExprKind = | { type: "sym"; ident: string; - defType: "let" | "fn" | "fn_param" | "builtin"; - stmt?: Stmt; - param?: Param; + sym: Sym; }; export type Sym = { ident: string; - type: "let" | "fn" | "fn_param" | "builtin"; pos?: Pos; - stmt?: Stmt; - param?: Param; -}; +} & SymKind; + +export type SymKind = + | { type: "let"; stmt: Stmt; param: Param } + | { type: "let_static"; stmt: Stmt; param: Param } + | { type: "fn"; stmt: Stmt } + | { type: "fn_param"; param: Param } + | { type: "closure"; inner: Sym } + | { type: "builtin" }; export type EType = { kind: ETypeKind; diff --git a/compiler/example-no-types.slg b/compiler/example-no-types.slg deleted file mode 100644 index b1bd82b..0000000 --- a/compiler/example-no-types.slg +++ /dev/null @@ -1,29 +0,0 @@ - -fn sum(a, b) { - + a b; -} - -sum(2,3); // -> 5 - -let a = "Hello"; - -let b = "world"; - -println(+ + + a " " b "!"); // -> "Hello world!" - -if == a b { - println("whaaaat"); -} -else { - println(":o"); -} - -loop { - let i = 0; - - if >= i 10 { - break; - } - - i = + i 1; -} \ No newline at end of file diff --git a/compiler/example.slg b/compiler/example.slg index aa9fd79..f502665 100755 --- a/compiler/example.slg +++ b/compiler/example.slg @@ -6,27 +6,29 @@ fn sum(a: int, b: int) -> int { + a b } -sum(2,3); // -> 5 +fn main() { + sum(2,3); // -> 5 -let a: string = "Hello"; - -let b = "world"; + let a: string = "Hello"; + + let b = "world"; -println(+ + + a " " b "!"); // -> "Hello world!" + println(+ + + a " " b "!"); // -> "Hello world!" -if == a b { - println("whaaaat"); -} -else { - println(":o"); -} - -loop { - let i = 0; - - if >= i 10 { - break; + if == a b { + println("whaaaat"); + } + else { + println(":o"); } - i = + i 1; -} \ No newline at end of file + loop { + let i = 0; + + if >= i 10 { + break; + } + + i = + i 1; + } +} diff --git a/compiler/lowerer_locals.ts b/compiler/lowerer_locals.ts new file mode 100644 index 0000000..2d43d5c --- /dev/null +++ b/compiler/lowerer_locals.ts @@ -0,0 +1,59 @@ +export interface Locals { + reserveId(id: number): void; + allocSym(ident: string): void; + symId(ident: string): number; +} + +export class LocalsFnRoot implements Locals { + private localsAmount = 0; + private localIdCounter = 0; + private symLocalMap: { [key: string]: number } = {}; + + constructor(private parent?: Locals) { + } + + public reserveId(id: number): void { + this.localsAmount = Math.max(id + 1, this.localsAmount); + } + + public allocSym(ident: string) { + this.symLocalMap[ident] = this.localIdCounter; + this.localIdCounter++; + this.reserveId(this.localIdCounter); + } + + public symId(ident: string): number { + if (ident in this.symLocalMap) { + return this.symLocalMap[ident]; + } + if (this.parent) { + return this.parent.symId(ident); + } + throw new Error(`undefined symbol '${ident}'`); + } +} + +export class LocalLeaf implements Locals { + private localIdCounter = 0; + private symLocalMap: { [key: string]: number } = {}; + + constructor(private parent: Locals) { + } + + public reserveId(id: number): void { + this.parent.reserveId(id); + } + + public allocSym(ident: string) { + this.symLocalMap[ident] = this.localIdCounter; + this.localIdCounter++; + this.reserveId(this.localIdCounter); + } + + public symId(ident: string): number { + if (ident in this.symLocalMap) { + return this.symLocalMap[ident]; + } + return this.parent.symId(ident); + } +} diff --git a/compiler/main.ts b/compiler/main.ts index 81b2f42..ac550c8 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -1,21 +1,19 @@ import { Checker } from "./Checker.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 lexer = new Lexer(text); -// while (token !== null) { -// const value = token.identValue ?? token.intValue ?? token.stringValue ?? ""; -// console.log(`${token.type}\t${value}`) -// token = lexer.next(); -// } - const parser = new Parser(lexer); const ast = parser.parseStmts(); new Resolver().resolve(ast); new Checker().check(ast); // console.log(JSON.stringify(ast, null, 4)) +const lowerer = new Lowerer(); +lowerer.lower(ast); +const program = lowerer.finish(); +console.log(JSON.stringify(program, null, 4)); diff --git a/compiler/resolver_syms.ts b/compiler/resolver_syms.ts new file mode 100644 index 0000000..ce976ef --- /dev/null +++ b/compiler/resolver_syms.ts @@ -0,0 +1,115 @@ +import { Sym } from "./ast.ts"; + +export type SymMap = { [ident: string]: Sym }; + +export interface Syms { + define(ident: string, sym: Sym): void; + definedLocally(ident: string): boolean; + get(ident: string): { ok: true; sym: Sym } | { ok: false }; +} + +export class GlobalSyms implements Syms { + private syms: SymMap = {}; + + public constructor() {} + + public define(ident: string, sym: Sym) { + if (sym.type === "let") { + this.define(ident, { + ...sym, + type: "let_static", + }); + return; + } + this.syms[ident] = sym; + } + + public definedLocally(ident: string): boolean { + return ident in this.syms; + } + + public get(ident: string): { ok: true; sym: Sym } | { ok: false } { + if (ident in this.syms) { + return { ok: true, sym: this.syms[ident] }; + } + return { ok: false }; + } +} + +export class StaticSyms implements Syms { + private syms: SymMap = {}; + + public constructor(private parent: GlobalSyms) {} + + public define(ident: string, sym: Sym) { + if (sym.type === "let") { + this.define(ident, { + ...sym, + type: "let_static", + }); + return; + } + this.syms[ident] = sym; + } + + public definedLocally(ident: string): boolean { + return ident in this.syms; + } + + public get(ident: string): { ok: true; sym: Sym } | { ok: false } { + if (ident in this.syms) { + return { ok: true, sym: this.syms[ident] }; + } + return this.parent.get(ident); + } +} + +export class FnSyms implements Syms { + private syms: SymMap = {}; + + public constructor(private parent: Syms) {} + + public define(ident: string, sym: Sym) { + if (sym.type === "let") { + this.define(ident, { + ...sym, + type: "closure", + inner: sym, + }); + return; + } + this.syms[ident] = sym; + } + + public definedLocally(ident: string): boolean { + return ident in this.syms; + } + + public get(ident: string): { ok: true; sym: Sym } | { ok: false } { + if (ident in this.syms) { + return { ok: true, sym: this.syms[ident] }; + } + return this.parent.get(ident); + } +} + +export class LeafSyms implements Syms { + private syms: SymMap = {}; + + public constructor(private parent: Syms) {} + + public define(ident: string, sym: Sym) { + this.syms[ident] = sym; + } + + public definedLocally(ident: string): boolean { + return ident in this.syms; + } + + public get(ident: string): { ok: true; sym: Sym } | { ok: false } { + if (ident in this.syms) { + return { ok: true, sym: this.syms[ident] }; + } + return this.parent.get(ident); + } +}