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.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
+32
-14
@@ -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.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.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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+16
-4
@@ -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 `<T,U>`
|
||||
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 `<T,U>`
|
||||
4. Match arm guards (`p if cond => …`)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+109
-26
@@ -805,7 +805,6 @@ 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; }
|
||||
@@ -824,6 +823,7 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+28
-6
@@ -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 {
|
||||
if arg.kind == pkIdent {
|
||||
var ftype: String = "int";
|
||||
if ai == 0 { ftype = fieldType0; }
|
||||
else if ai == 1 { ftype = fieldType1; }
|
||||
if arg.kind == pkIdent {
|
||||
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,12 +486,9 @@ 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, "") {
|
||||
@@ -491,6 +496,7 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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,7 +532,6 @@ 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);
|
||||
@@ -527,7 +539,9 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||
var fi: int = 0;
|
||||
while fi < ssym.decl.fieldCount {
|
||||
let sf: StructField = ssym.decl.fields[fi];
|
||||
if String_Eq(sf.name, field.patFieldName) {
|
||||
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;
|
||||
}
|
||||
@@ -536,6 +550,7 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user