compiler: types and strings

This commit is contained in:
SimonFJ20 2025-03-26 16:06:36 +01:00
parent 0b65a5841f
commit f291516c87
10 changed files with 306 additions and 82 deletions

View File

@ -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);

View File

@ -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[];

View File

@ -1,8 +1,8 @@
#include <stdint.h>
extern int32_t sbc__main(void);
extern int32_t sbc_main(void);
int main(void)
{
sbc__main();
sbc_main();
}

View File

@ -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<number, Ty>();
private exprTys = new Map<number, Ty>();
private tyTys = new Map<number, Ty>();
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)

View File

@ -1,6 +1,11 @@
#include <stdint.h>
#include <stdio.h>
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;
}

View File

@ -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 });

View File

@ -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}`;

View File

@ -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;

View File

@ -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

View File

@ -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 `<error>`;
case "unknown":
return `<unknown>`;
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