From f291516c87ba723420f305ee89c35e519827c64d Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Wed, 26 Mar 2025 16:06:36 +0100 Subject: [PATCH] compiler: types and strings --- backup-compiler/asm_gen.ts | 65 ++++++++--- backup-compiler/ast.ts | 30 ++++- backup-compiler/entry.c | 4 +- backup-compiler/front.ts | 216 ++++++++++++++++++++++++++++++------ backup-compiler/lib.c | 11 ++ backup-compiler/lir_gen.ts | 2 +- backup-compiler/mir.ts | 4 +- backup-compiler/mir_gen.ts | 10 +- backup-compiler/program.sbl | 34 +++--- backup-compiler/ty.ts | 12 +- 10 files changed, 306 insertions(+), 82 deletions(-) diff --git a/backup-compiler/asm_gen.ts b/backup-compiler/asm_gen.ts index 051fbb3..70d30fa 100644 --- a/backup-compiler/asm_gen.ts +++ b/backup-compiler/asm_gen.ts @@ -1,3 +1,4 @@ +import { AttrView } from "./attr.ts"; import * as lir from "./lir.ts"; export class AsmGen { @@ -34,40 +35,57 @@ export class AsmGen { } private generateFn(fn: lir.Fn) { - const query = this.queryCFunction(fn); - if (query.found) { - const { label, args } = query; + const cFunctionQuery = this.queryCFunction(fn); + if (cFunctionQuery.found) { + const { label, args } = cFunctionQuery; this.generateCFunctionBody(fn, label, args); return; } this.generateFnBody(fn); + + const cExportQuery = this.queryCExport(fn); + if (cExportQuery.found) { + const { label } = cExportQuery; + this.generateCExporter(fn, label); + } } private queryCFunction( fn: lir.Fn, ): { found: false } | { found: true; label: string; args: number } { - const stmtKind = fn.mir.stmt.kind; - if (stmtKind.tag !== "fn") { - throw new Error(); - } - if (stmtKind.attrs.at(0)?.ident === "c_function") { - const arg = stmtKind.attrs.at(0)!.args.at(0); - if (!arg || arg.kind.tag !== "string") { + const attrs = AttrView.fromStmt(fn.mir.stmt); + if (attrs.has("c_function")) { + const attr = attrs.get("c_function"); + if (attr.args !== 1 || !attr.isStr(0)) { throw new Error("incorrect args for attribute"); } return { found: true, - label: arg.kind.val, + label: attr.strVal(0), args: fn.mir.paramLocals.size, }; } return { found: false }; } + private queryCExport( + fn: lir.Fn, + ): { found: false } | { found: true; label: string } { + const attrs = AttrView.fromStmt(fn.mir.stmt); + if (attrs.has("c_export")) { + const attr = attrs.get("c_export"); + if (attr.args !== 1 || !attr.isStr(0)) { + throw new Error("incorrect args for attribute"); + } + const label = attr.strVal(0); + return { found: true, label }; + } + return { found: false }; + } + private generateCFunctionBody(fn: lir.Fn, label: string, args: number) { this.writeln(`extern ${label}`); - this.writeln(`global ${fn.label}`); this.writeln(`${fn.label}:`); this.writeIns(`push rbp`); @@ -90,7 +108,6 @@ export class AsmGen { } private generateFnBody(fn: lir.Fn) { - this.writeln(`global ${fn.label}:`); this.writeln(`${fn.label}:`); this.writeIns(`push rbp`); this.writeIns(`mov rbp, rsp`); @@ -112,6 +129,28 @@ export class AsmGen { this.writeIns(`ret`); } + private generateCExporter(fn: lir.Fn, label: string) { + this.writeln(`global ${label}`); + this.writeln(`${label}:`); + + this.writeIns(`push rbp`); + this.writeIns(`mov rbp, rsp`); + this.writeIns(`sub rsp, 8`); + + const args = fn.mir.paramLocals.size; + + const argRegs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; + for (const reg of argRegs.slice(0, args)) { + this.writeIns(`push ${reg}`); + } + + this.writeIns(`call ${fn.label}`); + + this.writeIns(`mov rsp, rbp`); + this.writeIns(`pop rbp`); + this.writeIns(`ret`); + } + private generateIns(ins: lir.Ins) { const r = (reg: lir.Reg) => this.reg(reg); diff --git a/backup-compiler/ast.ts b/backup-compiler/ast.ts index 47d72ae..4e68000 100644 --- a/backup-compiler/ast.ts +++ b/backup-compiler/ast.ts @@ -14,7 +14,7 @@ export type Stmt = { export type StmtKind = | { tag: "error" } | { tag: "fn" } & FnStmt - | { tag: "let"; ident: string; expr?: Expr } + | { tag: "let" } & LetStmt | { tag: "loop"; body: Block } | { tag: "if"; expr: Expr; truthy: Block; falsy?: Block } | { tag: "return"; expr?: Expr } @@ -25,10 +25,23 @@ export type StmtKind = export type FnStmt = { ident: string; attrs: Attr[]; - params: string[]; + params: Param[]; + returnTy: Ty; body: Block; }; +export type LetStmt = { + ident: string; + ty?: Ty; + expr?: Expr; +}; + +export type Param = { + ident: string; + line: number; + ty: Ty; +}; + export type Expr = { id: number; line: number; @@ -39,12 +52,23 @@ export type ExprKind = | { tag: "error" } | { tag: "ident"; ident: string } | { tag: "int"; val: number } - | { tag: "string"; val: string } + | { tag: "str"; val: string } | { tag: "call"; expr: Expr; args: Expr[] } | { tag: "binary"; op: BinaryOp; left: Expr; right: Expr }; export type BinaryOp = "<" | "==" | "+" | "*"; +export type Ty = { + id: number; + line: number; + kind: TyKind; +}; + +export type TyKind = + | { tag: "error" } + | { tag: "ident"; ident: string } + | { tag: "ptr"; ty: Ty }; + export type Attr = { ident: string; args: Expr[]; diff --git a/backup-compiler/entry.c b/backup-compiler/entry.c index ddc6bc3..1889115 100644 --- a/backup-compiler/entry.c +++ b/backup-compiler/entry.c @@ -1,8 +1,8 @@ #include -extern int32_t sbc__main(void); +extern int32_t sbc_main(void); int main(void) { - sbc__main(); + sbc_main(); } diff --git a/backup-compiler/front.ts b/backup-compiler/front.ts index 2c48bbc..c23df95 100644 --- a/backup-compiler/front.ts +++ b/backup-compiler/front.ts @@ -4,14 +4,18 @@ import { Block, Expr, ExprKind, + Param, Stmt, StmtKind, + Ty as AstTy, + TyKind, } from "./ast.ts"; import { Ty, tyToString } from "./ty.ts"; export class Checker { private stmtTys = new Map(); private exprTys = new Map(); + private tyTys = new Map(); public errorOccured = false; @@ -27,8 +31,9 @@ export class Checker { if (this.stmtTys.has(stmt.id)) { return this.stmtTys.get(stmt.id)!; } - const params = k.params.map((_): Ty => ({ tag: "int" })); - const returnTy: Ty = { tag: "int" }; + const params = k.params + .map((param): Ty => this.tyTy(param.ty)); + const returnTy: Ty = this.tyTy(k.returnTy); const ty: Ty = { tag: "fn", stmt, params, returnTy }; this.stmtTys.set(stmt.id, ty); return ty; @@ -50,7 +55,30 @@ export class Checker { if (this.stmtTys.has(stmt.id)) { return this.stmtTys.get(stmt.id)!; } - const ty: Ty = k.expr ? this.exprTy(k.expr) : { tag: "int" }; + const declTy = k.ty && this.tyTy(k.ty); + const exprTy = k.expr && this.exprTy(k.expr); + const tyRes = declTy && exprTy + ? this.resolveTys(declTy, exprTy) + : declTy + ? { ok: true, ty: declTy } as const + : exprTy + ? { ok: true, ty: exprTy } as const + : { ok: true, ty: { tag: "unknown" } as Ty } as const; + + if (!tyRes.ok) { + this.report(tyRes.msg, stmt.line); + const ty: Ty = { tag: "error" }; + this.stmtTys.set(stmt.id, ty); + return ty; + } + const ty = tyRes.ty; + if (ty.tag === "unknown") { + this.report("could not infer type", stmt.line); + const ty: Ty = { tag: "error" }; + this.stmtTys.set(stmt.id, ty); + return ty; + } + this.stmtTys.set(stmt.id, ty); return ty; } @@ -83,8 +111,8 @@ export class Checker { } case "int": return { tag: "int" }; - case "string": - return { tag: "string" }; + case "str": + return { tag: "ptr", ty: { tag: "str" } }; case "call": { const callee = this.exprTy(k.expr); if (callee.tag !== "fn") { @@ -98,13 +126,15 @@ export class Checker { ); return { tag: "error" }; } - const args = k.args.map((arg) => this.exprTy(arg)); + + const argTys = k.args + .map((arg) => this.exprTy(arg)); for (const [i, param] of callee.params.entries()) { - if (!this.assignable(args[i], param)) { + console.log({ arg: argTys[i], param }); + const tyRes = this.resolveTys(argTys[i], param); + if (!tyRes.ok) { this.report( - `argument mismatch, type '${ - tyToString(args[i]) - }' not assignable to '${tyToString(param)}'`, + `argument mismatch, ${tyRes.msg}`, expr.line, ); } @@ -147,6 +177,40 @@ export class Checker { return ty; } + public tyTy(astTy: AstTy): Ty { + if (this.tyTys.has(astTy.id)) { + return this.tyTys.get(astTy.id)!; + } + const ty = ((): Ty => { + const k = astTy.kind; + switch (k.tag) { + case "error": + return { tag: "error" }; + case "ident": { + switch (k.ident) { + case "int": + return { tag: "int" }; + case "str": + return { tag: "str" }; + default: + this.report( + `unknown type '${k.ident}'`, + astTy.line, + ); + return { tag: "error" }; + } + } + case "ptr": { + const ty = this.tyTy(k.ty); + return { tag: "ptr", ty }; + } + } + const _: never = k; + })(); + this.tyTys.set(astTy.id, ty); + return ty; + } + private assignable(a: Ty, b: Ty): boolean { if (a.tag !== b.tag) { return false; @@ -157,16 +221,52 @@ export class Checker { return true; } + private resolveTys(a: Ty, b: Ty): + | { ok: true; ty: Ty } + | { ok: false; msg: string } { + const ok = (ty: Ty) => ({ ok: true, ty } as const); + const both = (tag: Ty["tag"]): boolean => + a.tag === tag && b.tag === tag; + + if (a.tag === "error" || b.tag === "error") { + return ok(a); + } else if (both("int")) { + return ok(a); + } else if (both("str")) { + return ok(a); + } else if ( + a.tag === "ptr" && b.tag === "ptr" + ) { + const tyRes = this.resolveTys(a.ty, b.ty); + if (!tyRes.ok) { + return tyRes; + } + return ok(a); + } else if ( + a.tag === "fn" && b.tag === "fn" && + a.stmt.id !== b.stmt.id + ) { + return ok(a); + } else { + return { + ok: false, + msg: `type '${tyToString(a)}' is not assignable to '${ + tyToString(b) + }'`, + }; + } + } + private report(msg: string, line: number) { this.errorOccured = true; - //console.error(`parser: ${msg} on line ${line}`); - throw new Error(`parser: ${msg} on line ${line}`); + //console.error(`Checker: ${msg} on line ${line}`); + throw new Error(`Checker: ${msg} on line ${line}`); } } export type Resolve = | { tag: "fn"; stmt: Stmt } - | { tag: "param"; stmt: Stmt; i: number } + | { tag: "param"; stmt: Stmt; param: Param; i: number } | { tag: "let"; stmt: Stmt } | { tag: "loop"; stmt: Stmt }; @@ -298,7 +398,12 @@ export class Resolver { throw new Error(); } for (const [i, param] of k.params.entries()) { - this.syms.defineVal(param, { tag: "param", stmt: fn, i }); + this.syms.defineVal(param.ident, { + tag: "param", + stmt: fn, + param, + i, + }); } this.resolveBlock(k.body); @@ -375,7 +480,7 @@ export class Resolver { } case "int": return; - case "string": + case "str": return; case "call": this.resolveExpr(k.expr); @@ -393,8 +498,8 @@ export class Resolver { private report(msg: string, line: number) { this.errorOccured = true; - //console.error(`parser: ${msg} on line ${line}`); - throw new Error(`parser: ${msg} on line ${line}`); + //console.error(`Resolver: ${msg} on line ${line}`); + throw new Error(`Resolver: ${msg} on line ${line}`); } } @@ -405,6 +510,7 @@ export class Parser { private blockIds = 0; private stmtIds = 0; private exprIds = 0; + private tyIds = 0; private last: Tok; private eaten?: Tok; @@ -534,13 +640,13 @@ export class Parser { this.report("expected '('"); return this.stmt({ tag: "error" }, line); } - const params: string[] = []; + const params: Param[] = []; if (!this.done() && !this.test(")")) { - if (!this.eat("ident")) { - this.report("expected 'ident'"); + const paramRes = this.parseParam(); + if (!paramRes.ok) { return this.stmt({ tag: "error" }, line); } - params.push(this.eaten!.identVal!); + params.push(paramRes.param); while (!this.done() && !this.test(")")) { if (!this.eat(",")) { this.report("expected ','"); @@ -549,23 +655,48 @@ export class Parser { if (this.test(")")) { break; } - if (!this.eat("ident")) { - this.report("expected 'ident'"); + const paramRes = this.parseParam(); + if (!paramRes.ok) { return this.stmt({ tag: "error" }, line); } - params.push(this.eaten!.identVal!); + params.push(paramRes.param); } } if (!this.eat(")")) { this.report("expected ')'"); return this.stmt({ tag: "error" }, line); } + if (!this.eat("->")) { + this.report("expected '->'"); + return this.stmt({ tag: "error" }, line); + } + const returnTy = this.parseTy(); if (!this.test("{")) { this.report("expected block"); return this.stmt({ tag: "error" }, line); } const body = this.parseBlock(); - return this.stmt({ tag: "fn", ident, attrs, params, body }, line); + return this.stmt( + { tag: "fn", ident, attrs, params, returnTy, body }, + line, + ); + } + + private parseParam(): + | { ok: true; param: Param } + | { ok: false } { + const line = this.curr().line; + if (!this.eat("ident")) { + this.report("expected 'ident'"); + return { ok: false }; + } + const ident = this.eaten!.identVal!; + if (!this.eat(":")) { + this.report("expected ':'"); + return { ok: false }; + } + const ty = this.parseTy(); + return { ok: true, param: { ident, line, ty } }; } private parseLetStmt(): Stmt { @@ -722,17 +853,32 @@ export class Parser { { tag: "int", val: this.eaten!.intVal! }, this.eaten!.line, ); - } else if (this.eat("string")) { + } else if (this.eat("str")) { return this.expr( - { tag: "string", val: this.eaten?.stringVal! }, + { tag: "str", val: this.eaten?.stringVal! }, this.eaten!.line, ); } else { - this.report("expected expr"); + this.report("expected expression"); return this.expr({ tag: "error" }, this.last!.line); } } + private parseTy(): AstTy { + if (this.eat("ident")) { + return this.ty( + { tag: "ident", ident: this.eaten!.identVal! }, + this.eaten!.line, + ); + } else if (this.eat("*")) { + const ty = this.parseTy(); + return this.ty({ tag: "ptr", ty }, this.eaten!.line); + } else { + this.report("expected type"); + return this.ty({ tag: "error" }, this.last!.line); + } + } + private stmt(kind: StmtKind, line: number): Stmt { const id = this.stmtIds++; return { id, line, kind }; @@ -743,6 +889,11 @@ export class Parser { return { id, line, kind }; } + private ty(kind: TyKind, line: number): AstTy { + const id = this.tyIds++; + return { id, line, kind }; + } + private eat(type: string): boolean { if (this.test(type)) { this.eaten = this.curr(); @@ -769,8 +920,8 @@ export class Parser { private report(msg: string, line = this.last.line) { this.errorOccured = true; - //console.error(`parser: ${msg} on line ${line}`); - throw new Error(`parser: ${msg} on line ${line}`); + //console.error(`Parser: ${msg} on line ${line}`); + throw new Error(`Parser: ${msg} on line ${line}`); } } @@ -783,7 +934,7 @@ export type Tok = { }; export function lex(text: string): Tok[] { - const ops = "(){}[]<>+*=,;#\n"; + const ops = "(){}[]<>+-*=:,;#\n"; const kws = ["let", "fn", "return", "if", "else", "loop", "break"]; return ops @@ -792,6 +943,7 @@ export function lex(text: string): Tok[] { text .replaceAll(/\/\/.*?$/mg, "") .replaceAll(op, ` ${op} `) + .replaceAll(" - > ", " -> ") .replaceAll(" = = ", " == ") .replaceAll(/\\ /g, "\\SPACE"), text) .split(/[ \t\r]/) @@ -814,7 +966,7 @@ export function lex(text: string): Tok[] { : { type: "ident", line, identVal: val }; } else if (/^".*?"$/.test(val)) { return { - type: "string", + type: "str", line, stringVal: val .slice(1, val.length - 1) diff --git a/backup-compiler/lib.c b/backup-compiler/lib.c index 322264d..bc16261 100644 --- a/backup-compiler/lib.c +++ b/backup-compiler/lib.c @@ -1,6 +1,11 @@ #include #include +typedef struct { + int64_t length; + char data[]; +} Str; + int64_t notice(void) { printf("NOTICE!\n"); @@ -12,3 +17,9 @@ int64_t print_int(int64_t value) printf("%ld\n", value); return 0; } + +int64_t println(const Str* value) +{ + printf("%.*s\n", (int)value->length, value->data); + return 0; +} diff --git a/backup-compiler/lir_gen.ts b/backup-compiler/lir_gen.ts index 65558e7..095e5f0 100644 --- a/backup-compiler/lir_gen.ts +++ b/backup-compiler/lir_gen.ts @@ -141,7 +141,7 @@ class FnGen { return; case "push": { switch (k.val.tag) { - case "string": { + case "str": { const reg = this.reg(); const stringId = this.strings.intern(k.val.val); this.pushIns({ tag: "mov_string", reg, stringId }); diff --git a/backup-compiler/mir.ts b/backup-compiler/mir.ts index 7db3d3c..a0b9609 100644 --- a/backup-compiler/mir.ts +++ b/backup-compiler/mir.ts @@ -58,7 +58,7 @@ export type TerKind = export type Val = | { tag: "int"; val: number } - | { tag: "string"; val: string } + | { tag: "str"; val: string } | { tag: "fn"; stmt: ast.Stmt }; export class FnStringifyer { @@ -129,7 +129,7 @@ export class FnStringifyer { private val(val: Val): string { switch (val.tag) { - case "string": + case "str": return JSON.stringify(val.val); case "int": return `${val.val}`; diff --git a/backup-compiler/mir_gen.ts b/backup-compiler/mir_gen.ts index 33a7d9e..618fa0e 100644 --- a/backup-compiler/mir_gen.ts +++ b/backup-compiler/mir_gen.ts @@ -45,7 +45,7 @@ export class FnMirGen { this.returnLocal = this.local(fnTy.returnTy); for (const [i, param] of this.stmtKind.params.entries()) { const ty = this.ch.paramTy(this.stmt, i); - const local = this.local(ty, param); + const local = this.local(ty, param.ident); this.paramLocals.set(i, local); } @@ -182,6 +182,7 @@ export class FnMirGen { } private lowerExpr(expr: ast.Expr) { + const ty = this.ch.exprTy(expr); const k = expr.kind; switch (k.tag) { case "error": @@ -191,7 +192,6 @@ export class FnMirGen { if (!re) { throw new Error(); } - const ty = this.ch.exprTy(expr); switch (re.tag) { case "fn": { this.pushStmt({ @@ -224,7 +224,6 @@ export class FnMirGen { return; } case "int": { - const ty = this.ch.exprTy(expr); this.pushStmt({ tag: "push", val: { tag: "int", val: k.val }, @@ -232,11 +231,10 @@ export class FnMirGen { }); return; } - case "string": { - const ty = this.ch.exprTy(expr); + case "str": { this.pushStmt({ tag: "push", - val: { tag: "string", val: k.val }, + val: { tag: "str", val: k.val }, ty, }); return; diff --git a/backup-compiler/program.sbl b/backup-compiler/program.sbl index d1b34e0..4fc59ac 100644 --- a/backup-compiler/program.sbl +++ b/backup-compiler/program.sbl @@ -1,28 +1,22 @@ #[c_function("notice")] -fn notice() {} +fn notice() -> int {} #[c_function("print_int")] -fn print_int(value) {} +fn print_int(value: int) -> int {} -fn inner(value) { - print_int(value); - return 0; + + + +#[c_function("println")] +fn println(value: *str) -> int {} + + +#[c_export("sbc_main")] +fn main() -> int { + println("hello_world"); + return 0; } -fn main() { - let i = 0; - loop { - if 10 < i + 1 { - break; - } - let a = 4; - inner(a + 2); - - i = i + 1; - } - return i; -} - -// vim: syntax=rust commentstring=//\ %s +// vim: syntax=slige commentstring=//\ %s diff --git a/backup-compiler/ty.ts b/backup-compiler/ty.ts index 8c17206..08b8886 100644 --- a/backup-compiler/ty.ts +++ b/backup-compiler/ty.ts @@ -2,18 +2,24 @@ import * as ast from "./ast.ts"; export type Ty = | { tag: "error" } + | { tag: "unknown" } | { tag: "int" } - | { tag: "string" } + | { tag: "str" } + | { tag: "ptr"; ty: Ty } | { tag: "fn"; stmt: ast.Stmt; params: Ty[]; returnTy: Ty }; export function tyToString(ty: Ty): string { switch (ty.tag) { case "error": return ``; + case "unknown": + return ``; case "int": return `int`; - case "string": - return `string`; + case "str": + return `str`; + case "ptr": + return `*${tyToString(ty.ty)}`; case "fn": { const k = ty.stmt.kind as ast.StmtKind & { tag: "fn" }; const params = ty.params