compiler: match expr first half

This commit is contained in:
sfja 2025-02-24 00:02:16 +01:00
parent 5c9e3ce73f
commit 8f3523d4e1
12 changed files with 349 additions and 29 deletions

View File

@ -148,6 +148,7 @@ export type ExprKind =
| { tag: "binary" } & BinaryExpr
| { tag: "block" } & BlockExpr
| { tag: "if" } & IfExpr
| { tag: "match" } & MatchExpr
| { tag: "loop" } & LoopExpr
| { tag: "while" } & WhileExpr
| { tag: "for" } & ForExpr
@ -171,11 +172,18 @@ export type UnaryExpr = { unaryType: UnaryType; expr: Expr };
export type BinaryExpr = { binaryType: BinaryType; left: Expr; right: Expr };
export type BlockExpr = { block: Block };
export type IfExpr = { cond: Expr; truthy: Expr; falsy?: Expr };
export type MatchExpr = { expr: Expr; arms: MatchArm[] };
export type LoopExpr = { body: Expr };
export type WhileExpr = { cond: Expr; body: Expr };
export type ForExpr = { pat: Pat; expr: Expr; body: Expr };
export type CForExpr = { decl?: Stmt; cond?: Expr; incr?: Stmt; body: Expr };
export type MatchArm = {
pat: Pat;
expr: Expr;
span: Span;
};
export type RefType = "ref" | "ptr";
export type UnaryType = "not" | "-";
export type BinaryType =
@ -213,10 +221,20 @@ export type Pat = {
export type PatKind =
| { tag: "error" }
| { tag: "bind" } & BindPat
| { tag: "path" } & PathPat;
| { tag: "path" } & PathPat
| { tag: "tuple" } & TuplePat
| { tag: "struct" } & StructPat;
export type BindPat = { ident: Ident; mut: boolean };
export type PathPat = { qty?: Ty; path: Path };
export type TuplePat = { path?: Path; elems: Pat[] };
export type StructPat = { path?: Path; fields: PatField[] };
export type PatField = {
ident: Ident;
pat: Pat;
span: Span;
};
export type Ty = {
id: AstId;

View File

@ -33,6 +33,8 @@ import {
ItemStmt,
LetStmt,
LoopExpr,
MatchArm,
MatchExpr,
ModBlockItem,
ModFileItem,
Param,
@ -51,6 +53,8 @@ import {
StringExpr,
StructExpr,
StructItem,
StructPat,
TuplePat,
TupleTy,
Ty,
TypeAliasItem,
@ -116,15 +120,20 @@ export interface Visitor<
visitBinaryExpr?(expr: Expr, kind: BinaryExpr, ...p: P): R;
visitBlockExpr?(expr: Expr, kind: BlockExpr, ...p: P): R;
visitIfExpr?(expr: Expr, kind: IfExpr, ...p: P): R;
visitMatchExpr?(expr: Expr, kind: MatchExpr, ...p: P): R;
visitLoopExpr?(expr: Expr, kind: LoopExpr, ...p: P): R;
visitWhileExpr?(expr: Expr, kind: WhileExpr, ...p: P): R;
visitForExpr?(expr: Expr, kind: ForExpr, ...p: P): R;
visitCForExpr?(expr: Expr, kind: CForExpr, ...p: P): R;
visitMatchArm?(arm: MatchArm, ...p: P): R;
visitPat?(pat: Pat, ...p: P): R;
visitErrorPat?(pat: Pat, ...p: P): R;
visitBindPat?(pat: Pat, kind: BindPat, ...p: P): R;
visitPathPat?(pat: Pat, kind: PathPat, ...p: P): R;
visitTuplePat?(pat: Pat, kind: TuplePat, ...p: P): R;
visitStructPat?(pat: Pat, kind: StructPat, ...p: P): R;
visitTy?(ty: Ty, ...p: P): R;
visitErrorTy?(ty: Ty, ...p: P): R;
@ -453,6 +462,13 @@ export function visitExpr<
visitExpr(v, kind.falsy, ...p);
}
return;
case "match":
if (v.visitMatchExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
for (const arm of kind.arms) {
visitMatchArm(v, arm, ...p);
}
return;
case "loop":
if (v.visitLoopExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.body, ...p);
@ -484,6 +500,18 @@ export function visitExpr<
exhausted(kind);
}
export function visitMatchArm<
P extends PM = [],
>(
v: Visitor<P>,
arm: MatchArm,
...p: P
) {
if (v.visitMatchArm?.(arm, ...p) === "stop") return;
visitPat(v, arm.pat, ...p);
visitExpr(v, arm.expr, ...p);
}
export function visitPat<
P extends PM = [],
>(
@ -504,6 +532,21 @@ export function visitPat<
if (v.visitPathPat?.(pat, kind, ...p) === "stop") return;
visitPath(v, kind.path, ...p);
return;
case "tuple":
if (v.visitTuplePat?.(pat, kind, ...p) === "stop") return;
kind.path && visitPath(v, kind.path, ...p);
for (const pat of kind.elems) {
visitPat(v, pat, ...p);
}
return;
case "struct":
if (v.visitStructPat?.(pat, kind, ...p) === "stop") return;
kind.path && visitPath(v, kind.path, ...p);
for (const field of kind.fields) {
visitIdent(v, field.ident, ...p);
visitPat(v, field.pat, ...p);
}
return;
}
exhausted(kind);
}

View File

@ -88,6 +88,10 @@ export class Checker {
return Ok(undefined);
case "path":
return todo();
case "tuple":
return todo();
case "struct":
return todo();
}
exhausted(k);
}
@ -230,6 +234,7 @@ export class Checker {
case "binary":
case "block":
case "if":
case "match":
case "loop":
case "while":
case "for":
@ -358,6 +363,8 @@ export class Checker {
return this.checkBlock(k.block, expected);
case "if":
return this.checkIfExpr(expr, k, expected);
case "match":
return this.checkMatchExpr(expr, k, expected);
case "loop":
return this.checkLoopExpr(expr, k, expected);
case "while":
@ -760,6 +767,36 @@ export class Checker {
return bothRes.val;
}
private checkMatchExpr(
expr: ast.Expr,
kind: ast.MatchExpr,
expected: Ty,
): Ty {
const ty = this.exprTy(kind.expr);
for (const arm of kind.arms) {
const res = this.assignPatTy(arm.pat, ty);
if (!res.ok) {
this.report(res.val, arm.pat.span);
continue;
}
}
const tyRes = kind.arms
.reduce<Res<Ty, string>>((earlier, arm) => {
if (!earlier.ok) {
return earlier;
}
const exprTy = this.exprTy(arm.expr);
return this.resolveTys(exprTy, earlier.val);
}, Res.Ok(Ty({ tag: "null" })));
if (!tyRes.ok) {
this.report(tyRes.val, expr.span);
this.exprTys.set(expr.id, Ty({ tag: "error" }));
return Ty({ tag: "error" });
}
this.exprTys.set(expr.id, tyRes.val);
return todo();
}
private checkLoopExpr(
expr: ast.Expr,
kind: ast.LoopExpr,
@ -911,15 +948,23 @@ export class Checker {
}
case "let": {
this.checkLetStmt(patRes.kind.stmt, patRes.kind.kind);
const ty = this.patTy(pat);
this.patTys.set(pat.id, ty);
return ty;
return this.patTy(pat);
}
case "match": {
this.checkMatchExpr(
patRes.kind.expr,
patRes.kind.kind,
Ty({ tag: "unknown" }),
);
return this.patTy(pat);
}
}
exhausted(patRes.kind);
return todo();
}
case "path":
case "tuple":
case "struct":
return todo();
}
exhausted(k);

View File

@ -119,9 +119,15 @@ export class Ctx {
return this.maxSeverity >= severityCode.error;
}
private amountReportedImmediately = 0;
public enableReportImmediately = false;
public enableStacktrace = false;
private reportImmediately(rep: Report) {
if (this.amountReportedImmediately >= 2) {
return;
}
this.amountReportedImmediately += 1;
if (this.enableReportImmediately) {
prettyPrintReport(this, rep);
if (this.enableStacktrace) {

View File

@ -79,7 +79,7 @@ export function prettyPrintReport(ctx: Ctx, rep: Report) {
"color: cyan",
);
console.error(
`${rep.span.begin.line.toString().padStart(4, " ")}%c| %c${
`%c${rep.span.begin.line.toString().padStart(4, " ")}| %c${
spanLines[0]
}`,
"color: cyan",
@ -92,6 +92,7 @@ export function prettyPrintReport(ctx: Ctx, rep: Report) {
"color: cyan",
`color: ${sevColor}; font-weight: bold`,
);
console.error(""); // empty newline
return;
}
for (let i = 0; i < spanLines.length; i++) {
@ -125,6 +126,7 @@ export function prettyPrintReport(ctx: Ctx, rep: Report) {
);
}
}
console.error(""); // empty newline
} else if (rep.pos) {
console.error(
` %c|`,
@ -142,6 +144,7 @@ export function prettyPrintReport(ctx: Ctx, rep: Report) {
"color: cyan",
`color: ${sevColor}; font-weight: bold`,
);
console.error(""); // empty newline
}
}

View File

@ -152,6 +152,8 @@ export class FnLowerer {
return;
}
case "path":
case "tuple":
case "struct":
return todo();
}
exhausted(k);
@ -172,6 +174,8 @@ export class FnLowerer {
return;
}
case "path":
case "tuple":
case "struct":
return todo();
}
exhausted(k);
@ -246,6 +250,7 @@ export class FnLowerer {
case "binary":
case "block":
case "if":
case "match":
case "loop":
case "while":
case "for":
@ -292,6 +297,8 @@ export class FnLowerer {
return this.lowerBlock(k.block);
case "if":
return this.lowerIfExpr(expr, k);
case "match":
return this.lowerMatchExpr(expr, k);
case "loop":
return this.lowerLoopExpr(expr, k);
case "while":
@ -507,6 +514,10 @@ export class FnLowerer {
}
}
private lowerMatchExpr(expr: ast.Expr, kind: ast.MatchExpr): RVal {
return todo();
}
private lowerLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): RVal {
const entryBlock = this.currentBlock!;
const loopBlock = this.pushBlock();
@ -681,6 +692,7 @@ export class FnLowerer {
case "binary":
case "block":
case "if":
case "match":
case "loop":
case "while":
case "for":

View File

@ -291,6 +291,7 @@ const keywords = new Set([
"while",
"for",
"in",
"match",
"mod",
"pub",
"use",
@ -300,6 +301,7 @@ const keywords = new Set([
const staticTokens = [
"=",
"==",
"=>",
"<",
"<=",
">",

View File

@ -14,8 +14,10 @@ import {
Ident,
Item,
ItemKind,
MatchArm,
Param,
Pat,
PatField,
Path,
PathSegment,
PatKind,
@ -81,7 +83,9 @@ export class Parser {
this.eatSemicolon();
return expr;
} else if (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
["{", "if", "match", "loop", "while", "for"].some((tt) =>
this.test(tt)
)
) {
const expr = this.parseMultiLineBlockExpr();
return (this.stmt({ tag: "expr", expr }, expr.span));
@ -139,6 +143,9 @@ export class Parser {
if (this.test("if")) {
return this.parseIf();
}
if (this.test("match")) {
return this.parseMatch();
}
if (this.test("loop")) {
return this.parseLoop();
}
@ -204,7 +211,9 @@ export class Parser {
stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon();
} else if (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
["{", "if", "match", "loop", "while", "for"].some((tt) =>
this.test(tt)
)
) {
const expr = this.parseMultiLineBlockExpr();
const span = expr.span;
@ -847,6 +856,53 @@ export class Parser {
return this.expr({ tag: "if", cond, truthy, falsy }, pos);
}
private parseMatch(): Expr {
const begin = this.span();
let end = begin;
this.step();
const expr = this.parseExpr(ExprRestricts.NoStructs);
if (!this.test("{")) {
this.report("expected '{'");
return this.expr({ tag: "error" }, begin);
}
this.step();
const arms: MatchArm[] = [];
if (!this.done() && !this.test("}")) {
let needsComma = false;
while (!this.done() && !this.test("}")) {
if (this.test(",")) {
this.step();
} else if (needsComma) {
this.report("expected ','");
}
if (this.done() || this.test("}")) {
break;
}
const pat = this.parsePat();
if (!this.test("=>")) {
this.report("expected '=>'");
return this.expr({ tag: "error" }, begin);
}
this.step();
needsComma = !["{", "if", "match", "loop", "while", "for"]
.some((tt) => this.test(tt));
const expr = this.parseExpr();
arms.push({
pat,
expr,
span: Span.fromto(pat.span, expr.span),
});
}
}
if (!this.test("}")) {
this.report("expected '}'");
return this.expr({ tag: "error" }, begin);
}
end = this.span();
this.step();
return this.expr({ tag: "match", expr, arms }, Span.fromto(begin, end));
}
private parseBinary(rs: ExprRestricts): Expr {
return this.parseOr(rs);
}
@ -1117,23 +1173,79 @@ export class Parser {
}
private parsePat(): Pat {
const pos = this.span();
const begin = this.span();
if (this.test("ident")) {
const ident = this.parseIdent();
return this.pat({ tag: "bind", ident, mut: false }, ident.span);
const pathRes = this.parsePath();
if (!pathRes.ok) {
return this.pat({ tag: "error" }, begin);
}
const path = pathRes.val;
if (this.test("(")) {
const end: [Span] = [begin];
const elems = this.parseDelimitedList(
this.parsePatRes,
")",
",",
end,
);
return this.pat(
{ tag: "tuple", path, elems },
Span.fromto(begin, end[0]),
);
}
if (this.test("{")) {
const fields = this.parseDelimitedList(
this.parsePatField,
"}",
",",
);
return this.pat(
{ tag: "struct", path: pathRes.val, fields },
pathRes.val.span,
);
}
if (path.segments.length === 1) {
const ident = path.segments[0].ident;
return this.pat({ tag: "bind", ident, mut: false }, ident.span);
} else {
return this.pat({ tag: "path", path }, path.span);
}
}
if (this.test("mut")) {
this.step();
if (!this.test("ident")) {
this.report("expected 'ident'");
return this.pat({ tag: "error" }, pos);
return this.pat({ tag: "error" }, begin);
}
const ident = this.parseIdent();
return this.pat({ tag: "bind", ident, mut: true }, pos);
return this.pat({ tag: "bind", ident, mut: true }, begin);
}
this.report(`expected pattern, got '${this.current().type}'`, pos);
this.report(`expected pattern, got '${this.current().type}'`, begin);
this.step();
return this.pat({ tag: "error" }, pos);
return this.pat({ tag: "error" }, begin);
}
private parsePatRes(): ParseRes<Pat> {
return Res.Ok(this.parsePat());
}
private parsePatField(): ParseRes<PatField> {
if (!this.test("ident")) {
this.report("expected 'ident'");
return Res.Err(undefined);
}
const ident = this.parseIdent();
if (!this.test(":")) {
this.report("expected ':'");
return Res.Err(undefined);
}
this.step();
const pat = this.parsePat();
return Res.Ok({
ident,
pat,
span: Span.fromto(ident.span, pat.span),
});
}
private parseTy(): Ty {

View File

@ -1,11 +1,15 @@
enum S {
A,
B,
A(int),
B { v: int },
}
fn main() {
let v = S::A;
let s = S::A(123);
match s {
S::A(v) => {},
S::B { v: v } => {},
}
}

View File

@ -36,7 +36,8 @@ export type PatResolve = {
export type PatResolveKind =
| { tag: "fn_param"; item: ast.Item; kind: ast.FnItem; paramIdx: number }
| { tag: "let"; stmt: ast.Stmt; kind: ast.LetStmt };
| { tag: "let"; stmt: ast.Stmt; kind: ast.LetStmt }
| { tag: "match"; expr: ast.Expr; kind: ast.MatchExpr };
export const ResolveError = (ident: ast.Ident): Resolve => ({
ident,

View File

@ -121,9 +121,12 @@ export class Resolver implements ast.Visitor {
visitBlock(block: ast.Block): ast.VisitRes {
this.fnBodiesToCheck.push([]);
const outerSyms = this.syms;
this.syms = new LocalSyms(this.syms);
ast.visitStmts(this, block.stmts);
this.popAndVisitFnBodies();
block.expr && ast.visitExpr(this, block.expr);
this.syms = outerSyms;
return "stop";
}
@ -289,6 +292,25 @@ export class Resolver implements ast.Visitor {
return "stop";
}
visitMatchExpr(expr: ast.Expr, kind: ast.MatchExpr): ast.VisitRes {
ast.visitExpr(this, kind.expr);
this.patResolveStack.push({ tag: "match", expr, kind });
for (const arm of kind.arms) {
ast.visitMatchArm(this, arm);
}
this.patResolveStack.pop();
return "stop";
}
visitMatchArm(arm: ast.MatchArm): ast.VisitRes {
const outerSyms = this.syms;
this.syms = new LocalSyms(this.syms);
ast.visitPat(this, arm.pat);
ast.visitExpr(this, arm.expr);
this.syms = outerSyms;
return "stop";
}
visitBindPat(pat: ast.Pat, kind: ast.BindPat): ast.VisitRes {
this.patResols.set(pat.id, { pat, kind: this.patResolveStack.at(-1)! });
const res = this.syms.defVal(kind.ident, {
@ -303,7 +325,30 @@ export class Resolver implements ast.Visitor {
}
visitPathPat(pat: ast.Pat, kind: ast.PathPat): ast.VisitRes {
todo(pat, kind);
this.resolveTyPath(kind.path);
return "stop";
}
visitTuplePat(pat: ast.Pat, kind: ast.TuplePat): ast.VisitRes {
if (!kind.path) {
return todo();
}
this.resolveTyPath(kind.path);
for (const elem of kind.elems) {
ast.visitPat(this, elem);
}
return "stop";
}
visitStructPat(pat: ast.Pat, kind: ast.StructPat): ast.VisitRes {
if (!kind.path) {
return todo();
}
this.resolveTyPath(kind.path);
for (const field of kind.fields) {
ast.visitPat(this, field.pat);
}
return "stop";
}
visitPathTy(ty: ast.Ty, kind: ast.PathTy): ast.VisitRes {

View File

@ -23,7 +23,7 @@ export class HirStringifyer {
case "item":
return this.item(k.item);
case "let": {
return `let ${this.pat(k.pat)}${
return `let ${this.pat(k.pat, d)}${
k.expr && ` = ${this.expr(k.expr, d)}` || ""
};`;
}
@ -43,7 +43,7 @@ export class HirStringifyer {
exhausted(k);
}
public item(item: ast.Item, depth = 0): string {
public item(item: ast.Item, d = 0): string {
const ident = item.ident.text;
const pub = item.pub ? "pub " : "";
const k = item.kind;
@ -51,11 +51,9 @@ export class HirStringifyer {
case "error":
return "<error>;";
case "mod_block":
return `${pub}mod ${ident} ${this.block(k.block, depth)}`;
return `${pub}mod ${ident} ${this.block(k.block, d)}`;
case "mod_file":
return `${pub}mod ${ident} {\n${
this.file(k.ast!, depth + 1)
}\n}`;
return `${pub}mod ${ident} {\n${this.file(k.ast!, d + 1)}\n}`;
case "enum":
return `enum ${ident}: ${
this.ty(this.ch.enumItemTy(item, k))
@ -70,11 +68,11 @@ export class HirStringifyer {
throw new Error();
}
const params = k.params
.map((param) => this.pat(param.pat))
.map((param) => this.pat(param.pat, d))
.join(", ");
return `${pub}fn ${ident}(${params}) -> ${
this.ty(ty.kind.returnTy)
} ${this.block(k.body!, depth)}`;
} ${this.block(k.body!, d)}`;
}
case "use":
return todo();
@ -137,6 +135,17 @@ export class HirStringifyer {
return `if ${this.expr(k.cond, d)} ${this.expr(k.truthy, d)}${
k.falsy && ` else ${this.expr(k.falsy, d)}` || ""
}`;
case "match":
return `match ${this.expr(k.expr, d)} ${
k.arms.length === 0
? "{}"
: `{${
k.arms.map((arm) => this.matchArm(arm, d + 1)).map(
(s) =>
`\n${s},`,
)
}\n${indent(d)}}`
}`;
case "loop":
return `loop ${this.expr(k.body, d)}`;
case "while":
@ -153,7 +162,11 @@ export class HirStringifyer {
exhausted(k);
}
public pat(pat: ast.Pat): string {
public matchArm(arm: ast.MatchArm, d: number): string {
return `${this.pat(arm.pat, d)} => ${this.expr(arm.expr, d + 1)}`;
}
public pat(pat: ast.Pat, d: number): string {
const k = pat.kind;
switch (k.tag) {
case "error":
@ -163,7 +176,23 @@ export class HirStringifyer {
this.ty(this.ch.patTy(pat))
}`;
case "path":
return todo();
return this.path(k.path);
case "tuple":
return `${k.path && this.path(k.path) || ""}(${
k.elems.map((pat) => this.pat(pat, d)).join(", ")
})`;
case "struct":
return `${k.path ? `${this.path(k.path)}` : "struct "} {${
[
k.fields
.map((field) =>
`${indent(d + 1)}${field.ident.text}: ${
this.pat(field.pat, d + 1)
},`
)
.join("\n"),
].map((s) => `\n${s}\n${indent(d)}`)
}}`;
}
exhausted(k);
}