feat: full match expressions in bootstrap and selfhost
Lower match to if-else for literals, ranges, enum tags, and wildcards. Fix bootstrap literal arms that always matched; port real arm AST/parser and Lcx_LowerMatch to selfhost with last-expression return. Expand pattern_matching example and add parse/use-after-move/double-mut golden diagnostics. Selfhost-loop remains binary-identical.
This commit is contained in:
@@ -401,7 +401,8 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
|||||||
return "0"
|
return "0"
|
||||||
|
|
||||||
of hMatch:
|
of hMatch:
|
||||||
return "0" # TODO: match expression lowering
|
# Match should be desugared in hir_lower to if-else. Fallback: 0.
|
||||||
|
return "0"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return "0"
|
return "0"
|
||||||
|
|||||||
+98
-63
@@ -55,13 +55,85 @@ proc enumHasDataVariants(ctx: var LowerCtx, enumName: string): bool =
|
|||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc litTokenType(tok: Token): Type =
|
||||||
|
case tok.kind
|
||||||
|
of tkIntLiteral: makeInt()
|
||||||
|
of tkFloatLiteral: makeFloat64()
|
||||||
|
of tkStringLiteral: makeStr()
|
||||||
|
of tkCharLiteral: makeChar32()
|
||||||
|
of tkBoolLiteral: makeBool()
|
||||||
|
else: makeUnknown()
|
||||||
|
|
||||||
|
proc patternLiteralNode(pat: Pattern, loc: SourceLocation): HirNode =
|
||||||
|
## Convert a pkLiteral pattern into an hLit node, or nil if not a literal.
|
||||||
|
if pat == nil or pat.kind != pkLiteral:
|
||||||
|
return nil
|
||||||
|
return hirLit(pat.patLit, litTokenType(pat.patLit), loc)
|
||||||
|
|
||||||
|
proc matchAlwaysTrue(loc: SourceLocation): HirNode =
|
||||||
|
hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
|
||||||
|
|
||||||
|
proc matchPatternCond(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
|
||||||
|
subjectEnumName: string, subjectHasData: bool,
|
||||||
|
loc: SourceLocation): HirNode =
|
||||||
|
## Build a boolean condition for a match pattern.
|
||||||
|
## Returns nil for always-true arms (wildcard / catch-all).
|
||||||
|
if pattern == nil:
|
||||||
|
return nil
|
||||||
|
case pattern.kind
|
||||||
|
of pkWildcard, pkIdent:
|
||||||
|
return nil
|
||||||
|
of pkLiteral:
|
||||||
|
let litNode = patternLiteralNode(pattern, loc)
|
||||||
|
if litNode == nil:
|
||||||
|
return nil
|
||||||
|
return hirBinary(tkEq, subject, litNode, makeBool(), loc)
|
||||||
|
of pkRange:
|
||||||
|
let loNode = patternLiteralNode(pattern.patRangeLo, loc)
|
||||||
|
let hiNode = patternLiteralNode(pattern.patRangeHi, loc)
|
||||||
|
if loNode == nil or hiNode == nil:
|
||||||
|
# Non-literal range endpoints — treat as always-true (best-effort)
|
||||||
|
return nil
|
||||||
|
let loOk = hirBinary(tkGe, subject, loNode, makeBool(), loc)
|
||||||
|
let hiOp = if pattern.patRangeInclusive: tkLe else: tkLt
|
||||||
|
let hiOk = hirBinary(hiOp, subject, hiNode, makeBool(), loc)
|
||||||
|
return hirBinary(tkAmpAmp, loOk, hiOk, makeBool(), loc)
|
||||||
|
of pkEnum:
|
||||||
|
let path = pattern.patEnumPath
|
||||||
|
if path.len >= 2:
|
||||||
|
let enumName = path[0]
|
||||||
|
let variantName = path[^1]
|
||||||
|
let tagName = enumName & "_" & variantName
|
||||||
|
if subjectHasData and enumName == subjectEnumName:
|
||||||
|
# Algebraic enum: compare subject.tag
|
||||||
|
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
|
||||||
|
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
|
||||||
|
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
|
||||||
|
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
|
||||||
|
return hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
|
||||||
|
else:
|
||||||
|
# Simple enum or cross-enum match: compare subject directly
|
||||||
|
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName), loc)
|
||||||
|
return hirBinary(tkEq, subject, tagConst, makeBool(), loc)
|
||||||
|
# Single-segment enum path — always-true fallback
|
||||||
|
return nil
|
||||||
|
of pkGuarded:
|
||||||
|
# Guard: inner pattern AND guard expression (lowered later if needed)
|
||||||
|
# For now only support always-true inner + guard as bool expr via lowerExpr path.
|
||||||
|
# Guards are not yet fully lowered here (require expr lowering of guard).
|
||||||
|
return nil
|
||||||
|
else:
|
||||||
|
# Struct/tuple patterns: not yet fully lowered — always-true
|
||||||
|
return nil
|
||||||
|
|
||||||
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
||||||
# Lower match expression to a block with if-else chain.
|
## Lower match expression to a block with if-else chain.
|
||||||
# For now, supports enum tag matching and wildcard/ident fallbacks.
|
## Supports: enum tags, integer/bool/char/string literals, ranges, wildcard/ident.
|
||||||
|
let hasResult = typ != nil and typ.kind != tkVoid and typ.kind != tkUnknown
|
||||||
let resultName = ctx.freshName()
|
let resultName = ctx.freshName()
|
||||||
var stmts: seq[HirNode] = @[]
|
var stmts: seq[HirNode] = @[]
|
||||||
|
|
||||||
# Allocate result variable
|
if hasResult:
|
||||||
stmts.add(hirAlloca(resultName, typ, loc))
|
stmts.add(hirAlloca(resultName, typ, loc))
|
||||||
|
|
||||||
# Determine whether the matched enum has data variants (needs .tag access).
|
# Determine whether the matched enum has data variants (needs .tag access).
|
||||||
@@ -71,81 +143,44 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
|
|||||||
subjectEnumName = subject.typ.name
|
subjectEnumName = subject.typ.name
|
||||||
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||||
|
|
||||||
|
proc makeArmBlock(body: HirNode): HirNode =
|
||||||
|
var armStmts: seq[HirNode] = @[]
|
||||||
|
if hasResult:
|
||||||
|
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
||||||
|
elif body != nil:
|
||||||
|
# Void match: evaluate body for side effects
|
||||||
|
armStmts.add(body)
|
||||||
|
return hirBlock(armStmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
# Build if-else chain from arms (last arm is the outermost else)
|
# Build if-else chain from arms (last arm is the outermost else)
|
||||||
var ifChain: HirNode = nil
|
var ifChain: HirNode = nil
|
||||||
|
|
||||||
for i in countdown(arms.len - 1, 0):
|
for i in countdown(arms.len - 1, 0):
|
||||||
let arm = arms[i]
|
let arm = arms[i]
|
||||||
let body = arm.body
|
let armBlock = makeArmBlock(arm.body)
|
||||||
|
let cond = matchPatternCond(ctx, subject, arm.pattern, subjectEnumName, subjectHasData, loc)
|
||||||
|
|
||||||
case arm.pattern.kind
|
if cond == nil:
|
||||||
of pkEnum:
|
# Always-true arm (wildcard / incomplete pattern)
|
||||||
let path = arm.pattern.patEnumPath
|
if ifChain == nil:
|
||||||
if path.len >= 2:
|
ifChain = armBlock
|
||||||
let enumName = path[0]
|
else:
|
||||||
let variantName = path[^1]
|
ifChain = HirNode(kind: hIf, ifCond: matchAlwaysTrue(loc), ifThen: armBlock,
|
||||||
let tagName = enumName & "_" & variantName
|
ifElse: ifChain, typ: makeVoid(), loc: loc)
|
||||||
|
|
||||||
var cond: HirNode
|
|
||||||
if subjectHasData and enumName == subjectEnumName:
|
|
||||||
# Algebraic enum: compare subject.tag
|
|
||||||
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
|
|
||||||
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
|
|
||||||
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
|
|
||||||
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
|
|
||||||
cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
|
|
||||||
else:
|
else:
|
||||||
# Simple enum or cross-enum match: compare subject directly
|
|
||||||
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName), loc)
|
|
||||||
cond = hirBinary(tkEq, subject, tagConst, makeBool(), loc)
|
|
||||||
|
|
||||||
# body: result = arm_body
|
|
||||||
var armStmts: seq[HirNode] = @[]
|
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
|
||||||
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
|
||||||
|
|
||||||
if ifChain == nil:
|
if ifChain == nil:
|
||||||
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: nil,
|
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: nil,
|
||||||
typ: makeVoid(), loc: loc)
|
typ: makeVoid(), loc: loc)
|
||||||
else:
|
else:
|
||||||
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: ifChain,
|
ifChain = HirNode(kind: hIf, ifCond: cond, ifThen: armBlock, ifElse: ifChain,
|
||||||
typ: makeVoid(), loc: loc)
|
typ: makeVoid(), loc: loc)
|
||||||
else:
|
|
||||||
var armStmts: seq[HirNode] = @[]
|
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
|
||||||
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
|
||||||
if ifChain == nil:
|
|
||||||
ifChain = armBlock
|
|
||||||
else:
|
|
||||||
ifChain = HirNode(kind: hIf,
|
|
||||||
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
|
||||||
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
|
||||||
of pkWildcard, pkIdent:
|
|
||||||
# Default arm — always matches
|
|
||||||
var armStmts: seq[HirNode] = @[]
|
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
|
||||||
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
|
||||||
if ifChain == nil:
|
|
||||||
ifChain = armBlock
|
|
||||||
else:
|
|
||||||
ifChain = HirNode(kind: hIf,
|
|
||||||
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
|
||||||
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
|
||||||
else:
|
|
||||||
var armStmts: seq[HirNode] = @[]
|
|
||||||
armStmts.add(hirStore(hirVar(resultName, typ, loc), body, loc))
|
|
||||||
let armBlock = hirBlock(armStmts, nil, makeVoid(), loc)
|
|
||||||
if ifChain == nil:
|
|
||||||
ifChain = armBlock
|
|
||||||
else:
|
|
||||||
ifChain = HirNode(kind: hIf,
|
|
||||||
ifCond: hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc),
|
|
||||||
ifThen: armBlock, ifElse: ifChain, typ: makeVoid(), loc: loc)
|
|
||||||
|
|
||||||
|
if ifChain != nil:
|
||||||
stmts.add(ifChain)
|
stmts.add(ifChain)
|
||||||
|
|
||||||
# Return the result variable as the block expression
|
if hasResult:
|
||||||
return hirBlock(stmts, hirVar(resultName, typ, loc), typ, loc)
|
return hirBlock(stmts, hirVar(resultName, typ, loc), typ, loc)
|
||||||
|
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||||
|
|
||||||
proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||||
result.module = module
|
result.module = module
|
||||||
@@ -1672,8 +1707,8 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
|||||||
var arms: seq[HirMatchArm] = @[]
|
var arms: seq[HirMatchArm] = @[]
|
||||||
for arm in stmt.stmtMatchArms:
|
for arm in stmt.stmtMatchArms:
|
||||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
||||||
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
# Statement match: lower to if-else chain (void result)
|
||||||
typ: makeVoid(), loc: loc))
|
return ctx.flushPending(lowerMatch(ctx, subject, arms, makeVoid(), loc))
|
||||||
|
|
||||||
of skSwitch:
|
of skSwitch:
|
||||||
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
||||||
|
|||||||
+34
-10
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-15 (обновено вечерта)
|
> **Дата:** 2026-07-16 (вечерта)
|
||||||
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **40+ examples** ✅
|
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **40+ examples**, match expr **bootstrap+selfhost** ✅
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -57,11 +57,11 @@
|
|||||||
|---|--------|------|--------|
|
|---|--------|------|--------|
|
||||||
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ bootstrap + selfhost |
|
| B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ bootstrap + selfhost |
|
||||||
| B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
| B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost |
|
||||||
| B.3 | Match expression до край в C (не `return "0"`) | Expression-context match | ⏳ |
|
| B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost |
|
||||||
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
| B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost |
|
||||||
| B.4b | Closures: loop/return edge cases in body | По-сложни body control-flow | ⏳ |
|
| B.4b | Closures: loop/return edge cases in body | По-сложни body control-flow | ⏳ |
|
||||||
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
| B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ |
|
||||||
| B.6 | Bootstrap ↔ selfhost feature parity | Tuples/closures done; string interp / ops still bootstrap-heavy | 🔄 |
|
| B.6 | Bootstrap ↔ selfhost feature parity | Tuples/closures/**match** done; string interp / some ops still bootstrap-heavy | 🔄 |
|
||||||
|
|
||||||
### C — Gradual Ownership 2.0 (P1)
|
### C — Gradual Ownership 2.0 (P1)
|
||||||
|
|
||||||
@@ -177,13 +177,37 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
3. Example `iter_hof.bux` (sum=15, product=120)
|
3. Example `iter_hof.bux` (sum=15, product=120)
|
||||||
4. Selfhost fix: pointer `->` field access; no bogus bounds check on `arr.data[len]` (Push)
|
4. Selfhost fix: pointer `->` field access; no bogus bounds check on `arr.data[len]` (Push)
|
||||||
|
|
||||||
|
## Сесия 8 (match expression + error goldens)
|
||||||
|
|
||||||
|
1. **Match expression lowering** (B.3): `pkLiteral`, `pkRange` (`a..b` / `a..=b`), enum tags, wildcard
|
||||||
|
- Bugfix: literal arms were always-true (`else` branch) → `match 1 { 1=>10, 2=>20 }` returned 10 for all
|
||||||
|
- `skMatch` now also lowers via `lowerMatch` (void result)
|
||||||
|
2. Expanded `examples/pattern_matching.bux` (enums + classify ranges + string arms)
|
||||||
|
3. **Error golden cases** (`tests/error_golden/`):
|
||||||
|
- `parse_error` — incomplete `let x = ;`
|
||||||
|
- `use_after_move` — `@[Checked]` + `own String`
|
||||||
|
- `double_mut_borrow` — two `&mut` of same var
|
||||||
|
4. `run.sh` normalize: absolute paths in "parse errors in …" lines
|
||||||
|
|
||||||
|
## Сесия 9 (selfhost match — B.6 parity)
|
||||||
|
|
||||||
|
1. **AST:** `MatchArm` linked list; `Pattern.patChild1/2` for ranges; `Expr.matchArms`
|
||||||
|
2. **Parser:** full `parserParsePattern` / `parserParseMatchExpr` (was skip-arms stub)
|
||||||
|
- literals, `_`, ident, `Enum::Variant` / `Enum::Variant(...)`, ranges
|
||||||
|
- statement `match` → `skExpr` + `ekMatch`
|
||||||
|
3. **Sema:** type-check subject + arms; propagate first-arm type to `expr.refType`
|
||||||
|
4. **HIR lower:** `Lcx_LowerMatch` → alloca result + if-else stores (enum `.tag` / simple / literal / range)
|
||||||
|
5. **Last-expr return:** `Lcx_LowerBlock` converts final `skExpr` into `return` (needed for `func F() -> T { match ... }`)
|
||||||
|
6. Verified: `pattern_matching` via **buxc2**; simple enum + ranges; **selfhost-loop IDENTICAL ✓**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Утре — предложени следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. **Match expression** lowering докрай (B.3)
|
1. **LSP hover / go-to-def** (над текущите diagnostics)
|
||||||
2. **Още error golden cases** (parse error, use-after-move)
|
2. **Generic Iter map** (не само int), ако monomorphization с `func` params е стабилна
|
||||||
3. **LSP hover / go-to-def** (над текущите diagnostics)
|
3. **Closures B.4b** — loop/return edge cases in closure body
|
||||||
4. **Generic Iter map** (не само int), ако monomorphization с `func` params е стабилна
|
4. Struct/tuple patterns + pattern bindings (`Some(value)` binds `value`)
|
||||||
5. **Selfhost-loop** re-check след fat-func + tuples промените
|
5. `let x = match ...` multi-stmt yield (beyond tail-position return)
|
||||||
|
6. String interpolation / remaining bootstrap-only features (B.6)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Pattern Matching - Match expressions with algebraic enums
|
// Pattern Matching — enum tags, literals, ranges, wildcard
|
||||||
import Std::Io::{PrintLine, PrintInt};
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
|
|
||||||
|
|
||||||
@@ -14,6 +14,26 @@ func GetValue(opt: Option) -> int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Classify(n: int) -> int {
|
||||||
|
// literal arms + exclusive range + inclusive range + wildcard
|
||||||
|
match n {
|
||||||
|
0 => 100,
|
||||||
|
1 => 101,
|
||||||
|
2..5 => 200,
|
||||||
|
6..=10 => 300,
|
||||||
|
_ => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColorName(c: int) -> String {
|
||||||
|
match c {
|
||||||
|
1 => "red",
|
||||||
|
2 => "green",
|
||||||
|
3 => "blue",
|
||||||
|
_ => "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let opt1: Option = Option { tag: Option_Some };
|
let opt1: Option = Option { tag: Option_Some };
|
||||||
opt1.data.Some_0 = 42;
|
opt1.data.Some_0 = 42;
|
||||||
@@ -28,5 +48,18 @@ func Main() -> int {
|
|||||||
PrintInt(GetValue(opt2));
|
PrintInt(GetValue(opt2));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
|
PrintLine("classify:");
|
||||||
|
PrintInt(Classify(0));
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(Classify(3));
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(Classify(8));
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(Classify(99));
|
||||||
|
PrintLine("");
|
||||||
|
|
||||||
|
PrintLine("color:");
|
||||||
|
PrintLine(ColorName(2));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -75,8 +75,19 @@ struct Pattern {
|
|||||||
patLitKind: int, // for pkLiteral (token kind)
|
patLitKind: int, // for pkLiteral (token kind)
|
||||||
patLitText: String, // for pkLiteral (token text)
|
patLitText: String, // for pkLiteral (token text)
|
||||||
patRangeInclusive: bool, // for pkRange
|
patRangeInclusive: bool, // for pkRange
|
||||||
patEnumPath: String, // for pkEnum (path joined)
|
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
||||||
patStructName: String, // for pkStruct
|
patStructName: String, // for pkStruct
|
||||||
|
patChild1: *Pattern, // range lo / nested
|
||||||
|
patChild2: *Pattern, // range hi / nested
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match arm: pattern => body
|
||||||
|
struct MatchArm {
|
||||||
|
line: uint32,
|
||||||
|
column: uint32,
|
||||||
|
pattern: *Pattern,
|
||||||
|
body: *Expr,
|
||||||
|
next: *MatchArm,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -165,6 +176,9 @@ struct Expr {
|
|||||||
// Call arguments (linked list for multi-arg support)
|
// Call arguments (linked list for multi-arg support)
|
||||||
callArgs: *ExprList,
|
callArgs: *ExprList,
|
||||||
callArgCount: int,
|
callArgCount: int,
|
||||||
|
// Match arms (for ekMatch)
|
||||||
|
matchArms: *MatchArm,
|
||||||
|
matchArmCount: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+299
-3
@@ -406,6 +406,251 @@ func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Match lowering helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
|
||||||
|
if String_Eq(enumName, "") { return false; }
|
||||||
|
let sym: Symbol = Scope_Lookup(ctx.scope, enumName);
|
||||||
|
if sym.decl == null as *Decl || sym.decl.kind != dkEnum { return false; }
|
||||||
|
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { return true; }
|
||||||
|
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
|
||||||
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
n.kind = hLit;
|
||||||
|
n.line = line;
|
||||||
|
n.column = col;
|
||||||
|
n.intValue = litKind;
|
||||||
|
n.strValue = litText;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
|
||||||
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
n.kind = hBinary;
|
||||||
|
n.line = line;
|
||||||
|
n.column = col;
|
||||||
|
n.intValue = op;
|
||||||
|
n.child1 = left;
|
||||||
|
n.child2 = right;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
|
||||||
|
return Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build condition HirNode for a match pattern. Returns null = always-true.
|
||||||
|
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
||||||
|
subjectEnumName: String, subjectHasData: bool,
|
||||||
|
line: uint32, col: uint32) -> *HirNode {
|
||||||
|
if pat == null as *Pattern { return null as *HirNode; }
|
||||||
|
let kind: int = pat.kind;
|
||||||
|
|
||||||
|
if kind == pkWildcard || kind == pkIdent {
|
||||||
|
return null as *HirNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == pkLiteral {
|
||||||
|
let lit: *HirNode = Lcx_MakeLitHir(pat.patLitKind, pat.patLitText, line, col);
|
||||||
|
return Lcx_MakeBinHir(tkEq, subject, lit, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == pkRange {
|
||||||
|
let loPat: *Pattern = pat.patChild1;
|
||||||
|
let hiPat: *Pattern = pat.patChild2;
|
||||||
|
if loPat == null as *Pattern || hiPat == null as *Pattern { return null as *HirNode; }
|
||||||
|
if loPat.kind != pkLiteral || hiPat.kind != pkLiteral { return null as *HirNode; }
|
||||||
|
let lo: *HirNode = Lcx_MakeLitHir(loPat.patLitKind, loPat.patLitText, line, col);
|
||||||
|
let hi: *HirNode = Lcx_MakeLitHir(hiPat.patLitKind, hiPat.patLitText, line, col);
|
||||||
|
let loOk: *HirNode = Lcx_MakeBinHir(tkGe, subject, lo, line, col);
|
||||||
|
var hiOp: int = tkLt;
|
||||||
|
if pat.patRangeInclusive { hiOp = tkLe; }
|
||||||
|
let hiOk: *HirNode = Lcx_MakeBinHir(hiOp, subject, hi, line, col);
|
||||||
|
return Lcx_MakeBinHir(tkAmpAmp, loOk, hiOk, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == pkEnum {
|
||||||
|
let path: String = pat.patEnumPath;
|
||||||
|
// path is "Enum::Variant" or just "Variant"
|
||||||
|
var enumName: String = "";
|
||||||
|
var variantName: String = path;
|
||||||
|
if String_Contains(path, "::") {
|
||||||
|
enumName = String_SplitPart(path, "::", 0);
|
||||||
|
variantName = String_SplitPart(path, "::", 1);
|
||||||
|
}
|
||||||
|
let tagName: String = String_Concat(String_Concat(enumName, "_"), variantName);
|
||||||
|
// Prefer subject enum name when path is full
|
||||||
|
var useEnum: String = enumName;
|
||||||
|
if String_Eq(useEnum, "") { useEnum = subjectEnumName; }
|
||||||
|
let fullTag: String = String_Concat(String_Concat(useEnum, "_"), variantName);
|
||||||
|
|
||||||
|
if subjectHasData && (String_Eq(enumName, subjectEnumName) || String_Eq(enumName, "")) {
|
||||||
|
// Algebraic: subject.tag == Enum_Variant
|
||||||
|
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tagPtr.kind = hFieldPtr;
|
||||||
|
tagPtr.line = line;
|
||||||
|
tagPtr.column = col;
|
||||||
|
tagPtr.strValue = "tag";
|
||||||
|
tagPtr.child1 = subject;
|
||||||
|
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tagLoad.kind = hLoad;
|
||||||
|
tagLoad.line = line;
|
||||||
|
tagLoad.column = col;
|
||||||
|
tagLoad.child1 = tagPtr;
|
||||||
|
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tagConst.kind = hVar;
|
||||||
|
tagConst.line = line;
|
||||||
|
tagConst.column = col;
|
||||||
|
tagConst.strValue = fullTag;
|
||||||
|
return Lcx_MakeBinHir(tkEq, tagLoad, tagConst, line, col);
|
||||||
|
} else {
|
||||||
|
// Simple enum: subject == Enum_Variant
|
||||||
|
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
tagConst.kind = hVar;
|
||||||
|
tagConst.line = line;
|
||||||
|
tagConst.column = col;
|
||||||
|
if !String_Eq(enumName, "") {
|
||||||
|
tagConst.strValue = fullTag;
|
||||||
|
} else {
|
||||||
|
tagConst.strValue = tagName;
|
||||||
|
}
|
||||||
|
return Lcx_MakeBinHir(tkEq, subject, tagConst, line, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null as *HirNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
|
||||||
|
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||||
|
let line: uint32 = expr.line;
|
||||||
|
let col: uint32 = expr.column;
|
||||||
|
let subject: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
|
|
||||||
|
ctx.varCounter = ctx.varCounter + 1;
|
||||||
|
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
|
||||||
|
|
||||||
|
// Result type from sema refType, default int
|
||||||
|
var typeName: String = "int";
|
||||||
|
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
|
||||||
|
typeName = expr.refType.typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subject enum info
|
||||||
|
var subjectEnumName: String = "";
|
||||||
|
var subjectHasData: bool = false;
|
||||||
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
||||||
|
if expr.child1.refType.kind == tekNamed {
|
||||||
|
subjectEnumName = expr.child1.refType.typeName;
|
||||||
|
subjectHasData = Lcx_EnumHasData(ctx, subjectEnumName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alloca result
|
||||||
|
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
allocaNode.kind = hAlloca;
|
||||||
|
allocaNode.line = line;
|
||||||
|
allocaNode.column = col;
|
||||||
|
allocaNode.strValue = resultName;
|
||||||
|
allocaNode.typeName = typeName;
|
||||||
|
|
||||||
|
// Collect arms into a temporary array via reverse build of if-chain
|
||||||
|
// First count arms and build from last to first
|
||||||
|
var armCount: int = 0;
|
||||||
|
var arm: *MatchArm = expr.matchArms;
|
||||||
|
while arm != null as *MatchArm {
|
||||||
|
armCount = armCount + 1;
|
||||||
|
arm = arm.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build if-else from last arm to first
|
||||||
|
var ifChain: *HirNode = null as *HirNode;
|
||||||
|
var ai: int = armCount - 1;
|
||||||
|
while ai >= 0 {
|
||||||
|
// Find arm at index ai
|
||||||
|
var cur: *MatchArm = expr.matchArms;
|
||||||
|
var j: int = 0;
|
||||||
|
while j < ai && cur != null as *MatchArm {
|
||||||
|
cur = cur.next;
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
if cur == null as *MatchArm {
|
||||||
|
ai = ai - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
||||||
|
// result = body
|
||||||
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
storeNode.kind = hStore;
|
||||||
|
storeNode.line = line;
|
||||||
|
storeNode.column = col;
|
||||||
|
let resVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
resVar.kind = hVar;
|
||||||
|
resVar.strValue = resultName;
|
||||||
|
storeNode.child1 = resVar;
|
||||||
|
storeNode.child2 = bodyHir;
|
||||||
|
|
||||||
|
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
armBlock.kind = hBlock;
|
||||||
|
armBlock.line = line;
|
||||||
|
armBlock.column = col;
|
||||||
|
armBlock.child1 = storeNode;
|
||||||
|
|
||||||
|
let cond: *HirNode = Lcx_PatternCond(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
||||||
|
if cond == null as *HirNode {
|
||||||
|
// Always-true arm
|
||||||
|
if ifChain == null as *HirNode {
|
||||||
|
ifChain = armBlock;
|
||||||
|
} else {
|
||||||
|
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
ifNode.kind = hIf;
|
||||||
|
ifNode.line = line;
|
||||||
|
ifNode.column = col;
|
||||||
|
ifNode.child1 = Lcx_MakeTrueHir(line, col);
|
||||||
|
ifNode.child2 = armBlock;
|
||||||
|
ifNode.extraData = ifChain as *void;
|
||||||
|
ifChain = ifNode;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
ifNode.kind = hIf;
|
||||||
|
ifNode.line = line;
|
||||||
|
ifNode.column = col;
|
||||||
|
ifNode.child1 = cond;
|
||||||
|
ifNode.child2 = armBlock;
|
||||||
|
ifNode.extraData = ifChain as *void;
|
||||||
|
ifChain = ifNode;
|
||||||
|
}
|
||||||
|
ai = ai - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain: alloca → ifChain
|
||||||
|
allocaNode.child3 = ifChain;
|
||||||
|
|
||||||
|
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
block.kind = hBlock;
|
||||||
|
block.line = line;
|
||||||
|
block.column = col;
|
||||||
|
block.child1 = allocaNode;
|
||||||
|
// Yield marker: strValue = result var name for last-expr return / let init
|
||||||
|
block.strValue = resultName;
|
||||||
|
block.typeName = typeName;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Expression lowering
|
// Expression lowering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -422,6 +667,11 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
|
|
||||||
let kind: int = expr.kind;
|
let kind: int = expr.kind;
|
||||||
|
|
||||||
|
// Match expression
|
||||||
|
if kind == ekMatch {
|
||||||
|
return Lcx_LowerMatch(ctx, expr);
|
||||||
|
}
|
||||||
|
|
||||||
// Literal
|
// Literal
|
||||||
if kind == ekLiteral {
|
if kind == ekLiteral {
|
||||||
n.kind = hLit;
|
n.kind = hLit;
|
||||||
@@ -2286,8 +2536,54 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
var firstNode: *HirNode = null as *HirNode;
|
var firstNode: *HirNode = null as *HirNode;
|
||||||
var prevNode: *HirNode = null as *HirNode;
|
var prevNode: *HirNode = null as *HirNode;
|
||||||
var stmt: *Stmt = block.firstStmt;
|
var stmt: *Stmt = block.firstStmt;
|
||||||
|
var lastStmt: *Stmt = null as *Stmt;
|
||||||
while stmt != null as *Stmt {
|
while stmt != null as *Stmt {
|
||||||
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
|
lastStmt = stmt;
|
||||||
|
let isLast: bool = stmt.nextStmt == null as *Stmt;
|
||||||
|
// Last expression statement in a non-void function → implicit return
|
||||||
|
// (supports `func F() -> T { match ... }` / bare value as body)
|
||||||
|
var lowered: *HirNode = null as *HirNode;
|
||||||
|
if isLast && retTypeKind != tyVoid && retTypeKind != tyUnknown && retTypeKind >= 0
|
||||||
|
&& stmt.kind == skExpr && stmt.child1 != null as *Expr {
|
||||||
|
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
||||||
|
if exprNode != null as *HirNode {
|
||||||
|
// Match (and similar multi-stmt yields): hBlock with strValue = result var
|
||||||
|
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
|
||||||
|
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
retVar.kind = hVar;
|
||||||
|
retVar.strValue = exprNode.strValue;
|
||||||
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
retNode.kind = hReturn;
|
||||||
|
retNode.line = stmt.line;
|
||||||
|
retNode.column = stmt.column;
|
||||||
|
retNode.child1 = retVar;
|
||||||
|
// Append return at end of match block's child3 chain
|
||||||
|
var lastInBlock: *HirNode = exprNode.child1;
|
||||||
|
if lastInBlock == null as *HirNode {
|
||||||
|
exprNode.child1 = retNode;
|
||||||
|
} else {
|
||||||
|
while lastInBlock.child3 != null as *HirNode {
|
||||||
|
lastInBlock = lastInBlock.child3;
|
||||||
|
}
|
||||||
|
lastInBlock.child3 = retNode;
|
||||||
|
}
|
||||||
|
// Clear yield marker so emit treats it as plain block
|
||||||
|
exprNode.strValue = "";
|
||||||
|
lowered = exprNode;
|
||||||
|
} else if exprNode.kind == hReturn {
|
||||||
|
lowered = exprNode;
|
||||||
|
} else {
|
||||||
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
|
retNode.kind = hReturn;
|
||||||
|
retNode.line = stmt.line;
|
||||||
|
retNode.column = stmt.column;
|
||||||
|
retNode.child1 = exprNode;
|
||||||
|
lowered = retNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lowered = Lcx_LowerStmt(ctx, stmt);
|
||||||
|
}
|
||||||
if lowered != null as *HirNode {
|
if lowered != null as *HirNode {
|
||||||
if firstNode == null as *HirNode {
|
if firstNode == null as *HirNode {
|
||||||
firstNode = lowered;
|
firstNode = lowered;
|
||||||
@@ -2430,11 +2726,11 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
|||||||
pi = pi + 1;
|
pi = pi + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lower body with function scope active
|
// Lower body with function scope active (pass ret kind for last-expr return)
|
||||||
let prevScope: *Scope = ctx.scope;
|
let prevScope: *Scope = ctx.scope;
|
||||||
ctx.scope = &funcScope;
|
ctx.scope = &funcScope;
|
||||||
if decl.refBody != null as *Block {
|
if decl.refBody != null as *Block {
|
||||||
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
|
f.body = Lcx_LowerBlock(ctx, decl.refBody, f.retTypeKind);
|
||||||
} else {
|
} else {
|
||||||
f.body = null as *HirNode;
|
f.body = null as *HirNode;
|
||||||
}
|
}
|
||||||
|
|||||||
+180
-19
@@ -13,6 +13,8 @@ func parserParsePrimary(p: *Parser) -> *Expr;
|
|||||||
func parserParsePostfixExpr(p: *Parser) -> *Expr;
|
func parserParsePostfixExpr(p: *Parser) -> *Expr;
|
||||||
func parserParseUnary(p: *Parser) -> *Expr;
|
func parserParseUnary(p: *Parser) -> *Expr;
|
||||||
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
|
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
|
||||||
|
func parserParsePattern(p: *Parser) -> *Pattern;
|
||||||
|
func parserParseMatchExpr(p: *Parser) -> *Expr;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Parser state
|
// Parser state
|
||||||
@@ -346,6 +348,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
|
|||||||
e.structFieldCount = 0;
|
e.structFieldCount = 0;
|
||||||
e.callArgs = null as *ExprList;
|
e.callArgs = null as *ExprList;
|
||||||
e.callArgCount = 0;
|
e.callArgCount = 0;
|
||||||
|
e.matchArms = null as *MatchArm;
|
||||||
|
e.matchArmCount = 0;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,10 +494,182 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// match expr { arms }
|
||||||
|
if kind == tkMatch {
|
||||||
|
return parserParseMatchExpr(p);
|
||||||
|
}
|
||||||
|
|
||||||
parserEmitDiag(p, line, col, "expected expression");
|
parserEmitDiag(p, line, col, "expected expression");
|
||||||
return parserMakeExpr(ekLiteral, line, col);
|
return parserMakeExpr(ekLiteral, line, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Patterns (for match arms)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
|
||||||
|
let pat: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
|
||||||
|
pat.kind = kind;
|
||||||
|
pat.line = line;
|
||||||
|
pat.column = col;
|
||||||
|
pat.patIdent = "";
|
||||||
|
pat.patLitKind = 0;
|
||||||
|
pat.patLitText = "";
|
||||||
|
pat.patRangeInclusive = false;
|
||||||
|
pat.patEnumPath = "";
|
||||||
|
pat.patStructName = "";
|
||||||
|
pat.patChild1 = null as *Pattern;
|
||||||
|
pat.patChild2 = null as *Pattern;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
|
||||||
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||||
|
let tok: LexToken = parserCurToken(p);
|
||||||
|
let line: uint32 = tok.line;
|
||||||
|
let col: uint32 = tok.column;
|
||||||
|
let kind: int = tok.kind;
|
||||||
|
|
||||||
|
// _
|
||||||
|
if kind == tkUnderscore {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
return parserMakePattern(pkWildcard, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|
||||||
|
|| kind == tkCharLiteral || kind == tkBoolLiteral {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
|
||||||
|
pat.patLitKind = kind;
|
||||||
|
pat.patLitText = tok.text;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ident / enum path / true|false as names
|
||||||
|
if kind == tkIdent {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let name: String = tok.text;
|
||||||
|
if String_Eq(name, "true") || String_Eq(name, "false") {
|
||||||
|
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
|
||||||
|
pat.patLitKind = tkBoolLiteral;
|
||||||
|
pat.patLitText = name;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
// Enum path: Enum::Variant or Enum::Variant(...)
|
||||||
|
if parserCheck(p, tkColonColon) {
|
||||||
|
var path: String = name;
|
||||||
|
while parserCheck(p, tkColonColon) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let seg: LexToken = parserExpectIdentOrKeyword(p, "expected identifier in pattern path");
|
||||||
|
path = String_Concat(path, "::");
|
||||||
|
path = String_Concat(path, seg.text);
|
||||||
|
}
|
||||||
|
// Optional (args) for algebraic variants — parse and ignore bindings for now
|
||||||
|
if parserCheck(p, tkLParen) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
|
discard parserParsePattern(p);
|
||||||
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||||
|
else { break; }
|
||||||
|
}
|
||||||
|
discard parserExpect(p, tkRParen, "expected ')' to close enum pattern");
|
||||||
|
}
|
||||||
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
||||||
|
pat.patEnumPath = path;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
// Bare name with (args): Variant(...) treated as single-segment enum
|
||||||
|
if parserCheck(p, tkLParen) {
|
||||||
|
discard parserAdvance(p);
|
||||||
|
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
|
discard parserParsePattern(p);
|
||||||
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||||
|
else { break; }
|
||||||
|
}
|
||||||
|
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
|
||||||
|
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
|
||||||
|
pat.patEnumPath = name;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
// Ident binding / catch-all name
|
||||||
|
let pat: *Pattern = parserMakePattern(pkIdent, line, col);
|
||||||
|
pat.patIdent = name;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
parserEmitDiag(p, line, col, "expected pattern");
|
||||||
|
return parserMakePattern(pkWildcard, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserParsePattern(p: *Parser) -> *Pattern {
|
||||||
|
let locTok: LexToken = parserCurToken(p);
|
||||||
|
let line: uint32 = locTok.line;
|
||||||
|
let col: uint32 = locTok.column;
|
||||||
|
let left: *Pattern = parserParsePrimaryPattern(p);
|
||||||
|
// Range pattern: lo..hi or lo..=hi
|
||||||
|
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
|
||||||
|
let inclusive: bool = parserCheck(p, tkDotDotEqual);
|
||||||
|
discard parserAdvance(p);
|
||||||
|
let right: *Pattern = parserParsePrimaryPattern(p);
|
||||||
|
let pat: *Pattern = parserMakePattern(pkRange, line, col);
|
||||||
|
pat.patRangeInclusive = inclusive;
|
||||||
|
pat.patChild1 = left;
|
||||||
|
pat.patChild2 = right;
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// match subject { pat => body, ... }
|
||||||
|
func parserParseMatchExpr(p: *Parser) -> *Expr {
|
||||||
|
let line: uint32 = parserCurToken(p).line;
|
||||||
|
let col: uint32 = parserCurToken(p).column;
|
||||||
|
discard parserAdvance(p); // match
|
||||||
|
p.structInitAllowed = false;
|
||||||
|
let subject: *Expr = parserParseExpr(p);
|
||||||
|
p.structInitAllowed = true;
|
||||||
|
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
|
||||||
|
|
||||||
|
var firstArm: *MatchArm = null as *MatchArm;
|
||||||
|
var lastArm: *MatchArm = null as *MatchArm;
|
||||||
|
var armCount: int = 0;
|
||||||
|
|
||||||
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
|
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
|
||||||
|
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
|
||||||
|
let armLine: uint32 = parserCurToken(p).line;
|
||||||
|
let armCol: uint32 = parserCurToken(p).column;
|
||||||
|
let mp: int = p.pos;
|
||||||
|
let pat: *Pattern = parserParsePattern(p);
|
||||||
|
discard parserExpect(p, tkFatArrow, "expected '=>' in match arm");
|
||||||
|
let body: *Expr = parserParseExpr(p);
|
||||||
|
let arm: *MatchArm = bux_alloc(sizeof(MatchArm)) as *MatchArm;
|
||||||
|
arm.line = armLine;
|
||||||
|
arm.column = armCol;
|
||||||
|
arm.pattern = pat;
|
||||||
|
arm.body = body;
|
||||||
|
arm.next = null as *MatchArm;
|
||||||
|
if firstArm == null as *MatchArm {
|
||||||
|
firstArm = arm;
|
||||||
|
lastArm = arm;
|
||||||
|
} else {
|
||||||
|
lastArm.next = arm;
|
||||||
|
lastArm = arm;
|
||||||
|
}
|
||||||
|
armCount = armCount + 1;
|
||||||
|
if parserCheck(p, tkComma) { discard parserAdvance(p); }
|
||||||
|
if p.pos == mp { discard parserAdvance(p); }
|
||||||
|
}
|
||||||
|
discard parserExpect(p, tkRBrace, "expected '}' to close match");
|
||||||
|
|
||||||
|
let e: *Expr = parserMakeExpr(ekMatch, line, col);
|
||||||
|
e.child1 = subject;
|
||||||
|
e.matchArms = firstArm;
|
||||||
|
e.matchArmCount = armCount;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Closure: |params| -> Ret { body }
|
// Closure: |params| -> Ret { body }
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1117,29 +1293,14 @@ func parserParseStmt(p: *Parser) -> *Stmt {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// match
|
// match — expression statement (arms fully parsed)
|
||||||
if kind == tkMatch {
|
if kind == tkMatch {
|
||||||
discard parserAdvance(p);
|
let matchExpr: *Expr = parserParseMatchExpr(p);
|
||||||
p.structInitAllowed = false;
|
|
||||||
let subject: *Expr = parserParseExpr(p);
|
|
||||||
p.structInitAllowed = true;
|
|
||||||
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
|
|
||||||
// Skip match body (simplified — just parse arms as empty)
|
|
||||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
|
||||||
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
|
||||||
let mp: int = p.pos;
|
|
||||||
discard parserParseExpr(p); // pattern
|
|
||||||
if parserMatch(p, tkFatArrow) {
|
|
||||||
discard parserParseExpr(p); // body
|
|
||||||
}
|
|
||||||
if p.pos == mp { discard parserAdvance(p); }
|
|
||||||
}
|
|
||||||
discard parserExpect(p, tkRBrace, "expected '}' to close match");
|
|
||||||
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
|
||||||
s.kind = skMatch;
|
s.kind = skExpr;
|
||||||
s.line = line;
|
s.line = line;
|
||||||
s.column = col;
|
s.column = col;
|
||||||
s.child1 = subject;
|
s.child1 = matchExpr;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-1
@@ -783,6 +783,46 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
return tyVoid;
|
return tyVoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match expression
|
||||||
|
if kind == ekMatch {
|
||||||
|
discard Sema_CheckExpr(sema, expr.child1); // subject
|
||||||
|
var armType: int = tyUnknown;
|
||||||
|
var first: bool = true;
|
||||||
|
var arm: *MatchArm = expr.matchArms;
|
||||||
|
while arm != null as *MatchArm {
|
||||||
|
let bt: int = Sema_CheckExpr(sema, arm.body);
|
||||||
|
if first {
|
||||||
|
armType = bt;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
arm = arm.next;
|
||||||
|
}
|
||||||
|
// Propagate type of first arm for codegen (result temp type)
|
||||||
|
if armType == tyStr {
|
||||||
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te.kind = tekNamed;
|
||||||
|
te.typeName = "String";
|
||||||
|
expr.refType = te;
|
||||||
|
} else if armType == tyBool {
|
||||||
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te.kind = tekNamed;
|
||||||
|
te.typeName = "bool";
|
||||||
|
expr.refType = te;
|
||||||
|
} else if armType == tyInt || armType == tyUnknown {
|
||||||
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te.kind = tekNamed;
|
||||||
|
te.typeName = "int";
|
||||||
|
expr.refType = te;
|
||||||
|
if armType == tyUnknown { armType = tyInt; }
|
||||||
|
} else {
|
||||||
|
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||||
|
te.kind = tekNamed;
|
||||||
|
te.typeName = "int";
|
||||||
|
expr.refType = te;
|
||||||
|
}
|
||||||
|
return armType;
|
||||||
|
}
|
||||||
|
|
||||||
// Closure: |params| -> Ret { body }
|
// Closure: |params| -> Ret { body }
|
||||||
if kind == ekClosure {
|
if kind == ekClosure {
|
||||||
let savedRetType: int = sema.currentRetType;
|
let savedRetType: int = sema.currentRetType;
|
||||||
@@ -1052,7 +1092,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match
|
// Match (legacy skMatch — prefer ekMatch via skExpr)
|
||||||
if kind == skMatch {
|
if kind == skMatch {
|
||||||
discard Sema_CheckExpr(sema, stmt.child1);
|
discard Sema_CheckExpr(sema, stmt.child1);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "double_mut_borrow"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: mutable borrow conflict: arguments 1 and 2 both borrow '&mut x'
|
||||||
|
--> FILE:10:11
|
||||||
|
|
|
||||||
|
10 | TwoMut(&x, &x);
|
||||||
|
| ^
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@[Checked]
|
||||||
|
func TwoMut(a: &mut int, b: &mut int) {
|
||||||
|
*a = 1;
|
||||||
|
*b = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
var x: int = 0;
|
||||||
|
TwoMut(&x, &x);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "parse_error"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: parse errors in FILE
|
||||||
|
error: expected expression
|
||||||
|
--> FILE:2:13
|
||||||
|
|
|
||||||
|
2 | let x = ;
|
||||||
|
| ^
|
||||||
|
= help: the previous statement may be incomplete (missing value or ';')
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
func Main() -> int {
|
||||||
|
let x = ;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -16,10 +16,13 @@ passed=0
|
|||||||
failed=0
|
failed=0
|
||||||
|
|
||||||
normalize() {
|
normalize() {
|
||||||
# Replace absolute path prefix with FILE, drop trailing blank lines
|
# Replace absolute paths with FILE (location markers and "in <path>" lines).
|
||||||
|
# Drop trailing blank lines.
|
||||||
sed -E \
|
sed -E \
|
||||||
-e "s|$DIR/[^:]+:|FILE:|g" \
|
-e "s|$DIR/[^:[:space:]]+:|FILE:|g" \
|
||||||
-e "s|$ROOT/[^:]+:|FILE:|g" \
|
-e "s|$ROOT/[^:[:space:]]+:|FILE:|g" \
|
||||||
|
-e "s|$DIR/[^:[:space:]]+|FILE|g" \
|
||||||
|
-e "s|$ROOT/[^:[:space:]]+|FILE|g" \
|
||||||
-e "s|//+|/|g" \
|
-e "s|//+|/|g" \
|
||||||
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
|
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "use_after_move"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
error: type errors in project
|
||||||
|
error: use of moved value 'a'
|
||||||
|
--> FILE:10:25
|
||||||
|
|
|
||||||
|
10 | let c: own String = a;
|
||||||
|
| ^
|
||||||
|
= help: the value was moved; clone it or restructure ownership
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
@[Checked]
|
||||||
|
func Take(s: own String) -> own String {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@[Checked]
|
||||||
|
func Main() -> int {
|
||||||
|
let a: own String = "hello";
|
||||||
|
let b: own String = Take(a);
|
||||||
|
let c: own String = a;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user