diff --git a/Makefile b/Makefile index 3ceaaed..659ae04 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 match_block +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 nested_patterns .PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp diff --git a/bootstrap/c_backend.nim b/bootstrap/c_backend.nim index ac84fc5..cd875fa 100644 --- a/bootstrap/c_backend.nim +++ b/bootstrap/c_backend.nim @@ -600,23 +600,12 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) = let typ = typeToC(be, v.fields[0]) be.emitLine(&"{typ} {v.name}_0;") elif v.fields.len > 1: - # Multi positional fields — nested struct so fields don't overlay - be.emitLine(&"struct {{") - inc be.indent - for i, f in v.fields: - let typ = typeToC(be, f) - be.emitLine(&"{typ} {v.name}_{i};") - dec be.indent - be.emitLine(&"}} {v.name};") + # Multi positional — named nested typedef Enum_Variant_Payload + let nestedName = name & "_" & v.name & "_Payload" + be.emitLine(&"{nestedName} {v.name};") elif v.namedFields.len > 0: - # Named fields - generate as struct - be.emitLine(&"struct {{") - inc be.indent - for nf in v.namedFields: - let typ = typeToC(be, nf.typ) - be.emitLine(&"{typ} {nf.name};") - dec be.indent - be.emitLine(&"}} {v.name};") + let nestedName = name & "_" & v.name & "_Payload" + be.emitLine(&"{nestedName} {v.name};") dec be.indent be.emitLine(&"}} {name}_Data;") be.emitLine("") @@ -716,15 +705,27 @@ proc emitModule*(be: var CBackend, module: HirModule): string = if module.structs.len > 0: be.emitLine("") - # Enum definitions (must come before structs that reference them) + # Nested multi-field enum payloads (Enum_Variant_Payload) must be fully + # defined before the algebraic enum union that embeds them by value. + var payloadStructNames: seq[string] = @[] + for e in module.enums: + for v in e.variants: + if v.fields.len > 1 or v.namedFields.len > 0: + payloadStructNames.add(e.name & "_" & v.name & "_Payload") + for s in module.structs: + if s.name in payloadStructNames: + be.emitStruct(s.name, s.fields) + + # Enum definitions (after payload structs; before user structs that may use them) for e in module.enums: be.emitEnum(e.name, e.variants) if module.enums.len > 0: be.emitLine("") - # Struct definitions + # Remaining struct definitions (skip payloads already emitted) for s in module.structs: - be.emitStruct(s.name, s.fields) + if s.name notin payloadStructNames: + be.emitStruct(s.name, s.fields) # Slice fat-pointer typedefs if sliceTypes.len > 0: diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index b07a54d..6efd1a4 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -180,7 +180,9 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern, let multiField = fieldTypes.len > 1 var payloadBase = dataLoad if multiField: - let variantStructTy = makeNamed(variantName) + # Nested struct type Enum_Variant_Payload (avoids clash with tag Enum_Variant) + let nestedName = enumName & "_" & variantName & "_Payload" + let variantStructTy = makeNamed(nestedName) let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName, typ: makePointer(variantStructTy), loc: loc) payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc) @@ -208,10 +210,12 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern, if entry.name == nf.name: fieldTy = entry.typ break - # Named payload fields live under data.VariantName.name + # Named payload fields live under data.Variant.name on Enum_Variant_Payload + let nestedName = enumName & "_" & variantName & "_Payload" + let variantStructTy = makeNamed(nestedName) let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName, - typ: makePointer(makeNamed(variantName)), loc: loc) - let variantLoad = HirNode(kind: hLoad, loadPtr: variantPtr, typ: makeNamed(variantName), loc: loc) + typ: makePointer(variantStructTy), loc: loc) + let variantLoad = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc) 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) @@ -2217,33 +2221,29 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = for v in decl.declEnumVariants: var fields: seq[Type] = @[] for f in v.fields: - var fType = makeUnknown() - if f != nil and f.kind == tekNamed: - case f.typeName - of "int", "int32": fType = makeInt() - of "int64": fType = makeInt64() - of "float64": fType = makeFloat64() - of "float32": fType = makeFloat32() - of "bool": fType = makeBool() - of "String", "str": fType = makeStr() - else: fType = makeNamed(f.typeName) - fields.add(fType) + # Full resolve — supports tuples, pointers, named types, etc. + fields.add(if f != nil: ctx.resolveTypeExpr(f) else: makeUnknown()) var namedFields: seq[tuple[name: string, typ: Type]] = @[] for nf in v.namedFields: - var fType = makeUnknown() - if nf.ftype != nil and nf.ftype.kind == tekNamed: - case nf.ftype.typeName - of "int", "int32": fType = makeInt() - of "int64": fType = makeInt64() - of "float64": fType = makeFloat64() - of "float32": fType = makeFloat32() - of "bool": fType = makeBool() - of "String", "str": fType = makeStr() - else: fType = makeNamed(nf.ftype.typeName) + let fType = if nf.ftype != nil: ctx.resolveTypeExpr(nf.ftype) else: makeUnknown() namedFields.add((nf.name, fType)) variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields)) + # Multi-field / named-field variants get a named nested struct type + # Enum_Variant_Payload (suffix avoids clash with tag constant Enum_Variant). + if fields.len > 1: + var nestedFields: seq[tuple[name: string, typ: Type]] = @[] + for i, ft in fields: + nestedFields.add((v.name & "_" & $i, ft)) + let nestedName = decl.declEnumName & "_" & v.name & "_Payload" + structs.add((nestedName, nestedFields)) + elif namedFields.len > 0: + var nestedFields: seq[tuple[name: string, typ: Type]] = @[] + for nf in namedFields: + nestedFields.add((nf.name, nf.typ)) + let nestedName = decl.declEnumName & "_" & v.name & "_Payload" + structs.add((nestedName, nestedFields)) enums.add((decl.declEnumName, variants)) of dkConst: let value = ctx.lowerExpr(decl.declConstValue) diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim index ee35716..2325dad 100644 --- a/bootstrap/lir_c_backend.nim +++ b/bootstrap/lir_c_backend.nim @@ -500,20 +500,12 @@ proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant # Single positional field — flat (compat: data.Variant_0) be.emitLine(&"{typeToCStr(v.fields[0])} {v.name}_0;") elif v.fields.len > 1: - # Multi positional — nested struct so fields don't share union storage - be.emitLine(&"struct {{") - be.indent += 1 - for i, f in v.fields: - be.emitLine(&"{typeToCStr(f)} {v.name}_{i};") - be.indent -= 1 - be.emitLine(&"}} {v.name};") + # Multi positional — named nested struct Enum_Variant_Payload + let nestedName = name & "_" & v.name & "_Payload" + be.emitLine(&"{nestedName} {v.name};") elif v.namedFields.len > 0: - be.emitLine(&"struct {{") - be.indent += 1 - for nf in v.namedFields: - be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};") - be.indent -= 1 - be.emitLine(&"}} {v.name};") + let nestedName = name & "_" & v.name & "_Payload" + be.emitLine(&"{nestedName} {v.name};") be.indent -= 1 be.emitLine(&"}} {name}_Data;") be.emitLine("") @@ -648,104 +640,8 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s for e in module.enums: localTypeNames.incl(e.name) - # Collect slice types used in struct fields and enum payloads. - var sliceTypes: seq[tuple[name: string, elem: string]] = @[] - var sliceNames: HashSet[string] - proc registerSlice(t: Type) = - if t == nil or t.kind != tkSlice: return - let name = typeToCStr(t) - if sliceNames.contains(name): return - sliceNames.incl(name) - let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void" - sliceTypes.add((name, elem)) - - for s in module.structs: - for f in s.fields: - registerSlice(f.typ) - for e in module.enums: - for v in e.variants: - for ft in v.fields: - registerSlice(ft) - for nf in v.namedFields: - registerSlice(nf.typ) - - # Build dependency graph among structs, enums, and slice types. - # Edge A -> B means "A depends on B, so B must be emitted before A". - var deps: Table[string, seq[string]] - for s in module.structs: - deps[s.name] = @[] - for e in module.enums: - deps[e.name] = @[] - for st in sliceTypes: - deps[st.name] = @[] - - proc addDeps(node: string, t: Type) = - for dep in collectValueDeps(t): - if dep == node: continue - if localTypeNames.contains(dep) or sliceNames.contains(dep): - if dep notin deps[node]: - deps[node].add(dep) - - for s in module.structs: - for f in s.fields: - addDeps(s.name, f.typ) - for e in module.enums: - for v in e.variants: - for ft in v.fields: - addDeps(e.name, ft) - for nf in v.namedFields: - addDeps(e.name, nf.typ) - - # Topological sort (Kahn's algorithm). - var inDegree: Table[string, int] - var dependents: Table[string, seq[string]] - for node in deps.keys: - inDegree[node] = 0 - for node, nodeDeps in deps: - for d in nodeDeps: - if not inDegree.hasKey(d): inDegree[d] = 0 - inDegree[node] += 1 - dependents.mgetOrPut(d, @[]).add(node) - - var queue: seq[string] = @[] - for node, deg in inDegree: - if deg == 0: - queue.add(node) - - var sorted: seq[string] = @[] - while queue.len > 0: - let node = queue.pop() - sorted.add(node) - for depNode in dependents.getOrDefault(node): - inDegree[depNode] -= 1 - if inDegree[depNode] == 0: - queue.add(depNode) - - if sorted.len < deps.len: - # Cycle detected; fall back to a safe deterministic order. - sorted = @[] - for s in module.structs: sorted.add(s.name) - for e in module.enums: sorted.add(e.name) - for st in sliceTypes: sorted.add(st.name) - - # Map type names back to their definitions. - var structMap: Table[string, seq[tuple[name: string, typ: Type]]] - for s in module.structs: structMap[s.name] = s.fields - var enumMap: Table[string, seq[HirEnumVariant]] - for e in module.enums: enumMap[e.name] = e.variants - var sliceMap: Table[string, string] - for st in sliceTypes: sliceMap[st.name] = st.elem - - # Emit type definitions in dependency order. - for name in sorted: - if structMap.hasKey(name): - be.emitStructDef(name, structMap[name]) - elif enumMap.hasKey(name): - be.emitEnumDef(name, enumMap[name]) - elif sliceMap.hasKey(name): - be.emitSliceTypeDef(name, sliceMap[name]) - - # Collect and emit tuple typedefs used in the module (and nested tuples first). + # Emit tuple typedefs early — enums/structs may embed them by value + # (e.g. Box::Val((int,int)) → Tuple_int_int Val_0 in the union). var tupleTypes: seq[Type] = @[] var tupleNames: HashSet[string] proc registerTuple(t: Type) = @@ -840,6 +736,106 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s for tt in tupleTypes: be.emitTupleDef(tt) + # Collect slice types used in struct fields and enum payloads. + var sliceTypes: seq[tuple[name: string, elem: string]] = @[] + var sliceNames: HashSet[string] + proc registerSlice(t: Type) = + if t == nil or t.kind != tkSlice: return + let name = typeToCStr(t) + if sliceNames.contains(name): return + sliceNames.incl(name) + let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void" + sliceTypes.add((name, elem)) + + for s in module.structs: + for f in s.fields: + registerSlice(f.typ) + for e in module.enums: + for v in e.variants: + for ft in v.fields: + registerSlice(ft) + for nf in v.namedFields: + registerSlice(nf.typ) + + # Build dependency graph among structs, enums, and slice types. + # Edge A -> B means "A depends on B, so B must be emitted before A". + var deps: Table[string, seq[string]] + for s in module.structs: + deps[s.name] = @[] + for e in module.enums: + deps[e.name] = @[] + for st in sliceTypes: + deps[st.name] = @[] + + proc addDeps(node: string, t: Type) = + for dep in collectValueDeps(t): + if dep == node: continue + if localTypeNames.contains(dep) or sliceNames.contains(dep): + if dep notin deps[node]: + deps[node].add(dep) + + for s in module.structs: + for f in s.fields: + addDeps(s.name, f.typ) + for e in module.enums: + for v in e.variants: + for ft in v.fields: + addDeps(e.name, ft) + for nf in v.namedFields: + addDeps(e.name, nf.typ) + # Multi-field / named-field nested struct must be defined before the enum + if v.fields.len > 1 or v.namedFields.len > 0: + addDeps(e.name, makeNamed(e.name & "_" & v.name & "_Payload")) + + # Topological sort (Kahn's algorithm). + var inDegree: Table[string, int] + var dependents: Table[string, seq[string]] + for node in deps.keys: + inDegree[node] = 0 + for node, nodeDeps in deps: + for d in nodeDeps: + if not inDegree.hasKey(d): inDegree[d] = 0 + inDegree[node] += 1 + dependents.mgetOrPut(d, @[]).add(node) + + var queue: seq[string] = @[] + for node, deg in inDegree: + if deg == 0: + queue.add(node) + + var sorted: seq[string] = @[] + while queue.len > 0: + let node = queue.pop() + sorted.add(node) + for depNode in dependents.getOrDefault(node): + inDegree[depNode] -= 1 + if inDegree[depNode] == 0: + queue.add(depNode) + + if sorted.len < deps.len: + # Cycle detected; fall back to a safe deterministic order. + sorted = @[] + for s in module.structs: sorted.add(s.name) + for e in module.enums: sorted.add(e.name) + for st in sliceTypes: sorted.add(st.name) + + # Map type names back to their definitions. + var structMap: Table[string, seq[tuple[name: string, typ: Type]]] + for s in module.structs: structMap[s.name] = s.fields + var enumMap: Table[string, seq[HirEnumVariant]] + for e in module.enums: enumMap[e.name] = e.variants + var sliceMap: Table[string, string] + for st in sliceTypes: sliceMap[st.name] = st.elem + + # Emit type definitions in dependency order. + for name in sorted: + if structMap.hasKey(name): + be.emitStructDef(name, structMap[name]) + elif enumMap.hasKey(name): + be.emitEnumDef(name, enumMap[name]) + elif sliceMap.hasKey(name): + be.emitSliceTypeDef(name, sliceMap[name]) + # Fat function-pointer typedefs (BuxFn_*) — before forward decls that use them var fatTypes: seq[Type] = @[] var fatNames: HashSet[string] diff --git a/bootstrap/sema.nim b/bootstrap/sema.nim index ec1d8c8..576b068 100644 --- a/bootstrap/sema.nim +++ b/bootstrap/sema.nim @@ -1330,12 +1330,18 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum: # Look for the field in enum variants for variant in enumSym.decl.declEnumVariants: - # Check positional fields: Ok_0, Ok_1, etc. + # Multi-field / named-field variant: data.Variant → Enum_Variant_Payload + # (suffix avoids clashing with tag constant Enum_Variant) + if variant.fields.len > 1 and variant.name == expr.exprFieldName: + return makeNamed(enumName & "_" & variant.name & "_Payload") + if variant.namedFields.len > 0 and variant.name == expr.exprFieldName: + return makeNamed(enumName & "_" & variant.name & "_Payload") + # Single positional fields: Ok_0, Ok_1, etc. (flat on the union) for i, f in variant.fields: let fieldName = variant.name & "_" & $i if fieldName == expr.exprFieldName: return sema.resolveType(f) - # Check named fields + # Named fields nested under data.Variant.name for nf in variant.namedFields: if nf.name == expr.exprFieldName: return sema.resolveType(nf.ftype) @@ -1378,7 +1384,28 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = else: sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") else: - sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") + # Synthetic nested multi-field type Enum_Variant_Payload — generated for + # multi-field / named-field algebraic variants (not a user-declared type). + var foundNested = false + for (_, gsym) in sema.globalScope.table.pairs: + if gsym.decl == nil or gsym.decl.kind != dkEnum: continue + let ename = gsym.decl.declEnumName + for variant in gsym.decl.declEnumVariants: + let nestedName = ename & "_" & variant.name & "_Payload" + if nestedName != objType.name: continue + foundNested = true + for i, f in variant.fields: + let fieldName = variant.name & "_" & $i + if fieldName == expr.exprFieldName: + return sema.resolveType(f) + for nf in variant.namedFields: + if nf.name == expr.exprFieldName: + return sema.resolveType(nf.ftype) + sema.emitError(expr.loc, &"nested variant type '{objType.name}' has no field '{expr.exprFieldName}'") + return makeUnknown() + if not foundNested: + sema.emitError(expr.loc, &"undeclared type '{objType.name}'") + return makeUnknown() elif objType.kind == tkDynRef: # Trait object: methods come from the interface let ifaceName = objType.name diff --git a/docs/LanguageRef.md b/docs/LanguageRef.md index 69f8237..304b2ef 100644 --- a/docs/LanguageRef.md +++ b/docs/LanguageRef.md @@ -345,10 +345,21 @@ enum Result { Err(String) } +enum Pair { + Two(int, int), // multi-field → nested payload + One(int), // single-field → flat data.One_0 + None +} + func Main() -> int { let r: Result = Result { tag: Result_Ok }; r.data.Ok_0 = 42; + // Multi-field construction: data.Variant.Variant_i + var p: Pair = Pair { tag: Pair_Two }; + p.data.Two.Two_0 = 3; + p.data.Two.Two_1 = 4; + if r.tag == Result_Ok { PrintInt(r.data.Ok_0); } @@ -356,6 +367,10 @@ func Main() -> int { } ``` +Layout notes: +- Single positional field: flat union member `data.Variant_0` +- Multi-field: nested payload `data.Variant.Variant_0` / `data.Variant.Variant_1` (C type `Enum_Variant_Payload`) + --- ## Pattern Matching @@ -383,6 +398,7 @@ Supported patterns: - Identifier catch-all: `name` (binds whole subject) - Range: `1..9`, `1..=9` - Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)` +- **Nested**: `Box::Val((a, b))`, `Shape::Dot(Point { x, y })` - **Tuple patterns**: `(a, b)` → binds `subject._0`, `subject._1` - **Struct patterns**: `Point { x: px, y: py }` or shorthand `Point { x, y }` - Guard patterns: parsed; full lowering still evolving @@ -396,6 +412,14 @@ match p { Point { x, y } => x * 10 + y, _ => -1 } +match bx { + Box::Val((a, c)) => a + c, + Box::Empty => 0 +} +match sh { + Shape::Dot(Point { x, y }) => x * 10 + y, + Shape::Empty => -1 +} // Multi-statement arm bodies (block expression; last expr is the value) match n { diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index ea4c056..17ebc2f 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -285,9 +285,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth) --- +## Сесия 17 (deeper nested patterns + multi-field enum layout) + +1. **Multi-field payload type:** nested struct `Enum_Variant_Payload` (suffix avoids clash with tag `Enum_Variant`) +2. Bootstrap: full `resolveTypeExpr` for enum field types (tuples/pointers); sema synthetic field lookup on payload types; `data.Two.Two_0` construction +3. LIR C backend: emit tuple typedefs **before** enums that embed them; topo deps for `*_Payload` +4. Selfhost: enum variants parse full types (`parserParseType`) — fixes `Val((int,int))`; store `fieldTypeName*`; structs before enums (e.g. `Dot(Point)`) +5. Patterns: `Pair::Two(a, b)`, `Box::Val((a, c))`, `Shape::Dot(Point { x, y })` + block arms +6. Example: `examples/nested_patterns.bux` +7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ + +--- + ## Следващи стъпки -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 => …`) +1. LSP: wire hover types from real sema +2. Generic type inference for `Iter_Map` without explicit `` +3. Match arm guards (`p if cond => …`) +4. Pattern binding name shadowing (C locals are function-scoped) diff --git a/examples/nested_patterns.bux b/examples/nested_patterns.bux new file mode 100644 index 0000000..7209f3b --- /dev/null +++ b/examples/nested_patterns.bux @@ -0,0 +1,79 @@ +import Std::Io::{PrintLine, PrintInt}; +import Std::Test::{Test_AssertEqInt, Test_Pass}; + +enum Pair { + Two(int, int), + One(int), + None +} + +enum Box { + Val((int, int)), + Empty +} + +struct Point { + x: int, + y: int, +} + +enum Shape { + Dot(Point), + Empty +} + +func SumPair(p: Pair) -> int { + match p { + Pair::Two(a, b) => a + b, + Pair::One(x) => x, + Pair::None => 0 + } +} + +func SumBox(bx: Box) -> int { + match bx { + Box::Val((a, c)) => a + c, + Box::Empty => 0 + } +} + +func SumShape(sh: Shape) -> int { + match sh { + Shape::Dot(Point { x, y }) => x * 10 + y, + Shape::Empty => -1 + } +} + +func Main() -> int { + var p: Pair = Pair { tag: Pair_Two }; + p.data.Two.Two_0 = 3; + p.data.Two.Two_1 = 4; + Test_AssertEqInt(SumPair(p), 7); + + var p1: Pair = Pair { tag: Pair_One }; + p1.data.One_0 = 42; + Test_AssertEqInt(SumPair(p1), 42); + + var bx: Box = Box { tag: Box_Val }; + bx.data.Val_0 = (10, 20); + Test_AssertEqInt(SumBox(bx), 30); + + let pt: Point = Point { x: 2, y: 5 }; + var sh: Shape = Shape { tag: Shape_Dot }; + sh.data.Dot_0 = pt; + Test_AssertEqInt(SumShape(sh), 25); + + let z: int = match bx { + Box::Val((a, c)) => { + let s: int = a + c; + s * 2 + }, + Box::Empty => 0 + }; + Test_AssertEqInt(z, 60); + + PrintInt(SumPair(p)); + PrintLine(""); + Test_Pass("nested_patterns"); + return 0; +} diff --git a/src/c_backend.bux b/src/c_backend.bux index 562b0d9..3557a03 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -1309,6 +1309,42 @@ func CBackend_Generate(mod: *HirModule) -> String { } StringBuilder_Append(&cbe.sb, "\n"); + // Tuple typedefs before enums/structs that embed them by value + StringBuilder_Append(&cbe.sb, "/* Tuple types */\n"); + StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n"); + StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n"); + StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n"); + + // Struct definitions before enums (enums may embed structs by value, e.g. Shape::Dot(Point)) + // Pass 1: emit structs with no value-typed struct fields (leaf structs) + si = 0; + while si < mod.structCount { + if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { + si = si + 1; + continue; + } + if CBE_StructHasValueStructField(&mod.structs[si]) { + si = si + 1; + continue; + } + CBE_EmitStructDef(cbe, &mod.structs[si]); + si = si + 1; + } + // Pass 2: emit structs that contain value-typed struct fields + si = 0; + while si < mod.structCount { + if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { + si = si + 1; + continue; + } + if !CBE_StructHasValueStructField(&mod.structs[si]) { + si = si + 1; + continue; + } + CBE_EmitStructDef(cbe, &mod.structs[si]); + si = si + 1; + } + // Enum definitions var ei: int = 0; while ei < mod.enumCount { @@ -1364,22 +1400,28 @@ func CBackend_Generate(mod: *HirModule) -> String { while vi < en.variantCount { let ev: *HirEnumVariant = &en.variants[vi]; if ev.fieldCount > 0 { + // Prefer stored C type name (handles Tuple_*, named types) + var ft0: String = CBackend_TypeToC(ev.fieldType0); + if !String_Eq(ev.fieldTypeName0, "") { ft0 = ev.fieldTypeName0; } + var ft1: String = CBackend_TypeToC(ev.fieldType1); + if !String_Eq(ev.fieldTypeName1, "") { ft1 = ev.fieldTypeName1; } if ev.fieldCount == 1 { StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0)); + StringBuilder_Append(&cbe.sb, ft0); StringBuilder_Append(&cbe.sb, " "); StringBuilder_Append(&cbe.sb, ev.name); StringBuilder_Append(&cbe.sb, "_0;\n"); } else { + // Nested anonymous struct (layout matches bootstrap data.Variant.Variant_i) StringBuilder_Append(&cbe.sb, " struct {\n"); StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0)); + StringBuilder_Append(&cbe.sb, ft0); StringBuilder_Append(&cbe.sb, " "); StringBuilder_Append(&cbe.sb, ev.fieldName0); StringBuilder_Append(&cbe.sb, ";\n"); if ev.fieldCount > 1 { StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType1)); + StringBuilder_Append(&cbe.sb, ft1); StringBuilder_Append(&cbe.sb, " "); StringBuilder_Append(&cbe.sb, ev.fieldName1); StringBuilder_Append(&cbe.sb, ";\n"); @@ -1423,45 +1465,9 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, "\n"); } - // Struct definitions (skip generics) - // Pass 1: emit structs with no value-typed struct fields (leaf structs) - si = 0; - while si < mod.structCount { - if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { - si = si + 1; - continue; - } - if CBE_StructHasValueStructField(&mod.structs[si]) { - si = si + 1; - continue; - } - CBE_EmitStructDef(cbe, &mod.structs[si]); - si = si + 1; - } - // Pass 2: emit structs that contain value-typed struct fields - si = 0; - while si < mod.structCount { - if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { - si = si + 1; - continue; - } - if !CBE_StructHasValueStructField(&mod.structs[si]) { - si = si + 1; - continue; - } - CBE_EmitStructDef(cbe, &mod.structs[si]); - si = si + 1; - } - // Fat function-pointer typedefs (BuxFn_*) — before forward decls CBE_EmitFatFuncTypedefs(cbe, mod); - // Common tuple struct types - StringBuilder_Append(&cbe.sb, "/* Tuple types */\n"); - StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n"); - StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n"); - StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n"); - // Env structs for capturing closures (no static instance — heap per value) var ei2: int = 0; while ei2 < mod.funcCount { diff --git a/src/hir.bux b/src/hir.bux index 57bae94..aaa87a4 100644 --- a/src/hir.bux +++ b/src/hir.bux @@ -126,8 +126,10 @@ struct HirEnumVariant { fieldCount: int; fieldType0: int; fieldName0: String; + fieldTypeName0: String; // C type name (e.g. "int", "Tuple_int_int") fieldType1: int; fieldName1: String; + fieldTypeName1: String; } // --------------------------------------------------------------------------- diff --git a/src/hir_lower.bux b/src/hir_lower.bux index dd81390..89c1a8d 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -782,9 +782,12 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, dataLoad.child1 = dataPtr; dataLoad.typeName = String_Concat(enumName, "_Data"); - // Multi-field: nested struct data.Variant; single-field: flat data.Variant_0 + // Multi-field: nested struct data.Variant (anonymous or Enum_Variant_Payload); + // single-field: flat data.Variant_0 var payloadBase: *HirNode = dataLoad; if fieldCount > 1 { + let nestedName: String = String_Concat(String_Concat(enumName, "_"), + String_Concat(variantName, "_Payload")); let vPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; vPtr.kind = hFieldPtr; vPtr.line = line; @@ -796,7 +799,7 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern, vLoad.line = line; vLoad.column = col; vLoad.child1 = vPtr; - vLoad.typeName = variantName; + vLoad.typeName = nestedName; payloadBase = vLoad; } @@ -3977,10 +3980,12 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { // Positional field names: Variant_0, Variant_1 (matches data.Variant_i / nested struct) hm.enums[ei].variants[vi].fieldName0 = String_Concat(v.name, "_0"); hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0); + hm.enums[ei].variants[vi].fieldTypeName0 = v.fieldTypeName0; } if v.fieldCount > 1 { hm.enums[ei].variants[vi].fieldName1 = String_Concat(v.name, "_1"); hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1); + hm.enums[ei].variants[vi].fieldTypeName1 = v.fieldTypeName1; } } vi = vi + 1; diff --git a/src/parser.bux b/src/parser.bux index c37f9aa..5f0d2c8 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -148,6 +148,28 @@ func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken { // Type parsing // --------------------------------------------------------------------------- +// C-friendly type name from a TypeExpr (named, tuple, pointer, …). +func parserTypeExprCName(te: *TypeExpr) -> String { + if te == null as *TypeExpr { return "int"; } + if te.kind == tekTuple { + if !String_Eq(te.typeName, "") { return te.typeName; } + return "Tuple_Empty"; + } + if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { + if te.pointerPointee != null as *TypeExpr { + return String_Concat(parserTypeExprCName(te.pointerPointee), "*"); + } + return "void*"; + } + if !String_Eq(te.typeName, "") { + if String_Eq(te.typeName, "String") || String_Eq(te.typeName, "str") { + return "const char*"; + } + return te.typeName; + } + return "int"; +} + func parserParseType(p: *Parser) -> *TypeExpr { let line: uint32 = parserCurToken(p).line; let col: uint32 = parserCurToken(p).column; @@ -1953,14 +1975,14 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl { v.fieldTypeName0 = ""; v.fieldTypeName1 = ""; - // Optional (Type, Type) data + // Optional payload: (Type, Type) or (Tuple, ...) — full type expressions if parserMatch(p, tkLParen) { - let t0: LexToken = parserExpect(p, tkIdent, "expected data type"); - v.fieldTypeName0 = t0.text; + let te0: *TypeExpr = parserParseType(p); + v.fieldTypeName0 = parserTypeExprCName(te0); v.fieldCount = 1; if parserMatch(p, tkComma) { - let t1: LexToken = parserExpect(p, tkIdent, "expected data type"); - v.fieldTypeName1 = t1.text; + let te1: *TypeExpr = parserParseType(p); + v.fieldTypeName1 = parserTypeExprCName(te1); v.fieldCount = 2; } discard parserExpect(p, tkRParen, "expected ')'");