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
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
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
|
.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)
|
typ: makePointer(variantStructTy), loc: loc)
|
||||||
payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
|
payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
|
||||||
for i, arg in pattern.patEnumArgs:
|
for i, arg in pattern.patEnumArgs:
|
||||||
if arg == nil or arg.kind != pkIdent:
|
if arg == nil:
|
||||||
continue
|
continue
|
||||||
let fieldName = variantName & "_" & $i
|
let fieldName = variantName & "_" & $i
|
||||||
let fieldTy = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
let fieldTy = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
|
||||||
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: payloadBase, fieldName: fieldName,
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: payloadBase, fieldName: fieldName,
|
||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
|
||||||
|
if arg.kind == pkIdent:
|
||||||
if arg.patIdent notin ctx.patternBoundNames:
|
if arg.patIdent notin ctx.patternBoundNames:
|
||||||
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
|
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
|
||||||
ctx.patternBoundNames.incl(arg.patIdent)
|
ctx.patternBoundNames.incl(arg.patIdent)
|
||||||
result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc))
|
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:
|
for nf in pattern.patEnumNamed:
|
||||||
if nf.pattern == nil or nf.pattern.kind != pkIdent:
|
if nf.pattern == nil:
|
||||||
continue
|
continue
|
||||||
var fieldTy = makeInt()
|
var fieldTy = makeInt()
|
||||||
for entry in namedFields:
|
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,
|
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: variantLoad, fieldName: nf.name,
|
||||||
typ: makePointer(fieldTy), loc: loc)
|
typ: makePointer(fieldTy), loc: loc)
|
||||||
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: 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:
|
if nf.pattern.patIdent notin ctx.patternBoundNames:
|
||||||
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
|
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
|
||||||
ctx.patternBoundNames.incl(nf.pattern.patIdent)
|
ctx.patternBoundNames.incl(nf.pattern.patIdent)
|
||||||
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
|
||||||
|
else:
|
||||||
|
result.add(ctx.matchPatternBindings(fieldLoad, nf.pattern, subjectEnumName, subjectHasData, loc))
|
||||||
of pkGuarded:
|
of pkGuarded:
|
||||||
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
|
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
|
||||||
of pkTuple:
|
of pkTuple:
|
||||||
@@ -536,7 +543,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
|||||||
# Forward declarations
|
# Forward declarations
|
||||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): 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 lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
|
||||||
|
|
||||||
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||||
@@ -1339,7 +1346,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
typ: typ, loc: loc)
|
typ: typ, loc: loc)
|
||||||
|
|
||||||
of ekBlock:
|
of ekBlock:
|
||||||
return ctx.lowerBlock(expr.exprBlock)
|
return ctx.lowerBlock(expr.exprBlock, asExpr = true)
|
||||||
|
|
||||||
of ekPostfix:
|
of ekPostfix:
|
||||||
let operand = ctx.lowerExpr(expr.exprPostfixOperand)
|
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),
|
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||||
typ: makeVoid(), 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
|
if blk == nil: return nil
|
||||||
var stmts: seq[HirNode] = @[]
|
var stmts: seq[HirNode] = @[]
|
||||||
for s in blk.stmts:
|
for s in blk.stmts:
|
||||||
let hir = ctx.lowerStmt(s)
|
let hir = ctx.lowerStmt(s)
|
||||||
if hir != nil:
|
if hir != nil:
|
||||||
stmts.add(hir)
|
stmts.add(hir)
|
||||||
# If the last statement is an expression, make it the block's result expression
|
|
||||||
var expr: HirNode = nil
|
var expr: HirNode = nil
|
||||||
if stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil:
|
if asExpr and stmts.len > 0 and blk.stmts.len > 0 and blk.stmts[^1].kind == skExpr:
|
||||||
# Nested block expression (e.g., from match lowering) — lift it
|
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]
|
let last = stmts[^1]
|
||||||
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
|
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
|
||||||
expr = last.blockExpr
|
expr = last.blockExpr
|
||||||
elif stmts.len > 0 and stmts[^1].kind != hBlock:
|
let typ = if expr != nil and expr.typ != nil: expr.typ else: makeVoid()
|
||||||
# Last stmt is a simple expression-like node — we can't easily extract it,
|
return hirBlock(stmts, expr, typ, blk.loc, isScope = true)
|
||||||
# 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)
|
|
||||||
|
|
||||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||||
# Set up type substitution for generic functions
|
# Set up type substitution for generic functions
|
||||||
|
|||||||
@@ -396,6 +396,21 @@ match p {
|
|||||||
Point { x, y } => x * 10 + y,
|
Point { x, y } => x * 10 + y,
|
||||||
_ => -1
|
_ => -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)
|
1. Deeper nested patterns (`Some((a, b))` with multi-field enum layout ergonomics)
|
||||||
2. Nested patterns deeper (`Some((a, b))`, `Point { x: (a, b) }`)
|
2. LSP: wire hover types from real sema
|
||||||
3. LSP: wire hover types from real sema
|
3. Generic type inference for `Iter_Map` without explicit `<T,U>`
|
||||||
4. 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 arg: *Pattern = pat.patArgs;
|
||||||
var ai: int = 0;
|
var ai: int = 0;
|
||||||
while arg != null as *Pattern {
|
while arg != null as *Pattern {
|
||||||
if arg.kind == pkIdent {
|
|
||||||
var ftype: String = "int";
|
var ftype: String = "int";
|
||||||
if ai == 0 { ftype = fieldType0; }
|
if ai == 0 { ftype = fieldType0; }
|
||||||
else if ai == 1 { ftype = fieldType1; }
|
else if ai == 1 { ftype = fieldType1; }
|
||||||
@@ -824,6 +823,7 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
fLoad.child1 = fPtr;
|
fLoad.child1 = fPtr;
|
||||||
fLoad.typeName = ftype;
|
fLoad.typeName = ftype;
|
||||||
|
|
||||||
|
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
|
||||||
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
alloca.kind = hAlloca;
|
alloca.kind = hAlloca;
|
||||||
alloca.line = line;
|
alloca.line = line;
|
||||||
@@ -842,7 +842,6 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
store.child2 = fLoad;
|
store.child2 = fLoad;
|
||||||
alloca.child3 = store;
|
alloca.child3 = store;
|
||||||
|
|
||||||
// Define in scope so body idents resolve
|
|
||||||
var bsym: Symbol;
|
var bsym: Symbol;
|
||||||
bsym.kind = skVar;
|
bsym.kind = skVar;
|
||||||
bsym.name = arg.patIdent;
|
bsym.name = arg.patIdent;
|
||||||
@@ -861,6 +860,19 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
tail.child3 = alloca;
|
tail.child3 = alloca;
|
||||||
tail = store;
|
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;
|
arg = arg.patNext;
|
||||||
ai = ai + 1;
|
ai = ai + 1;
|
||||||
@@ -872,6 +884,8 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|||||||
func Lcx_IsMatchYield(n: *HirNode) -> bool {
|
func Lcx_IsMatchYield(n: *HirNode) -> bool {
|
||||||
if n == null as *HirNode { return false; }
|
if n == null as *HirNode { return false; }
|
||||||
if n.kind != hBlock { 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, "");
|
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)
|
// Pattern bindings before body (so body idents resolve)
|
||||||
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
|
||||||
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
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;
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
storeNode.kind = hStore;
|
storeNode.kind = hStore;
|
||||||
storeNode.line = line;
|
storeNode.line = line;
|
||||||
@@ -966,20 +980,32 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
resVar.kind = hVar;
|
resVar.kind = hVar;
|
||||||
resVar.strValue = resultName;
|
resVar.strValue = resultName;
|
||||||
storeNode.child1 = resVar;
|
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;
|
storeNode.child2 = bodyHir;
|
||||||
|
}
|
||||||
|
|
||||||
// armBlock = bindings... → storeNode
|
// armBlock = bindings → body stmts → store
|
||||||
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
armBlock.kind = hBlock;
|
armBlock.kind = hBlock;
|
||||||
armBlock.line = line;
|
armBlock.line = line;
|
||||||
armBlock.column = col;
|
armBlock.column = col;
|
||||||
if bindHead != null as *HirNode {
|
var chainHead: *HirNode = bindHead;
|
||||||
armBlock.child1 = bindHead;
|
if chainHead == null as *HirNode {
|
||||||
// find tail of bind chain
|
chainHead = bodyPrefix;
|
||||||
var bt: *HirNode = bindHead;
|
} else if bodyPrefix != null as *HirNode {
|
||||||
while bt.child3 != null as *HirNode {
|
var bt0: *HirNode = chainHead;
|
||||||
bt = bt.child3;
|
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;
|
bt.child3 = storeNode;
|
||||||
} else {
|
} else {
|
||||||
armBlock.child1 = storeNode;
|
armBlock.child1 = storeNode;
|
||||||
@@ -2158,6 +2184,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block expression (boolValue = true means unsafe block)
|
// Block expression (boolValue = true means unsafe block)
|
||||||
|
// retTypeKind -2 → yield last expression as block value
|
||||||
if kind == ekBlock {
|
if kind == ekBlock {
|
||||||
if expr.refBlock != null as *Block {
|
if expr.refBlock != null as *Block {
|
||||||
if expr.boolValue {
|
if expr.boolValue {
|
||||||
@@ -2165,12 +2192,12 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
let oldRelease: bool = ctx.releaseFunc;
|
let oldRelease: bool = ctx.releaseFunc;
|
||||||
ctx.checkedFunc = false;
|
ctx.checkedFunc = false;
|
||||||
ctx.releaseFunc = 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.checkedFunc = oldChecked;
|
||||||
ctx.releaseFunc = oldRelease;
|
ctx.releaseFunc = oldRelease;
|
||||||
return blockNode;
|
return blockNode;
|
||||||
} else {
|
} else {
|
||||||
return Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
return Lcx_LowerBlock(ctx, expr.refBlock, -2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n;
|
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 == null as *Block { return null as *HirNode; }
|
||||||
if block.stmtCount == 0 { return null as *HirNode; }
|
if block.stmtCount == 0 { return null as *HirNode; }
|
||||||
|
|
||||||
// Build a linked list of HirNodes via child3:
|
// retTypeKind:
|
||||||
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
|
// >= 0 → function body; last skExpr becomes return
|
||||||
// child3 is safe for chaining because:
|
// -1 → statement block (if/while/for); no return, no yield
|
||||||
// hStore: child1=alloca, child2=value, child3 unused
|
// -2 → block-as-expression (match arm / let { ... }); yield last skExpr
|
||||||
// hReturn: child1=value, child2/child3 unused
|
let asExpr: bool = retTypeKind == -2;
|
||||||
// hCall: child1=arg1, child2=arg2, child3 unused
|
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 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 {
|
||||||
lastStmt = stmt;
|
|
||||||
let isLast: bool = stmt.nextStmt == null as *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;
|
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 {
|
&& stmt.kind == skExpr && stmt.child1 != null as *Expr {
|
||||||
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
||||||
if exprNode != null as *HirNode {
|
if exprNode != null as *HirNode {
|
||||||
// Match (and similar multi-stmt yields): hBlock with strValue = result var
|
|
||||||
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
|
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
|
||||||
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||||
retVar.kind = hVar;
|
retVar.kind = hVar;
|
||||||
@@ -3083,7 +3113,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
retNode.line = stmt.line;
|
retNode.line = stmt.line;
|
||||||
retNode.column = stmt.column;
|
retNode.column = stmt.column;
|
||||||
retNode.child1 = retVar;
|
retNode.child1 = retVar;
|
||||||
// Append return at end of match block's child3 chain
|
|
||||||
var lastInBlock: *HirNode = exprNode.child1;
|
var lastInBlock: *HirNode = exprNode.child1;
|
||||||
if lastInBlock == null as *HirNode {
|
if lastInBlock == null as *HirNode {
|
||||||
exprNode.child1 = retNode;
|
exprNode.child1 = retNode;
|
||||||
@@ -3093,7 +3122,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
}
|
}
|
||||||
lastInBlock.child3 = retNode;
|
lastInBlock.child3 = retNode;
|
||||||
}
|
}
|
||||||
// Clear yield marker so emit treats it as plain block
|
|
||||||
exprNode.strValue = "";
|
exprNode.strValue = "";
|
||||||
lowered = exprNode;
|
lowered = exprNode;
|
||||||
} else if exprNode.kind == hReturn {
|
} else if exprNode.kind == hReturn {
|
||||||
@@ -3107,6 +3135,53 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
lowered = retNode;
|
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 {
|
} else {
|
||||||
lowered = Lcx_LowerStmt(ctx, stmt);
|
lowered = Lcx_LowerStmt(ctx, stmt);
|
||||||
}
|
}
|
||||||
@@ -3115,8 +3190,12 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
firstNode = lowered;
|
firstNode = lowered;
|
||||||
prevNode = lowered;
|
prevNode = lowered;
|
||||||
} else {
|
} else {
|
||||||
|
// Walk to end of chain (lowered may itself be a multi-node chain)
|
||||||
prevNode.child3 = lowered;
|
prevNode.child3 = lowered;
|
||||||
prevNode = lowered;
|
prevNode = lowered;
|
||||||
|
while prevNode.child3 != null as *HirNode {
|
||||||
|
prevNode = prevNode.child3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stmt = stmt.nextStmt;
|
stmt = stmt.nextStmt;
|
||||||
@@ -3129,6 +3208,10 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
|
|||||||
n.column = block.column;
|
n.column = block.column;
|
||||||
n.boolValue = true;
|
n.boolValue = true;
|
||||||
n.child1 = firstNode;
|
n.child1 = firstNode;
|
||||||
|
if asExpr && !String_Eq(yieldName, "") {
|
||||||
|
n.strValue = yieldName;
|
||||||
|
n.typeName = "int";
|
||||||
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -661,6 +661,14 @@ func parserParsePrimary(p: *Parser) -> *Expr {
|
|||||||
return parserParseClosure(p);
|
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
|
// unsafe { ... } — unsafe block expression
|
||||||
if kind == tkUnsafe {
|
if kind == tkUnsafe {
|
||||||
discard parserAdvance(p);
|
discard parserAdvance(p);
|
||||||
|
|||||||
+28
-6
@@ -446,10 +446,10 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
var arg: *Pattern = pat.patArgs;
|
var arg: *Pattern = pat.patArgs;
|
||||||
var ai: int = 0;
|
var ai: int = 0;
|
||||||
while arg != null as *Pattern {
|
while arg != null as *Pattern {
|
||||||
if arg.kind == pkIdent {
|
|
||||||
var ftype: String = "int";
|
var ftype: String = "int";
|
||||||
if ai == 0 { ftype = fieldType0; }
|
if ai == 0 { ftype = fieldType0; }
|
||||||
else if ai == 1 { ftype = fieldType1; }
|
else if ai == 1 { ftype = fieldType1; }
|
||||||
|
if arg.kind == pkIdent {
|
||||||
var bsym: Symbol;
|
var bsym: Symbol;
|
||||||
Sema_ZeroInitSymbol(&bsym);
|
Sema_ZeroInitSymbol(&bsym);
|
||||||
bsym.kind = skVar;
|
bsym.kind = skVar;
|
||||||
@@ -467,6 +467,14 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
bsym.isPublic = false;
|
bsym.isPublic = false;
|
||||||
bsym.decl = null as *Decl;
|
bsym.decl = null as *Decl;
|
||||||
discard Scope_Define(sema.scope, bsym);
|
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;
|
arg = arg.patNext;
|
||||||
ai = ai + 1;
|
ai = ai + 1;
|
||||||
@@ -478,12 +486,9 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
var ei: int = 0;
|
var ei: int = 0;
|
||||||
var elem: *Pattern = pat.patArgs;
|
var elem: *Pattern = pat.patArgs;
|
||||||
while elem != null as *Pattern {
|
while elem != null as *Pattern {
|
||||||
if elem.kind == pkIdent {
|
|
||||||
var ety: String = "int";
|
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 != null as *Expr && subject.refType != null as *TypeExpr {
|
||||||
if subject.refType.kind == tekTuple {
|
if subject.refType.kind == tekTuple {
|
||||||
// typeArgName0 / typeArgName1 store element type names when available
|
|
||||||
if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") {
|
if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") {
|
||||||
ety = subject.refType.typeArgName0;
|
ety = subject.refType.typeArgName0;
|
||||||
} else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") {
|
} 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;
|
var bsym: Symbol;
|
||||||
Sema_ZeroInitSymbol(&bsym);
|
Sema_ZeroInitSymbol(&bsym);
|
||||||
bsym.kind = skVar;
|
bsym.kind = skVar;
|
||||||
@@ -505,6 +511,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
bsym.refType = te;
|
bsym.refType = te;
|
||||||
bsym.isMutable = false;
|
bsym.isMutable = false;
|
||||||
discard Scope_Define(sema.scope, bsym);
|
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;
|
elem = elem.patNext;
|
||||||
ei = ei + 1;
|
ei = ei + 1;
|
||||||
@@ -519,7 +532,6 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
}
|
}
|
||||||
var field: *Pattern = pat.patArgs;
|
var field: *Pattern = pat.patArgs;
|
||||||
while field != null as *Pattern {
|
while field != null as *Pattern {
|
||||||
if field.kind == pkIdent {
|
|
||||||
var ftype: String = "int";
|
var ftype: String = "int";
|
||||||
if !String_Eq(structName, "") {
|
if !String_Eq(structName, "") {
|
||||||
let ssym: Symbol = Scope_Lookup(sema.scope, 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;
|
var fi: int = 0;
|
||||||
while fi < ssym.decl.fieldCount {
|
while fi < ssym.decl.fieldCount {
|
||||||
let sf: StructField = ssym.decl.fields[fi];
|
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, "") {
|
if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") {
|
||||||
ftype = 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;
|
var bsym: Symbol;
|
||||||
Sema_ZeroInitSymbol(&bsym);
|
Sema_ZeroInitSymbol(&bsym);
|
||||||
bsym.kind = skVar;
|
bsym.kind = skVar;
|
||||||
@@ -550,6 +565,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
|
|||||||
bsym.refType = te;
|
bsym.refType = te;
|
||||||
bsym.isMutable = false;
|
bsym.isMutable = false;
|
||||||
discard Scope_Define(sema.scope, bsym);
|
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;
|
field = field.patNext;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user