// hir_lower.bux — HIR lowering: AST → HIR transformation (ported from hir_lower.nim) // Transforms the typed AST into a lower-level IR suitable for code generation. module HirLower { extern func bux_str_slice(s: String, start: uint, len: uint) -> String; extern func bux_strlen(s: String) -> uint; // --------------------------------------------------------------------------- // Lowering context // --------------------------------------------------------------------------- struct LowerCtx { module: *Module, scope: *Scope, funcs: *HirFunc, funcCount: int, externFuncs: *HirFunc, externCount: int, varCounter: int, tryCounter: int, closureDepth: int, currentClosureExpr: *Expr, envInstanceName: String, hm: *HirModule, // Generic monomorphization genFuncCount: int, genFuncs: *Decl, genStructCount: int, genStructs: *Decl, // Type substitution (active during generic instance lowering) substParam0: String, substArg0: String, substParam1: String, substArg1: String, // Borrow checker state checkedFunc: bool, releaseFunc: bool, } // --------------------------------------------------------------------------- // TypeExpr.kind → Type.kind resolver // TypeExpr.kind values (0-5) overlap with Type.kind values — this // resolves the correct Type.kind for codegen. // --------------------------------------------------------------------------- func Lcx_ResolveTypeKindFromName(name: String) -> int { if String_Eq(name, "void") { return tyVoid; } if String_Eq(name, "bool") { return tyBool; } if String_Eq(name, "bool8") { return tyBool8; } if String_Eq(name, "bool16") { return tyBool16; } if String_Eq(name, "bool32") { return tyBool32; } if String_Eq(name, "char8") { return tyChar8; } if String_Eq(name, "char16") { return tyChar16; } if String_Eq(name, "char32") { return tyChar32; } if String_Eq(name, "String") { return tyStr; } if String_Eq(name, "str") { return tyStr; } if String_Eq(name, "int8") { return tyInt8; } if String_Eq(name, "int16") { return tyInt16; } if String_Eq(name, "int32") { return tyInt32; } if String_Eq(name, "int64") { return tyInt64; } if String_Eq(name, "int") { return tyInt; } if String_Eq(name, "uint8") { return tyUInt8; } if String_Eq(name, "uint16") { return tyUInt16; } if String_Eq(name, "uint32") { return tyUInt32; } if String_Eq(name, "uint64") { return tyUInt64; } if String_Eq(name, "uint") { return tyUInt; } if String_Eq(name, "float32") { return tyFloat32; } if String_Eq(name, "float64") { return tyFloat64; } if String_Eq(name, "float") { return tyFloat64; } return tyNamed; } func Lcx_TypeKindToName(kind: int) -> String { if kind == tyVoid { return "void"; } if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; } if kind == tyChar8 { return "char"; } if kind == tyChar16 { return "uint16"; } if kind == tyChar32 { return "uint32"; } if kind == tyStr { return "String"; } if kind == tyInt8 { return "int8"; } if kind == tyInt16 { return "int16"; } if kind == tyInt32 { return "int32"; } if kind == tyInt64 { return "int64"; } if kind == tyInt { return "int"; } if kind == tyUInt8 { return "uint8"; } if kind == tyUInt16 { return "uint16"; } if kind == tyUInt32 { return "uint32"; } if kind == tyUInt64 { return "uint64"; } if kind == tyUInt { return "uint"; } if kind == tyFloat32 { return "float32"; } if kind == tyFloat64 { return "float64"; } if kind == tyPointer { return "void*"; } return "int"; } func Lcx_ResolveTypeKind(te: *TypeExpr) -> int { if te == null as *TypeExpr { return tyUnknown; } if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { return tyPointer; } if te.kind == tekSlice { return tySlice; } if te.kind == tekTuple { return tyTuple; } if te.kind == tekFunc { return tyFunc; } return Lcx_ResolveTypeKindFromName(te.typeName); } // --------------------------------------------------------------------------- // Type substitution for generic monomorphization // --------------------------------------------------------------------------- func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr { if te == null as *TypeExpr { return te; } // Generic named type with type args: check if concrete or parametric if te.kind == tekNamed && te.typeArgCount > 0 { let genStruct: *Decl = Lcx_FindGenericStruct(ctx, te.typeName); if genStruct != null as *Decl { // Check if type args are the struct's own type params (parametric) var isParametric: bool = false; if te.typeArgCount > 0 && String_Eq(te.typeArgName0, genStruct.typeParam0) { isParametric = true; } if te.typeArgCount > 1 && String_Eq(te.typeArgName1, genStruct.typeParam1) { isParametric = true; } // If parametric and NOT inside a generic instantiation, keep as generic (don't mangle) if isParametric && String_Eq(ctx.substParam0, "") && String_Eq(ctx.substParam1, "") { return te; } // Otherwise: substitute active type params and mangle to concrete name let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; r.kind = tekNamed; r.line = te.line; r.column = te.column; r.typeArgCount = te.typeArgCount; r.typeArgName0 = te.typeArgName0; r.typeArgName1 = te.typeArgName1; if String_Eq(r.typeArgName0, ctx.substParam0) { r.typeArgName0 = ctx.substArg0; } if String_Eq(r.typeArgName0, ctx.substParam1) { r.typeArgName0 = ctx.substArg1; } if String_Eq(r.typeArgName1, ctx.substParam0) { r.typeArgName1 = ctx.substArg0; } if String_Eq(r.typeArgName1, ctx.substParam1) { r.typeArgName1 = ctx.substArg1; } r.typeName = Lcx_MangleName(te.typeName, r.typeArgName0, r.typeArgName1, te.typeArgCount); Lcx_GenerateStructInstance(ctx, genStruct, r.typeArgName0, r.typeArgName1, te.typeArgCount); return r; } } // Named type that is a type parameter (only when in instance mode) if te.kind == tekNamed { if String_Eq(te.typeName, ctx.substParam0) { let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; r.kind = tekNamed; r.typeName = ctx.substArg0; r.line = te.line; r.column = te.column; return r; } if String_Eq(te.typeName, ctx.substParam1) { let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; r.kind = tekNamed; r.typeName = ctx.substArg1; r.line = te.line; r.column = te.column; return r; } } // Pointer type: substitute recursively if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; r.kind = tekPointer; r.pointerPointee = Lcx_SubstituteType(ctx, te.pointerPointee); if r.pointerPointee != null as *TypeExpr && !String_Eq(r.pointerPointee.typeName, "") { r.typeName = String_Concat(r.pointerPointee.typeName, "*"); } r.line = te.line; r.column = te.column; return r; } return te; } // Build C function-pointer type string from a tekFunc TypeExpr, e.g. "int (*)(int)" func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String { if te == null as *TypeExpr || te.kind != tekFunc { return "void (*)(void)"; } var retName: String = "void"; if te.funcRet != null as *TypeExpr { if te.funcRet.kind == tekPointer && te.funcRet.pointerPointee != null as *TypeExpr { retName = String_Concat(te.funcRet.pointerPointee.typeName, "*"); } else { retName = te.funcRet.typeName; } if String_Eq(retName, "") { retName = "int"; } } var result: String = retName; result = String_Concat(result, " (*)("); var cur: *TypeExprList = te.funcParams; var first: bool = true; while cur != null as *TypeExprList { if !first { result = String_Concat(result, ", "); } var pName: String = "int"; if cur.te.kind == tekPointer && cur.te.pointerPointee != null as *TypeExpr { pName = String_Concat(cur.te.pointerPointee.typeName, "*"); } else { pName = cur.te.typeName; } if String_Eq(pName, "") { pName = "int"; } result = String_Concat(result, pName); first = false; cur = cur.next; } result = String_Concat(result, ")"); return result; } // --------------------------------------------------------------------------- // Generic monomorphization helpers // --------------------------------------------------------------------------- func Lcx_FindGenericFunc(ctx: *LowerCtx, name: String) -> *Decl { var i: int = 0; while i < ctx.genFuncCount { if String_Eq(ctx.genFuncs[i].strValue, name) { return &ctx.genFuncs[i]; } i = i + 1; } return null as *Decl; } func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl { var i: int = 0; while i < ctx.genStructCount { if String_Eq(ctx.genStructs[i].strValue, name) { return &ctx.genStructs[i]; } i = i + 1; } return null as *Decl; } func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { let r: String = String_Concat(base, "_"); r = String_Concat(r, typeArg0); if typeArgCount > 1 && !String_Eq(typeArg1, "") { r = String_Concat(r, "_"); r = String_Concat(r, typeArg1); } return r; } func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { if String_Eq(genDecl.strValue, "") { return ""; } let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount); // Check if already generated (linear search in hm.structs) var i: int = 0; while i < ctx.hm.structCount { if String_Eq(ctx.hm.structs[i].name, mangled) { return mangled; } i = i + 1; } // Save old substitution let oldParam0: String = ctx.substParam0; let oldArg0: String = ctx.substArg0; let oldParam1: String = ctx.substParam1; let oldArg1: String = ctx.substArg1; ctx.substParam0 = genDecl.typeParam0; ctx.substArg0 = typeArg0; ctx.substParam1 = genDecl.typeParam1; ctx.substArg1 = typeArg1; // Generate concrete HirStruct with substituted field types // Reserve the slot BEFORE processing fields so nested generic instantiations // get their own distinct indices and cannot overwrite our slot. let si: int = ctx.hm.structCount; ctx.hm.structCount = ctx.hm.structCount + 1; ctx.hm.structs[si].name = mangled; ctx.hm.structs[si].fieldCount = genDecl.fieldCount; ctx.hm.structs[si].fields = bux_alloc(genDecl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField; var fi: int = 0; while fi < genDecl.fieldCount { let fname: String = genDecl.fields[fi].name; let ftype: *TypeExpr = genDecl.fields[fi].refFieldType; ctx.hm.structs[si].fields[fi].name = fname; if ftype != null as *TypeExpr { let subTe: *TypeExpr = Lcx_SubstituteType(ctx, ftype); if subTe.kind == tekPointer && subTe.pointerPointee != null as *TypeExpr { if !String_Eq(subTe.pointerPointee.typeName, "") { ctx.hm.structs[si].fields[fi].typeName = String_Concat(subTe.pointerPointee.typeName, "*"); } } else if !String_Eq(subTe.typeName, "") { ctx.hm.structs[si].fields[fi].typeName = subTe.typeName; } } fi = fi + 1; } // Restore old substitution ctx.substParam0 = oldParam0; ctx.substArg0 = oldArg0; ctx.substParam1 = oldParam1; ctx.substArg1 = oldArg1; return mangled; } func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount); // Check if already generated (linear search in ctx.funcs) var i: int = 0; while i < ctx.funcCount { if String_Eq(ctx.funcs[i].name, mangled) { return mangled; } i = i + 1; } // Save old substitution let oldParam0: String = ctx.substParam0; let oldArg0: String = ctx.substArg0; let oldParam1: String = ctx.substParam1; let oldArg1: String = ctx.substArg1; // Set up substitution ctx.substParam0 = genDecl.typeParam0; ctx.substArg0 = typeArg0; ctx.substParam1 = genDecl.typeParam1; ctx.substArg1 = typeArg1; // Lower the generic function with substitution active let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl); f.name = mangled; // Add to module ctx.funcs[ctx.funcCount] = *f; ctx.funcCount = ctx.funcCount + 1; // Restore old substitution ctx.substParam0 = oldParam0; ctx.substArg0 = oldArg0; ctx.substParam1 = oldParam1; ctx.substArg1 = oldArg1; return mangled; } // Strip type-arg suffix from a mangled generic instance name. // E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair". func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { var suffix: String = "_"; suffix = String_Concat(suffix, typeArg0); if typeArgCount > 1 && !String_Eq(typeArg1, "") { suffix = String_Concat(suffix, "_"); suffix = String_Concat(suffix, typeArg1); } let fullLen: int = bux_strlen(typeName) as int; let suffixLen: int = bux_strlen(suffix) as int; if fullLen > suffixLen { let endPart: String = bux_str_slice(typeName, (fullLen - suffixLen) as uint, suffixLen as uint); if String_Eq(endPart, suffix) { return bux_str_slice(typeName, 0, (fullLen - suffixLen) as uint); } } return typeName; } // --------------------------------------------------------------------------- // Array type helpers for bounds-checking desugaring // --------------------------------------------------------------------------- func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool { if te == null as *TypeExpr { return false; } if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { te = te.pointerPointee; } if te.kind == tekNamed { if String_Eq(te.typeName, "Array") { return true; } let name: String = te.typeName; if name[0] as int == 65 && name[1] as int == 114 && name[2] as int == 114 && name[3] as int == 97 && name[4] as int == 121 && name[5] as int == 95 { return true; } } return false; } func Lcx_GetArrayElemType(te: *TypeExpr) -> String { if te == null as *TypeExpr { return ""; } if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { te = te.pointerPointee; } if te.kind == tekNamed { if String_Eq(te.typeName, "Array") && te.typeArgCount > 0 { return te.typeArgName0; } let name: String = te.typeName; if name[0] as int == 65 && name[1] as int == 114 && name[2] as int == 114 && name[3] as int == 97 && name[4] as int == 121 && name[5] as int == 95 { let prefixLen: uint = 6; let totalLen: uint = bux_strlen(name); if totalLen > prefixLen { return bux_str_slice(name, prefixLen, totalLen - prefixLen); } } } return ""; } // --------------------------------------------------------------------------- // Expression lowering // --------------------------------------------------------------------------- func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { if expr == null as *Expr { return null as *HirNode; } let line: uint32 = expr.line; let col: uint32 = expr.column; let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; n.kind = hBlock; n.line = line; n.column = col; let kind: int = expr.kind; // Literal if kind == ekLiteral { n.kind = hLit; n.intValue = expr.tokKind; n.strValue = expr.tokText; return n; } // Identifier → variable reference if kind == ekIdent { // Capture rewriting: if inside closure body and this ident is captured, // emit field access on env instance instead of bare variable if ctx.closureDepth > 0 && ctx.currentClosureExpr != null as *Expr && !String_Eq(ctx.envInstanceName, "") { let capCount: int = ctx.currentClosureExpr.captureCount; var ci: int = 0; var isCaptured: bool = false; var capType: int = 0; while ci < capCount { var capName: String = ""; if ci == 0 { capName = ctx.currentClosureExpr.captureName0; capType = ctx.currentClosureExpr.captureType0; } else if ci == 1 { capName = ctx.currentClosureExpr.captureName1; capType = ctx.currentClosureExpr.captureType1; } else if ci == 2 { capName = ctx.currentClosureExpr.captureName2; capType = ctx.currentClosureExpr.captureType2; } else if ci == 3 { capName = ctx.currentClosureExpr.captureName3; capType = ctx.currentClosureExpr.captureType3; } else if ci == 4 { capName = ctx.currentClosureExpr.captureName4; capType = ctx.currentClosureExpr.captureType4; } else if ci == 5 { capName = ctx.currentClosureExpr.captureName5; capType = ctx.currentClosureExpr.captureType5; } else if ci == 6 { capName = ctx.currentClosureExpr.captureName6; capType = ctx.currentClosureExpr.captureType6; } else if ci == 7 { capName = ctx.currentClosureExpr.captureName7; capType = ctx.currentClosureExpr.captureType7; } if String_Eq(capName, expr.strValue) { isCaptured = true; } ci = ci + 1; } if isCaptured { n.kind = hFieldAccess; n.strValue = expr.strValue; let baseNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; baseNode.kind = hVar; baseNode.strValue = ctx.envInstanceName; n.child1 = baseNode; n.typeKind = capType; n.typeName = Lcx_TypeKindToName(capType); return n; } } n.kind = hVar; n.strValue = expr.strValue; let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue); n.typeKind = sym.typeKind; if expr.refType != null as *TypeExpr { n.typeName = expr.refType.typeName; } if sym.typeName != null as String && !String_Eq(sym.typeName, "") { n.typeName = sym.typeName; } return n; } // self → variable reference named "self" if kind == ekSelf { n.kind = hVar; n.strValue = "self"; let sym: Symbol = Scope_Lookup(ctx.scope, "self"); n.typeKind = sym.typeKind; if sym.typeName != null as String && !String_Eq(sym.typeName, "") { n.typeName = sym.typeName; } return n; } // Binary if kind == ekBinary { // Assignment operator → use hAssign if expr.intValue == tkAssign { n.kind = hAssign; n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } // Operator overloading: try method call var opMethodName: String = ""; if expr.intValue == tkPlus { opMethodName = "operator_add"; } else if expr.intValue == tkMinus { opMethodName = "operator_sub"; } else if expr.intValue == tkStar { opMethodName = "operator_mul"; } else if expr.intValue == tkSlash { opMethodName = "operator_div"; } else if expr.intValue == tkPercent { opMethodName = "operator_mod"; } else if expr.intValue == tkEq { opMethodName = "operator_eq"; } else if expr.intValue == tkNe { opMethodName = "operator_ne"; } else if expr.intValue == tkLt { opMethodName = "operator_lt"; } else if expr.intValue == tkLe { opMethodName = "operator_le"; } else if expr.intValue == tkGt { opMethodName = "operator_gt"; } else if expr.intValue == tkGe { opMethodName = "operator_ge"; } else if expr.intValue == tkAmp { opMethodName = "operator_bitand"; } else if expr.intValue == tkPipe { opMethodName = "operator_bitor"; } else if expr.intValue == tkCaret { opMethodName = "operator_xor"; } else if expr.intValue == tkShl { opMethodName = "operator_shl"; } else if expr.intValue == tkShr { opMethodName = "operator_shr"; } if !String_Eq(opMethodName, "") { var receiverTypeName: String = ""; if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { let refTe: *TypeExpr = expr.child1.refType; if refTe.kind == tekNamed { receiverTypeName = refTe.typeName; } else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed { receiverTypeName = refTe.pointerPointee.typeName; } } if !String_Eq(receiverTypeName, "") { let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName); let sym: Symbol = Scope_Lookup(ctx.scope, funcName); if sym.kind == skFunc && sym.decl != null as *Decl { n.kind = hCall; n.strValue = funcName; let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1); // If method expects pointer/reference but receiver is a value, add & if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr { let paramKind: int = sym.decl.param0.refParamType.kind; if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef { if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = recv; n.child1 = addrNode; } else { n.child1 = recv; } } else { n.child1 = recv; } } else { n.child1 = recv; } n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } } } n.kind = hBinary; n.intValue = expr.intValue; // operator n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } // Unary if kind == ekUnary { n.kind = hUnary; n.intValue = expr.intValue; n.child1 = Lcx_LowerExpr(ctx, expr.child1); if expr.intValue == tkAmp { n.typeKind = tyPointer; if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekFunc { n.typeName = Lcx_BuildFuncTypeName(expr.child1.refType); } } return n; } // Call if kind == ekCall { // Method call desugaring: obj.method(args) → Type_method(obj, args) if expr.child1 != null as *Expr && expr.child1.kind == ekField { n.kind = hCall; let methodName: String = expr.child1.strValue; var receiverTypeName: String = ""; var receiverRefType: *TypeExpr = null as *TypeExpr; if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent { let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue); receiverTypeName = sym.typeName; receiverRefType = sym.refType; } if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr { receiverTypeName = expr.child1.child1.refType.typeName; receiverRefType = expr.child1.child1.refType; } var methodDecl: *Decl = null as *Decl; if !String_Eq(receiverTypeName, "") { // Strip trailing '*' from pointer type names (e.g. "Box*" → "Box") var baseName: String = receiverTypeName; let len: int = bux_strlen(baseName) as int; if len > 0 { let lastChar: String = bux_str_slice(baseName, (len - 1) as uint, 1); if String_Eq(lastChar, "*") { baseName = bux_str_slice(baseName, 0, (len - 1) as uint); } } n.strValue = String_Concat(baseName, "_"); n.strValue = String_Concat(n.strValue, methodName); // Generic method monomorphization: Box_Get on Box -> Box_Get_int var genericRecvType: *TypeExpr = receiverRefType; if genericRecvType != null as *TypeExpr && genericRecvType.kind == tekPointer && genericRecvType.pointerPointee != null as *TypeExpr { genericRecvType = genericRecvType.pointerPointee; } if genericRecvType != null as *TypeExpr && genericRecvType.typeArgCount > 0 { let baseTypeName: String = Lcx_StripTypeArgs(genericRecvType.typeName, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount); let baseMethodName: String = String_Concat(String_Concat(baseTypeName, "_"), methodName); let genDecl: *Decl = Lcx_FindGenericFunc(ctx, baseMethodName); if genDecl != null as *Decl { let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount); n.strValue = mangled; methodDecl = genDecl; } } } // Lower receiver as first argument let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); // Find method decl if not already found (non-generic case) if methodDecl == null as *Decl { let sym: Symbol = Scope_Lookup(ctx.scope, n.strValue); if sym.kind == skFunc && sym.decl != null as *Decl { methodDecl = sym.decl; } } // Auto-address if method expects pointer/reference but receiver is a value if methodDecl != null as *Decl && methodDecl.paramCount > 0 && methodDecl.param0.refParamType != null as *TypeExpr { let paramKind: int = methodDecl.param0.refParamType.kind; if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef { if expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr && expr.child1.child1.refType.kind != tekPointer && expr.child1.child1.refType.kind != tekRef && expr.child1.child1.refType.kind != tekMutRef { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = recv; n.child1 = addrNode; } else { n.child1 = recv; } } else { n.child1 = recv; } } else { n.child1 = recv; } // Lower remaining arguments from linked list var arg: *ExprList = expr.callArgs; var argIdx: int = 0; while arg != null as *ExprList { let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr); if argIdx == 0 { n.child2 = lowered; } else if argIdx == 1 { // Third argument — start linked list let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; firstExtra.node = lowered; firstExtra.next = null as *HirArgList; n.extraData = firstExtra as *void; n.extraCount = 1; } else { // Additional args — append to linked list var cur: *HirArgList = n.extraData as *HirArgList; while cur.next != null as *HirArgList { cur = cur.next; } let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; newNode.node = lowered; newNode.next = null as *HirArgList; cur.next = newNode; n.extraCount = n.extraCount + 1; } arg = arg.next; argIdx = argIdx + 1; } return n; } // Decide direct vs indirect call var isDirectFunc: bool = false; if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue); if sym.kind == skFunc { isDirectFunc = true; } } if isDirectFunc { n.kind = hCall; n.strValue = expr.child1.strValue; // Generic call monomorphization if expr.child1 != null as *Expr && expr.child1.genericTypeArgCount > 0 { let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue); if genDecl != null as *Decl { var typeArg0: String = expr.child1.genericTypeArg0; var typeArg1: String = expr.child1.genericTypeArg1; if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; } if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; } if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; } if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; } let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, expr.child1.genericTypeArgCount); n.strValue = mangled; } } // Lower arguments into child1/child2/extraData var arg: *ExprList = expr.callArgs; var argIdx: int = 0; while arg != null as *ExprList { let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr); if argIdx == 0 { n.child1 = lowered; } else if argIdx == 1 { n.child2 = lowered; } else if argIdx == 2 { let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; firstExtra.node = lowered; firstExtra.next = null as *HirArgList; n.extraData = firstExtra as *void; n.extraCount = 1; } else { var cur: *HirArgList = n.extraData as *HirArgList; while cur.next != null as *HirArgList { cur = cur.next; } let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; newNode.node = lowered; newNode.next = null as *HirArgList; cur.next = newNode; n.extraCount = n.extraCount + 1; } arg = arg.next; argIdx = argIdx + 1; } } else { n.kind = hCallIndirect; n.child1 = Lcx_LowerExpr(ctx, expr.child1); // Lower arguments into child2/child3/extraData (child1 is callee) var arg: *ExprList = expr.callArgs; var argIdx: int = 0; while arg != null as *ExprList { let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr); if argIdx == 0 { n.child2 = lowered; } else if argIdx == 1 { n.child3 = lowered; } else if argIdx == 2 { let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; firstExtra.node = lowered; firstExtra.next = null as *HirArgList; n.extraData = firstExtra as *void; n.extraCount = 1; } else { var cur: *HirArgList = n.extraData as *HirArgList; while cur.next != null as *HirArgList { cur = cur.next; } let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; newNode.node = lowered; newNode.next = null as *HirArgList; cur.next = newNode; n.extraCount = n.extraCount + 1; } arg = arg.next; argIdx = argIdx + 1; } } return n; } // Sizeof if kind == ekSizeOf { n.kind = hSizeOf; if expr.refType != null as *TypeExpr { let substTe: *TypeExpr = Lcx_SubstituteType(ctx, expr.refType); if substTe != null as *TypeExpr { n.typeName = substTe.typeName; } else { n.typeName = expr.refType.typeName; } } return n; } // Field access if kind == ekField { // Check if this is enum variant access: Color::Green if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue); if sym.decl != null as *Decl && sym.decl.kind == dkEnum { // Emit as variable reference: Color_Green n.kind = hVar; n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue); return n; } } // Simple enum .tag is the enum value itself if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekNamed && String_Eq(expr.strValue, "tag") { let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.refType.typeName); if sym.decl != null as *Decl && sym.decl.kind == dkEnum { var hasData: bool = false; if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { hasData = true; } if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { hasData = true; } if !hasData { return Lcx_LowerExpr(ctx, expr.child1); } } } n.kind = hFieldPtr; n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.strValue = expr.strValue; // Get struct type from base expr refType if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { n.typeName = expr.child1.refType.typeName; } return n; } // spawn Callee(args) if kind == ekSpawn { n.kind = hSpawn; n.boolValue = expr.boolValue; if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { n.strValue = expr.child1.strValue; } if expr.child2 != null as *Expr { n.child1 = Lcx_LowerExpr(ctx, expr.child2); } return n; } // expr.await if kind == ekAwait { n.kind = hAwait; n.child1 = Lcx_LowerExpr(ctx, expr.child1); return n; } // Index: arr[idx] if kind == ekIndex { // In @[Checked] functions, Array access goes through Array_Get with bounds check if ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) { let elemType: String = Lcx_GetArrayElemType(expr.child1.refType); if !String_Eq(elemType, "") { let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1); let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2); var isPtr: bool = false; if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekPointer { isPtr = true; } let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; callNode.kind = hCall; callNode.strValue = Lcx_MangleName("Array_Get", elemType, "", 1); callNode.line = line; callNode.column = col; let genGet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Get"); if genGet != null as *Decl { Lcx_GenerateFuncInstance(ctx, genGet, elemType, "", 1); } if isPtr { callNode.child1 = baseNode; } else { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = baseNode; callNode.child1 = addrNode; } callNode.child2 = idxNode; callNode.extraCount = 0; callNode.extraData = null as *void; return callNode; } } // For Array or *Array, desugar arr[idx] → arr.data[idx] if expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) { let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1); let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2); let fieldPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; fieldPtr.kind = hFieldPtr; fieldPtr.line = line; fieldPtr.column = col; fieldPtr.strValue = "data"; fieldPtr.child1 = baseNode; n.kind = hIndexPtr; n.child1 = fieldPtr; n.child2 = idxNode; return n; } // Operator overloading: try operator_index_get if expr.child1 != null as *Expr { var receiverTypeName: String = ""; if expr.child1.refType != null as *TypeExpr { let refTe: *TypeExpr = expr.child1.refType; if refTe.kind == tekNamed { receiverTypeName = refTe.typeName; } else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed { receiverTypeName = refTe.pointerPointee.typeName; } } if !String_Eq(receiverTypeName, "") { var funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get"); let sym: Symbol = Scope_Lookup(ctx.scope, funcName); var didMono: bool = false; // Generic monomorphization: if not found or is generic, monomorphize if sym.kind != skFunc || sym.decl == null as *Decl || sym.decl.typeParamCount > 0 { var typeArg0: String = ""; if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { let refTe2: *TypeExpr = expr.child1.refType; if refTe2.kind == tekNamed && refTe2.typeArgCount > 0 { typeArg0 = refTe2.typeArgName0; } else if refTe2.kind == tekPointer && refTe2.pointerPointee != null as *TypeExpr && refTe2.pointerPointee.typeArgCount > 0 { typeArg0 = refTe2.pointerPointee.typeArgName0; } } if !String_Eq(typeArg0, "") { let genericFuncName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get"); let genDecl: *Decl = Lcx_FindGenericFunc(ctx, genericFuncName); if genDecl != null as *Decl { funcName = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, "", 1); didMono = true; } } } let sym2: Symbol = Scope_Lookup(ctx.scope, funcName); let targetDecl: *Decl = sym2.decl; if didMono { targetDecl = sym.decl; } if sym2.kind == skFunc && sym2.decl != null as *Decl || didMono { n.kind = hCall; n.strValue = funcName; let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1); // If method expects pointer/reference but receiver is a value, add & if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr { let paramKind: int = targetDecl.param0.refParamType.kind; if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef { if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = recv; n.child1 = addrNode; } else { n.child1 = recv; } } else { n.child1 = recv; } } else { n.child1 = recv; } n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } } } n.kind = hIndexPtr; n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } // Assign: target = value if kind == ekAssign { // Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val) // Only for plain assignment (=), not compound operators (+=, -=, etc.) if expr.intValue == tkAssign && ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.child1.refType) { let elemType: String = Lcx_GetArrayElemType(expr.child1.child1.refType); if !String_Eq(elemType, "") { let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child2); let valNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2); var isPtr: bool = false; if expr.child1.child1.refType != null as *TypeExpr && expr.child1.child1.refType.kind == tekPointer { isPtr = true; } let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; callNode.kind = hCall; callNode.strValue = Lcx_MangleName("Array_Set", elemType, "", 1); callNode.line = line; callNode.column = col; let genSet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Set"); if genSet != null as *Decl { Lcx_GenerateFuncInstance(ctx, genSet, elemType, "", 1); } if isPtr { callNode.child1 = baseNode; } else { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = baseNode; callNode.child1 = addrNode; } callNode.child2 = idxNode; let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; extra.node = valNode; extra.next = null as *HirArgList; callNode.extraData = extra as *void; callNode.extraCount = 1; return callNode; } } // Operator overloading: try operator_index_set if expr.intValue == tkAssign && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr { var receiverTypeName: String = ""; let objExpr: *Expr = expr.child1.child1; if objExpr.refType != null as *TypeExpr { let refTe: *TypeExpr = objExpr.refType; if refTe.kind == tekNamed { receiverTypeName = refTe.typeName; } else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed { receiverTypeName = refTe.pointerPointee.typeName; } } if !String_Eq(receiverTypeName, "") { var funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set"); let sym: Symbol = Scope_Lookup(ctx.scope, funcName); var didMono: bool = false; // Generic monomorphization: if not found or is generic, monomorphize if sym.kind != skFunc || sym.decl == null as *Decl || sym.decl.typeParamCount > 0 { var typeArg0: String = ""; if objExpr != null as *Expr && objExpr.refType != null as *TypeExpr { let refTe2: *TypeExpr = objExpr.refType; if refTe2.kind == tekNamed && refTe2.typeArgCount > 0 { typeArg0 = refTe2.typeArgName0; } else if refTe2.kind == tekPointer && refTe2.pointerPointee != null as *TypeExpr && refTe2.pointerPointee.typeArgCount > 0 { typeArg0 = refTe2.pointerPointee.typeArgName0; } } if !String_Eq(typeArg0, "") { let genericFuncName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set"); let genDecl: *Decl = Lcx_FindGenericFunc(ctx, genericFuncName); if genDecl != null as *Decl { funcName = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, "", 1); didMono = true; } } } let sym2: Symbol = Scope_Lookup(ctx.scope, funcName); let targetDecl: *Decl = sym2.decl; if didMono { targetDecl = sym.decl; } if sym2.kind == skFunc && sym2.decl != null as *Decl || didMono { let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; callNode.kind = hCall; callNode.strValue = funcName; callNode.line = line; callNode.column = col; let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr); // If method expects pointer/reference but receiver is a value, add & if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr { let paramKind: int = targetDecl.param0.refParamType.kind; if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef { if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer && objExpr.refType.kind != tekRef && objExpr.refType.kind != tekMutRef { let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = recv; callNode.child1 = addrNode; } else { callNode.child1 = recv; } } else { callNode.child1 = recv; } } else { callNode.child1 = recv; } callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2); let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; extra.node = Lcx_LowerExpr(ctx, expr.child2); extra.next = null as *HirArgList; callNode.extraData = extra as *void; callNode.extraCount = 1; return callNode; } } } n.kind = hAssign; n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value return n; } // Closure: generate function and return address-of if kind == ekClosure { let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr); n.kind = hUnary; n.intValue = tkAmp; let varNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; varNode.kind = hVar; varNode.strValue = f.name; varNode.typeKind = tyFunc; n.child1 = varNode; n.typeKind = tyFunc; if expr.refType != null as *TypeExpr { n.typeName = Lcx_BuildFuncTypeName(expr.refType); } return n; } // Cast if kind == ekCast { n.kind = hCast; n.child1 = Lcx_LowerExpr(ctx, expr.child1); if expr.refType != null as *TypeExpr { let substTe: *TypeExpr = Lcx_SubstituteType(ctx, expr.refType); if substTe == null as *TypeExpr { substTe = expr.refType; } let resolvedKind: int = Lcx_ResolveTypeKind(substTe); n.typeKind = resolvedKind; // For pointer types, construct "PointeeType*" if substTe.kind == tekPointer && substTe.pointerPointee != null as *TypeExpr { n.typeName = String_Concat(substTe.pointerPointee.typeName, "*"); } else if !String_Eq(substTe.typeName, "") { n.typeName = substTe.typeName; } } return n; } // Struct init: TypeName { field: value, ... } if kind == ekStructInit { // Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant let enumSym: Symbol = Scope_Lookup(ctx.scope, expr.structName); if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum { var hasData: bool = false; if enumSym.decl.variantCount > 0 && enumSym.decl.variant0.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 1 && enumSym.decl.variant1.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 2 && enumSym.decl.variant2.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 3 && enumSym.decl.variant3.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 4 && enumSym.decl.variant4.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 5 && enumSym.decl.variant5.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 6 && enumSym.decl.variant6.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 7 && enumSym.decl.variant7.fieldCount > 0 { hasData = true; } if enumSym.decl.variantCount > 8 && enumSym.decl.variant8.fieldCount > 0 { hasData = true; } if !hasData && expr.child1 != null as *Expr && String_Eq(expr.child1.strValue, "tag") { return Lcx_LowerExpr(ctx, expr.child1.child1); } } n.kind = hStructInit; var structName: String = expr.structName; // Generic struct monomorphization if expr.genericTypeArgCount > 0 { let genDecl: *Decl = Lcx_FindGenericStruct(ctx, expr.structName); if genDecl != null as *Decl { var typeArg0: String = expr.genericTypeArg0; var typeArg1: String = expr.genericTypeArg1; if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; } if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; } if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; } if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; } structName = Lcx_GenerateStructInstance(ctx, genDecl, typeArg0, typeArg1, expr.genericTypeArgCount); } } n.strValue = structName; // Lower each field (fields are chained via child3 on synthetic ekField exprs) var field: *Expr = expr.child1; var firstField: *HirNode = null as *HirNode; var lastField: *HirNode = null as *HirNode; while field != null as *Expr { let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; fNode.kind = hBlock; // placeholder, field is identified by name+value fNode.line = expr.line; fNode.column = expr.column; fNode.strValue = field.strValue; // field name fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value if firstField == null as *HirNode { firstField = fNode; lastField = fNode; } else { lastField.child3 = fNode; lastField = fNode; } field = field.child3; } n.child1 = firstField; return n; } return n; } // --------------------------------------------------------------------------- // Statement lowering // --------------------------------------------------------------------------- func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode { if stmt == null as *Stmt { return null as *HirNode; } let line: uint32 = stmt.line; let col: uint32 = stmt.column; let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; n.kind = hBlock; n.line = line; n.column = col; let kind: int = stmt.kind; // Let/var → alloca + store if kind == skLet { // Try operator: let x: T = operand? -> tmp = operand; if tmp.tag == Err { return tmp; } let x = tmp.data.Ok; if stmt.child1 != null as *Expr && stmt.child1.kind == ekTry { let tryExpr: *Expr = stmt.child1; let operandExpr: *Expr = tryExpr.child1; let operandTypeExpr: *TypeExpr = operandExpr.refType; var typeName: String = "Result"; var errTag: String = "Result_Err"; var okField: String = "Ok_0"; if operandTypeExpr != null as *TypeExpr && operandTypeExpr.kind == tekNamed { typeName = operandTypeExpr.typeName; if String_Eq(typeName, "Option") { errTag = "Option_None"; okField = "Some_0"; } else if !String_Eq(typeName, "Result") { errTag = String_Concat(String_Concat(typeName, "_"), "Err"); okField = "Ok_0"; } } let tmpName: String = String_Concat("__try_tmp_", String_FromInt(ctx.tryCounter as int64)); ctx.tryCounter = ctx.tryCounter + 1; // alloca tmp let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; tmpAlloca.kind = hAlloca; tmpAlloca.line = line; tmpAlloca.column = col; tmpAlloca.strValue = tmpName; tmpAlloca.typeName = typeName; // tmp = operand let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; tmpStore.kind = hStore; tmpStore.line = line; tmpStore.column = col; let tmpVarRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; tmpVarRef.kind = hVar; tmpVarRef.strValue = tmpName; tmpStore.child1 = tmpVarRef; tmpStore.child2 = Lcx_LowerExpr(ctx, operandExpr); // if (tmp.tag == errTag) return tmp let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; tagPtr.kind = hFieldPtr; tagPtr.line = line; tagPtr.column = col; tagPtr.strValue = "tag"; tagPtr.child1 = tmpVarRef; let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; tagLoad.kind = hLoad; tagLoad.line = line; tagLoad.column = col; tagLoad.child1 = tagPtr; let errConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; errConst.kind = hVar; errConst.strValue = errTag; let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; cond.kind = hBinary; cond.line = line; cond.column = col; cond.intValue = tkEq; cond.child1 = tagLoad; cond.child2 = errConst; let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; retNode.kind = hReturn; retNode.line = line; retNode.column = col; retNode.child1 = tmpVarRef; let thenBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; thenBlock.kind = hBlock; thenBlock.line = line; thenBlock.column = col; thenBlock.child1 = retNode; let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; ifNode.kind = hIf; ifNode.line = line; ifNode.column = col; ifNode.child1 = cond; ifNode.child2 = thenBlock; // New initializer: tmp.data.OkField let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; dataPtr.kind = hFieldPtr; dataPtr.line = line; dataPtr.column = col; dataPtr.strValue = "data"; dataPtr.child1 = tmpVarRef; let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; dataLoad.kind = hLoad; dataLoad.line = line; dataLoad.column = col; dataLoad.child1 = dataPtr; let okPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; okPtr.kind = hFieldPtr; okPtr.line = line; okPtr.column = col; okPtr.strValue = okField; okPtr.child1 = dataLoad; let okLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; okLoad.kind = hLoad; okLoad.line = line; okLoad.column = col; okLoad.child1 = okPtr; // alloca x let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xAlloca.kind = hAlloca; xAlloca.line = line; xAlloca.column = col; xAlloca.strValue = stmt.strValue; xAlloca.typeName = ""; let letTe: *TypeExpr = Lcx_SubstituteType(ctx, stmt.refStmtType); if letTe != null as *TypeExpr { xAlloca.intValue = letTe.kind; xAlloca.typeKind = Lcx_ResolveTypeKind(letTe); if letTe.kind == tekFunc { xAlloca.typeName = Lcx_BuildFuncTypeName(letTe); } else if letTe.kind == tekPointer && letTe.pointerPointee != null as *TypeExpr { xAlloca.typeName = String_Concat(letTe.pointerPointee.typeName, "*"); } else if !String_Eq(letTe.typeName, "") { xAlloca.typeName = letTe.typeName; } } var xSym: Symbol; xSym.kind = skVar; xSym.name = stmt.strValue; xSym.typeKind = xAlloca.typeKind; xSym.typeName = xAlloca.typeName; xSym.refType = letTe; xSym.isMutable = false; xSym.isPublic = false; xSym.decl = null as *Decl; discard Scope_Define(ctx.scope, xSym); let xStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xStore.kind = hStore; xStore.line = line; xStore.column = col; xStore.child1 = xAlloca; xStore.child2 = okLoad; tmpAlloca.child3 = tmpStore; tmpStore.child3 = ifNode; ifNode.child3 = xStore; let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; blockNode.child1 = tmpAlloca; return blockNode; } let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1); // alloca for the variable let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; alloca.kind = hAlloca; alloca.line = line; alloca.column = col; alloca.strValue = stmt.strValue; // Set type from the declared type expression (with generic substitution) alloca.typeName = ""; let letTe: *TypeExpr = Lcx_SubstituteType(ctx, stmt.refStmtType); if letTe != null as *TypeExpr { alloca.intValue = letTe.kind; alloca.typeKind = Lcx_ResolveTypeKind(letTe); // For function types, build C function-pointer syntax if letTe.kind == tekFunc { alloca.typeName = Lcx_BuildFuncTypeName(letTe); } else if letTe.kind == tekPointer && letTe.pointerPointee != null as *TypeExpr { alloca.typeName = String_Concat(letTe.pointerPointee.typeName, "*"); } else if !String_Eq(letTe.typeName, "") { alloca.typeName = letTe.typeName; } } // Add to scope for field offset lookups (skip if already defined) var sym: Symbol; sym.kind = skVar; sym.name = stmt.strValue; sym.typeKind = alloca.typeKind; sym.typeName = alloca.typeName; sym.refType = letTe; sym.isMutable = false; sym.isPublic = false; sym.decl = null as *Decl; discard Scope_Define(ctx.scope, sym); // store the init value let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; storeNode.kind = hStore; storeNode.child1 = alloca; storeNode.child2 = init; // Auto-Drop for @[Drop] types and heap-allocated stdlib types var deferNode: *HirNode = null as *HirNode; if !String_Eq(alloca.typeName, "") { let typeName: String = alloca.typeName; let freeName: String = Lcx_BuildAutoDropFree(ctx, typeName); if !String_Eq(freeName, "") { let varRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; varRef.kind = hVar; varRef.strValue = stmt.strValue; let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrNode.kind = hUnary; addrNode.intValue = tkAmp; addrNode.child1 = varRef; let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; callNode.kind = hCall; callNode.strValue = freeName; callNode.child1 = addrNode; deferNode = bux_alloc(sizeof(HirNode)) as *HirNode; deferNode.kind = hDefer; deferNode.child1 = callNode; } } // If init is a closure with captures, emit capture assignments before the let if stmt.child1 != null as *Expr && stmt.child1.kind == ekClosure && stmt.child1.captureCount > 0 { let closureIdx: int = ctx.funcCount - 1; let envInst: String = String_Concat("__closure_env_instance_", String_FromInt(closureIdx)); // Build a block: capture assignments + let store let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; var firstStmt: *HirNode = null as *HirNode; var lastStmt: *HirNode = null as *HirNode; var ci: int = 0; while ci < stmt.child1.captureCount { var capName: String = ""; if ci == 0 { capName = stmt.child1.captureName0; } else if ci == 1 { capName = stmt.child1.captureName1; } else if ci == 2 { capName = stmt.child1.captureName2; } else if ci == 3 { capName = stmt.child1.captureName3; } else if ci == 4 { capName = stmt.child1.captureName4; } else if ci == 5 { capName = stmt.child1.captureName5; } else if ci == 6 { capName = stmt.child1.captureName6; } else if ci == 7 { capName = stmt.child1.captureName7; } // Build: envInst.capName = capName; let assignNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; assignNode.kind = hAssign; assignNode.line = line; assignNode.column = col; let fieldNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; fieldNode.kind = hFieldAccess; fieldNode.strValue = capName; let baseNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; baseNode.kind = hVar; baseNode.strValue = envInst; fieldNode.child1 = baseNode; let valNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; valNode.kind = hVar; valNode.strValue = capName; assignNode.child1 = fieldNode; assignNode.child2 = valNode; if firstStmt == null as *HirNode { firstStmt = assignNode; lastStmt = assignNode; } else { lastStmt.child3 = assignNode; lastStmt = assignNode; } ci = ci + 1; } // Append the let store if firstStmt == null as *HirNode { firstStmt = storeNode; lastStmt = storeNode; } else { lastStmt.child3 = storeNode; lastStmt = storeNode; } // Append auto-Drop defer if present if deferNode != null as *HirNode { lastStmt.child3 = deferNode; lastStmt = deferNode; } blockNode.child1 = firstStmt; return blockNode; } // Non-closure: wrap with defer if present if deferNode != null as *HirNode { storeNode.child3 = deferNode; let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; blockNode.child1 = storeNode; return blockNode; } return storeNode; } // Return if kind == skReturn { n.kind = hReturn; if stmt.child1 != null as *Expr { n.child1 = Lcx_LowerExpr(ctx, stmt.child1); } return n; } // Expression statement if kind == skExpr && stmt.child1 != null as *Expr { return Lcx_LowerExpr(ctx, stmt.child1); } // If if kind == skIf { n.kind = hIf; n.child1 = Lcx_LowerExpr(ctx, stmt.child1); // condition if stmt.refStmtBlock != null as *Block { n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); } if stmt.refStmtElse != null as *Block { n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void; } return n; } // While if kind == skWhile { n.kind = hWhile; n.child1 = Lcx_LowerExpr(ctx, stmt.child1); if stmt.refStmtBlock != null as *Block { n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); } return n; } // Loop if kind == skLoop { n.kind = hLoop; if stmt.refStmtBlock != null as *Block { n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); } return n; } // For if kind == skFor { let iterExpr: *Expr = stmt.child1; let varName: String = stmt.strValue; let body: *Block = stmt.refStmtBlock; // Range-based for: for i in lo..hi { body } // (selfhost parses .. as ekBinary; bootstrap parses as ekRange) let isRangeExpr: bool = iterExpr != null as *Expr && (iterExpr.kind == ekRange || (iterExpr.kind == ekBinary && (iterExpr.intValue == tkDotDot || iterExpr.intValue == tkDotDotEqual))); if isRangeExpr { let lo: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child1); let hi: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child2); var inclusive: bool = iterExpr.boolValue; if iterExpr.kind == ekBinary && iterExpr.intValue == tkDotDotEqual { inclusive = true; } let varTypeKind: int = tyInt; let varTypeName: String = "int"; // alloca for loop variable let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; alloca.kind = hAlloca; alloca.line = line; alloca.column = col; alloca.strValue = varName; alloca.typeName = varTypeName; alloca.typeKind = varTypeKind; // store init value let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; store.kind = hStore; store.child1 = alloca; store.child2 = lo; // var node for reading in condition let varRead: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; varRead.kind = hVar; varRead.strValue = varName; varRead.typeName = varTypeName; varRead.typeKind = varTypeKind; // condition: var < hi (or <=) let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; cond.kind = hBinary; if inclusive { cond.intValue = tkLe; } else { cond.intValue = tkLt; } cond.child1 = varRead; cond.child2 = hi; // Build while body: original body + increment let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1); // increment: var = var + 1 let varRead2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; varRead2.kind = hVar; varRead2.strValue = varName; varRead2.typeName = varTypeName; varRead2.typeKind = varTypeKind; let one: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; one.kind = hLit; one.intValue = tkIntLiteral; one.strValue = "1"; one.typeKind = varTypeKind; let inc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; inc.kind = hBinary; inc.intValue = tkPlus; inc.child1 = varRead2; inc.child2 = one; let storeInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; storeInc.kind = hStore; // Use hVar (not alloca) for assignment to existing variable let varForInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; varForInc.kind = hVar; varForInc.strValue = varName; varForInc.typeName = varTypeName; varForInc.typeKind = varTypeKind; storeInc.child1 = varForInc; storeInc.child2 = inc; // Append storeInc to body block chain if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock { if bodyBlock.child1 != null as *HirNode { var last: *HirNode = bodyBlock.child1; while last.child3 != null as *HirNode { last = last.child3; } last.child3 = storeInc; } else { bodyBlock.child1 = storeInc; } } else if bodyBlock != null as *HirNode { // Wrap single node into a block let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; wrapBlock.kind = hBlock; wrapBlock.child1 = bodyBlock; var last: *HirNode = bodyBlock; while last.child3 != null as *HirNode { last = last.child3; } last.child3 = storeInc; bodyBlock = wrapBlock; } else { let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; wrapBlock.kind = hBlock; wrapBlock.child1 = storeInc; bodyBlock = wrapBlock; } // while node let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; whileNode.kind = hWhile; whileNode.child1 = cond; whileNode.child2 = bodyBlock; // Chain: store (contains alloca as child1) -> while // C backend emits hStore with hAlloca child1 as "Type x = value;" store.child3 = whileNode; let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; blockNode.child1 = store; return blockNode; } // Collection-based for: for x in arr { body } // Desugar to: // let __iter = Array_Iter_T(&arr); // while Iter_HasNext_T(&__iter) { // let x = Iter_Next_T(&__iter); // body // } if iterExpr != null as *Expr { let collTypeExpr: *TypeExpr = iterExpr.refType; if collTypeExpr == null as *TypeExpr && iterExpr.kind == ekIdent { // Fallback: try scope lookup (may have substituted type) let collSym: Symbol = Scope_Lookup(ctx.scope, iterExpr.strValue); collTypeExpr = collSym.refType; } if collTypeExpr != null as *TypeExpr && collTypeExpr.kind == tekNamed { let collTypeName: String = collTypeExpr.typeName; var elemTypeName: String = ""; var elemTypeKind: int = tyInt; if collTypeExpr.typeArgCount > 0 { elemTypeName = collTypeExpr.typeArgName0; elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName); } // Handle already-monomorphized types like Array_int, Iter_string var isArray: bool = String_Eq(collTypeName, "Array"); var isIter: bool = String_Eq(collTypeName, "Iter"); var isChannel: bool = String_Eq(collTypeName, "Channel"); // Also check for mangled names like Array_int, Iter_string if !isArray && !isIter && !isChannel { if String_StartsWith(collTypeName, "Array_") { isArray = true; let prefixLen: uint = 6; // len("Array_") let totalLen: uint = bux_strlen(collTypeName); if totalLen > prefixLen { elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen); elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName); } } else if String_StartsWith(collTypeName, "Iter_") { isIter = true; let prefixLen: uint = 5; // len("Iter_") let totalLen: uint = bux_strlen(collTypeName); if totalLen > prefixLen { elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen); elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName); } } else if String_StartsWith(collTypeName, "Channel_") { isChannel = true; let prefixLen: uint = 8; // len("Channel_") let totalLen: uint = bux_strlen(collTypeName); if totalLen > prefixLen { elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen); elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName); } } } if !String_Eq(elemTypeName, "") { if isChannel { // Channel-based for: for x in ch { body } // Desugar to: // while true { // let x: T; // if !Channel_Recv_Ok_T(&ch, &x) { break; } // body // } let recvOkFuncName: String = Lcx_MangleName("Channel_Recv_Ok", elemTypeName, "", 1); let genRecvOk: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Recv_Ok"); if genRecvOk != null as *Decl { Lcx_GenerateFuncInstance(ctx, genRecvOk, elemTypeName, "", 1); } let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1); // alloca for x let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xAlloca.kind = hAlloca; xAlloca.line = line; xAlloca.column = col; xAlloca.strValue = varName; xAlloca.typeName = elemTypeName; xAlloca.typeKind = elemTypeKind; // call Channel_Recv_Ok_T(&ch, &x) let recvOkCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; recvOkCall.kind = hCall; recvOkCall.strValue = recvOkFuncName; let addrCh: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrCh.kind = hUnary; addrCh.intValue = tkAmp; if iterExpr.kind == ekIdent { let chVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; chVar.kind = hVar; chVar.strValue = iterExpr.strValue; addrCh.child1 = chVar; } else { addrCh.child1 = Lcx_LowerExpr(ctx, iterExpr); } let addrX: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrX.kind = hUnary; addrX.intValue = tkAmp; let xVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xVar.kind = hVar; xVar.strValue = varName; addrX.child1 = xVar; recvOkCall.child1 = addrCh; recvOkCall.child2 = addrX; // if !recvOk { break; } let notRecvOk: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; notRecvOk.kind = hUnary; notRecvOk.intValue = tkBang; notRecvOk.child1 = recvOkCall; let breakNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; breakNode.kind = hBreak; let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; ifNode.kind = hIf; ifNode.child1 = notRecvOk; ifNode.child2 = breakNode; // Build while body block: ifNode -> bodyBlock let whileBody: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; whileBody.kind = hBlock; whileBody.child1 = xAlloca; xAlloca.child3 = ifNode; if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock { ifNode.child3 = bodyBlock.child1; } else if bodyBlock != null as *HirNode { ifNode.child3 = bodyBlock; } // while true let trueLit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; trueLit.kind = hLit; trueLit.intValue = tkBoolLiteral; trueLit.strValue = "true"; let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; whileNode.kind = hWhile; whileNode.child1 = trueLit; whileNode.child2 = whileBody; let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; blockNode.child1 = whileNode; return blockNode; } if isArray || isIter { let iterVarName: String = String_Concat("__iter_", varName); let iterTypeName: String = Lcx_MangleName("Iter", elemTypeName, "", 1); // Ensure struct instances exist let iterGenStruct: *Decl = Lcx_FindGenericStruct(ctx, "Iter"); if iterGenStruct != null as *Decl { Lcx_GenerateStructInstance(ctx, iterGenStruct, elemTypeName, "", 1); } // Ensure function instances exist let genIter: *Decl = Lcx_FindGenericFunc(ctx, "Array_Iter"); let genHasNext: *Decl = Lcx_FindGenericFunc(ctx, "Iter_HasNext"); let genNext: *Decl = Lcx_FindGenericFunc(ctx, "Iter_Next"); let iterFuncName: String = Lcx_MangleName("Array_Iter", elemTypeName, "", 1); let hasNextFuncName: String = Lcx_MangleName("Iter_HasNext", elemTypeName, "", 1); let nextFuncName: String = Lcx_MangleName("Iter_Next", elemTypeName, "", 1); if genIter != null as *Decl { Lcx_GenerateFuncInstance(ctx, genIter, elemTypeName, "", 1); } if genHasNext != null as *Decl { Lcx_GenerateFuncInstance(ctx, genHasNext, elemTypeName, "", 1); } if genNext != null as *Decl { Lcx_GenerateFuncInstance(ctx, genNext, elemTypeName, "", 1); } // alloca for __iter let iterAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; iterAlloca.kind = hAlloca; iterAlloca.line = line; iterAlloca.column = col; iterAlloca.strValue = iterVarName; iterAlloca.typeName = iterTypeName; iterAlloca.typeKind = tyNamed; // __iter = Array_Iter_T(&arr) or just copy if already Iter var iterInit: *HirNode = null as *HirNode; var collStore: *HirNode = null as *HirNode; if isArray { let callIter: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; callIter.kind = hCall; callIter.strValue = iterFuncName; let addrArr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrArr.kind = hUnary; addrArr.intValue = tkAmp; if iterExpr.kind == ekIdent { let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; arrVar.kind = hVar; arrVar.strValue = iterExpr.strValue; addrArr.child1 = arrVar; } else { // Non-identifier: create temp variable for collection ctx.varCounter = ctx.varCounter + 1; let tmpName: String = String_Concat("__tmp_coll_", String_FromInt(ctx.varCounter)); // Ensure Array struct instance exists and get mangled name let arrayGenStruct: *Decl = Lcx_FindGenericStruct(ctx, "Array"); var arrayMangledName: String = collTypeName; if arrayGenStruct != null as *Decl { arrayMangledName = Lcx_GenerateStructInstance(ctx, arrayGenStruct, elemTypeName, "", 1); } let collAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; collAlloca.kind = hAlloca; collAlloca.strValue = tmpName; collAlloca.typeName = arrayMangledName; collAlloca.typeKind = tyNamed; collStore = bux_alloc(sizeof(HirNode)) as *HirNode; collStore.kind = hStore; collStore.child1 = collAlloca; collStore.child2 = Lcx_LowerExpr(ctx, iterExpr); let collVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; collVar.kind = hVar; collVar.strValue = tmpName; addrArr.child1 = collVar; } callIter.child1 = addrArr; iterInit = callIter; } else { // Already an iterator: __iter = arr if iterExpr.kind == ekIdent { let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; arrVar.kind = hVar; arrVar.strValue = iterExpr.strValue; iterInit = arrVar; } else { iterInit = Lcx_LowerExpr(ctx, iterExpr); } } let iterStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; iterStore.kind = hStore; iterStore.child1 = iterAlloca; iterStore.child2 = iterInit; // Chain collStore -> iterStore if temp was created if collStore != null as *HirNode { collStore.child3 = iterStore; } // condition: Iter_HasNext_T(&__iter) let condCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; condCall.kind = hCall; condCall.strValue = hasNextFuncName; let addrIter: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrIter.kind = hUnary; addrIter.intValue = tkAmp; let iterVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; iterVar.kind = hVar; iterVar.strValue = iterVarName; addrIter.child1 = iterVar; condCall.child1 = addrIter; // while body: alloca x + store x = Iter_Next_T(&__iter) + original body let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1); // alloca for x let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xAlloca.kind = hAlloca; xAlloca.line = line; xAlloca.column = col; xAlloca.strValue = varName; xAlloca.typeName = elemTypeName; xAlloca.typeKind = elemTypeKind; // x = Iter_Next_T(&__iter) let nextCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; nextCall.kind = hCall; nextCall.strValue = nextFuncName; let addrIter2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; addrIter2.kind = hUnary; addrIter2.intValue = tkAmp; let iterVar2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; iterVar2.kind = hVar; iterVar2.strValue = iterVarName; addrIter2.child1 = iterVar2; nextCall.child1 = addrIter2; let xStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; xStore.kind = hStore; xStore.child1 = xAlloca; xStore.child2 = nextCall; // Build while body block: xStore -> bodyBlock let whileBody: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; whileBody.kind = hBlock; whileBody.child1 = xStore; if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock { xStore.child3 = bodyBlock.child1; } else if bodyBlock != null as *HirNode { xStore.child3 = bodyBlock; } // while node let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; whileNode.kind = hWhile; whileNode.child1 = condCall; whileNode.child2 = whileBody; // Chain: iterStore -> whileNode iterStore.child3 = whileNode; let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; blockNode.kind = hBlock; blockNode.line = line; blockNode.column = col; if collStore != null as *HirNode { blockNode.child1 = collStore; } else { blockNode.child1 = iterStore; } return blockNode; } } } } // Fallback: infinite loop n.kind = hLoop; if stmt.refStmtBlock != null as *Block { n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); } return n; } // Break if kind == skBreak { n.kind = hBreak; return n; } // Continue if kind == skContinue { n.kind = hContinue; return n; } // Defer if kind == skDefer { n.kind = hDefer; if stmt.child1 != null as *Expr { n.child1 = Lcx_LowerExpr(ctx, stmt.child1); } return n; } // Switch — desugar to if-else chain if kind == skSwitch { let subject: *HirNode = Lcx_LowerExpr(ctx, stmt.child1); var current: *HirNode = null as *HirNode; // Default first (bottom of chain) if stmt.refStmtElse != null as *Block { current = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1); } // Cases in reverse order (from caseBlock) if stmt.refStmtBlock != null as *Block { let caseBlock: *Block = stmt.refStmtBlock; var caseCount: int = caseBlock.stmtCount; // Collect cases into array for reverse iteration var c0: *Stmt = null as *Stmt; var c1: *Stmt = null as *Stmt; var c2: *Stmt = null as *Stmt; var c3: *Stmt = null as *Stmt; var c4: *Stmt = null as *Stmt; var c5: *Stmt = null as *Stmt; var c6: *Stmt = null as *Stmt; var c7: *Stmt = null as *Stmt; var ci: int = 0; var cs: *Stmt = caseBlock.firstStmt; while cs != null as *Stmt && ci < 8 { if ci == 0 { c0 = cs; } if ci == 1 { c1 = cs; } if ci == 2 { c2 = cs; } if ci == 3 { c3 = cs; } if ci == 4 { c4 = cs; } if ci == 5 { c5 = cs; } if ci == 6 { c6 = cs; } if ci == 7 { c7 = cs; } ci = ci + 1; cs = cs.nextStmt; } while caseCount > 0 { caseCount = caseCount - 1; var c: *Stmt = null as *Stmt; if caseCount == 0 { c = c0; } if caseCount == 1 { c = c1; } if caseCount == 2 { c = c2; } if caseCount == 3 { c = c3; } if caseCount == 4 { c = c4; } if caseCount == 5 { c = c5; } if caseCount == 6 { c = c6; } if caseCount == 7 { c = c7; } if c != null as *Stmt { let caseVal: *HirNode = Lcx_LowerExpr(ctx, c.child1); let caseBody: *HirNode = Lcx_LowerBlock(ctx, c.refStmtBlock, -1); let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; cond.kind = hBinary; cond.intValue = 74; // tkEq cond.child1 = subject; cond.child2 = caseVal; let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; ifNode.kind = hIf; ifNode.child1 = cond; ifNode.child2 = caseBody; ifNode.child3 = current; current = ifNode; } } } return current; } return n; } // --------------------------------------------------------------------------- // Block lowering // --------------------------------------------------------------------------- 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 var firstNode: *HirNode = null as *HirNode; var prevNode: *HirNode = null as *HirNode; var stmt: *Stmt = block.firstStmt; while stmt != null as *Stmt { let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt); if lowered != null as *HirNode { if firstNode == null as *HirNode { firstNode = lowered; prevNode = lowered; } else { prevNode.child3 = lowered; prevNode = lowered; } } stmt = stmt.nextStmt; } // Wrap in an hBlock node with child1 = first statement in chain let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; n.kind = hBlock; n.line = block.line; n.column = block.column; n.boolValue = true; n.child1 = firstNode; return n; } // --------------------------------------------------------------------------- // Param → HirParam conversion // --------------------------------------------------------------------------- func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) { out.name = p.name; var te: *TypeExpr = p.refParamType; if ctx != null as *LowerCtx { te = Lcx_SubstituteType(ctx, te); } if te != null as *TypeExpr { out.typeKind = Lcx_ResolveTypeKind(te); // Function type: build C function-pointer syntax if te.kind == tekFunc { out.typeName = Lcx_BuildFuncTypeName(te); } else if !String_Eq(te.typeName, "") { out.typeName = te.typeName; } else if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr { out.typeName = String_Concat(te.pointerPointee.typeName, "*"); } else { out.typeName = ""; } } else { out.typeKind = 0; out.typeName = ""; } } // --------------------------------------------------------------------------- // Function lowering // --------------------------------------------------------------------------- func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc { let oldChecked: bool = ctx.checkedFunc; ctx.checkedFunc = decl.isChecked != 0; let oldRelease: bool = ctx.releaseFunc; ctx.releaseFunc = decl.isRelease != 0; let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; f.name = decl.strValue; f.isPublic = decl.isPublic; f.checkedFunc = ctx.checkedFunc; f.paramCount = decl.paramCount; f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, &decl.param0, ctx); f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, &decl.param1, ctx); f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, &decl.param2, ctx); f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, &decl.param3, ctx); f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, &decl.param4, ctx); f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, &decl.param5, ctx); f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, &decl.param6, ctx); f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, &decl.param7, ctx); f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, &decl.param8, ctx); let retTe: *TypeExpr = Lcx_SubstituteType(ctx, decl.retType); if retTe != null as *TypeExpr { f.retTypeName = retTe.typeName; f.retTypeKind = Lcx_ResolveTypeKind(retTe); } else { f.retTypeName = ""; f.retTypeKind = 0; } // Create function scope as child of current scope var funcScope: Scope = Scope_NewChild(ctx.scope); // Add parameters to function scope for field offset lookups var pi: int = 0; while pi < decl.paramCount { var p: *Param = null as *Param; if pi == 0 { p = &decl.param0; } else if pi == 1 { p = &decl.param1; } else if pi == 2 { p = &decl.param2; } else if pi == 3 { p = &decl.param3; } else if pi == 4 { p = &decl.param4; } else if pi == 5 { p = &decl.param5; } else if pi == 6 { p = &decl.param6; } else if pi == 7 { p = &decl.param7; } else if pi == 8 { p = &decl.param8; } if p != null as *Param && p.refParamType != null as *TypeExpr { let pTe: *TypeExpr = Lcx_SubstituteType(ctx, p.refParamType); var sym: Symbol; sym.kind = skVar; sym.name = p.name; sym.typeKind = Lcx_ResolveTypeKind(pTe); sym.refType = pTe; // Build typeName same as Lcx_LowerParam if pTe.kind == tekFunc { sym.typeName = Lcx_BuildFuncTypeName(pTe); } else if !String_Eq(pTe.typeName, "") { sym.typeName = pTe.typeName; } else if pTe.kind == tekPointer && pTe.pointerPointee != null as *TypeExpr { sym.typeName = String_Concat(pTe.pointerPointee.typeName, "*"); } else { sym.typeName = ""; } sym.isMutable = false; sym.isPublic = false; sym.decl = null as *Decl; discard Scope_Define(&funcScope, sym); } pi = pi + 1; } // Lower body with function scope active let prevScope: *Scope = ctx.scope; ctx.scope = &funcScope; if decl.refBody != null as *Block { f.body = Lcx_LowerBlock(ctx, decl.refBody, -1); } else { f.body = null as *HirNode; } ctx.scope = prevScope; ctx.checkedFunc = oldChecked; ctx.releaseFunc = oldRelease; return f; } // --------------------------------------------------------------------------- // Closure lowering — generate a global function for a closure expression // --------------------------------------------------------------------------- func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc { let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; // Generate unique name let numStr: String = String_FromInt(ctx.funcCount); f.name = String_Concat("__closure_", numStr); f.isPublic = false; let params: *Decl = expr.closureParams; if params != null as *Decl { f.paramCount = params.paramCount; if params.paramCount > 0 { f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, ¶ms.param0, ctx); } if params.paramCount > 1 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param1, ctx); } if params.paramCount > 2 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param2, ctx); } if params.paramCount > 3 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param3, ctx); } if params.paramCount > 4 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param4, ctx); } if params.paramCount > 5 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param5, ctx); } if params.paramCount > 6 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param6, ctx); } if params.paramCount > 7 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param7, ctx); } if params.paramCount > 8 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param8, ctx); } } if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc && expr.refType.funcRet != null as *TypeExpr { f.retTypeName = expr.refType.funcRet.typeName; f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet); } else { f.retTypeName = ""; f.retTypeKind = 0; } // Copy capture metadata from AST f.captureCount = expr.captureCount; f.captureName0 = expr.captureName0; f.captureName1 = expr.captureName1; f.captureName2 = expr.captureName2; f.captureName3 = expr.captureName3; f.captureName4 = expr.captureName4; f.captureName5 = expr.captureName5; f.captureName6 = expr.captureName6; f.captureName7 = expr.captureName7; f.captureType0 = expr.captureType0; f.captureType1 = expr.captureType1; f.captureType2 = expr.captureType2; f.captureType3 = expr.captureType3; f.captureType4 = expr.captureType4; f.captureType5 = expr.captureType5; f.captureType6 = expr.captureType6; f.captureType7 = expr.captureType7; // Generate env struct and instance names if there are captures var envStructName: String = ""; var envInstanceName: String = ""; if f.captureCount > 0 { envStructName = String_Concat("__closure_env_", numStr); envInstanceName = String_Concat("__closure_env_instance_", numStr); f.envStructName = envStructName; f.envInstanceName = envInstanceName; // Env struct is emitted directly by C backend from func capture metadata } // Create function scope var funcScope: Scope = Scope_NewChild(ctx.scope); var pi: int = 0; while pi < params.paramCount { var p: *Param = null as *Param; if pi == 0 { p = ¶ms.param0; } else if pi == 1 { p = ¶ms.param1; } else if pi == 2 { p = ¶ms.param2; } else if pi == 3 { p = ¶ms.param3; } else if pi == 4 { p = ¶ms.param4; } else if pi == 5 { p = ¶ms.param5; } else if pi == 6 { p = ¶ms.param6; } else if pi == 7 { p = ¶ms.param7; } else if pi == 8 { p = ¶ms.param8; } if p != null as *Param && p.refParamType != null as *TypeExpr { let pTe: *TypeExpr = Lcx_SubstituteType(ctx, p.refParamType); var sym: Symbol; sym.kind = skVar; sym.name = p.name; sym.typeKind = Lcx_ResolveTypeKind(pTe); sym.refType = pTe; if pTe.kind == tekFunc { sym.typeName = Lcx_BuildFuncTypeName(pTe); } else if !String_Eq(pTe.typeName, "") { sym.typeName = pTe.typeName; } else if pTe.kind == tekPointer && pTe.pointerPointee != null as *TypeExpr { sym.typeName = String_Concat(pTe.pointerPointee.typeName, "*"); } else { sym.typeName = ""; } sym.isMutable = false; sym.isPublic = false; sym.decl = null as *Decl; discard Scope_Define(&funcScope, sym); } pi = pi + 1; } let prevScope: *Scope = ctx.scope; let prevClosureDepth: int = ctx.closureDepth; let prevClosureExpr: *Expr = ctx.currentClosureExpr; let prevEnvInstanceName: String = ctx.envInstanceName; ctx.scope = &funcScope; ctx.closureDepth = ctx.closureDepth + 1; ctx.currentClosureExpr = expr; ctx.envInstanceName = envInstanceName; if expr.refBlock != null as *Block { f.body = Lcx_LowerBlock(ctx, expr.refBlock, -1); } else { f.body = null as *HirNode; } ctx.scope = prevScope; ctx.closureDepth = prevClosureDepth; ctx.currentClosureExpr = prevClosureExpr; ctx.envInstanceName = prevEnvInstanceName; // Add to module functions ctx.funcs[ctx.funcCount] = *f; ctx.funcCount = ctx.funcCount + 1; return f; } // --------------------------------------------------------------------------- // Compile-Time Function Execution (CTFE) — constant expression evaluator // --------------------------------------------------------------------------- const CTFE_MAX_LOCALS: int = 64; struct CtfeLocal { name: String, value: int, } struct CtfeEnv { locals: *CtfeLocal, count: int, } struct CtVal { value: int, isReturn: bool, } func CtfeEnv_New() -> CtfeEnv { let locals: *CtfeLocal = bux_alloc(CTFE_MAX_LOCALS as uint * sizeof(CtfeLocal)) as *CtfeLocal; return CtfeEnv { locals: locals, count: 0 }; } func CtfeEnv_Get(env: *CtfeEnv, name: String) -> int { var i: int = env.count - 1; while i >= 0 { if String_Eq(env.locals[i].name, name) { return env.locals[i].value; } i = i - 1; } return 0; } func CtfeEnv_Set(env: *CtfeEnv, name: String, value: int) { if env.count >= CTFE_MAX_LOCALS { return; } env.locals[env.count].name = name; env.locals[env.count].value = value; env.count = env.count + 1; } func Lcx_FindConstFunc(ctx: *LowerCtx, name: String) -> *Decl { var decl: *Decl = ctx.module.firstItem; while decl != null as *Decl { if decl.kind == dkFunc && decl.isConst == 1 && String_Eq(decl.strValue, name) { return decl; } decl = decl.childDecl2; } return null as *Decl; } func Lcx_ParamName(fd: *Decl, idx: int) -> String { if fd == null as *Decl { return ""; } if idx == 0 { return fd.param0.name; } if idx == 1 { return fd.param1.name; } if idx == 2 { return fd.param2.name; } if idx == 3 { return fd.param3.name; } if idx == 4 { return fd.param4.name; } if idx == 5 { return fd.param5.name; } if idx == 6 { return fd.param6.name; } if idx == 7 { return fd.param7.name; } if idx == 8 { return fd.param8.name; } return ""; } func CtVal_Make(value: int) -> CtVal { return CtVal { value: value, isReturn: false }; } func CtVal_Return(value: int) -> CtVal { return CtVal { value: value, isReturn: true }; } func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal { if expr == null as *Expr { return CtVal_Make(0); } // Literal integer if expr.kind == ekLiteral { return CtVal_Make(expr.intValue); } // Reference to local or another constant if expr.kind == ekIdent { let localVal: int = CtfeEnv_Get(env, expr.strValue); // Local takes precedence; 0 from Get could mean not found, but global const 0 // will still be looked up below if needed. For Factorial parameters this works // because params are always set in env. if localVal != 0 { return CtVal_Make(localVal); } let name: String = expr.strValue; var i: int = 0; while i < ctx.hm.constCount { if String_Eq(ctx.hm.consts[i].name, name) { return CtVal_Make(ctx.hm.consts[i].value); } i = i + 1; } return CtVal_Make(0); } // Unary operators if expr.kind == ekUnary { let operand: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env); let op: int = expr.intValue; if op == tkMinus { return CtVal_Make(-operand.value); } if op == tkBang { return CtVal_Make((operand.value == 0) as int); } if op == tkTilde { return CtVal_Make(~operand.value); } return CtVal_Make(operand.value); } // Binary operators if expr.kind == ekBinary { let left: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env); let right: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child2, env); let op: int = expr.intValue; if op == tkPlus { return CtVal_Make(left.value + right.value); } if op == tkMinus { return CtVal_Make(left.value - right.value); } if op == tkStar { return CtVal_Make(left.value * right.value); } if op == tkSlash { if right.value == 0 { return CtVal_Make(0); } return CtVal_Make(left.value / right.value); } if op == tkPercent { if right.value == 0 { return CtVal_Make(0); } return CtVal_Make(left.value % right.value); } if op == tkLt { return CtVal_Make((left.value < right.value) as int); } if op == tkLe { return CtVal_Make((left.value <= right.value) as int); } if op == tkGt { return CtVal_Make((left.value > right.value) as int); } if op == tkGe { return CtVal_Make((left.value >= right.value) as int); } if op == tkEq { return CtVal_Make((left.value == right.value) as int); } if op == tkNe { return CtVal_Make((left.value != right.value) as int); } if op == tkAmp { return CtVal_Make(left.value & right.value); } if op == tkPipe { return CtVal_Make(left.value | right.value); } if op == tkCaret { return CtVal_Make(left.value ^ right.value); } if op == tkShl { return CtVal_Make(left.value << right.value); } if op == tkShr { return CtVal_Make(left.value >> right.value); } if op == tkAmpAmp { return CtVal_Make((left.value != 0 && right.value != 0) as int); } if op == tkPipePipe { return CtVal_Make((left.value != 0 || right.value != 0) as int); } return CtVal_Make(0); } // Ternary operator if expr.kind == ekTernary { let cond: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env); if cond.value != 0 { return Lcx_EvalConstExprEnv(ctx, expr.child2, env); } else { return Lcx_EvalConstExprEnv(ctx, expr.child3, env); } } // Cast — evaluate operand (types don't affect integer values) if expr.kind == ekCast { return Lcx_EvalConstExprEnv(ctx, expr.child1, env); } // Const function call if expr.kind == ekCall { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { let funcName: String = expr.child1.strValue; let fd: *Decl = Lcx_FindConstFunc(ctx, funcName); if fd != null as *Decl && fd.refBody != null as *Block { // Evaluate arguments var argExpr: *ExprList = expr.callArgs; var ai: int = 0; var argVals: *int = bux_alloc(fd.paramCount as uint * sizeof(int)) as *int; while ai < fd.paramCount { argVals[ai] = 0; ai = ai + 1; } ai = 0; while argExpr != null as *ExprList && ai < fd.paramCount { let av: CtVal = Lcx_EvalConstExprEnv(ctx, argExpr.expr, env); argVals[ai] = av.value; argExpr = argExpr.next; ai = ai + 1; } // Set up call environment let callEnv: CtfeEnv = CtfeEnv_New(); var pi: int = 0; while pi < fd.paramCount { CtfeEnv_Set(&callEnv, Lcx_ParamName(fd, pi), argVals[pi]); pi = pi + 1; } let result: CtVal = Lcx_EvalConstBlock(ctx, fd.refBody, &callEnv); return CtVal_Make(result.value); } } return CtVal_Make(0); } return CtVal_Make(0); } func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal { if block == null as *Block { return CtVal_Make(0); } var stmt: *Stmt = block.firstStmt; while stmt != null as *Stmt { if stmt.kind == skLet && stmt.child1 != null as *Expr { let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env); CtfeEnv_Set(env, stmt.strValue, val.value); } else if stmt.kind == skIf { let cond: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env); if cond.value != 0 { let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtBlock, env); if r.isReturn { return r; } } else if stmt.refStmtElse != null as *Block { let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtElse, env); if r.isReturn { return r; } } } else if stmt.kind == skReturn { if stmt.child1 != null as *Expr { let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env); return CtVal_Return(val.value); } return CtVal_Return(0); } else if stmt.kind == skExpr { discard Lcx_EvalConstExprEnv(ctx, stmt.child1, env); } stmt = stmt.nextStmt; } return CtVal_Make(0); } func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int { let env: CtfeEnv = CtfeEnv_New(); let result: CtVal = Lcx_EvalConstExprEnv(ctx, expr, &env); return result.value; } // --------------------------------------------------------------------------- // Auto-Drop: build the Free function name for a given type // --------------------------------------------------------------------------- func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String { if String_StartsWith(typeName, "Array_") { let elemType: String = bux_str_slice(typeName, 6, bux_strlen(typeName) - 6); // Ensure inner free is also monomorphized since Drop calls Free let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Array_Free"); if genFree != null as *Decl { Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1); } let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Array_Drop"); if genDrop != null as *Decl { Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1); } return String_Concat("Array_Drop_", elemType); } if String_StartsWith(typeName, "Channel_") { let elemType: String = bux_str_slice(typeName, 8, bux_strlen(typeName) - 8); let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Free"); if genFree != null as *Decl { Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1); } let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Drop"); if genDrop != null as *Decl { Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1); } return String_Concat("Channel_Drop_", elemType); } if String_StartsWith(typeName, "Set_") { let elemType: String = bux_str_slice(typeName, 4, bux_strlen(typeName) - 4); let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Set_Free"); if genFree != null as *Decl { Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1); } let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Set_Drop"); if genDrop != null as *Decl { Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1); } return String_Concat("Set_Drop_", elemType); } if String_StartsWith(typeName, "Map_") { let rest: String = bux_str_slice(typeName, 4, bux_strlen(typeName) - 4); // Find underscore separator between K and V var ki: int = 0; var klen: int = bux_strlen(rest) as int; while ki < klen { if (rest[ki] as int) == ('_' as int) { break; } ki = ki + 1; } if ki < klen { let kType: String = bux_str_slice(rest, 0, ki); let vType: String = bux_str_slice(rest, ki + 1, klen - ki - 1); let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Map_Free"); if genFree != null as *Decl { Lcx_GenerateFuncInstance(ctx, genFree, kType, vType, 2); } let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Map_Drop"); if genDrop != null as *Decl { Lcx_GenerateFuncInstance(ctx, genDrop, kType, vType, 2); } return String_Concat(String_Concat("Map_Drop_", kType), String_Concat("_", vType)); } } // User-defined types with @[Drop] OR an explicit TypeName_Drop method let typeSym: Symbol = Scope_Lookup(ctx.scope, typeName); if typeSym.kind == skType && typeSym.decl != null as *Decl { let dropName: String = String_Concat(typeName, "_Drop"); let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName); let hasAttr: bool = typeSym.decl.isDrop != 0; let hasMethod: bool = dropSym.decl != null as *Decl; if hasAttr || hasMethod { return dropName; } } return ""; } // --------------------------------------------------------------------------- // Module lowering — main entry point // --------------------------------------------------------------------------- func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx; ctx.module = mod; ctx.scope = sema.scope; ctx.funcs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc; ctx.funcCount = 0; ctx.externFuncs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc; ctx.externCount = 0; ctx.varCounter = 0; ctx.genFuncCount = 0; ctx.genFuncs = bux_alloc(256 as uint * sizeof(Decl)) as *Decl; ctx.genStructCount = 0; ctx.genStructs = bux_alloc(256 as uint * sizeof(Decl)) as *Decl; ctx.substParam0 = ""; ctx.substArg0 = ""; ctx.substParam1 = ""; ctx.substArg1 = ""; let hm: *HirModule = bux_alloc(sizeof(HirModule)) as *HirModule; hm.funcCount = 0; hm.funcs = ctx.funcs; hm.structCount = 0; hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct; hm.enumCount = 0; hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum; hm.constCount = 0; hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst; ctx.hm = hm; // First pass: count structs (to allocate field arrays later) // Second pass: actually collect them // For simplicity, do single pass with pre-allocated field arrays // Pass 1: collect generic declarations for monomorphization var decl: *Decl = mod.firstItem; while decl != null as *Decl { if decl.kind == dkFunc && decl.typeParamCount > 0 { ctx.genFuncs[ctx.genFuncCount] = *decl; ctx.genFuncCount = ctx.genFuncCount + 1; } if decl.kind == dkStruct && decl.typeParamCount > 0 { ctx.genStructs[ctx.genStructCount] = *decl; ctx.genStructCount = ctx.genStructCount + 1; } // Generic impl/extend blocks: methods inherit the impl's type params if decl.kind == dkImpl && decl.typeParamCount > 0 { let implTypeName: String = decl.strValue; var implDecl: *Decl = decl.childDecl1; while implDecl != null as *Decl { if implDecl.kind == dkFunc { let renamed: String = String_Concat(String_Concat(implTypeName, "_"), implDecl.strValue); var copy: Decl = *implDecl; copy.strValue = renamed; copy.typeParam0 = decl.typeParam0; copy.typeParam1 = decl.typeParam1; copy.typeParamCount = decl.typeParamCount; ctx.genFuncs[ctx.genFuncCount] = copy; ctx.genFuncCount = ctx.genFuncCount + 1; } implDecl = implDecl.childDecl2; } } decl = decl.childDecl2; } // Pass 2: lower all declarations decl = mod.firstItem; while decl != null as *Decl { if decl.kind == dkStruct && decl.typeParamCount == 0 && !String_Eq(decl.strValue, "") { // Collect struct definition for C codegen let si: int = hm.structCount; hm.structs[si].name = decl.strValue; hm.structs[si].fieldCount = decl.fieldCount; hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField; var fi: int = 0; while fi < decl.fieldCount { var fname: String = ""; var ftype: *TypeExpr = null as *TypeExpr; fname = decl.fields[fi].name; ftype = decl.fields[fi].refFieldType; // Skip empty field names if String_Eq(fname, "") { fi = fi + 1; continue; } hm.structs[si].fields[fi].name = fname; if ftype != null as *TypeExpr { if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr { // Pointer type: emit "TypeName*" if !String_Eq(ftype.pointerPointee.typeName, "") { hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*"); } } else if !String_Eq(ftype.typeName, "") { hm.structs[si].fields[fi].typeName = ftype.typeName; } } fi = fi + 1; } hm.structCount = hm.structCount + 1; } if decl.kind == dkFunc && decl.refBody != null as *Block && decl.typeParamCount == 0 { let f: *HirFunc = Lcx_LowerFunc(ctx, decl); ctx.funcs[ctx.funcCount] = *f; ctx.funcCount = ctx.funcCount + 1; } if decl.kind == dkImpl { let implTypeName: String = decl.strValue; var implDecl: *Decl = decl.childDecl1; while implDecl != null as *Decl { if implDecl.kind == dkFunc && implDecl.refBody != null as *Block { // Generic impl methods are monomorphized on demand; skip direct lowering if decl.typeParamCount > 0 { implDecl = implDecl.childDecl2; continue; } let mangled: String = String_Concat(implTypeName, "_"); implDecl.strValue = String_Concat(mangled, implDecl.strValue); let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl); ctx.funcs[ctx.funcCount] = *f; ctx.funcCount = ctx.funcCount + 1; } implDecl = implDecl.childDecl2; } } if decl.kind == dkExternFunc { let f: *HirFunc = Lcx_LowerFunc(ctx, decl); ctx.externFuncs[ctx.externCount] = *f; ctx.externCount = ctx.externCount + 1; } // Pass 1: collect const names (expressions evaluated in Pass 2) if decl.kind == dkConst && hm.constCount < 512 { let ci: int = hm.constCount; hm.consts[ci].name = decl.strValue; hm.consts[ci].value = 0; hm.constCount = hm.constCount + 1; } if decl.kind == dkEnum { let ei: int = hm.enumCount; hm.enums[ei].name = decl.strValue; // Populate variants hm.enums[ei].variantCount = decl.variantCount; if decl.variantCount > 0 { hm.enums[ei].variants = bux_alloc(decl.variantCount as uint * sizeof(HirEnumVariant)) as *HirEnumVariant; } var vi: int = 0; while vi < decl.variantCount { var v: *EnumVariant = null as *EnumVariant; if vi == 0 { v = &decl.variant0; } if vi == 1 { v = &decl.variant1; } if vi == 2 { v = &decl.variant2; } if vi == 3 { v = &decl.variant3; } if vi == 4 { v = &decl.variant4; } if vi == 5 { v = &decl.variant5; } if vi == 6 { v = &decl.variant6; } if vi == 7 { v = &decl.variant7; } if vi == 8 { v = &decl.variant8; } if v != null as *EnumVariant { hm.enums[ei].variants[vi].name = v.name; hm.enums[ei].variants[vi].fieldCount = v.fieldCount; if v.fieldCount > 0 { hm.enums[ei].variants[vi].fieldName0 = "value"; hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0); } if v.fieldCount > 1 { hm.enums[ei].variants[vi].fieldName1 = "value2"; hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1); } } vi = vi + 1; } hm.enumCount = hm.enumCount + 1; } decl = decl.childDecl2; } // Pass 2: evaluate all const expressions (multiple passes for forward refs) var changed: bool = true; var maxPasses: int = 10; var pass: int = 0; while changed && pass < maxPasses { changed = false; decl = mod.firstItem; var ci2: int = 0; while decl != null as *Decl && ci2 < hm.constCount { if decl.kind == dkConst { if decl.constValue != null as *Expr { let newVal: int = Lcx_EvalConstExpr(ctx, decl.constValue); if newVal != hm.consts[ci2].value { hm.consts[ci2].value = newVal; changed = true; } } ci2 = ci2 + 1; } decl = decl.childDecl2; } pass = pass + 1; } hm.funcCount = ctx.funcCount; hm.funcs = ctx.funcs; hm.externCount = ctx.externCount; hm.externFuncs = ctx.externFuncs; return hm; } }