Files
bux-lang/src/hir_lower.bux
T
dimgigov 6f2a3b1d88
ci / make test (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
feat(selfhost): quote/graft hygiene API for macros and mono
Add Ast_Graft/Clone/Quote helpers, HIR Lcx_GraftSourceFile, and force
definition-site paths on monomorphized bodies. Smoke checks Array mono
#line stays in lib while Main stays isolated.
2026-07-20 00:31:23 +03:00

4289 lines
199 KiB
Plaintext

// 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,
// Pattern binding renames: source name → unique C local (shadowing-safe)
patMapCount: int,
patMapFrom0: String,
patMapTo0: String,
patMapFrom1: String,
patMapTo1: String,
patMapFrom2: String,
patMapTo2: String,
patMapFrom3: String,
patMapTo3: String,
patMapFrom4: String,
patMapTo4: String,
patMapFrom5: String,
patMapTo5: String,
patMapFrom6: String,
patMapTo6: String,
patMapFrom7: String,
patMapTo7: String,
// Active source path for #line stamping (from Decl.sourceFile)
currentSourceFile: String,
}
/// Fill empty HirNode.sourceFile from `file` (does not overwrite set paths).
/// Walks child1/2/3, call arg lists, and if-else via extraData.
func Lcx_StampSourceFile(node: *HirNode, file: String) {
if node == null as *HirNode { return; }
if file == null as String || String_Eq(file, "") { return; }
if node.sourceFile == null as String || String_Eq(node.sourceFile, "") {
node.sourceFile = file;
}
Lcx_StampSourceFile(node.child1, file);
Lcx_StampSourceFile(node.child2, file);
Lcx_StampSourceFile(node.child3, file);
// hCall / multi-arg: linked HirArgList in extraData
if (node.kind == hCall || node.kind == hCallIndirect) && node.extraData != null as *void {
var cur: *HirArgList = node.extraData as *HirArgList;
while cur != null as *HirArgList {
Lcx_StampSourceFile(cur.node, file);
cur = cur.next;
}
}
// hIf else branch may live in extraData (*HirNode)
if node.kind == hIf && node.extraData != null as *void {
Lcx_StampSourceFile(node.extraData as *HirNode, file);
}
}
/// Force-set sourceFile on HIR tree (definition-site hygiene for mono / macro body).
func Lcx_GraftSourceFile(node: *HirNode, file: String) {
if node == null as *HirNode { return; }
if file == null as String || String_Eq(file, "") { return; }
node.sourceFile = file;
Lcx_GraftSourceFile(node.child1, file);
Lcx_GraftSourceFile(node.child2, file);
Lcx_GraftSourceFile(node.child3, file);
if (node.kind == hCall || node.kind == hCallIndirect) && node.extraData != null as *void {
var cur: *HirArgList = node.extraData as *HirArgList;
while cur != null as *HirArgList {
Lcx_GraftSourceFile(cur.node, file);
cur = cur.next;
}
}
if node.kind == hIf && node.extraData != null as *void {
Lcx_GraftSourceFile(node.extraData as *HirNode, file);
}
}
/// Prefer Expr/Stmt.sourceFile (macro / cross-file graft); else enclosing Decl path.
func Lcx_SourceFileFor(ctx: *LowerCtx, nodeFile: String) -> String {
if nodeFile != null as String && !String_Eq(nodeFile, "") {
return nodeFile;
}
if ctx.currentSourceFile != null as String && !String_Eq(ctx.currentSourceFile, "") {
return ctx.currentSourceFile;
}
return "";
}
/// Set line/column/sourceFile on a freshly allocated HirNode from AST location.
func Lcx_SetNodeLoc(ctx: *LowerCtx, n: *HirNode, line: uint32, col: uint32, nodeFile: String) {
if n == null as *HirNode { return; }
n.line = line;
n.column = col;
let f: String = Lcx_SourceFileFor(ctx, nodeFile);
if f != null as String && !String_Eq(f, "") {
n.sourceFile = f;
}
}
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
// Most recent rename wins (scan from end)
var i: int = ctx.patMapCount - 1;
while i >= 0 {
var from: String = "";
var to: String = "";
if i == 0 { from = ctx.patMapFrom0; to = ctx.patMapTo0; }
else if i == 1 { from = ctx.patMapFrom1; to = ctx.patMapTo1; }
else if i == 2 { from = ctx.patMapFrom2; to = ctx.patMapTo2; }
else if i == 3 { from = ctx.patMapFrom3; to = ctx.patMapTo3; }
else if i == 4 { from = ctx.patMapFrom4; to = ctx.patMapTo4; }
else if i == 5 { from = ctx.patMapFrom5; to = ctx.patMapTo5; }
else if i == 6 { from = ctx.patMapFrom6; to = ctx.patMapTo6; }
else if i == 7 { from = ctx.patMapFrom7; to = ctx.patMapTo7; }
if String_Eq(from, src) { return to; }
i = i - 1;
}
return "";
}
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
if ctx.patMapCount >= 8 { return; }
let i: int = ctx.patMapCount;
if i == 0 { ctx.patMapFrom0 = src; ctx.patMapTo0 = dst; }
else if i == 1 { ctx.patMapFrom1 = src; ctx.patMapTo1 = dst; }
else if i == 2 { ctx.patMapFrom2 = src; ctx.patMapTo2 = dst; }
else if i == 3 { ctx.patMapFrom3 = src; ctx.patMapTo3 = dst; }
else if i == 4 { ctx.patMapFrom4 = src; ctx.patMapTo4 = dst; }
else if i == 5 { ctx.patMapFrom5 = src; ctx.patMapTo5 = dst; }
else if i == 6 { ctx.patMapFrom6 = src; ctx.patMapTo6 = dst; }
else if i == 7 { ctx.patMapFrom7 = src; ctx.patMapTo7 = dst; }
ctx.patMapCount = ctx.patMapCount + 1;
}
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
ctx.varCounter = ctx.varCounter + 1;
var safe: String = src;
if String_Eq(src, "") || String_Eq(src, "_") { safe = "x"; }
return String_Concat(String_Concat("__p", String_FromInt(ctx.varCounter as int64)),
String_Concat("_", safe));
}
// Alloca unique C local + store + scope define + rename map for pattern binding.
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
line: uint32, col: uint32) -> *HirNode {
if String_Eq(src, "") || String_Eq(src, "_") { return null as *HirNode; }
let cName: String = Lcx_FreshPatName(ctx, src);
Lcx_PatPush(ctx, src, cName);
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = cName;
alloca.typeName = ty;
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = cName;
store.child1 = v;
store.child2 = value;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = src;
bsym.typeKind = tyInt;
bsym.typeName = ty;
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
return alloca;
}
// ---------------------------------------------------------------------------
// 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 {
return Type_FromName(name);
}
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 tyNamed; /* Tuple_T_U is a C struct */ }
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;
}
// Fat function type: func(T)->U — substitute params and return
if te.kind == tekFunc {
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
r.kind = tekFunc;
r.line = te.line;
r.column = te.column;
r.funcParamCount = te.funcParamCount;
r.funcRet = Lcx_SubstituteType(ctx, te.funcRet);
var head: *TypeExprList = null as *TypeExprList;
var tail: *TypeExprList = null as *TypeExprList;
var cur: *TypeExprList = te.funcParams;
while cur != null as *TypeExprList {
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
node.te = Lcx_SubstituteType(ctx, cur.te);
node.next = null as *TypeExprList;
if head == null as *TypeExprList {
head = node;
tail = node;
} else {
tail.next = node;
tail = node;
}
cur = cur.next;
}
r.funcParams = head;
r.typeName = Lcx_BuildFuncTypeName(r);
return r;
}
return te;
}
// Sanitize a C type fragment for use inside BuxFn_* mangled names
func Lcx_SanitizeFatPart(s: String) -> String {
var r: String = s;
if String_Eq(r, "String") || String_Eq(r, "str") || String_Eq(r, "const char*") {
return "cstr";
}
if String_Eq(r, "unsigned int") { return "uint"; }
// crude replacements for * and spaces
r = String_ReplaceAll(r, "*", "Ptr");
r = String_ReplaceAll(r, " ", "_");
r = String_ReplaceAll(r, "(", "");
r = String_ReplaceAll(r, ")", "");
r = String_ReplaceAll(r, ",", "_");
if String_Eq(r, "") { return "int"; }
return r;
}
func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return "void"; }
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
return Lcx_SanitizeFatPart(String_Concat(te.pointerPointee.typeName, "Ptr"));
}
if te.kind == tekFunc {
return Lcx_SanitizeFatPart(Lcx_BuildFuncTypeName(te));
}
var n: String = te.typeName;
if String_Eq(n, "") { n = "int"; }
return Lcx_SanitizeFatPart(n);
}
// Fat function-pointer type name: BuxFn_<ret>_<p0>_<p1>...
// Enables multi-instance closures (code + env).
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
if te == null as *TypeExpr || te.kind != tekFunc {
return "BuxFn_void_void";
}
var retPart: String = "void";
if te.funcRet != null as *TypeExpr {
retPart = Lcx_TypeExprFatPart(te.funcRet);
}
var result: String = String_Concat("BuxFn_", retPart);
var cur: *TypeExprList = te.funcParams;
var anyParam: bool = false;
while cur != null as *TypeExprList {
result = String_Concat(result, "_");
result = String_Concat(result, Lcx_TypeExprFatPart(cur.te));
anyParam = true;
cur = cur.next;
}
if !anyParam {
result = String_Concat(result, "_void");
}
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;
}
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
func Lcx_ExtractElemFromName(typeName: String) -> String {
if String_Eq(typeName, "") { return ""; }
let len: uint = bux_strlen(typeName);
// "Array_" prefix (6 chars)
if len > 6 {
let p: String = bux_str_slice(typeName, 0, 6);
if String_Eq(p, "Array_") {
return bux_str_slice(typeName, 6, len - 6);
}
}
// "Iter_" prefix (5 chars)
if len > 5 {
let p: String = bux_str_slice(typeName, 0, 5);
if String_Eq(p, "Iter_") {
return bux_str_slice(typeName, 5, len - 5);
}
}
return "";
}
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;
// Definition-site hygiene: mono body always maps to the generic's source
// file (not the call-site module), even if synthetic nodes lacked a path.
if f.body != null as *HirNode && !String_Eq(genDecl.sourceFile, "") {
f.sourceFile = genDecl.sourceFile;
Lcx_GraftSourceFile(f.body, genDecl.sourceFile);
}
// 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 "";
}
// ---------------------------------------------------------------------------
// Match lowering helpers
// ---------------------------------------------------------------------------
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
if String_Eq(enumName, "") { return false; }
let sym: Symbol = Scope_Lookup(ctx.scope, enumName);
if sym.decl == null as *Decl || sym.decl.kind != dkEnum { return false; }
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { return true; }
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { return true; }
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { return true; }
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { return true; }
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { return true; }
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { return true; }
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { return true; }
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { return true; }
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { return true; }
return false;
}
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hLit;
n.line = line;
n.column = col;
n.intValue = litKind;
n.strValue = litText;
return n;
}
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBinary;
n.line = line;
n.column = col;
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
return Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
}
// Build condition HirNode for a match pattern. Returns null = always-true.
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
let kind: int = pat.kind;
// Guarded: condition is only the inner pattern; guard applied after binds
if kind == pkGuarded {
return Lcx_PatternCond(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
}
if kind == pkWildcard || kind == pkIdent {
return null as *HirNode;
}
if kind == pkLiteral {
let lit: *HirNode = Lcx_MakeLitHir(pat.patLitKind, pat.patLitText, line, col);
return Lcx_MakeBinHir(tkEq, subject, lit, line, col);
}
if kind == pkRange {
let loPat: *Pattern = pat.patChild1;
let hiPat: *Pattern = pat.patChild2;
if loPat == null as *Pattern || hiPat == null as *Pattern { return null as *HirNode; }
if loPat.kind != pkLiteral || hiPat.kind != pkLiteral { return null as *HirNode; }
let lo: *HirNode = Lcx_MakeLitHir(loPat.patLitKind, loPat.patLitText, line, col);
let hi: *HirNode = Lcx_MakeLitHir(hiPat.patLitKind, hiPat.patLitText, line, col);
let loOk: *HirNode = Lcx_MakeBinHir(tkGe, subject, lo, line, col);
var hiOp: int = tkLt;
if pat.patRangeInclusive { hiOp = tkLe; }
let hiOk: *HirNode = Lcx_MakeBinHir(hiOp, subject, hi, line, col);
return Lcx_MakeBinHir(tkAmpAmp, loOk, hiOk, line, col);
}
if kind == pkEnum {
let path: String = pat.patEnumPath;
// path is "Enum::Variant" or just "Variant"
var enumName: String = "";
var variantName: String = path;
if String_Contains(path, "::") {
enumName = String_SplitPart(path, "::", 0);
variantName = String_SplitPart(path, "::", 1);
}
let tagName: String = String_Concat(String_Concat(enumName, "_"), variantName);
// Prefer subject enum name when path is full
var useEnum: String = enumName;
if String_Eq(useEnum, "") { useEnum = subjectEnumName; }
let fullTag: String = String_Concat(String_Concat(useEnum, "_"), variantName);
if subjectHasData && (String_Eq(enumName, subjectEnumName) || String_Eq(enumName, "")) {
// Algebraic: subject.tag == Enum_Variant
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagPtr.kind = hFieldPtr;
tagPtr.line = line;
tagPtr.column = col;
tagPtr.strValue = "tag";
tagPtr.child1 = subject;
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagLoad.kind = hLoad;
tagLoad.line = line;
tagLoad.column = col;
tagLoad.child1 = tagPtr;
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagConst.kind = hVar;
tagConst.line = line;
tagConst.column = col;
tagConst.strValue = fullTag;
return Lcx_MakeBinHir(tkEq, tagLoad, tagConst, line, col);
} else {
// Simple enum: subject == Enum_Variant
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagConst.kind = hVar;
tagConst.line = line;
tagConst.column = col;
if !String_Eq(enumName, "") {
tagConst.strValue = fullTag;
} else {
tagConst.strValue = tagName;
}
return Lcx_MakeBinHir(tkEq, subject, tagConst, line, col);
}
}
return null as *HirNode;
}
// Emit binding stmts for pattern payload: Option::Some(value) → alloca value; value = subject.data.Some_0
// Returns head of child3-linked list of HirNodes (may be null).
func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
// Guarded: bind from inner pattern
if pat.kind == pkGuarded {
return Lcx_PatternBindings(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
}
if pat.kind == pkIdent {
let ty: String = "int";
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
ty = subject.typeName;
}
return Lcx_BindPatIdent(ctx, pat.patIdent, ty, subject, line, col);
}
// Tuple: (a, b) → a = subject._0; b = subject._1
if pat.kind == pkTuple {
var head: *HirNode = null as *HirNode;
var tail: *HirNode = null as *HirNode;
var ei: int = 0;
var elem: *Pattern = pat.patArgs;
while elem != null as *Pattern {
if elem.kind == pkIdent && !String_Eq(elem.patIdent, "_") {
var fieldName: String = "_0";
if ei == 1 { fieldName = "_1"; }
else if ei == 2 { fieldName = "_2"; }
else if ei == 3 { fieldName = "_3"; }
else if ei > 3 { fieldName = String_Concat("_", String_FromInt(ei as int64)); }
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fieldName;
fPtr.child1 = subject;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = "int";
let bound: *HirNode = Lcx_BindPatIdent(ctx, elem.patIdent, "int", fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
elem = elem.patNext;
ei = ei + 1;
}
return head;
}
// Struct: Point { x: a } → a = subject.x
if pat.kind == pkStruct {
var head: *HirNode = null as *HirNode;
var tail: *HirNode = null as *HirNode;
var field: *Pattern = pat.patArgs;
while field != null as *Pattern {
if field.kind == pkIdent && !String_Eq(field.patIdent, "_") {
var fname: String = field.patFieldName;
if String_Eq(fname, "") { fname = field.patIdent; }
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fname;
fPtr.child1 = subject;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = "int";
let bound: *HirNode = Lcx_BindPatIdent(ctx, field.patIdent, "int", fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
field = field.patNext;
}
return head;
}
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
var enumName: String = "";
var variantName: String = pat.patEnumPath;
if String_Contains(pat.patEnumPath, "::") {
enumName = String_SplitPart(pat.patEnumPath, "::", 0);
variantName = String_SplitPart(pat.patEnumPath, "::", 1);
} else {
enumName = subjectEnumName;
}
if String_Eq(enumName, "") || String_Eq(variantName, "") { return null as *HirNode; }
// Look up field type names from enum decl
var fieldType0: String = "int";
var fieldType1: String = "int";
var fieldCount: int = 0;
let enumSym: Symbol = Scope_Lookup(ctx.scope, enumName);
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
var vi: int = 0;
while vi < enumSym.decl.variantCount {
var vv: *EnumVariant = null as *EnumVariant;
if vi == 0 { vv = &enumSym.decl.variant0; }
else if vi == 1 { vv = &enumSym.decl.variant1; }
else if vi == 2 { vv = &enumSym.decl.variant2; }
else if vi == 3 { vv = &enumSym.decl.variant3; }
else if vi == 4 { vv = &enumSym.decl.variant4; }
else if vi == 5 { vv = &enumSym.decl.variant5; }
else if vi == 6 { vv = &enumSym.decl.variant6; }
else if vi == 7 { vv = &enumSym.decl.variant7; }
else if vi == 8 { vv = &enumSym.decl.variant8; }
if vv != null as *EnumVariant && String_Eq(vv.name, variantName) {
fieldCount = vv.fieldCount;
if !String_Eq(vv.fieldTypeName0, "") { fieldType0 = vv.fieldTypeName0; }
if !String_Eq(vv.fieldTypeName1, "") { fieldType1 = vv.fieldTypeName1; }
}
vi = vi + 1;
}
}
// dataLoad = subject.data
let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataPtr.kind = hFieldPtr;
dataPtr.line = line;
dataPtr.column = col;
dataPtr.strValue = "data";
dataPtr.child1 = subject;
let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataLoad.kind = hLoad;
dataLoad.line = line;
dataLoad.column = col;
dataLoad.child1 = dataPtr;
dataLoad.typeName = String_Concat(enumName, "_Data");
// Multi-field: nested struct data.Variant (anonymous or Enum_Variant_Payload);
// single-field: flat data.Variant_0
var payloadBase: *HirNode = dataLoad;
if fieldCount > 1 {
let nestedName: String = String_Concat(String_Concat(enumName, "_"),
String_Concat(variantName, "_Payload"));
let vPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
vPtr.kind = hFieldPtr;
vPtr.line = line;
vPtr.column = col;
vPtr.strValue = variantName;
vPtr.child1 = dataLoad;
let vLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
vLoad.kind = hLoad;
vLoad.line = line;
vLoad.column = col;
vLoad.child1 = vPtr;
vLoad.typeName = nestedName;
payloadBase = vLoad;
}
var head: *HirNode = null as *HirNode;
var tail: *HirNode = null as *HirNode;
var arg: *Pattern = pat.patArgs;
var ai: int = 0;
while arg != null as *Pattern {
var ftype: String = "int";
if ai == 0 { ftype = fieldType0; }
else if ai == 1 { ftype = fieldType1; }
let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64));
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fieldName;
fPtr.child1 = payloadBase;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = ftype;
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
let bound: *HirNode = Lcx_BindPatIdent(ctx, arg.patIdent, ftype, fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
// Nested pattern on payload field
let nested: *HirNode = Lcx_PatternBindings(ctx, fLoad, arg, subjectEnumName, subjectHasData, line, col);
if nested != null as *HirNode {
if head == null as *HirNode {
head = nested;
tail = nested;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = nested;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
arg = arg.patNext;
ai = ai + 1;
}
return head;
}
// True when n is a multi-stmt yield block (match result, etc.)
func Lcx_IsMatchYield(n: *HirNode) -> bool {
if n == null as *HirNode { return false; }
if n.kind != hBlock { return false; }
// strValue must be a real temp name — null/"" is a plain statement block
if n.strValue == null as String { return false; }
return !String_Eq(n.strValue, "");
}
func Lcx_YieldVarOf(n: *HirNode) -> *HirNode {
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = n.strValue;
v.typeName = n.typeName;
return v;
}
// Append `node` at the end of a child3-linked chain starting at `head` (or its child1 if head is hBlock).
func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
if head == null as *HirNode || node == null as *HirNode { return; }
var cur: *HirNode = head;
if head.kind == hBlock && head.child1 != null as *HirNode {
cur = head.child1;
}
while cur.child3 != null as *HirNode {
cur = cur.child3;
}
cur.child3 = node;
}
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let subject: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
ctx.varCounter = ctx.varCounter + 1;
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
ctx.varCounter = ctx.varCounter + 1;
let foundName: String = String_Concat("__found_", String_FromInt(ctx.varCounter as int64));
// Result type from sema refType, default int
var typeName: String = "int";
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
typeName = expr.refType.typeName;
}
// Subject enum info
var subjectEnumName: String = "";
var subjectHasData: bool = false;
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
if expr.child1.refType.kind == tekNamed {
subjectEnumName = expr.child1.refType.typeName;
subjectHasData = Lcx_EnumHasData(ctx, subjectEnumName);
}
}
// Alloca result + found flag
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
allocaNode.kind = hAlloca;
Lcx_SetNodeLoc(ctx, allocaNode, line, col, expr.sourceFile);
allocaNode.strValue = resultName;
allocaNode.typeName = typeName;
let foundAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundAlloca.kind = hAlloca;
Lcx_SetNodeLoc(ctx, foundAlloca, line, col, expr.sourceFile);
foundAlloca.strValue = foundName;
foundAlloca.typeName = "bool";
allocaNode.child3 = foundAlloca;
let foundInit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundInit.kind = hStore;
foundInit.line = line;
foundInit.column = col;
let foundVar0: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundVar0.kind = hVar;
foundVar0.strValue = foundName;
foundInit.child1 = foundVar0;
foundInit.child2 = Lcx_MakeLitHir(tkBoolLiteral, "false", line, col);
foundAlloca.child3 = foundInit;
// Chain of arm ifs (forward order), linked via child3
var tail: *HirNode = foundInit;
var cur: *MatchArm = expr.matchArms;
while cur != null as *MatchArm {
// Snapshot rename map so this arm's bindings don't leak to later arms
let savedMapCount: int = ctx.patMapCount;
var bindPat: *Pattern = cur.pattern;
var guardExpr: *Expr = null as *Expr;
if cur.pattern != null as *Pattern && cur.pattern.kind == pkGuarded {
bindPat = cur.pattern.patChild1;
guardExpr = cur.pattern.patGuardExpr;
}
// Bindings before guard/body so renames are active and allocas precede uses
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
var guardHirEarly: *HirNode = null as *HirNode;
if guardExpr != null as *Expr {
guardHirEarly = Lcx_LowerExpr(ctx, guardExpr);
}
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
// Pop this arm's renames (nested matches already restored themselves)
ctx.patMapCount = savedMapCount;
// store result = body
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeNode.kind = hStore;
storeNode.line = line;
storeNode.column = col;
let resVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
resVar.kind = hVar;
resVar.strValue = resultName;
storeNode.child1 = resVar;
var bodyPrefix: *HirNode = null as *HirNode;
if Lcx_IsMatchYield(bodyHir) {
storeNode.child2 = Lcx_YieldVarOf(bodyHir);
bodyHir.strValue = "";
bodyPrefix = bodyHir.child1;
} else {
storeNode.child2 = bodyHir;
}
// found = true
let foundSet: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundSet.kind = hStore;
foundSet.line = line;
foundSet.column = col;
let foundVar1: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundVar1.kind = hVar;
foundVar1.strValue = foundName;
foundSet.child1 = foundVar1;
foundSet.child2 = Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
storeNode.child3 = foundSet;
// success block: bodyPrefix → store → found=true
let successBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
successBlock.kind = hBlock;
successBlock.line = line;
successBlock.column = col;
if bodyPrefix != null as *HirNode {
successBlock.child1 = bodyPrefix;
var sbt: *HirNode = bodyPrefix;
while sbt.child3 != null as *HirNode { sbt = sbt.child3; }
sbt.child3 = storeNode;
} else {
successBlock.child1 = storeNode;
}
// Optional guard wraps success (already lowered with renames active)
var afterBinds: *HirNode = successBlock;
if guardHirEarly != null as *HirNode {
let guardIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
guardIf.kind = hIf;
guardIf.line = line;
guardIf.column = col;
guardIf.child1 = guardHirEarly;
guardIf.child2 = successBlock;
afterBinds = guardIf;
}
// armInner: binds → afterBinds
let armInner: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
armInner.kind = hBlock;
armInner.line = line;
armInner.column = col;
if bindHead != null as *HirNode {
armInner.child1 = bindHead;
var ibt: *HirNode = bindHead;
while ibt.child3 != null as *HirNode { ibt = ibt.child3; }
ibt.child3 = afterBinds;
} else {
armInner.child1 = afterBinds;
}
// Optional pattern condition
let cond: *HirNode = Lcx_PatternCond(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
var armBody: *HirNode = armInner;
if cond != null as *HirNode {
let condIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
condIf.kind = hIf;
condIf.line = line;
condIf.column = col;
condIf.child1 = cond;
condIf.child2 = armInner;
armBody = condIf;
}
// if (!found) armBody
let foundLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundLoad.kind = hVar;
foundLoad.strValue = foundName;
foundLoad.typeName = "bool";
let notFound: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
notFound.kind = hUnary;
notFound.line = line;
notFound.column = col;
notFound.intValue = tkBang;
notFound.child1 = foundLoad;
let tryIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tryIf.kind = hIf;
tryIf.line = line;
tryIf.column = col;
tryIf.child1 = notFound;
tryIf.child2 = armBody;
tail.child3 = tryIf;
tail = tryIf;
cur = cur.next;
}
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
block.kind = hBlock;
block.line = line;
block.column = col;
block.child1 = allocaNode;
// Yield marker: strValue = result var name for last-expr return / let init
block.strValue = resultName;
block.typeName = typeName;
return block;
}
// ---------------------------------------------------------------------------
// 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;
Lcx_SetNodeLoc(ctx, n, line, col, expr.sourceFile);
let kind: int = expr.kind;
// Match expression
if kind == ekMatch {
return Lcx_LowerMatch(ctx, expr);
}
// String interpolation: desugar to String_Concat + String_FromInt/Bool/Float
// Parts in callArgs are interleaved text lits and expressions.
if kind == ekStringInterp {
var result: *HirNode = null as *HirNode;
var part: *ExprList = expr.callArgs;
while part != null as *ExprList {
let pe: *Expr = part.expr;
var piece: *HirNode = null as *HirNode;
if pe != null as *Expr && pe.kind == ekLiteral && pe.tokKind == tkStringLiteral {
piece = Lcx_LowerExpr(ctx, pe);
} else {
let lowered: *HirNode = Lcx_LowerExpr(ctx, pe);
// Convert non-string to String
var needConv: bool = true;
var convName: String = "String_FromInt";
if pe != null as *Expr && pe.refType != null as *TypeExpr {
let tn: String = pe.refType.typeName;
if String_Eq(tn, "String") || String_Eq(tn, "str") {
needConv = false;
} else if String_Eq(tn, "bool") {
convName = "String_FromBool";
} else if String_Eq(tn, "float64") || String_Eq(tn, "float") || String_Eq(tn, "float32") {
convName = "String_FromFloat";
} else {
convName = "String_FromInt";
}
}
if needConv {
let callN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
callN.kind = hCall;
callN.line = line;
callN.column = col;
callN.strValue = convName;
callN.child1 = lowered;
piece = callN;
} else {
piece = lowered;
}
}
if result == null as *HirNode {
result = piece;
} else {
let cat: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
cat.kind = hCall;
cat.line = line;
cat.column = col;
cat.strValue = "String_Concat";
cat.child1 = result;
cat.child2 = piece;
result = cat;
}
part = part.next;
}
if result == null as *HirNode {
// empty f""
let empty: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
empty.kind = hLit;
empty.line = line;
empty.column = col;
empty.intValue = tkStringLiteral;
empty.strValue = "\"\"";
return empty;
}
return result;
}
// Literal
if kind == ekLiteral {
n.kind = hLit;
n.intValue = expr.tokKind;
n.strValue = expr.tokText;
return n;
}
// Identifier → variable reference
if kind == ekIdent {
// Pattern binding rename: source name → unique C local
let ren: String = Lcx_PatLookup(ctx, expr.strValue);
if !String_Eq(ren, "") {
n.kind = hVar;
n.strValue = ren;
let rsym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = rsym.typeKind;
if rsym.typeName != null as String && !String_Eq(rsym.typeName, "") {
n.typeName = rsym.typeName;
}
return n;
}
// 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;
}
}
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
// Named function used as a value → fat pointer via __adapt_ wrapper
if sym.kind == skFunc {
var fatName: String = "BuxFn_int_int";
if sym.refType != null as *TypeExpr && sym.refType.kind == tekFunc {
fatName = Lcx_BuildFuncTypeName(sym.refType);
} else if !String_Eq(sym.typeName, "") && String_StartsWith(sym.typeName, "BuxFn_") {
fatName = sym.typeName;
}
n.kind = hStructInit;
n.strValue = fatName;
n.typeKind = tyFunc;
n.typeName = fatName;
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
codeField.kind = hBlock;
codeField.strValue = "code";
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
codeVal.kind = hVar;
codeVal.strValue = String_Concat("__adapt_", expr.strValue);
codeField.child1 = codeVal;
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
envField.kind = hBlock;
envField.strValue = "env";
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
nullEnv.kind = hCast;
nullEnv.typeName = "void*";
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
zero.kind = hLit;
zero.intValue = tkIntLiteral;
zero.strValue = "0";
nullEnv.child1 = zero;
envField.child1 = nullEnv;
codeField.child3 = envField;
n.child1 = codeField;
return n;
}
n.kind = hVar;
n.strValue = 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 {
if refTe.typeName != null as String { receiverTypeName = refTe.typeName; }
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
if refTe.pointerPointee.typeName != null as String {
receiverTypeName = refTe.pointerPointee.typeName;
}
}
}
// Note: String_Eq(null, "") is false — must also reject null type names
if receiverTypeName != null as String && !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;
}
}
}
// Overflow checking: in @[Checked] mode, lower +, -, * on signed integers to checked calls
if ctx.checkedFunc && !ctx.releaseFunc {
var opKind: int = expr.intValue;
var isArithOp: bool = opKind == tkPlus || opKind == tkMinus || opKind == tkStar;
var isSignedInt: bool = false;
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
let lhsKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
isSignedInt = Type_IsSigned(lhsKind);
}
if isArithOp && isSignedInt {
var checkedFunc: String = "";
if opKind == tkPlus { checkedFunc = "bux_add_i64_checked"; }
else if opKind == tkMinus { checkedFunc = "bux_sub_i64_checked"; }
else if opKind == tkStar { checkedFunc = "bux_mul_i64_checked"; }
if !String_Eq(checkedFunc, "") {
n.kind = hCall;
n.strValue = checkedFunc;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
}
}
n.kind = hBinary;
n.intValue = expr.intValue; // operator
let leftHir: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
let rightHir: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
// If either side is a match yield block, expand to:
// match stmts...; int __binop_N = leftVal op rightVal; yield __binop_N
if Lcx_IsMatchYield(leftHir) || Lcx_IsMatchYield(rightHir) {
ctx.varCounter = ctx.varCounter + 1;
let tmpName: String = String_Concat("__binop_", String_FromInt(ctx.varCounter as int64));
var leftVal: *HirNode = leftHir;
var rightVal: *HirNode = rightHir;
let outer: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
outer.kind = hBlock;
outer.line = line;
outer.column = col;
outer.strValue = tmpName;
outer.typeName = "int";
var first: *HirNode = null as *HirNode;
if Lcx_IsMatchYield(leftHir) {
leftVal = Lcx_YieldVarOf(leftHir);
leftHir.strValue = "";
first = leftHir;
}
if Lcx_IsMatchYield(rightHir) {
rightVal = Lcx_YieldVarOf(rightHir);
rightHir.strValue = "";
if first == null as *HirNode {
first = rightHir;
} else {
Lcx_AppendToChain(first, rightHir);
}
}
let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpAlloca.kind = hAlloca;
tmpAlloca.line = line;
tmpAlloca.column = col;
tmpAlloca.strValue = tmpName;
tmpAlloca.typeName = "int";
let binNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
binNode.kind = hBinary;
binNode.line = line;
binNode.column = col;
binNode.intValue = expr.intValue;
binNode.child1 = leftVal;
binNode.child2 = rightVal;
let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpStore.kind = hStore;
tmpStore.line = line;
tmpStore.column = col;
let tmpVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpVar.kind = hVar;
tmpVar.strValue = tmpName;
tmpStore.child1 = tmpVar;
tmpStore.child2 = binNode;
tmpAlloca.child3 = tmpStore;
if first == null as *HirNode {
outer.child1 = tmpAlloca;
} else {
outer.child1 = first;
Lcx_AppendToChain(first, tmpAlloca);
}
return outer;
}
n.child1 = leftHir;
n.child2 = rightHir;
return n;
}
// Unary
if kind == ekUnary {
// Overflow checking: in @[Checked] mode, lower negation on signed integers to checked call
if ctx.checkedFunc && !ctx.releaseFunc && expr.intValue == tkMinus {
var isSignedInt: bool = false;
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
let operandKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
isSignedInt = Type_IsSigned(operandKind);
}
if isSignedInt {
n.kind = hCall;
n.strValue = "bux_neg_i64_checked";
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
}
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<T> on Box<int> -> 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 (explicit / inferred type args)
if expr.child1 != null as *Expr {
var argc: int = expr.child1.genericTypeArgCount;
var typeArg0: String = expr.child1.genericTypeArg0;
var typeArg1: String = expr.child1.genericTypeArg1;
// Fallback: infer T from first *Array/*Iter arg when sema left count=0
if argc == 0 {
let genTry: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
if genTry != null as *Decl && genTry.typeParamCount > 0 {
if expr.callArgs != null as *ExprList && expr.callArgs.expr != null as *Expr {
var a0: *Expr = expr.callArgs.expr;
var te: *TypeExpr = a0.refType;
if te == null as *TypeExpr && a0.kind == ekUnary && a0.intValue == tkAmp {
if a0.child1 != null as *Expr { te = a0.child1.refType; }
}
if te != null as *TypeExpr {
// Unwrap pointer
if (te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef)
&& te.pointerPointee != null as *TypeExpr {
te = te.pointerPointee;
}
var elem: String = "";
if te.typeArgCount > 0 {
elem = te.typeArgName0;
} else {
// Mangled Array_int / Iter_String
elem = Lcx_ExtractElemFromName(te.typeName);
}
if !String_Eq(elem, "") {
typeArg0 = elem;
argc = 1;
// Second type arg from func-typed second argument if needed
if genTry.typeParamCount >= 2 && expr.callArgs.next != null as *ExprList {
let a1e: *Expr = expr.callArgs.next.expr;
if a1e != null as *Expr && a1e.kind == ekIdent {
let fsym: Symbol = Scope_Lookup(ctx.scope, a1e.strValue);
if fsym.kind == skFunc && fsym.decl != null as *Decl
&& fsym.decl.retType != null as *TypeExpr
&& fsym.decl.retType.kind == tekNamed {
typeArg1 = fsym.decl.retType.typeName;
argc = 2;
}
}
}
}
}
}
}
}
if argc > 0 {
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
if genDecl != null as *Decl {
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, argc);
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<T> or *Array<T>, 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;
}
// Tuple expression (a, b, ...) → struct init Tuple_int_int { ._0 = a, ._1 = b }
if kind == ekTuple {
var tname: String = "Tuple";
var ti: int = 0;
while ti < expr.callArgCount {
tname = String_Concat(tname, "_int");
ti = ti + 1;
}
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
tname = expr.refType.typeName;
}
n.kind = hStructInit;
n.strValue = tname;
n.typeKind = tyNamed;
n.typeName = tname;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
var tcur: *ExprList = expr.callArgs;
var tidx: int = 0;
while tcur != null as *ExprList {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock;
fNode.strValue = String_Concat("_", String_FromInt(tidx as int64));
fNode.child1 = Lcx_LowerExpr(ctx, tcur.expr);
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
tcur = tcur.next;
tidx = tidx + 1;
}
n.child1 = firstField;
return n;
}
// Closure: fat function pointer (multi-instance via heap env + maker)
if kind == ekClosure {
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
var fatName: String = "BuxFn_int_int";
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
fatName = Lcx_BuildFuncTypeName(expr.refType);
} else if f.paramCount >= 2 {
// thunk has __env + user params; approximate from ret + user arity
fatName = "BuxFn_int_int";
if f.paramCount == 3 { fatName = "BuxFn_int_int_int"; }
if f.paramCount == 1 { fatName = "BuxFn_int_void"; }
}
if f.captureCount > 0 {
// Call __make_<closure>(captures...) which heap-allocs env
n.kind = hCall;
n.strValue = String_Concat("__make_", f.name);
n.typeKind = tyFunc;
n.typeName = fatName;
var ci: int = 0;
while ci < f.captureCount {
var capName: String = "";
if ci == 0 { capName = f.captureName0; }
else if ci == 1 { capName = f.captureName1; }
else if ci == 2 { capName = f.captureName2; }
else if ci == 3 { capName = f.captureName3; }
else if ci == 4 { capName = f.captureName4; }
else if ci == 5 { capName = f.captureName5; }
else if ci == 6 { capName = f.captureName6; }
else if ci == 7 { capName = f.captureName7; }
let capVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
capVar.kind = hVar;
capVar.strValue = capName;
if ci == 0 { n.child1 = capVar; }
else if ci == 1 { n.child2 = capVar; }
else if ci == 2 {
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = capVar;
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 = capVar;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
ci = ci + 1;
}
return n;
}
// Capture-less: compound literal fat pointer with NULL env
n.kind = hStructInit;
n.strValue = fatName;
n.typeKind = tyFunc;
n.typeName = fatName;
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
codeField.kind = hBlock;
codeField.strValue = "code";
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
codeVal.kind = hVar;
codeVal.strValue = f.name;
codeField.child1 = codeVal;
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
envField.kind = hBlock;
envField.strValue = "env";
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
nullEnv.kind = hCast;
nullEnv.typeName = "void*";
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
zero.kind = hLit;
zero.intValue = tkIntLiteral;
zero.strValue = "0";
nullEnv.child1 = zero;
envField.child1 = nullEnv;
codeField.child3 = envField;
n.child1 = codeField;
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;
}
// Block expression (boolValue = true means unsafe block)
// retTypeKind -2 → yield last expression as block value
if kind == ekBlock {
if expr.refBlock != null as *Block {
if expr.boolValue {
let oldChecked: bool = ctx.checkedFunc;
let oldRelease: bool = ctx.releaseFunc;
ctx.checkedFunc = false;
ctx.releaseFunc = false;
let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -2);
ctx.checkedFunc = oldChecked;
ctx.releaseFunc = oldRelease;
return blockNode;
} else {
return Lcx_LowerBlock(ctx, expr.refBlock, -2);
}
}
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;
Lcx_SetNodeLoc(ctx, n, line, col, stmt.sourceFile);
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);
// Match (or other multi-stmt yield) as let initializer:
// match stmts...; Type x = __match_N;
// instead of illegal `Type x = <block>;`
if Lcx_IsMatchYield(init) {
let yieldName: String = init.strValue;
init.strValue = "";
let yieldVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
yieldVar.kind = hVar;
yieldVar.strValue = yieldName;
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeNode.kind = hStore;
storeNode.line = line;
storeNode.column = col;
storeNode.child1 = alloca;
storeNode.child2 = yieldVar;
Lcx_AppendToChain(init, storeNode);
return init;
}
// 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;
}
}
// Capturing closures allocate env via __make_* at ekClosure site.
// 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 {
if stmt.child1 != null as *Expr {
let retVal: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// `return match { ... }` / other multi-stmt yields: expand stmts then return result var
if retVal != null as *HirNode && Lcx_IsMatchYield(retVal) {
let retVar: *HirNode = Lcx_YieldVarOf(retVal);
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = line;
retNode.column = col;
retNode.child1 = retVar;
retVal.strValue = "";
var lastIn: *HirNode = retVal.child1;
if lastIn == null as *HirNode {
retVal.child1 = retNode;
} else {
while lastIn.child3 != null as *HirNode {
lastIn = lastIn.child3;
}
lastIn.child3 = retNode;
}
return retVal;
}
n.kind = hReturn;
n.child1 = retVal;
return n;
}
n.kind = hReturn;
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<T> 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; }
// retTypeKind:
// >= 0 → function body; last skExpr becomes return
// -1 → statement block (if/while/for); no return, no yield
// -2 → block-as-expression (match arm / let { ... }); yield last skExpr
let asExpr: bool = retTypeKind == -2;
var yieldName: String = "";
if asExpr {
ctx.varCounter = ctx.varCounter + 1;
yieldName = String_Concat("__blk_", String_FromInt(ctx.varCounter as int64));
}
// Build a linked list of HirNodes via child3
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let isLast: bool = stmt.nextStmt == null as *Stmt;
var lowered: *HirNode = null as *HirNode;
// Last expression statement in a non-void function → implicit return
if isLast && !asExpr && retTypeKind != tyVoid && retTypeKind != tyUnknown && retTypeKind >= 0
&& stmt.kind == skExpr && stmt.child1 != null as *Expr {
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
if exprNode != null as *HirNode {
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retVar.kind = hVar;
retVar.strValue = exprNode.strValue;
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = stmt.line;
retNode.column = stmt.column;
retNode.child1 = retVar;
var lastInBlock: *HirNode = exprNode.child1;
if lastInBlock == null as *HirNode {
exprNode.child1 = retNode;
} else {
while lastInBlock.child3 != null as *HirNode {
lastInBlock = lastInBlock.child3;
}
lastInBlock.child3 = retNode;
}
exprNode.strValue = "";
lowered = exprNode;
} else if exprNode.kind == hReturn {
lowered = exprNode;
} else {
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = stmt.line;
retNode.column = stmt.column;
retNode.child1 = exprNode;
lowered = retNode;
}
}
} else if isLast && asExpr && stmt.kind == skExpr && stmt.child1 != null as *Expr {
// Block-as-expression: last expr is the yield value
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca yield temp
let allocaN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
allocaN.kind = hAlloca;
allocaN.line = stmt.line;
allocaN.column = stmt.column;
allocaN.strValue = yieldName;
allocaN.typeName = "int";
if exprNode != null as *HirNode && exprNode.typeName != null as String
&& !String_Eq(exprNode.typeName, "") {
allocaN.typeName = exprNode.typeName;
}
// store: yield = value (handle nested match yield)
let storeN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeN.kind = hStore;
storeN.line = stmt.line;
storeN.column = stmt.column;
let yv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
yv.kind = hVar;
yv.strValue = yieldName;
storeN.child1 = yv;
if exprNode != null as *HirNode && Lcx_IsMatchYield(exprNode) {
// Expand nested match stmts then store its result var
let yvar: *HirNode = Lcx_YieldVarOf(exprNode);
storeN.child2 = yvar;
exprNode.strValue = "";
// chain: exprNode stmts → alloca → store
var lastIn: *HirNode = exprNode.child1;
if lastIn == null as *HirNode {
exprNode.child1 = allocaN;
allocaN.child3 = storeN;
lowered = exprNode;
} else {
while lastIn.child3 != null as *HirNode {
lastIn = lastIn.child3;
}
lastIn.child3 = allocaN;
allocaN.child3 = storeN;
lowered = exprNode;
}
} else {
storeN.child2 = exprNode;
allocaN.child3 = storeN;
lowered = allocaN;
}
} else {
lowered = Lcx_LowerStmt(ctx, stmt);
}
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
// Walk to end of chain (lowered may itself be a multi-node chain)
prevNode.child3 = lowered;
prevNode = lowered;
while prevNode.child3 != null as *HirNode {
prevNode = prevNode.child3;
}
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
Lcx_SetNodeLoc(ctx, n, block.line, block.column, block.sourceFile);
n.boolValue = true;
n.child1 = firstNode;
if asExpr && !String_Eq(yieldName, "") {
n.strValue = yieldName;
n.typeName = "int";
}
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.sourceFile = decl.sourceFile;
let oldSourceFile: String = ctx.currentSourceFile;
ctx.currentSourceFile = decl.sourceFile;
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.retTypeKind = Lcx_ResolveTypeKind(retTe);
if retTe.kind == tekFunc {
f.retTypeName = Lcx_BuildFuncTypeName(retTe);
} else if !String_Eq(retTe.typeName, "") {
f.retTypeName = retTe.typeName;
} else if retTe.kind == tekPointer && retTe.pointerPointee != null as *TypeExpr {
f.retTypeName = String_Concat(retTe.pointerPointee.typeName, "*");
} else {
f.retTypeName = "";
}
} 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 (pass ret kind for last-expr return)
let prevScope: *Scope = ctx.scope;
ctx.scope = &funcScope;
if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, f.retTypeKind);
// Stamp every node with this function's source path (nodes may already
// carry a different file for rare multi-file spans; those are kept).
Lcx_StampSourceFile(f.body, f.sourceFile);
} else {
f.body = null as *HirNode;
}
ctx.scope = prevScope;
ctx.checkedFunc = oldChecked;
ctx.releaseFunc = oldRelease;
ctx.currentSourceFile = oldSourceFile;
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;
// Closures inherit the enclosing function's source path
f.sourceFile = ctx.currentSourceFile;
let params: *Decl = expr.closureParams;
// Fat-func ABI: leading void* __env, then user params
var userCount: int = 0;
if params != null as *Decl { userCount = params.paramCount; }
f.paramCount = userCount + 1;
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
f.param0.name = "__env";
f.param0.typeKind = tyPointer;
f.param0.typeName = "void*";
if userCount > 0 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, &params.param0, ctx); }
if userCount > 1 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, &params.param1, ctx); }
if userCount > 2 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, &params.param2, ctx); }
if userCount > 3 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, &params.param3, ctx); }
if userCount > 4 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, &params.param4, ctx); }
if userCount > 5 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, &params.param5, ctx); }
if userCount > 6 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, &params.param6, ctx); }
if userCount > 7 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, &params.param7, ctx); }
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
// Return type of the *thunk* is the closure's return type (not the fat type)
if expr.refType.funcRet != null as *TypeExpr {
f.retTypeName = expr.refType.funcRet.typeName;
if String_Eq(f.retTypeName, "") { f.retTypeName = "int"; }
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
} else {
f.retTypeName = "void";
f.retTypeKind = tyVoid;
}
} else {
f.retTypeName = "int";
f.retTypeKind = tyInt;
}
// 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 = &params.param0; }
else if pi == 1 { p = &params.param1; }
else if pi == 2 { p = &params.param2; }
else if pi == 3 { p = &params.param3; }
else if pi == 4 { p = &params.param4; }
else if pi == 5 { p = &params.param5; }
else if pi == 6 { p = &params.param6; }
else if pi == 7 { p = &params.param7; }
else if pi == 8 { p = &params.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);
Lcx_StampSourceFile(f.body, f.sourceFile);
} 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;
// Capacities sized for full selfhost compile (~600 funcs, ~120 structs).
// Undersized buffers overflowed heap → corrupt main.c / buxc2→buxc3 fail.
ctx.funcs = bux_alloc(4096 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.funcCount = 0;
ctx.externFuncs = bux_alloc(1024 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.externCount = 0;
ctx.varCounter = 0;
ctx.genFuncCount = 0;
ctx.genFuncs = bux_alloc(1024 as uint * sizeof(Decl)) as *Decl;
ctx.genStructCount = 0;
ctx.genStructs = bux_alloc(1024 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(512 as uint * sizeof(HirStruct)) as *HirStruct;
hm.enumCount = 0;
hm.enums = bux_alloc(256 as uint * sizeof(HirEnum)) as *HirEnum;
hm.constCount = 0;
hm.consts = bux_alloc(2048 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, "") {
// Monomorphize container fields: Array<int> → Array_int
// so C backend does not skip the parent struct as "generic".
var tn: String = ftype.typeName;
if ftype.typeArgCount >= 1 && !String_Eq(ftype.typeArgName0, "") {
if String_Eq(tn, "Array") || String_Eq(tn, "Set") ||
String_Eq(tn, "Channel") || String_Eq(tn, "Iter") {
tn = String_Concat(String_Concat(tn, "_"), ftype.typeArgName0);
} else if String_Eq(tn, "Map") && ftype.typeArgCount >= 2 {
tn = String_Concat(String_Concat(String_Concat("Map_", ftype.typeArgName0), "_"), ftype.typeArgName1);
}
}
hm.structs[si].fields[fi].typeName = tn;
}
}
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 < 2048 {
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 {
// Positional field names: Variant_0, Variant_1 (matches data.Variant_i / nested struct)
hm.enums[ei].variants[vi].fieldName0 = String_Concat(v.name, "_0");
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
hm.enums[ei].variants[vi].fieldTypeName0 = v.fieldTypeName0;
}
if v.fieldCount > 1 {
hm.enums[ei].variants[vi].fieldName1 = String_Concat(v.name, "_1");
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
hm.enums[ei].variants[vi].fieldTypeName1 = v.fieldTypeName1;
}
}
vi = vi + 1;
}
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;
}
}