From f9185c96b2b5be8daaff0974d9b7c2bae70771b4 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sat, 18 Jul 2026 01:24:48 +0300 Subject: [PATCH] feat: multi-stmt match arms and block-as-expression Blocks can yield a value: last expression statement is the result. Match arms accept block bodies, so multi-statement arms work: match n { 1 => { let a = 10; a + 1 }, _ => 0 } Bootstrap: lowerBlock(asExpr) lifts the trailing skExpr. Selfhost: parse {...} as ekBlock, emit __blk_N yield temps (retTypeKind -2), and treat only non-empty strValue as match/block yield. Nested enum/struct pattern bindings recurse in both compilers. Example: match_block.bux. Selfhost-loop remains binary-identical. --- Makefile | 2 +- bootstrap/hir_lower.nim | 62 +++++++++----- docs/LanguageRef.md | 15 ++++ docs/QUALITY_PLAN.md | 20 ++++- examples/match_block.bux | 81 +++++++++++++++++++ src/hir_lower.bux | 171 +++++++++++++++++++++++++++++---------- src/parser.bux | 8 ++ src/sema.bux | 76 ++++++++++------- 8 files changed, 337 insertions(+), 98 deletions(-) create mode 100644 examples/match_block.bux diff --git a/Makefile b/Makefile index f4d7c48..3ceaaed 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SRC := bootstrap/main.nim OUT := buxc BUILD_DIR := build -EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat +EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat match_block .PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index c902111..b07a54d 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -185,19 +185,23 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern, typ: makePointer(variantStructTy), loc: loc) payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc) for i, arg in pattern.patEnumArgs: - if arg == nil or arg.kind != pkIdent: + if arg == nil: continue let fieldName = variantName & "_" & $i let fieldTy = if i < fieldTypes.len: fieldTypes[i] else: makeInt() let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: payloadBase, fieldName: fieldName, typ: makePointer(fieldTy), loc: loc) let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc) - if arg.patIdent notin ctx.patternBoundNames: - result.add(hirAlloca(arg.patIdent, fieldTy, loc)) - ctx.patternBoundNames.incl(arg.patIdent) - result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc)) + if arg.kind == pkIdent: + if arg.patIdent notin ctx.patternBoundNames: + result.add(hirAlloca(arg.patIdent, fieldTy, loc)) + ctx.patternBoundNames.incl(arg.patIdent) + result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc)) + else: + # Nested: Option::Some((a, b)), Pair::Two(Point { x, y }) + result.add(ctx.matchPatternBindings(fieldLoad, arg, subjectEnumName, subjectHasData, loc)) for nf in pattern.patEnumNamed: - if nf.pattern == nil or nf.pattern.kind != pkIdent: + if nf.pattern == nil: continue var fieldTy = makeInt() for entry in namedFields: @@ -211,10 +215,13 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern, let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: variantLoad, fieldName: nf.name, typ: makePointer(fieldTy), loc: loc) let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc) - if nf.pattern.patIdent notin ctx.patternBoundNames: - result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc)) - ctx.patternBoundNames.incl(nf.pattern.patIdent) - result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc)) + if nf.pattern.kind == pkIdent: + if nf.pattern.patIdent notin ctx.patternBoundNames: + result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc)) + ctx.patternBoundNames.incl(nf.pattern.patIdent) + result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc)) + else: + result.add(ctx.matchPatternBindings(fieldLoad, nf.pattern, subjectEnumName, subjectHasData, loc)) of pkGuarded: result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc)) of pkTuple: @@ -536,7 +543,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type = # Forward declarations proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode -proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode +proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = @@ -1339,7 +1346,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = typ: typ, loc: loc) of ekBlock: - return ctx.lowerBlock(expr.exprBlock) + return ctx.lowerBlock(expr.exprBlock, asExpr = true) of ekPostfix: let operand = ctx.lowerExpr(expr.exprPostfixOperand) @@ -1926,26 +1933,37 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), typ: makeVoid(), loc: loc) -proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode = +proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode = + ## asExpr=true: block is used as a value (`let x = { ... }`, match arm body). + ## Last skExpr becomes the block result. Statement blocks (func body, if/while) + ## keep asExpr=false so trailing void calls stay as statements. if blk == nil: return nil var stmts: seq[HirNode] = @[] for s in blk.stmts: let hir = ctx.lowerStmt(s) if hir != nil: stmts.add(hir) - # If the last statement is an expression, make it the block's result expression var expr: HirNode = nil - if stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil: - # Nested block expression (e.g., from match lowering) — lift it + if asExpr and stmts.len > 0 and blk.stmts.len > 0 and blk.stmts[^1].kind == skExpr: + let last = stmts[^1] + if last.kind == hBlock and last.blockExpr != nil: + # Nested yield block (match, block-expr) — lift result, keep side-effect stmts + stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc) + expr = last.blockExpr + elif last.kind in {hIf, hWhile, hLoop, hReturn, hBreak, hContinue, hAlloca, hStore, hAssign}: + discard + else: + # hBinary, hCall, hLit, hVar, hLoad, … — value expression + expr = last + discard stmts.pop() + elif stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil: + # Nested block expression (e.g., match) inside statement context — lift for + # function last-expr return via blockExpr when present let last = stmts[^1] stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc) expr = last.blockExpr - elif stmts.len > 0 and stmts[^1].kind != hBlock: - # Last stmt is a simple expression-like node — we can't easily extract it, - # but for hVar/hLit/hCall etc. we could treat them as block expr. - # For now, leave as-is to avoid breaking control-flow statements. - discard - return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true) + let typ = if expr != nil and expr.typ != nil: expr.typ else: makeVoid() + return hirBlock(stmts, expr, typ, blk.loc, isScope = true) proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = # Set up type substitution for generic functions diff --git a/docs/LanguageRef.md b/docs/LanguageRef.md index 2bfa0e3..69f8237 100644 --- a/docs/LanguageRef.md +++ b/docs/LanguageRef.md @@ -396,6 +396,21 @@ match p { Point { x, y } => x * 10 + y, _ => -1 } + +// Multi-statement arm bodies (block expression; last expr is the value) +match n { + 1 => { + let a: int = 10; + a + 1 + }, + _ => 0 +} + +// Block as expression +let r: int = { + let x: int = 5; + x + 6 +}; ``` --- diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index 85031e2..ea4c056 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -273,9 +273,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth) --- +## Сесия 16 (match block arms + block expressions) + +1. **Block-as-expression:** `let r = { let x = 1; x + 2 }` — last skExpr is the value +2. **Multi-stmt match arms:** `1 => { PrintLine("…"); let a = 10; a + 1 }` +3. Bootstrap: `lowerBlock(..., asExpr)` promotes last expression; nested enum/struct pattern bindings +4. Selfhost: parse `{ … }` as `ekBlock`; yield temps `__blk_N`; fix `IsMatchYield` null-strValue false positive; retTypeKind `-2` for expr blocks +5. Nested: `Shape::Dot(Point { x, y })` works on bootstrap; selfhost covers struct/tuple/enum + block arms +6. Example: `examples/match_block.bux` +7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ + +--- + ## Следващи стъпки -1. Match arm multi-stmt bodies (beyond single expr) -2. Nested patterns deeper (`Some((a, b))`, `Point { x: (a, b) }`) -3. LSP: wire hover types from real sema -4. Generic type inference for `Iter_Map` without explicit `` +1. Deeper nested patterns (`Some((a, b))` with multi-field enum layout ergonomics) +2. LSP: wire hover types from real sema +3. Generic type inference for `Iter_Map` without explicit `` +4. Match arm guards (`p if cond => …`) diff --git a/examples/match_block.bux b/examples/match_block.bux new file mode 100644 index 0000000..236be3e --- /dev/null +++ b/examples/match_block.bux @@ -0,0 +1,81 @@ +// Multi-stmt match arms (block bodies) + block-as-expression +import Std::Io::{PrintLine, PrintInt}; +import Std::Test::{Test_AssertEqInt, Test_Pass}; + +struct Point { + x: int, + y: int, +} + +enum Option { + Some(int), + None +} + +func Classify(n: int) -> int { + match n { + 1 => { + let a: int = 10; + a + 1 + }, + 2 => { + PrintLine("two"); + let b: int = 20; + b * 2 + }, + _ => 0 + } +} + +func Main() -> int { + Test_AssertEqInt(Classify(1), 11); + Test_AssertEqInt(Classify(2), 40); + Test_AssertEqInt(Classify(9), 0); + + // Block as expression outside match + let r: int = { + let x: int = 5; + let y: int = 6; + x + y + }; + Test_AssertEqInt(r, 11); + + // Tuple pattern + block body + let t: (int, int) = (3, 4); + let u: int = match t { + (a, b) => { + let prod: int = a * b; + prod + }, + _ => 0 + }; + Test_AssertEqInt(u, 12); + + // Struct pattern + block body + let p: Point = Point { x: 2, y: 5 }; + let z: int = match p { + Point { x: px, y: py } => { + let sum: int = px + py; + sum + }, + _ => -1 + }; + Test_AssertEqInt(z, 7); + + // Enum payload + block + let opt: Option = Option { tag: Option_Some }; + opt.data.Some_0 = 7; + let v: int = match opt { + Option::Some(n) => { + let doubled: int = n + n; + doubled + }, + Option::None => 0 + }; + Test_AssertEqInt(v, 14); + + PrintInt(Classify(2)); + PrintLine(""); + Test_Pass("match_block"); + return 0; +} diff --git a/src/hir_lower.bux b/src/hir_lower.bux index ab3f2ce..dd81390 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -805,25 +805,25 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, var arg: *Pattern = pat.patArgs; var ai: int = 0; while arg != null as *Pattern { - if arg.kind == pkIdent { - var ftype: String = "int"; - if ai == 0 { ftype = fieldType0; } - else if ai == 1 { ftype = fieldType1; } - let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64)); + var ftype: String = "int"; + if ai == 0 { ftype = fieldType0; } + else if ai == 1 { ftype = fieldType1; } + let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64)); - let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; - fPtr.kind = hFieldPtr; - fPtr.line = line; - fPtr.column = col; - fPtr.strValue = fieldName; - fPtr.child1 = payloadBase; - let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; - fLoad.kind = hLoad; - fLoad.line = line; - fLoad.column = col; - fLoad.child1 = fPtr; - fLoad.typeName = ftype; + let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + fPtr.kind = hFieldPtr; + fPtr.line = line; + fPtr.column = col; + fPtr.strValue = fieldName; + fPtr.child1 = payloadBase; + let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + fLoad.kind = hLoad; + fLoad.line = line; + fLoad.column = col; + fLoad.child1 = fPtr; + fLoad.typeName = ftype; + if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") { let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; alloca.kind = hAlloca; alloca.line = line; @@ -842,7 +842,6 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, store.child2 = fLoad; alloca.child3 = store; - // Define in scope so body idents resolve var bsym: Symbol; bsym.kind = skVar; bsym.name = arg.patIdent; @@ -861,6 +860,19 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, tail.child3 = alloca; tail = store; } + } else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum { + // Nested pattern on payload field + let nested: *HirNode = Lcx_PatternBindings(ctx, fLoad, arg, subjectEnumName, subjectHasData, line, col); + if nested != null as *HirNode { + if head == null as *HirNode { + head = nested; + tail = nested; + while tail.child3 != null as *HirNode { tail = tail.child3; } + } else { + tail.child3 = nested; + while tail.child3 != null as *HirNode { tail = tail.child3; } + } + } } arg = arg.patNext; ai = ai + 1; @@ -872,6 +884,8 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, func Lcx_IsMatchYield(n: *HirNode) -> bool { if n == null as *HirNode { return false; } if n.kind != hBlock { return false; } + // strValue must be a real temp name — null/"" is a plain statement block + if n.strValue == null as String { return false; } return !String_Eq(n.strValue, ""); } @@ -957,7 +971,7 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode { // Pattern bindings before body (so body idents resolve) let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col); let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body); - // result = body + // result = body (expand block/match yield: run stmts then store result var) let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; storeNode.kind = hStore; storeNode.line = line; @@ -966,20 +980,32 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode { resVar.kind = hVar; resVar.strValue = resultName; storeNode.child1 = resVar; - storeNode.child2 = bodyHir; + var bodyPrefix: *HirNode = null as *HirNode; + if Lcx_IsMatchYield(bodyHir) { + storeNode.child2 = Lcx_YieldVarOf(bodyHir); + bodyHir.strValue = ""; + bodyPrefix = bodyHir.child1; + } else { + storeNode.child2 = bodyHir; + } - // armBlock = bindings... → storeNode + // armBlock = bindings → body stmts → store let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; armBlock.kind = hBlock; armBlock.line = line; armBlock.column = col; - if bindHead != null as *HirNode { - armBlock.child1 = bindHead; - // find tail of bind chain - var bt: *HirNode = bindHead; - while bt.child3 != null as *HirNode { - bt = bt.child3; - } + var chainHead: *HirNode = bindHead; + if chainHead == null as *HirNode { + chainHead = bodyPrefix; + } else if bodyPrefix != null as *HirNode { + var bt0: *HirNode = chainHead; + while bt0.child3 != null as *HirNode { bt0 = bt0.child3; } + bt0.child3 = bodyPrefix; + } + if chainHead != null as *HirNode { + armBlock.child1 = chainHead; + var bt: *HirNode = chainHead; + while bt.child3 != null as *HirNode { bt = bt.child3; } bt.child3 = storeNode; } else { armBlock.child1 = storeNode; @@ -2158,6 +2184,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { } // Block expression (boolValue = true means unsafe block) + // retTypeKind -2 → yield last expression as block value if kind == ekBlock { if expr.refBlock != null as *Block { if expr.boolValue { @@ -2165,12 +2192,12 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { let oldRelease: bool = ctx.releaseFunc; ctx.checkedFunc = false; ctx.releaseFunc = false; - let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -1); + let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -2); ctx.checkedFunc = oldChecked; ctx.releaseFunc = oldRelease; return blockNode; } else { - return Lcx_LowerBlock(ctx, expr.refBlock, -1); + return Lcx_LowerBlock(ctx, expr.refBlock, -2); } } return n; @@ -3053,27 +3080,30 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode if block == null as *Block { return null as *HirNode; } if block.stmtCount == 0 { return null as *HirNode; } - // Build a linked list of HirNodes via child3: - // node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null - // child3 is safe for chaining because: - // hStore: child1=alloca, child2=value, child3 unused - // hReturn: child1=value, child2/child3 unused - // hCall: child1=arg1, child2=arg2, child3 unused + // retTypeKind: + // >= 0 → function body; last skExpr becomes return + // -1 → statement block (if/while/for); no return, no yield + // -2 → block-as-expression (match arm / let { ... }); yield last skExpr + let asExpr: bool = retTypeKind == -2; + var yieldName: String = ""; + if asExpr { + ctx.varCounter = ctx.varCounter + 1; + yieldName = String_Concat("__blk_", String_FromInt(ctx.varCounter as int64)); + } + + // Build a linked list of HirNodes via child3 var firstNode: *HirNode = null as *HirNode; var prevNode: *HirNode = null as *HirNode; var stmt: *Stmt = block.firstStmt; - var lastStmt: *Stmt = null as *Stmt; while stmt != null as *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 + + // Last expression statement in a non-void function → implicit return + if isLast && !asExpr && 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; @@ -3083,7 +3113,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode 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; @@ -3093,7 +3122,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode } lastInBlock.child3 = retNode; } - // Clear yield marker so emit treats it as plain block exprNode.strValue = ""; lowered = exprNode; } else if exprNode.kind == hReturn { @@ -3107,6 +3135,53 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode lowered = retNode; } } + } else if isLast && asExpr && stmt.kind == skExpr && stmt.child1 != null as *Expr { + // Block-as-expression: last expr is the yield value + let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1); + // alloca yield temp + let allocaN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + allocaN.kind = hAlloca; + allocaN.line = stmt.line; + allocaN.column = stmt.column; + allocaN.strValue = yieldName; + allocaN.typeName = "int"; + if exprNode != null as *HirNode && exprNode.typeName != null as String + && !String_Eq(exprNode.typeName, "") { + allocaN.typeName = exprNode.typeName; + } + // store: yield = value (handle nested match yield) + let storeN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + storeN.kind = hStore; + storeN.line = stmt.line; + storeN.column = stmt.column; + let yv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + yv.kind = hVar; + yv.strValue = yieldName; + storeN.child1 = yv; + if exprNode != null as *HirNode && Lcx_IsMatchYield(exprNode) { + // Expand nested match stmts then store its result var + let yvar: *HirNode = Lcx_YieldVarOf(exprNode); + storeN.child2 = yvar; + exprNode.strValue = ""; + // chain: exprNode stmts → alloca → store + var lastIn: *HirNode = exprNode.child1; + if lastIn == null as *HirNode { + exprNode.child1 = allocaN; + allocaN.child3 = storeN; + lowered = exprNode; + } else { + while lastIn.child3 != null as *HirNode { + lastIn = lastIn.child3; + } + lastIn.child3 = allocaN; + allocaN.child3 = storeN; + lowered = exprNode; + } + } else { + storeN.child2 = exprNode; + allocaN.child3 = storeN; + lowered = allocaN; + } } else { lowered = Lcx_LowerStmt(ctx, stmt); } @@ -3115,8 +3190,12 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode firstNode = lowered; prevNode = lowered; } else { + // Walk to end of chain (lowered may itself be a multi-node chain) prevNode.child3 = lowered; prevNode = lowered; + while prevNode.child3 != null as *HirNode { + prevNode = prevNode.child3; + } } } stmt = stmt.nextStmt; @@ -3129,6 +3208,10 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode n.column = block.column; n.boolValue = true; n.child1 = firstNode; + if asExpr && !String_Eq(yieldName, "") { + n.strValue = yieldName; + n.typeName = "int"; + } return n; } diff --git a/src/parser.bux b/src/parser.bux index 5d10fa1..c37f9aa 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -661,6 +661,14 @@ func parserParsePrimary(p: *Parser) -> *Expr { return parserParseClosure(p); } + // Block expression { stmts; value } + if kind == tkLBrace { + let e: *Expr = parserMakeExpr(ekBlock, line, col); + e.boolValue = false; + e.refBlock = parserParseBlock(p); + return e; + } + // unsafe { ... } — unsafe block expression if kind == tkUnsafe { discard parserAdvance(p); diff --git a/src/sema.bux b/src/sema.bux index ae03ed9..84489df 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -446,10 +446,10 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { var arg: *Pattern = pat.patArgs; var ai: int = 0; while arg != null as *Pattern { + var ftype: String = "int"; + if ai == 0 { ftype = fieldType0; } + else if ai == 1 { ftype = fieldType1; } if arg.kind == pkIdent { - var ftype: String = "int"; - if ai == 0 { ftype = fieldType0; } - else if ai == 1 { ftype = fieldType1; } var bsym: Symbol; Sema_ZeroInitSymbol(&bsym); bsym.kind = skVar; @@ -467,6 +467,14 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { bsym.isPublic = false; bsym.decl = null as *Decl; discard Scope_Define(sema.scope, bsym); + } else { + // Nested pattern: synthesize a fake subject expr with payload type + let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr; + let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; + fte.kind = tekNamed; + fte.typeName = ftype; + fake.refType = fte; + Sema_BindPattern(sema, arg, fake); } arg = arg.patNext; ai = ai + 1; @@ -478,19 +486,17 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { var ei: int = 0; var elem: *Pattern = pat.patArgs; while elem != null as *Pattern { - if elem.kind == pkIdent { - var ety: String = "int"; - // Tuple_int_int → fields are ints by default; prefer subject type args if present - if subject != null as *Expr && subject.refType != null as *TypeExpr { - if subject.refType.kind == tekTuple { - // typeArgName0 / typeArgName1 store element type names when available - if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") { - ety = subject.refType.typeArgName0; - } else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") { - ety = subject.refType.typeArgName1; - } + var ety: String = "int"; + if subject != null as *Expr && subject.refType != null as *TypeExpr { + if subject.refType.kind == tekTuple { + if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") { + ety = subject.refType.typeArgName0; + } else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") { + ety = subject.refType.typeArgName1; } } + } + if elem.kind == pkIdent { var bsym: Symbol; Sema_ZeroInitSymbol(&bsym); bsym.kind = skVar; @@ -505,6 +511,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { bsym.refType = te; bsym.isMutable = false; discard Scope_Define(sema.scope, bsym); + } else { + let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr; + let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; + fte.kind = tekNamed; + fte.typeName = ety; + fake.refType = fte; + Sema_BindPattern(sema, elem, fake); } elem = elem.patNext; ei = ei + 1; @@ -519,23 +532,25 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { } var field: *Pattern = pat.patArgs; while field != null as *Pattern { - if field.kind == pkIdent { - var ftype: String = "int"; - if !String_Eq(structName, "") { - let ssym: Symbol = Scope_Lookup(sema.scope, structName); - if ssym.decl != null as *Decl && ssym.decl.kind == dkStruct && ssym.decl.fields != null as *StructField { - var fi: int = 0; - while fi < ssym.decl.fieldCount { - let sf: StructField = ssym.decl.fields[fi]; - if String_Eq(sf.name, field.patFieldName) { - if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") { - ftype = sf.refFieldType.typeName; - } + var ftype: String = "int"; + if !String_Eq(structName, "") { + let ssym: Symbol = Scope_Lookup(sema.scope, structName); + if ssym.decl != null as *Decl && ssym.decl.kind == dkStruct && ssym.decl.fields != null as *StructField { + var fi: int = 0; + while fi < ssym.decl.fieldCount { + let sf: StructField = ssym.decl.fields[fi]; + var fname: String = field.patFieldName; + if String_Eq(fname, "") { fname = field.patIdent; } + if String_Eq(sf.name, fname) { + if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") { + ftype = sf.refFieldType.typeName; } - fi = fi + 1; } + fi = fi + 1; } } + } + if field.kind == pkIdent { var bsym: Symbol; Sema_ZeroInitSymbol(&bsym); bsym.kind = skVar; @@ -550,6 +565,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) { bsym.refType = te; bsym.isMutable = false; discard Scope_Define(sema.scope, bsym); + } else { + let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr; + let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; + fte.kind = tekNamed; + fte.typeName = ftype; + fake.refType = fte; + Sema_BindPattern(sema, field, fake); } field = field.patNext; }