mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
compiler: add struct types
This commit is contained in:
parent
a97a128336
commit
53d3777ee0
@ -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;
|
||||
};
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
@ -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<P>,
|
||||
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 = [],
|
||||
>(
|
||||
|
@ -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<AstId>();
|
||||
@ -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<FieldDef>();
|
||||
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<Ty, string> {
|
||||
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);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
"./check",
|
||||
"./middle",
|
||||
"./parse",
|
||||
"./pat",
|
||||
"./resolve",
|
||||
"./ty",
|
||||
"./common",
|
||||
|
@ -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);
|
||||
|
@ -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<Expr, undefined> {
|
||||
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<Variant, undefined> {
|
||||
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<FieldDef, undefined> {
|
||||
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<T>,
|
||||
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<Ty> {
|
||||
@ -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 = {
|
||||
|
7
slige/compiler/pat/ctor.ts
Normal file
7
slige/compiler/pat/ctor.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type Ctor = {
|
||||
kind: CtorKind;
|
||||
};
|
||||
|
||||
export type CtorKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "struct" };
|
4
slige/compiler/pat/deno.jsonc
Normal file
4
slige/compiler/pat/deno.jsonc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@slige/pat",
|
||||
"exports": "./mod.ts",
|
||||
}
|
2
slige/compiler/pat/mod.ts
Normal file
2
slige/compiler/pat/mod.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * as ctor from "./ctor.ts";
|
||||
export * from "./ctor.ts";
|
@ -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 };
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<void, Redef> {
|
||||
return this.syms.defVal(ident, kind);
|
||||
}
|
||||
defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
|
||||
return this.syms.defTy(ident, kind);
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalSyms implements Syms {
|
||||
private syms = new SymsNsTab();
|
||||
|
||||
|
@ -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<AstId, Resolve>,
|
||||
private tyResols: IdMap<AstId, Resolve>,
|
||||
private pathResols: IdMap<AstId, Resolve>,
|
||||
private patResols: IdMap<AstId, PatResolve>,
|
||||
private loopsResols: IdMap<AstId, LoopResolve>,
|
||||
private loopBreakResols: IdMap<AstId, LoopBreakResolve[]>,
|
||||
@ -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<AstId, Resolve>();
|
||||
private tyResols = new IdMap<AstId, Resolve>();
|
||||
private pathResols = new IdMap<AstId, Resolve>();
|
||||
|
||||
private patResols = new IdMap<AstId, PatResolve>();
|
||||
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",
|
||||
|
@ -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":
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user