From 53d3777ee04f4bcd37c46f513608713aebd96fe4 Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Wed, 12 Feb 2025 23:53:33 +0100 Subject: [PATCH] compiler: add struct types --- slige/compiler/ast/ast.ts | 12 +- slige/compiler/ast/cx.ts | 11 +- slige/compiler/ast/visitor.ts | 21 ++- slige/compiler/check/checker.ts | 147 +++++++++++++++- slige/compiler/deno.jsonc | 1 + slige/compiler/middle/ast_lower.ts | 11 ++ slige/compiler/parse/parser.ts | 272 +++++++++++++++++++++-------- slige/compiler/pat/ctor.ts | 7 + slige/compiler/pat/deno.jsonc | 4 + slige/compiler/pat/mod.ts | 2 + slige/compiler/program.slg | 6 +- slige/compiler/resolve/cx.ts | 37 +++- slige/compiler/resolve/resolver.ts | 192 ++++++++++++++------ slige/compiler/stringify/hir.ts | 16 +- slige/compiler/ty/to_string.ts | 6 +- slige/compiler/ty/ty.ts | 24 +++ 16 files changed, 628 insertions(+), 141 deletions(-) create mode 100644 slige/compiler/pat/ctor.ts create mode 100644 slige/compiler/pat/deno.jsonc create mode 100644 slige/compiler/pat/mod.ts diff --git a/slige/compiler/ast/ast.ts b/slige/compiler/ast/ast.ts index 24bc778..203e9df 100644 --- a/slige/compiler/ast/ast.ts +++ b/slige/compiler/ast/ast.ts @@ -48,6 +48,7 @@ export type Item = { span: Span; ident: Ident; pub: boolean; + attrs: Attr[]; }; export type ItemKind = @@ -95,11 +96,11 @@ export type VariantDataKind = | { tag: "tuple" } & TupleVariantData | { tag: "struct" } & StructVariantData; -export type TupleVariantData = { elems: VariantData[] }; +export type TupleVariantData = { elems: FieldDef[] }; export type StructVariantData = { fields: FieldDef[] }; export type FieldDef = { - ident: Ident; + ident?: Ident; ty: Ty; pub: boolean; span: Span; @@ -251,6 +252,7 @@ export type AnonFieldDef = { }; export type Path = { + id: AstId; segments: PathSegment[]; span: Span; }; @@ -266,3 +268,9 @@ export type Ident = { text: string; span: Span; }; + +export type Attr = { + ident: Ident; + args?: Expr[]; + span: Span; +}; diff --git a/slige/compiler/ast/cx.ts b/slige/compiler/ast/cx.ts index 44535f5..750a0fc 100644 --- a/slige/compiler/ast/cx.ts +++ b/slige/compiler/ast/cx.ts @@ -1,11 +1,14 @@ import { AstId, Ids, Span } from "@slige/common"; import { + Attr, Expr, ExprKind, Ident, Item, ItemKind, Pat, + Path, + PathSegment, PatKind, Stmt, StmtKind, @@ -30,9 +33,10 @@ export class Cx { span: Span, ident: Ident, pub: boolean, + attrs: Attr[], ): Item { const id = this.id(); - return { id, kind, span, ident, pub }; + return { id, kind, span, ident, pub, attrs }; } public expr(kind: ExprKind, span: Span): Expr { @@ -49,4 +53,9 @@ export class Cx { const id = this.id(); return { id, kind, span }; } + + public path(segments: PathSegment[], span: Span): Path { + const id = this.id(); + return { id, segments, span }; + } } diff --git a/slige/compiler/ast/visitor.ts b/slige/compiler/ast/visitor.ts index 3b6e9f5..bbd52b4 100644 --- a/slige/compiler/ast/visitor.ts +++ b/slige/compiler/ast/visitor.ts @@ -18,6 +18,7 @@ import { EnumItem, Expr, ExprStmt, + FieldDef, FieldExpr, File, FnItem, @@ -91,6 +92,8 @@ export interface Visitor< visitTypeAliasItem?(item: Item, kind: TypeAliasItem, ...p: P): R; visitVariant?(variant: Variant, ...p: P): R; + visitVariantData?(data: VariantData, ...p: P): R; + visitFieldDef?(field: FieldDef, ...p: P): R; visitExpr?(expr: Expr, ...p: P): R; visitErrorExpr?(expr: Expr, ...p: P): R; @@ -291,6 +294,7 @@ export function visitVariantData< data: VariantData, ...p: P ) { + if (v.visitVariantData?.(data, ...p) === "stop") return; const dk = data.kind; switch (dk.tag) { case "error": @@ -299,19 +303,30 @@ export function visitVariantData< return; case "tuple": for (const elem of dk.elems) { - visitVariantData(v, elem, ...p); + visitFieldDef(v, elem, ...p); } return; case "struct": for (const field of dk.fields) { - visitIdent(v, field.ident, ...p); - visitTy(v, field.ty, ...p); + visitFieldDef(v, field, ...p); } return; } exhausted(dk); } +export function visitFieldDef< + P extends PM = [], +>( + v: Visitor

, + field: FieldDef, + ...p: P +) { + if (v.visitFieldDef?.(field, ...p) === "stop") return; + field.ident && visitIdent(v, field.ident, ...p); + visitTy(v, field.ty, ...p); +} + export function visitGenerics< P extends PM = [], >( diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index 7d9572f..a58f41d 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -12,7 +12,7 @@ import { todo, } from "@slige/common"; import * as resolve from "@slige/resolve"; -import { Ty, tyToString } from "@slige/ty"; +import { ElemDef, FieldDef, Ty, tyToString, VariantData } from "@slige/ty"; export class Checker { private stmtChecked = new IdSet(); @@ -232,6 +232,44 @@ export class Checker { exhausted(k); } + public structItemTy(item: ast.Item, kind: ast.StructItem): Ty { + return this.itemTys.get(item.id) ?? this.checkStructItem(item, kind); + } + + private checkStructItem(item: ast.Item, kind: ast.StructItem): Ty { + const data = this.checkVariantData(kind.data); + return Ty({ tag: "struct", item, kind, data }); + } + + private checkVariantData(data: ast.VariantData): VariantData { + const k = data.kind; + switch (k.tag) { + case "error": + return { tag: "error" }; + case "unit": + return { tag: "unit" }; + case "tuple": { + const elems = k.elems + .map(({ ty, pub }): ElemDef => ({ + ty: this.tyTy(ty), + pub, + })); + return { tag: "tuple", elems }; + } + case "struct": { + const fields = k.fields + .map(({ ident, ty, pub }): FieldDef => { + if (!ident) { + throw new Error(); + } + return { ident: ident.id, ty: this.tyTy(ty), pub }; + }); + return { tag: "struct", fields }; + } + } + exhausted(k); + } + public fnItemTy(item: ast.Item, kind: ast.FnItem): Ty { return this.itemTys.get(item.id) ?? this.checkFnItem(item, kind); } @@ -270,7 +308,7 @@ export class Checker { case "repeat": return todo(); case "struct": - return todo(); + return this.checkStructExpr(expr, k, expected); case "ref": return todo(); case "deref": @@ -312,6 +350,13 @@ export class Checker { switch (res.kind.tag) { case "error": return Ty({ tag: "error" }); + case "enum": + case "struct": + return todo("return a ctor here"); + case "variant": + return todo("return a ctor here"); + case "field": + throw new Error(); case "fn": { const fn = res.kind.item; const ty = this.fnItemTy(fn, res.kind.kind); @@ -336,6 +381,70 @@ export class Checker { exhausted(res.kind); } + private checkStructExpr( + expr: ast.Expr, + kind: ast.StructExpr, + expected: Ty, + ): Ty { + if (!kind.path) { + return todo(); + } + const res = this.re.pathRes(kind.path.id); + if (res.kind.tag !== "struct") { + 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") { + 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" }); + this.exprTys.set(expr.id, ty); + return ty; + } + const notCovered = new Set(); + for (const field of data.fields) { + notCovered.add(field); + } + for (const field of kind.fields) { + const found = data.fields + .find((f) => f.ident === field.ident.id); + if (!found) { + this.report(`no field named '${field.ident.text}'`, field.span); + return ty; + } + const fieldTy = this.exprTy(field.expr); + const tyRes = this.resolveTys(fieldTy, found.ty); + if (!tyRes.ok) { + this.report(tyRes.val, field.span); + return ty; + } + notCovered.delete(found); + } + if (notCovered.size !== 0) { + this.report( + `fields ${ + notCovered + .keys() + .toArray() + .map((field) => `'${field}'`) + .join(", ") + } not covered`, + expr.span, + ); + } + return ty; + } + private checkCallExpr( expr: ast.Expr, kind: ast.CallExpr, @@ -552,13 +661,32 @@ export class Checker { return Ty({ tag: "int" }); case "bool": case "str": - case "path": + return todo(k.tag); + case "path": { + const re = this.re.tyRes(ty.id); + const k = re.kind; + switch (k.tag) { + case "error": + return Ty({ tag: "error" }); + case "enum": + return todo(); + case "struct": + return this.structItemTy(k.item, k.kind); + case "fn": + case "variant": + case "field": + case "local": + return todo(); + } + exhausted(k); + return todo(); + } case "ref": case "ptr": case "slice": case "array": case "anon_struct": - return todo(k); + return todo(k.tag); } exhausted(k); } @@ -611,7 +739,7 @@ export class Checker { private resolveTys(a: Ty, b: Ty): Res { if (a.kind.tag === "error" || b.kind.tag === "error") { - return Res.Ok(a); + return Res.Ok(Ty({ tag: "error" })); } if (b.kind.tag === "unknown") { return Res.Ok(a); @@ -653,6 +781,15 @@ export class Checker { } return Res.Ok(a); } + case "struct": { + if (b.kind.tag !== "struct") { + return incompat(); + } + if (a.kind.item.id !== b.kind.item.id) { + return incompat(); + } + return Res.Ok(a); + } } exhausted(a.kind); } diff --git a/slige/compiler/deno.jsonc b/slige/compiler/deno.jsonc index 5f22490..50ada3c 100644 --- a/slige/compiler/deno.jsonc +++ b/slige/compiler/deno.jsonc @@ -4,6 +4,7 @@ "./check", "./middle", "./parse", + "./pat", "./resolve", "./ty", "./common", diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 4488aa7..66cda75 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -307,6 +307,11 @@ export class FnLowerer { switch (re.kind.tag) { case "error": return { tag: "error" }; + case "enum": + case "struct": + case "variant": + case "field": + return todo(); case "fn": case "local": return { @@ -617,6 +622,11 @@ export class FnLowerer { switch (re.kind.tag) { case "error": return { tag: "error" }; + case "enum": + case "struct": + case "variant": + case "field": + return todo(); case "local": { const patRes = this.re.patRes(re.kind.id); const ty = this.ch.exprTy(expr); @@ -651,6 +661,7 @@ export class FnLowerer { case "bool": return true; case "fn": + case "struct": return false; } exhausted(ty.kind); diff --git a/slige/compiler/parse/parser.ts b/slige/compiler/parse/parser.ts index 6bcc6df..80399e2 100644 --- a/slige/compiler/parse/parser.ts +++ b/slige/compiler/parse/parser.ts @@ -1,12 +1,14 @@ import { AnonFieldDef, AssignType, + Attr, BinaryType, Block, Cx, Expr, ExprField, ExprKind, + FieldDef, File, GenericParam, Ident, @@ -23,8 +25,10 @@ import { Ty, TyKind, UnaryType, + Variant, + VariantData, } from "@slige/ast"; -import { Ctx, File as CtxFile, Res, Span } from "@slige/common"; +import { Ctx, File as CtxFile, Res, Span, todo } from "@slige/common"; import { Lexer } from "./lexer.ts"; import { TokenIter } from "./token.ts"; import { SigFilter } from "./token.ts"; @@ -63,12 +67,15 @@ export class Parser { } private parseStmt(): Stmt { + const attrs = this.parseAttrs(); if ( - ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ["pub", "mod", "enum", "struct", "fn", "type_alias"].some((tt) => + this.test(tt) + ) ) { - return this.parseItemStmt(); + return this.parseItemStmt(undefined, false, attrs); } else if ( - ["let", "type_alias", "return", "break"].some((tt) => this.test(tt)) + ["let", "return", "break"].some((tt) => this.test(tt)) ) { const expr = this.parseSingleLineBlockStmt(); this.eatSemicolon(); @@ -85,6 +92,45 @@ export class Parser { } } + private parseAttrs(): Attr[] { + const attrs: Attr[] = []; + while (this.test("#")) { + const begin = this.span(); + this.step(); + if (!this.test("[")) { + this.report("expected '['"); + return attrs; + } + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return attrs; + } + const ident = this.parseIdent(); + let args: Expr[] | undefined = undefined; + if (this.test("(")) { + this.step(); + args = this.parseDelimitedList( + this.parseAttrArg, + ")", + ",", + ); + } + if (!this.test("]")) { + this.report("expected ']'"); + return attrs; + } + const end = this.span(); + this.step(); + attrs.push({ ident, args, span: Span.fromto(begin, end) }); + } + return attrs; + } + + private parseAttrArg(): Res { + return Res.Ok(this.parseExpr()); + } + private parseMultiLineBlockExpr(): Expr { const begin = this.span(); if (this.test("{")) { @@ -111,9 +157,6 @@ export class Parser { if (this.test("let")) { return this.parseLet(); } - if (this.test("type_alias")) { - return this.parseTypeAlias(); - } if (this.test("return")) { return this.parseReturn(); } @@ -143,16 +186,19 @@ export class Parser { this.step(); const stmts: Stmt[] = []; while (!this.done()) { + const attrs = this.parseAttrs(); if (this.test("}")) { const span = Span.fromto(begin, this.span()); this.step(); return Res.Ok({ stmts, span }); } else if ( - ["#", "pub", "mod", "fn"].some((tt) => this.test(tt)) + ["pub", "mod", "enum", "struct", "fn", "type_alias"].some(( + tt, + ) => this.test(tt)) ) { - stmts.push(this.parseItemStmt()); + stmts.push(this.parseItemStmt(undefined, false, attrs)); } else if ( - ["let", "type_alias", "return", "break"] + ["let", "return", "break"] .some((tt) => this.test(tt)) ) { stmts.push(this.parseSingleLineBlockStmt()); @@ -210,67 +256,29 @@ export class Parser { private parseItemStmt( pos = this.span(), - details: StmtDetails = { - pub: false, - annos: [], - }, + pub: boolean, + attrs: Attr[], ): Stmt { - const sbegin = this.span(); - if (this.test("#") && !details.pub) { + if (this.test("pub") && !pub) { this.step(); - if (!this.test("[")) { - this.report("expected '['"); - return this.stmt({ tag: "error" }, sbegin); - } - this.step(); - if (!this.test("ident")) { - this.report("expected 'ident'"); - return this.stmt({ tag: "error" }, sbegin); - } - const ident = this.parseIdent(); - const args: Expr[] = []; - if (this.test("(")) { - this.step(); - if (!this.done() && !this.test(")")) { - args.push(this.parseExpr()); - while (this.test(",")) { - this.step(); - if (this.done() || this.test(")")) { - break; - } - args.push(this.parseExpr()); - } - } - if (!this.test(")")) { - this.report("expected ')'"); - return this.stmt({ tag: "error" }, sbegin); - } - this.step(); - } - if (!this.test("]")) { - this.report("expected ']'"); - return this.stmt({ tag: "error" }, sbegin); - } - this.step(); - const anno = { ident, args, pos: sbegin }; - return this.parseItemStmt(pos, { - ...details, - annos: [...details.annos, anno], - }); - } else if (this.test("pub") && !details.pub) { - this.step(); - return this.parseItemStmt(pos, { ...details, pub: true }); + return this.parseItemStmt(pos, pub, attrs); } else if (this.test("mod")) { - return this.parseMod(details); + return this.parseModItem(pub, attrs); + } else if (this.test("enum")) { + return this.parseEnumItem(pub, attrs); + } else if (this.test("struct")) { + return this.parseStructItem(pub, attrs); } else if (this.test("fn")) { - return this.parseFn(details); + return this.parseFnItem(pub, attrs); + } else if (this.test("type_alias")) { + return this.parseTypeAliasItem(pub, attrs); } else { this.report("expected item statement"); return this.stmt({ tag: "error" }, pos); } } - private parseMod(details: StmtDetails): Stmt { + private parseModItem(pub: boolean, attrs: Attr[]): Stmt { const pos = this.span(); this.step(); if (!this.test("ident")) { @@ -288,7 +296,8 @@ export class Parser { { tag: "mod_file", filePath }, pos, ident, - details.pub, + pub, + attrs, ), }, pos); } @@ -322,12 +331,123 @@ export class Parser { }, pos, ident, - details.pub, + pub, + attrs, ), }, pos); } - private parseFn(details: StmtDetails): Stmt { + private parseEnumItem(pub: boolean, attrs: Attr[]): Stmt { + const begin = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.stmt({ tag: "error" }, begin); + } + const ident = this.parseIdent(); + if (!this.test("{")) { + this.report("expected '{'"); + return this.stmt({ tag: "error" }, begin); + } + this.step(); + const endSpan: [Span] = [begin]; + const variants = this.parseDelimitedList( + this.parseVariant, + "}", + ",", + endSpan, + ); + const span = Span.fromto(begin, endSpan[0]); + return this.stmt({ + tag: "item", + item: this.item({ tag: "enum", variants }, span, ident, pub, attrs), + }, span); + } + + private parseVariant(): Res { + const begin = this.span(); + let pub = false; + if (this.test("pub")) { + this.step(); + pub = true; + } + if (!this.test("ident")) { + this.report("expected 'ident'"); + return Res.Err(undefined); + } + const ident = this.parseIdent(); + const data = this.parseVariantData(); + return Res.Ok({ + ident, + data, + pub, + span: Span.fromto(begin, data.span), + }); + } + + private parseStructItem(pub: boolean, attrs: Attr[]): Stmt { + const begin = this.span(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.stmt({ tag: "error" }, begin); + } + const ident = this.parseIdent(); + const data = this.parseVariantData(); + const span = Span.fromto(begin, data.span); + return this.stmt({ + tag: "item", + item: this.item({ tag: "struct", data }, span, ident, pub, attrs), + }, span); + } + + private parseVariantData(): VariantData { + const begin = this.span(); + if (this.test("(")) { + const elems = this.parseDelimitedList(this.parseFieldDef, ")", ","); + return { + kind: { tag: "tuple", elems }, + span: Span.fromto(begin, elems.at(-1)?.span ?? begin), + }; + } else if (this.test("{")) { + const fields = this.parseDelimitedList( + this.parseFieldDef, + "}", + ",", + ); + return { + kind: { tag: "struct", fields }, + span: Span.fromto(begin, fields.at(-1)?.span ?? begin), + }; + } else { + return { + kind: { tag: "unit" }, + span: { begin: begin.end, end: begin.end }, + }; + } + } + + private parseFieldDef(): Res { + const begin = this.span(); + let pub = false; + if (this.test("pub")) { + this.step(); + pub = true; + } + let ident: Ident | undefined = undefined; + if (this.test("ident")) { + ident = this.parseIdent(); + if (!this.test(":")) { + this.report("expected ':'"); + return Res.Err(undefined); + } + this.step(); + } + const ty = this.parseTy(); + return Res.Ok({ ident, ty, pub, span: Span.fromto(begin, ty.span) }); + } + + private parseFnItem(pub: boolean, attrs: Attr[]): Stmt { const pos = this.span(); this.step(); if (!this.test("ident")) { @@ -373,7 +493,8 @@ export class Parser { }, pos, ident, - details.pub, + pub, + attrs, ), }, pos); } @@ -400,6 +521,7 @@ export class Parser { parseElem: (this: Parser, index: number) => ParseRes, endToken: string, delimiter: string, + outEndSpan?: [Span], ): T[] { this.step(); if (this.test(endToken)) { @@ -430,6 +552,7 @@ export class Parser { this.report(`expected '${endToken}'`); return elems; } + outEndSpan && (outEndSpan[0] = this.span()); this.step(); return elems; } @@ -468,7 +591,7 @@ export class Parser { return this.stmt({ tag: "let", pat, ty, expr }, pos); } - private parseTypeAlias(): Stmt { + private parseTypeAliasItem(pub: boolean, attrs: Attr[]): Stmt { const begin = this.span(); this.step(); if (!this.test("ident")) { @@ -491,7 +614,8 @@ export class Parser { }, Span.fromto(begin, ty.span), ident, - false, + pub, + attrs, ), }, begin); } @@ -644,7 +768,7 @@ export class Parser { return this.expr({ tag: "array", exprs }, pos); } - private parseStruct(): Expr { + private parseAnonStructExpr(): Expr { const pos = this.span(); this.step(); if (!this.test("{")) { @@ -906,7 +1030,6 @@ export class Parser { return this.expr({ tag: "error" }, pos); } if (this.test("{") && !(rs & ExprRestricts.NoStructs)) { - this.step(); const fields = this.parseDelimitedList( this.parseExprField, "}", @@ -955,7 +1078,7 @@ export class Parser { return this.parseArray(); } if (this.test("struct")) { - return this.parseStruct(); + return this.parseAnonStructExpr(); } if (this.test("{")) { return this.parseBlockExpr(); @@ -1166,7 +1289,7 @@ export class Parser { span: Span.fromto(begin, end), }); } - return Res.Ok({ segments, span: Span.fromto(begin, end) }); + return Res.Ok(this.path(segments, Span.fromto(begin, end))); } private parseTyRes(): ParseRes { @@ -1229,8 +1352,9 @@ export class Parser { span: Span, ident: Ident, pub: boolean, + attrs: Attr[], ): Item { - return this.cx.item(kind, span, ident, pub); + return this.cx.item(kind, span, ident, pub, attrs); } private expr(kind: ExprKind, span: Span): Expr { @@ -1244,6 +1368,10 @@ export class Parser { private ty(kind: TyKind, span: Span): Ty { return this.cx.ty(kind, span); } + + private path(segments: PathSegment[], span: Span): Path { + return this.cx.path(segments, span); + } } const ExprRestricts = { diff --git a/slige/compiler/pat/ctor.ts b/slige/compiler/pat/ctor.ts new file mode 100644 index 0000000..e26c7c0 --- /dev/null +++ b/slige/compiler/pat/ctor.ts @@ -0,0 +1,7 @@ +export type Ctor = { + kind: CtorKind; +}; + +export type CtorKind = + | { tag: "error" } + | { tag: "struct" }; diff --git a/slige/compiler/pat/deno.jsonc b/slige/compiler/pat/deno.jsonc new file mode 100644 index 0000000..3f0511b --- /dev/null +++ b/slige/compiler/pat/deno.jsonc @@ -0,0 +1,4 @@ +{ + "name": "@slige/pat", + "exports": "./mod.ts", +} diff --git a/slige/compiler/pat/mod.ts b/slige/compiler/pat/mod.ts new file mode 100644 index 0000000..4add797 --- /dev/null +++ b/slige/compiler/pat/mod.ts @@ -0,0 +1,2 @@ +export * as ctor from "./ctor.ts"; +export * from "./ctor.ts"; diff --git a/slige/compiler/program.slg b/slige/compiler/program.slg index 7cbefd6..1bdc8c0 100644 --- a/slige/compiler/program.slg +++ b/slige/compiler/program.slg @@ -1,6 +1,10 @@ +struct A { + v: int, +} + fn main() { - for (let mut i = 0; i < 10; i = i + 1) {} + let a: A = A { v: 123 }; } diff --git a/slige/compiler/resolve/cx.ts b/slige/compiler/resolve/cx.ts index 0abcbd2..31e3a78 100644 --- a/slige/compiler/resolve/cx.ts +++ b/slige/compiler/resolve/cx.ts @@ -1,5 +1,5 @@ import * as ast from "@slige/ast"; -import { AstId, IdBase, IdentId, IdMap, Res } from "@slige/common"; +import { AstId, IdentId, IdMap, Res } from "@slige/common"; export interface Syms { getVal(ident: ast.Ident): Resolve; @@ -16,7 +16,17 @@ export type Resolve = { export type ResolveKind = | { tag: "error" } + | { tag: "enum"; item: ast.Item; kind: ast.EnumItem } + | { tag: "struct"; item: ast.Item; kind: ast.StructItem } | { tag: "fn"; item: ast.Item; kind: ast.FnItem } + | { + tag: "variant"; + item: ast.Item; + kind: ast.EnumItem; + variant: ast.Variant; + variantIdx: number; + } + | { tag: "field"; item: ast.Item; field: ast.FieldDef } | { tag: "local"; id: AstId }; export type PatResolve = { @@ -127,6 +137,31 @@ export class FnSyms implements Syms { } } +export class ItemSyms implements Syms { + private syms = new SymsNsTab(); + + public constructor( + private parent: Syms, + ) {} + + getVal(ident: ast.Ident): Resolve { + const res = this.syms.getVal(ident) || this.parent.getVal(ident); + if (res.kind.tag === "local") { + return ResolveError(ident); + } + return res; + } + getTy(ident: ast.Ident): Resolve { + return this.syms.getTy(ident) || this.parent.getTy(ident); + } + defVal(ident: ast.Ident, kind: ResolveKind): Res { + return this.syms.defVal(ident, kind); + } + defTy(ident: ast.Ident, kind: ResolveKind): Res { + return this.syms.defTy(ident, kind); + } +} + export class LocalSyms implements Syms { private syms = new SymsNsTab(); diff --git a/slige/compiler/resolve/resolver.ts b/slige/compiler/resolve/resolver.ts index 0089566..7cb6f5a 100644 --- a/slige/compiler/resolve/resolver.ts +++ b/slige/compiler/resolve/resolver.ts @@ -12,6 +12,7 @@ import { } from "@slige/common"; import { FnSyms, + ItemSyms, LocalSyms, LoopBreakResolve, LoopResolve, @@ -27,6 +28,8 @@ import { export class Resols { public constructor( private exprResols: IdMap, + private tyResols: IdMap, + private pathResols: IdMap, private patResols: IdMap, private loopsResols: IdMap, private loopBreakResols: IdMap, @@ -39,6 +42,20 @@ export class Resols { return this.exprResols.get(id)!; } + public tyRes(id: AstId): Resolve { + if (!this.tyResols.has(id)) { + throw new Error(); + } + return this.tyResols.get(id)!; + } + + public pathRes(id: AstId): Resolve { + if (!this.pathResols.has(id)) { + throw new Error(); + } + return this.pathResols.get(id)!; + } + public patRes(id: AstId): PatResolve { if (!this.patResols.has(id)) { throw new Error(); @@ -67,6 +84,8 @@ export class Resolver implements ast.Visitor { private syms: Syms = this.rootSyms; private exprResols = new IdMap(); + private tyResols = new IdMap(); + private pathResols = new IdMap(); private patResols = new IdMap(); private patResolveStack: PatResolveKind[] = []; @@ -84,6 +103,8 @@ export class Resolver implements ast.Visitor { ast.visitFile(this, this.entryFileAst); return new Resols( this.exprResols, + this.tyResols, + this.pathResols, this.patResols, this.loopsResols, this.loopBreakResols, @@ -145,11 +166,33 @@ export class Resolver implements ast.Visitor { } visitEnumItem(item: ast.Item, kind: ast.EnumItem): ast.VisitRes { - todo(); + this.syms.defTy(item.ident, { tag: "enum", item, kind }); + const outerSyms = this.syms; + this.syms = new ItemSyms(this.syms); + for (const variant of kind.variants) { + ast.visitVariant(this, variant); + } + this.syms = outerSyms; + return "stop"; } visitStructItem(item: ast.Item, kind: ast.StructItem): ast.VisitRes { - todo(); + this.syms.defTy(item.ident, { tag: "struct", item, kind }); + const outerSyms = this.syms; + this.syms = new ItemSyms(this.syms); + ast.visitVariantData(this, kind.data); + this.syms = outerSyms; + return "stop"; + } + + visitVariant(variant: ast.Variant): ast.VisitRes { + ast.visitVariantData(this, variant.data); + return "stop"; + } + + visitFieldDef(field: ast.FieldDef): ast.VisitRes { + ast.visitTy(this, field.ty); + return "stop"; } private fnBodiesToCheck: [ast.Item, ast.FnItem][][] = []; @@ -160,6 +203,14 @@ export class Resolver implements ast.Visitor { return "stop"; } + visitUseItem(item: ast.Item, kind: ast.UseItem): ast.VisitRes { + todo(); + } + + visitTypeAliasItem(item: ast.Item, kind: ast.TypeAliasItem): ast.VisitRes { + todo(); + } + private popAndVisitFnBodies() { for (const [item, kind] of this.fnBodiesToCheck.at(-1)!) { const outerSyms = this.syms; @@ -182,41 +233,20 @@ export class Resolver implements ast.Visitor { } visitPathExpr(expr: ast.Expr, kind: ast.PathExpr): ast.VisitRes { - if (kind.path.segments.length === 1) { - const res = this.syms.getVal(kind.path.segments[0].ident); - switch (res.kind.tag) { - case "error": - return "stop"; - case "fn": - this.exprResols.set(expr.id, res); - return "stop"; - case "local": - this.exprResols.set(expr.id, res); - return "stop"; - } - exhausted(res.kind); + if (kind.qty) { + return todo(); } - const pathRes = this.resolveInnerPath(kind.path); - switch (pathRes.kind.tag) { - case "error": - todo(); - return "stop"; - case "fn": - todo(); - return "stop"; - case "local": - todo(); - return "stop"; - } - exhausted(pathRes.kind); + const res = this.resolveValPath(kind.path); + this.exprResols.set(expr.id, res); + return "stop"; } - visitUseItem(item: ast.Item, kind: ast.UseItem): ast.VisitRes { - todo(); - } - - visitTypeAliasItem(item: ast.Item, kind: ast.TypeAliasItem): ast.VisitRes { - todo(); + visitStructExpr(expr: ast.Expr, kind: ast.StructExpr): ast.VisitRes { + if (!kind.path) { + return todo(); + } + this.resolveValPath(kind.path); + return "stop"; } visitLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): ast.VisitRes { @@ -272,6 +302,15 @@ export class Resolver implements ast.Visitor { todo(pat, kind); } + visitPathTy(ty: ast.Ty, kind: ast.PathTy): ast.VisitRes { + if (kind.qty) { + return todo(); + } + const res = this.resolveTyPath(kind.path); + this.tyResols.set(ty.id, res); + return "stop"; + } + visitPath(_path: ast.Path): ast.VisitRes { throw new Error("should not be reached"); } @@ -280,35 +319,80 @@ export class Resolver implements ast.Visitor { throw new Error("should not be reached"); } - private resolveInnerPath(path: ast.Path): Resolve { - const res = path.segments.slice(1, path.segments.length) - .reduce((innerRes, seg) => { - const k = innerRes.kind; + private resolveValPath(path: ast.Path): Resolve { + let res: Resolve; + if (path.segments.length === 0) { + res = this.syms.getVal(path.segments[0].ident); + } else { + res = path.segments + .slice(1) + .reduce((inner, seg): Resolve => { + const k = inner.kind; + switch (k.tag) { + case "error": + return inner; + case "enum": + return this.resolveEnumVariant(k.item, k.kind, seg); + case "struct": + case "fn": + case "variant": + case "field": + case "local": + return todo(); + } + exhausted(); + }, this.syms.getTy(path.segments[0].ident)); + } + this.pathResols.set(path.id, res); + return res; + } + + private resolveTyPath(path: ast.Path): Resolve { + const res = path.segments + .slice(1) + .reduce((inner, seg): Resolve => { + const k = inner.kind; switch (k.tag) { case "error": - return innerRes; + return inner; + case "enum": + return this.resolveEnumVariant(k.item, k.kind, seg); + case "struct": case "fn": - this.ctx.report({ - severity: "error", - file: this.currentFile, - span: seg.ident.span, - msg: "function, not pathable", - }); - return ResolveError(seg.ident); + case "variant": + case "field": case "local": - this.ctx.report({ - severity: "error", - file: this.currentFile, - span: seg.ident.span, - msg: "local variable, not pathable", - }); - return ResolveError(seg.ident); + return todo(); } - exhausted(k); + exhausted(); }, this.syms.getTy(path.segments[0].ident)); + this.pathResols.set(path.id, res); return res; } + private resolveEnumVariant( + item: ast.Item, + kind: ast.EnumItem, + seg: ast.PathSegment, + ): Resolve { + const { ident } = seg; + const found = kind.variants + .map((v, idx) => [v, idx] as const) + .find(([variant]) => variant.ident.id === ident.id); + if (!found) { + this.report( + `enum ${item.ident.text} has no member '${ident.text}'`, + seg.span, + ); + return ResolveError(ident); + } + const [variant, variantIdx] = found; + return { + ident, + kind: { tag: "variant", item, kind, variant, variantIdx }, + }; + } + private report(msg: string, span: Span) { this.ctx.report({ severity: "error", diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts index cd2289e..5a47cf0 100644 --- a/slige/compiler/stringify/hir.ts +++ b/slige/compiler/stringify/hir.ts @@ -59,7 +59,9 @@ export class HirStringifyer { case "enum": return todo(); case "struct": - return todo(); + return `struct ${ident}: ${ + this.ty(this.ch.structItemTy(item, k)) + };`; case "fn": { const ty = this.ch.fnItemTy(item, k); if (ty.kind.tag !== "fn") { @@ -98,7 +100,19 @@ export class HirStringifyer { case "group": case "array": case "repeat": + return todo(k.tag); case "struct": + return `${k.path ? `${this.path(k.path)}` : "struct "} {${ + [ + k.fields + .map((field) => + `${indent(d + 1)}${field.ident.text}: ${ + this.expr(field.expr, d + 1) + },` + ) + .join("\n"), + ].map((s) => `\n${s}\n${indent(d)}`) + }}`; case "ref": case "deref": case "elem": diff --git a/slige/compiler/ty/to_string.ts b/slige/compiler/ty/to_string.ts index b3bae27..d885dc8 100644 --- a/slige/compiler/ty/to_string.ts +++ b/slige/compiler/ty/to_string.ts @@ -1,5 +1,5 @@ import { Ctx, exhausted } from "@slige/common"; -import { Ty } from "./ty.ts"; +import { Ty, VariantData } from "./ty.ts"; export function tyToString(ctx: Ctx, ty: Ty): string { const k = ty.kind; @@ -22,6 +22,10 @@ export function tyToString(ctx: Ctx, ty: Ty): string { const reTy = tyToString(ctx, k.returnTy); return `fn ${identText}(${params}) -> ${reTy}`; } + case "struct": { + const identText = ctx.identText(k.item.ident.id); + return identText; + } } exhausted(k); } diff --git a/slige/compiler/ty/ty.ts b/slige/compiler/ty/ty.ts index ef01dd4..c26ab46 100644 --- a/slige/compiler/ty/ty.ts +++ b/slige/compiler/ty/ty.ts @@ -1,4 +1,5 @@ import * as ast from "@slige/ast"; +import { IdentId } from "@slige/common"; export type Ty = { kind: TyKind; @@ -18,4 +19,27 @@ export type TyKind = kind: ast.FnItem; params: Ty[]; returnTy: Ty; + } + | { + tag: "struct"; + item: ast.Item; + kind: ast.StructItem; + data: VariantData; }; + +export type VariantData = + | { tag: "error" } + | { tag: "unit" } + | { tag: "tuple"; elems: ElemDef[] } + | { tag: "struct"; fields: FieldDef[] }; + +export type ElemDef = { + ty: Ty; + pub: boolean; +}; + +export type FieldDef = { + ident: IdentId; + ty: Ty; + pub: boolean; +};