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;
+};