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