diff --git a/slige/compiler/ast/ast.ts b/slige/compiler/ast/ast.ts index 203e9df..74cd281 100644 --- a/slige/compiler/ast/ast.ts +++ b/slige/compiler/ast/ast.ts @@ -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; diff --git a/slige/compiler/ast/visitor.ts b/slige/compiler/ast/visitor.ts index bbd52b4..af4d371 100644 --- a/slige/compiler/ast/visitor.ts +++ b/slige/compiler/ast/visitor.ts @@ -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

, + 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); } diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index 675baf3..5b45fed 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -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>((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); diff --git a/slige/compiler/common/ctx.ts b/slige/compiler/common/ctx.ts index efc3229..e6ca3d3 100644 --- a/slige/compiler/common/ctx.ts +++ b/slige/compiler/common/ctx.ts @@ -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) { diff --git a/slige/compiler/common/diagnostics.ts b/slige/compiler/common/diagnostics.ts index aa8eb13..9b62ff0 100644 --- a/slige/compiler/common/diagnostics.ts +++ b/slige/compiler/common/diagnostics.ts @@ -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 } } diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 9bb5093..1426a17 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -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": diff --git a/slige/compiler/parse/lexer.ts b/slige/compiler/parse/lexer.ts index b27ae40..2caf84f 100644 --- a/slige/compiler/parse/lexer.ts +++ b/slige/compiler/parse/lexer.ts @@ -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 = [ "=", "==", + "=>", "<", "<=", ">", diff --git a/slige/compiler/parse/parser.ts b/slige/compiler/parse/parser.ts index 450a8af..126d28a 100644 --- a/slige/compiler/parse/parser.ts +++ b/slige/compiler/parse/parser.ts @@ -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 { + return Res.Ok(this.parsePat()); + } + + private parsePatField(): ParseRes { + 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 { diff --git a/slige/compiler/program.slg b/slige/compiler/program.slg index edc2328..0e0e374 100644 --- a/slige/compiler/program.slg +++ b/slige/compiler/program.slg @@ -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 } => {}, + } } diff --git a/slige/compiler/resolve/cx.ts b/slige/compiler/resolve/cx.ts index 31e3a78..7775539 100644 --- a/slige/compiler/resolve/cx.ts +++ b/slige/compiler/resolve/cx.ts @@ -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, diff --git a/slige/compiler/resolve/resolver.ts b/slige/compiler/resolve/resolver.ts index da79ea5..d48d2f3 100644 --- a/slige/compiler/resolve/resolver.ts +++ b/slige/compiler/resolve/resolver.ts @@ -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 { diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts index 9733ec5..c969d9f 100644 --- a/slige/compiler/stringify/hir.ts +++ b/slige/compiler/stringify/hir.ts @@ -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 ";"; 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); }