From 66fc0ec0b5568b6eed4819a83dcdf2a33615b6c4 Mon Sep 17 00:00:00 2001 From: sfja Date: Sat, 22 Feb 2025 03:44:35 +0100 Subject: [PATCH] compiler: lower enums to mir --- slige/compiler/check/checker.ts | 141 +++++++++++++++++++++++++++-- slige/compiler/middle/ast_lower.ts | 54 ++++++++++- slige/compiler/middle/mir.ts | 2 +- slige/compiler/parse/parser.ts | 1 - slige/compiler/program.slg | 17 ++-- slige/compiler/stringify/hir.ts | 4 +- slige/compiler/stringify/mir.ts | 31 ++++++- slige/compiler/ty/to_string.ts | 4 + slige/compiler/ty/ty.ts | 11 +++ 9 files changed, 237 insertions(+), 28 deletions(-) diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index a707785..47e9d2f 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -4,6 +4,7 @@ import { Ctx, exhausted, File, + IdentId, IdMap, IdSet, Ok, @@ -12,7 +13,14 @@ import { todo, } from "@slige/common"; import * as resolve from "@slige/resolve"; -import { ElemDef, FieldDef, Ty, tyToString, VariantData } from "@slige/ty"; +import { + ElemDef, + FieldDef, + Ty, + tyToString, + Variant, + VariantData, +} from "@slige/ty"; export class Checker { private stmtChecked = new IdSet(); @@ -232,6 +240,27 @@ export class Checker { exhausted(k); } + public enumItemTy(item: ast.Item, kind: ast.EnumItem): Ty { + return this.itemTys.get(item.id) ?? this.checkEnumItem(item, kind); + } + + private checkEnumItem(item: ast.Item, kind: ast.EnumItem): Ty { + const variantIdents = new Set(); + const variants: Variant[] = []; + for (const variant of kind.variants) { + if (variantIdents.has(variant.ident.id)) { + this.report(`variant name already defined`, variant.span); + return Ty({ tag: "error" }); + } + variantIdents.add(variant.ident.id); + variants.push({ + ident: variant.ident.id, + data: this.checkVariantData(variant.data), + }); + } + return Ty({ tag: "enum", item, kind, variants }); + } + public structItemTy(item: ast.Item, kind: ast.StructItem): Ty { return this.itemTys.get(item.id) ?? this.checkStructItem(item, kind); } @@ -364,7 +393,30 @@ export class Checker { ); return Ty({ tag: "error" }); case "unit": - return this.structItemTy(res.kind.item, res.kind.kind); + //return this.structItemTy(res.kind.item, res.kind.kind); + return todo(); + case "tuple": + this.report( + "expected value, got struct type", + expr.span, + ); + return Ty({ tag: "error" }); + } + return exhausted(data.kind); + } + case "variant": { + const data = res.kind.variant.data; + switch (data.kind.tag) { + case "error": + return Ty({ tag: "error" }); + case "struct": + this.report( + "expected value, got struct type", + expr.span, + ); + return Ty({ tag: "error" }); + case "unit": + return todo(); case "tuple": this.report( "expected value, got struct type", @@ -374,8 +426,6 @@ export class Checker { } return exhausted(data.kind); } - case "variant": - return todo("return a ctor here"); case "field": throw new Error(); case "fn": { @@ -411,21 +461,32 @@ export class Checker { return todo(); } const res = this.re.pathRes(kind.path.id); - if (res.kind.tag !== "struct") { + let ty: Ty; + if (res.kind.tag === "struct") { + ty = this.structItemTy(res.kind.item, res.kind.kind); + } else if (res.kind.tag === "variant") { + ty = this.enumItemTy(res.kind.item, res.kind.kind); + } else { this.report("type is not a struct", kind.path.span); const ty = Ty({ tag: "error" }); this.exprTys.set(expr.id, ty); return ty; } - const ty = this.structItemTy(res.kind.item, res.kind.kind); this.exprTys.set(expr.id, ty); if (ty.kind.tag === "error") { return ty; } - if (ty.kind.tag !== "struct") { + let data: VariantData; + if (ty.kind.tag === "struct") { + data = ty.kind.data; + } else if (ty.kind.tag === "enum") { + if (res.kind.tag !== "variant") { + throw new Error(); + } + data = ty.kind.variants[res.kind.variantIdx].data; + } else { throw new Error(); } - const data = ty.kind.data; if (data.tag !== "struct") { this.report("struct data not a struct", kind.path.span); const ty = Ty({ tag: "error" }); @@ -471,6 +532,9 @@ export class Checker { kind: ast.CallExpr, expected: Ty, ): Ty { + if (this.callExprIsTupleVariantCtor(kind)) { + return this.checkCallExprTupleVariantCtor(expr, kind, expected); + } if (this.callExprIsTupleStructCtor(kind)) { return this.checkCallExprTupleStructCtor(expr, kind, expected); } @@ -501,6 +565,56 @@ export class Checker { return ty; } + private callExprIsTupleVariantCtor(kind: ast.CallExpr): boolean { + if (kind.expr.kind.tag !== "path") { + return false; + } + const res = this.re.exprRes(kind.expr.id); + return res.kind.tag === "variant" && + res.kind.variant.data.kind.tag === "tuple"; + } + + private checkCallExprTupleVariantCtor( + expr: ast.Expr, + kind: ast.CallExpr, + expected: Ty, + ): Ty { + if (kind.expr.kind.tag !== "path") { + throw new Error(); + } + const res = this.re.exprRes(kind.expr.id); + if (res.kind.tag !== "variant") { + throw new Error(); + } + const ty = this.enumItemTy(res.kind.item, res.kind.kind); + this.exprTys.set(expr.id, ty); + if (ty.kind.tag === "error") { + return ty; + } + if (ty.kind.tag !== "enum") { + throw new Error(); + } + const data = ty.kind.variants[res.kind.variantIdx].data; + if (data.tag !== "tuple") { + this.report( + "variant data not a tuple", + kind.expr.kind.path.span, + ); + const ty = Ty({ tag: "error" }); + this.exprTys.set(expr.id, ty); + return ty; + } + for (const [i, arg] of kind.args.entries()) { + const argTy = this.exprTy(arg); + const tyRes = this.resolveTys(argTy, data.elems[i].ty); + if (!tyRes.ok) { + this.report(tyRes.val, arg.span); + return ty; + } + } + return ty; + } + private callExprIsTupleStructCtor(kind: ast.CallExpr): boolean { if (kind.expr.kind.tag !== "path") { return false; @@ -742,7 +856,7 @@ export class Checker { case "error": return Ty({ tag: "error" }); case "enum": - return todo(); + return this.enumItemTy(k.item, k.kind); case "struct": return this.structItemTy(k.item, k.kind); case "fn": @@ -854,6 +968,15 @@ export class Checker { } return Res.Ok(a); } + case "enum": { + if (b.kind.tag !== "enum") { + return incompat(); + } + if (a.kind.item.id !== b.kind.item.id) { + return incompat(); + } + return Res.Ok(a); + } case "struct": { if (b.kind.tag !== "struct") { return incompat(); diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 6d1220a..38cdab5 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -326,15 +326,26 @@ export class FnLowerer { private lowerStructExpr(expr: ast.Expr, kind: ast.StructExpr): RVal { const ty = this.ch.exprTy(expr); + let variant: ast.Variant | undefined = undefined; + if (ty.kind.tag === "enum") { + const res = this.re.pathRes(kind.path!.id); + if (res.kind.tag !== "variant") { + throw new Error(); + } + variant = res.kind.variant; + } const fields = kind.fields .map((field) => this.lowerExprToOperand(field.expr)); - return { tag: "struct", ty, fields }; + return { tag: "adt", ty, fields, variant }; } private lowerCallExpr(expr: ast.Expr, kind: ast.CallExpr): RVal { if (this.callExprIsTupleStructCtor(kind)) { return this.lowerCallExprTupleStructCtor(expr, kind); } + if (this.callExprIsTupleVariantCtor(kind)) { + return this.lowerCallExprTupleVariantCtor(expr, kind); + } const args = kind.args.map((arg) => this.lowerExprToOperand(arg)); const func = this.lowerExprToOperand(kind.expr); return { tag: "call", func, args }; @@ -355,7 +366,31 @@ export class FnLowerer { const ty = this.ch.exprTy(expr); const fields = kind.args .map((arg) => this.lowerExprToOperand(arg)); - return { tag: "struct", ty, fields }; + return { tag: "adt", ty, fields }; + } + + private callExprIsTupleVariantCtor(kind: ast.CallExpr): boolean { + if (kind.expr.kind.tag !== "path") { + return false; + } + const res = this.re.exprRes(kind.expr.id); + return res.kind.tag === "variant" && + res.kind.variant.data.kind.tag === "tuple"; + } + + private lowerCallExprTupleVariantCtor( + expr: ast.Expr, + kind: ast.CallExpr, + ): RVal { + const res = this.re.exprRes(kind.expr.id); + if (res.kind.tag !== "variant") { + throw new Error(); + } + const variant = res.kind.variant; + const ty = this.ch.exprTy(expr); + const fields = kind.args + .map((arg) => this.lowerExprToOperand(arg)); + return { tag: "adt", ty, fields, variant }; } private lowerBinaryExpr(expr: ast.Expr, kind: ast.BinaryExpr): RVal { @@ -661,13 +696,23 @@ export class FnLowerer { return { tag: "error" }; case "struct": case "unit": - return todo(data.kind.tag); case "tuple": return todo(data.kind.tag); } return exhausted(data.kind); } - case "variant": + case "variant": { + const data = re.kind.variant.data; + switch (data.kind.tag) { + case "error": + return { tag: "error" }; + case "struct": + case "unit": + case "tuple": + return todo(data.kind.tag); + } + return exhausted(data.kind); + } case "field": return todo(); case "local": { @@ -704,6 +749,7 @@ export class FnLowerer { case "bool": return true; case "fn": + case "enum": case "struct": return false; } diff --git a/slige/compiler/middle/mir.ts b/slige/compiler/middle/mir.ts index 7efd559..7ef125f 100644 --- a/slige/compiler/middle/mir.ts +++ b/slige/compiler/middle/mir.ts @@ -85,7 +85,7 @@ export type RVal = | { tag: "ptr"; place: Place; mut: boolean } | { tag: "binary"; binaryType: BinaryType; left: Operand; right: Operand } | { tag: "unary"; unaryType: UnaryType; operand: Operand } - | { tag: "struct"; ty: Ty; fields: Operand[] } + | { tag: "adt"; ty: Ty; fields: Operand[]; variant?: ast.Variant } | { tag: "call"; func: Operand; args: Operand[] }; export type BinaryType = diff --git a/slige/compiler/parse/parser.ts b/slige/compiler/parse/parser.ts index 19a2e4d..450a8af 100644 --- a/slige/compiler/parse/parser.ts +++ b/slige/compiler/parse/parser.ts @@ -349,7 +349,6 @@ export class Parser { this.report("expected '{'"); return this.stmt({ tag: "error" }, begin); } - this.step(); const endSpan: [Span] = [begin]; const variants = this.parseDelimitedList( this.parseVariant, diff --git a/slige/compiler/program.slg b/slige/compiler/program.slg index e31f6a6..ba174be 100644 --- a/slige/compiler/program.slg +++ b/slige/compiler/program.slg @@ -1,18 +1,13 @@ -struct A(int); -struct B(int, int); -struct C { - a: A, - v: int, +enum Abc { + B(int), + C { v: int }, } fn main() { - let a: A = A(123); - let b = B(1, 2); - let c = C { - a: a, - v: 123, - }; + let b: Abc = Abc::B(123); + + let c = Abc::C { v: 123 }; } diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts index 5a47cf0..9733ec5 100644 --- a/slige/compiler/stringify/hir.ts +++ b/slige/compiler/stringify/hir.ts @@ -57,7 +57,9 @@ export class HirStringifyer { this.file(k.ast!, depth + 1) }\n}`; case "enum": - return todo(); + return `enum ${ident}: ${ + this.ty(this.ch.enumItemTy(item, k)) + };`; case "struct": return `struct ${ident}: ${ this.ty(this.ch.structItemTy(item, k)) diff --git a/slige/compiler/stringify/mir.ts b/slige/compiler/stringify/mir.ts index 2bd5a8e..c87c1cc 100644 --- a/slige/compiler/stringify/mir.ts +++ b/slige/compiler/stringify/mir.ts @@ -165,7 +165,7 @@ export class MirFnStringifyer { } case "unary": return todo(rval.tag); - case "struct": { + case "adt": { const tyk = rval.ty.kind; if (tyk.tag === "struct") { const datak = tyk.kind.data.kind; @@ -194,6 +194,35 @@ export class MirFnStringifyer { } } return exhausted(datak); + } else if (tyk.tag === "enum") { + const datak = rval.variant!.data.kind; + switch (datak.tag) { + case "error": + return ""; + case "unit": + return todo(); + case "tuple": { + const name = `${tyk.item.ident.text}::${ + rval.variant!.ident.text + }`; + const fields = rval.fields + .map((field, idx) => this.operand(field)) + .join(", "); + return `${name}(${fields})`; + } + case "struct": { + const name = tyk.item.ident.text; + const fields = rval.fields + .map((field, idx) => + `${datak.fields[idx].ident!.text}: ${ + this.operand(field) + }` + ) + .join(", "); + return `${name} { ${fields} }`; + } + } + return exhausted(datak); } else { return todo(); } diff --git a/slige/compiler/ty/to_string.ts b/slige/compiler/ty/to_string.ts index d885dc8..8580ad3 100644 --- a/slige/compiler/ty/to_string.ts +++ b/slige/compiler/ty/to_string.ts @@ -22,6 +22,10 @@ export function tyToString(ctx: Ctx, ty: Ty): string { const reTy = tyToString(ctx, k.returnTy); return `fn ${identText}(${params}) -> ${reTy}`; } + case "enum": { + const identText = ctx.identText(k.item.ident.id); + return identText; + } case "struct": { const identText = ctx.identText(k.item.ident.id); return identText; diff --git a/slige/compiler/ty/ty.ts b/slige/compiler/ty/ty.ts index c26ab46..745339c 100644 --- a/slige/compiler/ty/ty.ts +++ b/slige/compiler/ty/ty.ts @@ -20,6 +20,12 @@ export type TyKind = params: Ty[]; returnTy: Ty; } + | { + tag: "enum"; + item: ast.Item; + kind: ast.EnumItem; + variants: Variant[]; + } | { tag: "struct"; item: ast.Item; @@ -27,6 +33,11 @@ export type TyKind = data: VariantData; }; +export type Variant = { + ident: IdentId; + data: VariantData; +}; + export type VariantData = | { tag: "error" } | { tag: "unit" }