70321075a6
Stamp each merged .bux path onto decls, propagate to HirFunc, and emit #line N \"path\" per function without requiring BUX_DEBUG_FILE. - Decl.sourceFile + Cli_StampSourceFile on merge/project parse - HirFunc.sourceFile; CBE switches currentFile per function - Stdlib and multi-file user packages get distinct paths automatically
4208 lines
195 KiB
Plaintext
4208 lines
195 KiB
Plaintext
// hir_lower.bux — HIR lowering: AST → HIR transformation (ported from hir_lower.nim)
|
|
// Transforms the typed AST into a lower-level IR suitable for code generation.
|
|
module HirLower {
|
|
|
|
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
|
extern func bux_strlen(s: String) -> uint;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Lowering context
|
|
// ---------------------------------------------------------------------------
|
|
struct LowerCtx {
|
|
module: *Module,
|
|
scope: *Scope,
|
|
funcs: *HirFunc,
|
|
funcCount: int,
|
|
externFuncs: *HirFunc,
|
|
externCount: int,
|
|
varCounter: int,
|
|
tryCounter: int,
|
|
closureDepth: int,
|
|
currentClosureExpr: *Expr,
|
|
envInstanceName: String,
|
|
hm: *HirModule,
|
|
// Generic monomorphization
|
|
genFuncCount: int,
|
|
genFuncs: *Decl,
|
|
genStructCount: int,
|
|
genStructs: *Decl,
|
|
// Type substitution (active during generic instance lowering)
|
|
substParam0: String,
|
|
substArg0: String,
|
|
substParam1: String,
|
|
substArg1: String,
|
|
// Borrow checker state
|
|
checkedFunc: bool,
|
|
releaseFunc: bool,
|
|
// Pattern binding renames: source name → unique C local (shadowing-safe)
|
|
patMapCount: int,
|
|
patMapFrom0: String,
|
|
patMapTo0: String,
|
|
patMapFrom1: String,
|
|
patMapTo1: String,
|
|
patMapFrom2: String,
|
|
patMapTo2: String,
|
|
patMapFrom3: String,
|
|
patMapTo3: String,
|
|
patMapFrom4: String,
|
|
patMapTo4: String,
|
|
patMapFrom5: String,
|
|
patMapTo5: String,
|
|
patMapFrom6: String,
|
|
patMapTo6: String,
|
|
patMapFrom7: String,
|
|
patMapTo7: String,
|
|
}
|
|
|
|
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
|
|
// Most recent rename wins (scan from end)
|
|
var i: int = ctx.patMapCount - 1;
|
|
while i >= 0 {
|
|
var from: String = "";
|
|
var to: String = "";
|
|
if i == 0 { from = ctx.patMapFrom0; to = ctx.patMapTo0; }
|
|
else if i == 1 { from = ctx.patMapFrom1; to = ctx.patMapTo1; }
|
|
else if i == 2 { from = ctx.patMapFrom2; to = ctx.patMapTo2; }
|
|
else if i == 3 { from = ctx.patMapFrom3; to = ctx.patMapTo3; }
|
|
else if i == 4 { from = ctx.patMapFrom4; to = ctx.patMapTo4; }
|
|
else if i == 5 { from = ctx.patMapFrom5; to = ctx.patMapTo5; }
|
|
else if i == 6 { from = ctx.patMapFrom6; to = ctx.patMapTo6; }
|
|
else if i == 7 { from = ctx.patMapFrom7; to = ctx.patMapTo7; }
|
|
if String_Eq(from, src) { return to; }
|
|
i = i - 1;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
|
|
if ctx.patMapCount >= 8 { return; }
|
|
let i: int = ctx.patMapCount;
|
|
if i == 0 { ctx.patMapFrom0 = src; ctx.patMapTo0 = dst; }
|
|
else if i == 1 { ctx.patMapFrom1 = src; ctx.patMapTo1 = dst; }
|
|
else if i == 2 { ctx.patMapFrom2 = src; ctx.patMapTo2 = dst; }
|
|
else if i == 3 { ctx.patMapFrom3 = src; ctx.patMapTo3 = dst; }
|
|
else if i == 4 { ctx.patMapFrom4 = src; ctx.patMapTo4 = dst; }
|
|
else if i == 5 { ctx.patMapFrom5 = src; ctx.patMapTo5 = dst; }
|
|
else if i == 6 { ctx.patMapFrom6 = src; ctx.patMapTo6 = dst; }
|
|
else if i == 7 { ctx.patMapFrom7 = src; ctx.patMapTo7 = dst; }
|
|
ctx.patMapCount = ctx.patMapCount + 1;
|
|
}
|
|
|
|
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
var safe: String = src;
|
|
if String_Eq(src, "") || String_Eq(src, "_") { safe = "x"; }
|
|
return String_Concat(String_Concat("__p", String_FromInt(ctx.varCounter as int64)),
|
|
String_Concat("_", safe));
|
|
}
|
|
|
|
// Alloca unique C local + store + scope define + rename map for pattern binding.
|
|
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
|
|
line: uint32, col: uint32) -> *HirNode {
|
|
if String_Eq(src, "") || String_Eq(src, "_") { return null as *HirNode; }
|
|
let cName: String = Lcx_FreshPatName(ctx, src);
|
|
Lcx_PatPush(ctx, src, cName);
|
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
alloca.kind = hAlloca;
|
|
alloca.line = line;
|
|
alloca.column = col;
|
|
alloca.strValue = cName;
|
|
alloca.typeName = ty;
|
|
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
store.kind = hStore;
|
|
store.line = line;
|
|
store.column = col;
|
|
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
v.kind = hVar;
|
|
v.strValue = cName;
|
|
store.child1 = v;
|
|
store.child2 = value;
|
|
alloca.child3 = store;
|
|
var bsym: Symbol;
|
|
bsym.kind = skVar;
|
|
bsym.name = src;
|
|
bsym.typeKind = tyInt;
|
|
bsym.typeName = ty;
|
|
bsym.refType = null as *TypeExpr;
|
|
bsym.isMutable = false;
|
|
bsym.isPublic = false;
|
|
bsym.decl = null as *Decl;
|
|
discard Scope_Define(ctx.scope, bsym);
|
|
return alloca;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TypeExpr.kind → Type.kind resolver
|
|
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
|
|
// resolves the correct Type.kind for codegen.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_ResolveTypeKindFromName(name: String) -> int {
|
|
return Type_FromName(name);
|
|
}
|
|
|
|
func Lcx_TypeKindToName(kind: int) -> String {
|
|
if kind == tyVoid { return "void"; }
|
|
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
|
|
if kind == tyChar8 { return "char"; }
|
|
if kind == tyChar16 { return "uint16"; }
|
|
if kind == tyChar32 { return "uint32"; }
|
|
if kind == tyStr { return "String"; }
|
|
if kind == tyInt8 { return "int8"; }
|
|
if kind == tyInt16 { return "int16"; }
|
|
if kind == tyInt32 { return "int32"; }
|
|
if kind == tyInt64 { return "int64"; }
|
|
if kind == tyInt { return "int"; }
|
|
if kind == tyUInt8 { return "uint8"; }
|
|
if kind == tyUInt16 { return "uint16"; }
|
|
if kind == tyUInt32 { return "uint32"; }
|
|
if kind == tyUInt64 { return "uint64"; }
|
|
if kind == tyUInt { return "uint"; }
|
|
if kind == tyFloat32 { return "float32"; }
|
|
if kind == tyFloat64 { return "float64"; }
|
|
if kind == tyPointer { return "void*"; }
|
|
return "int";
|
|
}
|
|
|
|
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
|
|
if te == null as *TypeExpr { return tyUnknown; }
|
|
|
|
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { return tyPointer; }
|
|
if te.kind == tekSlice { return tySlice; }
|
|
if te.kind == tekTuple { return tyNamed; /* Tuple_T_U is a C struct */ }
|
|
if te.kind == tekFunc { return tyFunc; }
|
|
|
|
return Lcx_ResolveTypeKindFromName(te.typeName);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Type substitution for generic monomorphization
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
|
|
if te == null as *TypeExpr { return te; }
|
|
|
|
// Generic named type with type args: check if concrete or parametric
|
|
if te.kind == tekNamed && te.typeArgCount > 0 {
|
|
let genStruct: *Decl = Lcx_FindGenericStruct(ctx, te.typeName);
|
|
if genStruct != null as *Decl {
|
|
// Check if type args are the struct's own type params (parametric)
|
|
var isParametric: bool = false;
|
|
if te.typeArgCount > 0 && String_Eq(te.typeArgName0, genStruct.typeParam0) { isParametric = true; }
|
|
if te.typeArgCount > 1 && String_Eq(te.typeArgName1, genStruct.typeParam1) { isParametric = true; }
|
|
|
|
// If parametric and NOT inside a generic instantiation, keep as generic (don't mangle)
|
|
if isParametric && String_Eq(ctx.substParam0, "") && String_Eq(ctx.substParam1, "") {
|
|
return te;
|
|
}
|
|
|
|
// Otherwise: substitute active type params and mangle to concrete name
|
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
r.kind = tekNamed;
|
|
r.line = te.line;
|
|
r.column = te.column;
|
|
r.typeArgCount = te.typeArgCount;
|
|
r.typeArgName0 = te.typeArgName0;
|
|
r.typeArgName1 = te.typeArgName1;
|
|
if String_Eq(r.typeArgName0, ctx.substParam0) { r.typeArgName0 = ctx.substArg0; }
|
|
if String_Eq(r.typeArgName0, ctx.substParam1) { r.typeArgName0 = ctx.substArg1; }
|
|
if String_Eq(r.typeArgName1, ctx.substParam0) { r.typeArgName1 = ctx.substArg0; }
|
|
if String_Eq(r.typeArgName1, ctx.substParam1) { r.typeArgName1 = ctx.substArg1; }
|
|
r.typeName = Lcx_MangleName(te.typeName, r.typeArgName0, r.typeArgName1, te.typeArgCount);
|
|
Lcx_GenerateStructInstance(ctx, genStruct, r.typeArgName0, r.typeArgName1, te.typeArgCount);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// Named type that is a type parameter (only when in instance mode)
|
|
if te.kind == tekNamed {
|
|
if String_Eq(te.typeName, ctx.substParam0) {
|
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
r.kind = tekNamed;
|
|
r.typeName = ctx.substArg0;
|
|
r.line = te.line;
|
|
r.column = te.column;
|
|
return r;
|
|
}
|
|
if String_Eq(te.typeName, ctx.substParam1) {
|
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
r.kind = tekNamed;
|
|
r.typeName = ctx.substArg1;
|
|
r.line = te.line;
|
|
r.column = te.column;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// Pointer type: substitute recursively
|
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
r.kind = tekPointer;
|
|
r.pointerPointee = Lcx_SubstituteType(ctx, te.pointerPointee);
|
|
if r.pointerPointee != null as *TypeExpr && !String_Eq(r.pointerPointee.typeName, "") {
|
|
r.typeName = String_Concat(r.pointerPointee.typeName, "*");
|
|
}
|
|
r.line = te.line;
|
|
r.column = te.column;
|
|
return r;
|
|
}
|
|
|
|
// Fat function type: func(T)->U — substitute params and return
|
|
if te.kind == tekFunc {
|
|
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
|
r.kind = tekFunc;
|
|
r.line = te.line;
|
|
r.column = te.column;
|
|
r.funcParamCount = te.funcParamCount;
|
|
r.funcRet = Lcx_SubstituteType(ctx, te.funcRet);
|
|
var head: *TypeExprList = null as *TypeExprList;
|
|
var tail: *TypeExprList = null as *TypeExprList;
|
|
var cur: *TypeExprList = te.funcParams;
|
|
while cur != null as *TypeExprList {
|
|
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
|
|
node.te = Lcx_SubstituteType(ctx, cur.te);
|
|
node.next = null as *TypeExprList;
|
|
if head == null as *TypeExprList {
|
|
head = node;
|
|
tail = node;
|
|
} else {
|
|
tail.next = node;
|
|
tail = node;
|
|
}
|
|
cur = cur.next;
|
|
}
|
|
r.funcParams = head;
|
|
r.typeName = Lcx_BuildFuncTypeName(r);
|
|
return r;
|
|
}
|
|
|
|
return te;
|
|
}
|
|
|
|
// Sanitize a C type fragment for use inside BuxFn_* mangled names
|
|
func Lcx_SanitizeFatPart(s: String) -> String {
|
|
var r: String = s;
|
|
if String_Eq(r, "String") || String_Eq(r, "str") || String_Eq(r, "const char*") {
|
|
return "cstr";
|
|
}
|
|
if String_Eq(r, "unsigned int") { return "uint"; }
|
|
// crude replacements for * and spaces
|
|
r = String_ReplaceAll(r, "*", "Ptr");
|
|
r = String_ReplaceAll(r, " ", "_");
|
|
r = String_ReplaceAll(r, "(", "");
|
|
r = String_ReplaceAll(r, ")", "");
|
|
r = String_ReplaceAll(r, ",", "_");
|
|
if String_Eq(r, "") { return "int"; }
|
|
return r;
|
|
}
|
|
|
|
func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
|
|
if te == null as *TypeExpr { return "void"; }
|
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
return Lcx_SanitizeFatPart(String_Concat(te.pointerPointee.typeName, "Ptr"));
|
|
}
|
|
if te.kind == tekFunc {
|
|
return Lcx_SanitizeFatPart(Lcx_BuildFuncTypeName(te));
|
|
}
|
|
var n: String = te.typeName;
|
|
if String_Eq(n, "") { n = "int"; }
|
|
return Lcx_SanitizeFatPart(n);
|
|
}
|
|
|
|
// Fat function-pointer type name: BuxFn_<ret>_<p0>_<p1>...
|
|
// Enables multi-instance closures (code + env).
|
|
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
|
|
if te == null as *TypeExpr || te.kind != tekFunc {
|
|
return "BuxFn_void_void";
|
|
}
|
|
var retPart: String = "void";
|
|
if te.funcRet != null as *TypeExpr {
|
|
retPart = Lcx_TypeExprFatPart(te.funcRet);
|
|
}
|
|
var result: String = String_Concat("BuxFn_", retPart);
|
|
var cur: *TypeExprList = te.funcParams;
|
|
var anyParam: bool = false;
|
|
while cur != null as *TypeExprList {
|
|
result = String_Concat(result, "_");
|
|
result = String_Concat(result, Lcx_TypeExprFatPart(cur.te));
|
|
anyParam = true;
|
|
cur = cur.next;
|
|
}
|
|
if !anyParam {
|
|
result = String_Concat(result, "_void");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Generic monomorphization helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_FindGenericFunc(ctx: *LowerCtx, name: String) -> *Decl {
|
|
var i: int = 0;
|
|
while i < ctx.genFuncCount {
|
|
if String_Eq(ctx.genFuncs[i].strValue, name) {
|
|
return &ctx.genFuncs[i];
|
|
}
|
|
i = i + 1;
|
|
}
|
|
return null as *Decl;
|
|
}
|
|
|
|
func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
|
|
var i: int = 0;
|
|
while i < ctx.genStructCount {
|
|
if String_Eq(ctx.genStructs[i].strValue, name) {
|
|
return &ctx.genStructs[i];
|
|
}
|
|
i = i + 1;
|
|
}
|
|
return null as *Decl;
|
|
}
|
|
|
|
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
|
|
func Lcx_ExtractElemFromName(typeName: String) -> String {
|
|
if String_Eq(typeName, "") { return ""; }
|
|
let len: uint = bux_strlen(typeName);
|
|
// "Array_" prefix (6 chars)
|
|
if len > 6 {
|
|
let p: String = bux_str_slice(typeName, 0, 6);
|
|
if String_Eq(p, "Array_") {
|
|
return bux_str_slice(typeName, 6, len - 6);
|
|
}
|
|
}
|
|
// "Iter_" prefix (5 chars)
|
|
if len > 5 {
|
|
let p: String = bux_str_slice(typeName, 0, 5);
|
|
if String_Eq(p, "Iter_") {
|
|
return bux_str_slice(typeName, 5, len - 5);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
|
let r: String = String_Concat(base, "_");
|
|
r = String_Concat(r, typeArg0);
|
|
if typeArgCount > 1 && !String_Eq(typeArg1, "") {
|
|
r = String_Concat(r, "_");
|
|
r = String_Concat(r, typeArg1);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
|
if String_Eq(genDecl.strValue, "") { return ""; }
|
|
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
|
|
|
|
// Check if already generated (linear search in hm.structs)
|
|
var i: int = 0;
|
|
while i < ctx.hm.structCount {
|
|
if String_Eq(ctx.hm.structs[i].name, mangled) {
|
|
return mangled;
|
|
}
|
|
i = i + 1;
|
|
}
|
|
|
|
// Save old substitution
|
|
let oldParam0: String = ctx.substParam0;
|
|
let oldArg0: String = ctx.substArg0;
|
|
let oldParam1: String = ctx.substParam1;
|
|
let oldArg1: String = ctx.substArg1;
|
|
|
|
ctx.substParam0 = genDecl.typeParam0;
|
|
ctx.substArg0 = typeArg0;
|
|
ctx.substParam1 = genDecl.typeParam1;
|
|
ctx.substArg1 = typeArg1;
|
|
|
|
// Generate concrete HirStruct with substituted field types
|
|
// Reserve the slot BEFORE processing fields so nested generic instantiations
|
|
// get their own distinct indices and cannot overwrite our slot.
|
|
let si: int = ctx.hm.structCount;
|
|
ctx.hm.structCount = ctx.hm.structCount + 1;
|
|
ctx.hm.structs[si].name = mangled;
|
|
ctx.hm.structs[si].fieldCount = genDecl.fieldCount;
|
|
ctx.hm.structs[si].fields = bux_alloc(genDecl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
|
var fi: int = 0;
|
|
while fi < genDecl.fieldCount {
|
|
let fname: String = genDecl.fields[fi].name;
|
|
let ftype: *TypeExpr = genDecl.fields[fi].refFieldType;
|
|
ctx.hm.structs[si].fields[fi].name = fname;
|
|
if ftype != null as *TypeExpr {
|
|
let subTe: *TypeExpr = Lcx_SubstituteType(ctx, ftype);
|
|
if subTe.kind == tekPointer && subTe.pointerPointee != null as *TypeExpr {
|
|
if !String_Eq(subTe.pointerPointee.typeName, "") {
|
|
ctx.hm.structs[si].fields[fi].typeName = String_Concat(subTe.pointerPointee.typeName, "*");
|
|
}
|
|
} else if !String_Eq(subTe.typeName, "") {
|
|
ctx.hm.structs[si].fields[fi].typeName = subTe.typeName;
|
|
}
|
|
}
|
|
fi = fi + 1;
|
|
}
|
|
|
|
// Restore old substitution
|
|
ctx.substParam0 = oldParam0;
|
|
ctx.substArg0 = oldArg0;
|
|
ctx.substParam1 = oldParam1;
|
|
ctx.substArg1 = oldArg1;
|
|
|
|
return mangled;
|
|
}
|
|
|
|
func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
|
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
|
|
|
|
// Check if already generated (linear search in ctx.funcs)
|
|
var i: int = 0;
|
|
while i < ctx.funcCount {
|
|
if String_Eq(ctx.funcs[i].name, mangled) {
|
|
return mangled;
|
|
}
|
|
i = i + 1;
|
|
}
|
|
|
|
// Save old substitution
|
|
let oldParam0: String = ctx.substParam0;
|
|
let oldArg0: String = ctx.substArg0;
|
|
let oldParam1: String = ctx.substParam1;
|
|
let oldArg1: String = ctx.substArg1;
|
|
|
|
// Set up substitution
|
|
ctx.substParam0 = genDecl.typeParam0;
|
|
ctx.substArg0 = typeArg0;
|
|
ctx.substParam1 = genDecl.typeParam1;
|
|
ctx.substArg1 = typeArg1;
|
|
|
|
// Lower the generic function with substitution active
|
|
let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl);
|
|
f.name = mangled;
|
|
|
|
// Add to module
|
|
ctx.funcs[ctx.funcCount] = *f;
|
|
ctx.funcCount = ctx.funcCount + 1;
|
|
|
|
// Restore old substitution
|
|
ctx.substParam0 = oldParam0;
|
|
ctx.substArg0 = oldArg0;
|
|
ctx.substParam1 = oldParam1;
|
|
ctx.substArg1 = oldArg1;
|
|
|
|
return mangled;
|
|
}
|
|
|
|
// Strip type-arg suffix from a mangled generic instance name.
|
|
// E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair".
|
|
func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
|
var suffix: String = "_";
|
|
suffix = String_Concat(suffix, typeArg0);
|
|
if typeArgCount > 1 && !String_Eq(typeArg1, "") {
|
|
suffix = String_Concat(suffix, "_");
|
|
suffix = String_Concat(suffix, typeArg1);
|
|
}
|
|
let fullLen: int = bux_strlen(typeName) as int;
|
|
let suffixLen: int = bux_strlen(suffix) as int;
|
|
if fullLen > suffixLen {
|
|
let endPart: String = bux_str_slice(typeName, (fullLen - suffixLen) as uint, suffixLen as uint);
|
|
if String_Eq(endPart, suffix) {
|
|
return bux_str_slice(typeName, 0, (fullLen - suffixLen) as uint);
|
|
}
|
|
}
|
|
return typeName;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Array type helpers for bounds-checking desugaring
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool {
|
|
if te == null as *TypeExpr { return false; }
|
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
te = te.pointerPointee;
|
|
}
|
|
if te.kind == tekNamed {
|
|
if String_Eq(te.typeName, "Array") { return true; }
|
|
let name: String = te.typeName;
|
|
if name[0] as int == 65 && name[1] as int == 114 && name[2] as int == 114 && name[3] as int == 97 && name[4] as int == 121 && name[5] as int == 95 {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
|
|
if te == null as *TypeExpr { return ""; }
|
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
te = te.pointerPointee;
|
|
}
|
|
if te.kind == tekNamed {
|
|
if String_Eq(te.typeName, "Array") && te.typeArgCount > 0 {
|
|
return te.typeArgName0;
|
|
}
|
|
let name: String = te.typeName;
|
|
if name[0] as int == 65 && name[1] as int == 114 && name[2] as int == 114 && name[3] as int == 97 && name[4] as int == 121 && name[5] as int == 95 {
|
|
let prefixLen: uint = 6;
|
|
let totalLen: uint = bux_strlen(name);
|
|
if totalLen > prefixLen {
|
|
return bux_str_slice(name, prefixLen, totalLen - prefixLen);
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Match lowering helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
|
|
if String_Eq(enumName, "") { return false; }
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, enumName);
|
|
if sym.decl == null as *Decl || sym.decl.kind != dkEnum { return false; }
|
|
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { return true; }
|
|
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { return true; }
|
|
return false;
|
|
}
|
|
|
|
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
|
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
n.kind = hLit;
|
|
n.line = line;
|
|
n.column = col;
|
|
n.intValue = litKind;
|
|
n.strValue = litText;
|
|
return n;
|
|
}
|
|
|
|
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
|
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
n.kind = hBinary;
|
|
n.line = line;
|
|
n.column = col;
|
|
n.intValue = op;
|
|
n.child1 = left;
|
|
n.child2 = right;
|
|
return n;
|
|
}
|
|
|
|
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
|
|
return Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
|
|
}
|
|
|
|
// Build condition HirNode for a match pattern. Returns null = always-true.
|
|
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|
subjectEnumName: String, subjectHasData: bool,
|
|
line: uint32, col: uint32) -> *HirNode {
|
|
if pat == null as *Pattern { return null as *HirNode; }
|
|
let kind: int = pat.kind;
|
|
|
|
// Guarded: condition is only the inner pattern; guard applied after binds
|
|
if kind == pkGuarded {
|
|
return Lcx_PatternCond(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
|
|
}
|
|
|
|
if kind == pkWildcard || kind == pkIdent {
|
|
return null as *HirNode;
|
|
}
|
|
|
|
if kind == pkLiteral {
|
|
let lit: *HirNode = Lcx_MakeLitHir(pat.patLitKind, pat.patLitText, line, col);
|
|
return Lcx_MakeBinHir(tkEq, subject, lit, line, col);
|
|
}
|
|
|
|
if kind == pkRange {
|
|
let loPat: *Pattern = pat.patChild1;
|
|
let hiPat: *Pattern = pat.patChild2;
|
|
if loPat == null as *Pattern || hiPat == null as *Pattern { return null as *HirNode; }
|
|
if loPat.kind != pkLiteral || hiPat.kind != pkLiteral { return null as *HirNode; }
|
|
let lo: *HirNode = Lcx_MakeLitHir(loPat.patLitKind, loPat.patLitText, line, col);
|
|
let hi: *HirNode = Lcx_MakeLitHir(hiPat.patLitKind, hiPat.patLitText, line, col);
|
|
let loOk: *HirNode = Lcx_MakeBinHir(tkGe, subject, lo, line, col);
|
|
var hiOp: int = tkLt;
|
|
if pat.patRangeInclusive { hiOp = tkLe; }
|
|
let hiOk: *HirNode = Lcx_MakeBinHir(hiOp, subject, hi, line, col);
|
|
return Lcx_MakeBinHir(tkAmpAmp, loOk, hiOk, line, col);
|
|
}
|
|
|
|
if kind == pkEnum {
|
|
let path: String = pat.patEnumPath;
|
|
// path is "Enum::Variant" or just "Variant"
|
|
var enumName: String = "";
|
|
var variantName: String = path;
|
|
if String_Contains(path, "::") {
|
|
enumName = String_SplitPart(path, "::", 0);
|
|
variantName = String_SplitPart(path, "::", 1);
|
|
}
|
|
let tagName: String = String_Concat(String_Concat(enumName, "_"), variantName);
|
|
// Prefer subject enum name when path is full
|
|
var useEnum: String = enumName;
|
|
if String_Eq(useEnum, "") { useEnum = subjectEnumName; }
|
|
let fullTag: String = String_Concat(String_Concat(useEnum, "_"), variantName);
|
|
|
|
if subjectHasData && (String_Eq(enumName, subjectEnumName) || String_Eq(enumName, "")) {
|
|
// Algebraic: subject.tag == Enum_Variant
|
|
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tagPtr.kind = hFieldPtr;
|
|
tagPtr.line = line;
|
|
tagPtr.column = col;
|
|
tagPtr.strValue = "tag";
|
|
tagPtr.child1 = subject;
|
|
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tagLoad.kind = hLoad;
|
|
tagLoad.line = line;
|
|
tagLoad.column = col;
|
|
tagLoad.child1 = tagPtr;
|
|
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tagConst.kind = hVar;
|
|
tagConst.line = line;
|
|
tagConst.column = col;
|
|
tagConst.strValue = fullTag;
|
|
return Lcx_MakeBinHir(tkEq, tagLoad, tagConst, line, col);
|
|
} else {
|
|
// Simple enum: subject == Enum_Variant
|
|
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tagConst.kind = hVar;
|
|
tagConst.line = line;
|
|
tagConst.column = col;
|
|
if !String_Eq(enumName, "") {
|
|
tagConst.strValue = fullTag;
|
|
} else {
|
|
tagConst.strValue = tagName;
|
|
}
|
|
return Lcx_MakeBinHir(tkEq, subject, tagConst, line, col);
|
|
}
|
|
}
|
|
|
|
return null as *HirNode;
|
|
}
|
|
|
|
// Emit binding stmts for pattern payload: Option::Some(value) → alloca value; value = subject.data.Some_0
|
|
// Returns head of child3-linked list of HirNodes (may be null).
|
|
func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
|
|
subjectEnumName: String, subjectHasData: bool,
|
|
line: uint32, col: uint32) -> *HirNode {
|
|
if pat == null as *Pattern { return null as *HirNode; }
|
|
// Guarded: bind from inner pattern
|
|
if pat.kind == pkGuarded {
|
|
return Lcx_PatternBindings(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
|
|
}
|
|
if pat.kind == pkIdent {
|
|
let ty: String = "int";
|
|
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
|
|
ty = subject.typeName;
|
|
}
|
|
return Lcx_BindPatIdent(ctx, pat.patIdent, ty, subject, line, col);
|
|
}
|
|
|
|
// Tuple: (a, b) → a = subject._0; b = subject._1
|
|
if pat.kind == pkTuple {
|
|
var head: *HirNode = null as *HirNode;
|
|
var tail: *HirNode = null as *HirNode;
|
|
var ei: int = 0;
|
|
var elem: *Pattern = pat.patArgs;
|
|
while elem != null as *Pattern {
|
|
if elem.kind == pkIdent && !String_Eq(elem.patIdent, "_") {
|
|
var fieldName: String = "_0";
|
|
if ei == 1 { fieldName = "_1"; }
|
|
else if ei == 2 { fieldName = "_2"; }
|
|
else if ei == 3 { fieldName = "_3"; }
|
|
else if ei > 3 { fieldName = String_Concat("_", String_FromInt(ei as int64)); }
|
|
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fPtr.kind = hFieldPtr;
|
|
fPtr.line = line;
|
|
fPtr.column = col;
|
|
fPtr.strValue = fieldName;
|
|
fPtr.child1 = subject;
|
|
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fLoad.kind = hLoad;
|
|
fLoad.line = line;
|
|
fLoad.column = col;
|
|
fLoad.child1 = fPtr;
|
|
fLoad.typeName = "int";
|
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, elem.patIdent, "int", fLoad, line, col);
|
|
if bound != null as *HirNode {
|
|
if head == null as *HirNode {
|
|
head = bound;
|
|
tail = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
} else {
|
|
tail.child3 = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
}
|
|
}
|
|
}
|
|
elem = elem.patNext;
|
|
ei = ei + 1;
|
|
}
|
|
return head;
|
|
}
|
|
|
|
// Struct: Point { x: a } → a = subject.x
|
|
if pat.kind == pkStruct {
|
|
var head: *HirNode = null as *HirNode;
|
|
var tail: *HirNode = null as *HirNode;
|
|
var field: *Pattern = pat.patArgs;
|
|
while field != null as *Pattern {
|
|
if field.kind == pkIdent && !String_Eq(field.patIdent, "_") {
|
|
var fname: String = field.patFieldName;
|
|
if String_Eq(fname, "") { fname = field.patIdent; }
|
|
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fPtr.kind = hFieldPtr;
|
|
fPtr.line = line;
|
|
fPtr.column = col;
|
|
fPtr.strValue = fname;
|
|
fPtr.child1 = subject;
|
|
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fLoad.kind = hLoad;
|
|
fLoad.line = line;
|
|
fLoad.column = col;
|
|
fLoad.child1 = fPtr;
|
|
fLoad.typeName = "int";
|
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, field.patIdent, "int", fLoad, line, col);
|
|
if bound != null as *HirNode {
|
|
if head == null as *HirNode {
|
|
head = bound;
|
|
tail = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
} else {
|
|
tail.child3 = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
}
|
|
}
|
|
}
|
|
field = field.patNext;
|
|
}
|
|
return head;
|
|
}
|
|
|
|
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
|
|
|
|
var enumName: String = "";
|
|
var variantName: String = pat.patEnumPath;
|
|
if String_Contains(pat.patEnumPath, "::") {
|
|
enumName = String_SplitPart(pat.patEnumPath, "::", 0);
|
|
variantName = String_SplitPart(pat.patEnumPath, "::", 1);
|
|
} else {
|
|
enumName = subjectEnumName;
|
|
}
|
|
if String_Eq(enumName, "") || String_Eq(variantName, "") { return null as *HirNode; }
|
|
|
|
// Look up field type names from enum decl
|
|
var fieldType0: String = "int";
|
|
var fieldType1: String = "int";
|
|
var fieldCount: int = 0;
|
|
let enumSym: Symbol = Scope_Lookup(ctx.scope, enumName);
|
|
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
|
|
var vi: int = 0;
|
|
while vi < enumSym.decl.variantCount {
|
|
var vv: *EnumVariant = null as *EnumVariant;
|
|
if vi == 0 { vv = &enumSym.decl.variant0; }
|
|
else if vi == 1 { vv = &enumSym.decl.variant1; }
|
|
else if vi == 2 { vv = &enumSym.decl.variant2; }
|
|
else if vi == 3 { vv = &enumSym.decl.variant3; }
|
|
else if vi == 4 { vv = &enumSym.decl.variant4; }
|
|
else if vi == 5 { vv = &enumSym.decl.variant5; }
|
|
else if vi == 6 { vv = &enumSym.decl.variant6; }
|
|
else if vi == 7 { vv = &enumSym.decl.variant7; }
|
|
else if vi == 8 { vv = &enumSym.decl.variant8; }
|
|
if vv != null as *EnumVariant && String_Eq(vv.name, variantName) {
|
|
fieldCount = vv.fieldCount;
|
|
if !String_Eq(vv.fieldTypeName0, "") { fieldType0 = vv.fieldTypeName0; }
|
|
if !String_Eq(vv.fieldTypeName1, "") { fieldType1 = vv.fieldTypeName1; }
|
|
}
|
|
vi = vi + 1;
|
|
}
|
|
}
|
|
|
|
// dataLoad = subject.data
|
|
let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
dataPtr.kind = hFieldPtr;
|
|
dataPtr.line = line;
|
|
dataPtr.column = col;
|
|
dataPtr.strValue = "data";
|
|
dataPtr.child1 = subject;
|
|
let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
dataLoad.kind = hLoad;
|
|
dataLoad.line = line;
|
|
dataLoad.column = col;
|
|
dataLoad.child1 = dataPtr;
|
|
dataLoad.typeName = String_Concat(enumName, "_Data");
|
|
|
|
// Multi-field: nested struct data.Variant (anonymous or Enum_Variant_Payload);
|
|
// single-field: flat data.Variant_0
|
|
var payloadBase: *HirNode = dataLoad;
|
|
if fieldCount > 1 {
|
|
let nestedName: String = String_Concat(String_Concat(enumName, "_"),
|
|
String_Concat(variantName, "_Payload"));
|
|
let vPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
vPtr.kind = hFieldPtr;
|
|
vPtr.line = line;
|
|
vPtr.column = col;
|
|
vPtr.strValue = variantName;
|
|
vPtr.child1 = dataLoad;
|
|
let vLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
vLoad.kind = hLoad;
|
|
vLoad.line = line;
|
|
vLoad.column = col;
|
|
vLoad.child1 = vPtr;
|
|
vLoad.typeName = nestedName;
|
|
payloadBase = vLoad;
|
|
}
|
|
|
|
var head: *HirNode = null as *HirNode;
|
|
var tail: *HirNode = null as *HirNode;
|
|
var arg: *Pattern = pat.patArgs;
|
|
var ai: int = 0;
|
|
while arg != null as *Pattern {
|
|
var ftype: String = "int";
|
|
if ai == 0 { ftype = fieldType0; }
|
|
else if ai == 1 { ftype = fieldType1; }
|
|
let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64));
|
|
|
|
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fPtr.kind = hFieldPtr;
|
|
fPtr.line = line;
|
|
fPtr.column = col;
|
|
fPtr.strValue = fieldName;
|
|
fPtr.child1 = payloadBase;
|
|
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fLoad.kind = hLoad;
|
|
fLoad.line = line;
|
|
fLoad.column = col;
|
|
fLoad.child1 = fPtr;
|
|
fLoad.typeName = ftype;
|
|
|
|
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
|
|
let bound: *HirNode = Lcx_BindPatIdent(ctx, arg.patIdent, ftype, fLoad, line, col);
|
|
if bound != null as *HirNode {
|
|
if head == null as *HirNode {
|
|
head = bound;
|
|
tail = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
} else {
|
|
tail.child3 = bound;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
}
|
|
}
|
|
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
|
|
// Nested pattern on payload field
|
|
let nested: *HirNode = Lcx_PatternBindings(ctx, fLoad, arg, subjectEnumName, subjectHasData, line, col);
|
|
if nested != null as *HirNode {
|
|
if head == null as *HirNode {
|
|
head = nested;
|
|
tail = nested;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
} else {
|
|
tail.child3 = nested;
|
|
while tail.child3 != null as *HirNode { tail = tail.child3; }
|
|
}
|
|
}
|
|
}
|
|
arg = arg.patNext;
|
|
ai = ai + 1;
|
|
}
|
|
return head;
|
|
}
|
|
|
|
// True when n is a multi-stmt yield block (match result, etc.)
|
|
func Lcx_IsMatchYield(n: *HirNode) -> bool {
|
|
if n == null as *HirNode { return false; }
|
|
if n.kind != hBlock { return false; }
|
|
// strValue must be a real temp name — null/"" is a plain statement block
|
|
if n.strValue == null as String { return false; }
|
|
return !String_Eq(n.strValue, "");
|
|
}
|
|
|
|
func Lcx_YieldVarOf(n: *HirNode) -> *HirNode {
|
|
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
v.kind = hVar;
|
|
v.strValue = n.strValue;
|
|
v.typeName = n.typeName;
|
|
return v;
|
|
}
|
|
|
|
// Append `node` at the end of a child3-linked chain starting at `head` (or its child1 if head is hBlock).
|
|
func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
|
|
if head == null as *HirNode || node == null as *HirNode { return; }
|
|
var cur: *HirNode = head;
|
|
if head.kind == hBlock && head.child1 != null as *HirNode {
|
|
cur = head.child1;
|
|
}
|
|
while cur.child3 != null as *HirNode {
|
|
cur = cur.child3;
|
|
}
|
|
cur.child3 = node;
|
|
}
|
|
|
|
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
|
|
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
|
|
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
|
|
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|
let line: uint32 = expr.line;
|
|
let col: uint32 = expr.column;
|
|
let subject: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
let foundName: String = String_Concat("__found_", String_FromInt(ctx.varCounter as int64));
|
|
|
|
// Result type from sema refType, default int
|
|
var typeName: String = "int";
|
|
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
|
|
typeName = expr.refType.typeName;
|
|
}
|
|
|
|
// Subject enum info
|
|
var subjectEnumName: String = "";
|
|
var subjectHasData: bool = false;
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
if expr.child1.refType.kind == tekNamed {
|
|
subjectEnumName = expr.child1.refType.typeName;
|
|
subjectHasData = Lcx_EnumHasData(ctx, subjectEnumName);
|
|
}
|
|
}
|
|
|
|
// Alloca result + found flag
|
|
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
allocaNode.kind = hAlloca;
|
|
allocaNode.line = line;
|
|
allocaNode.column = col;
|
|
allocaNode.strValue = resultName;
|
|
allocaNode.typeName = typeName;
|
|
|
|
let foundAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundAlloca.kind = hAlloca;
|
|
foundAlloca.line = line;
|
|
foundAlloca.column = col;
|
|
foundAlloca.strValue = foundName;
|
|
foundAlloca.typeName = "bool";
|
|
allocaNode.child3 = foundAlloca;
|
|
|
|
let foundInit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundInit.kind = hStore;
|
|
foundInit.line = line;
|
|
foundInit.column = col;
|
|
let foundVar0: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundVar0.kind = hVar;
|
|
foundVar0.strValue = foundName;
|
|
foundInit.child1 = foundVar0;
|
|
foundInit.child2 = Lcx_MakeLitHir(tkBoolLiteral, "false", line, col);
|
|
foundAlloca.child3 = foundInit;
|
|
|
|
// Chain of arm ifs (forward order), linked via child3
|
|
var tail: *HirNode = foundInit;
|
|
var cur: *MatchArm = expr.matchArms;
|
|
while cur != null as *MatchArm {
|
|
// Snapshot rename map so this arm's bindings don't leak to later arms
|
|
let savedMapCount: int = ctx.patMapCount;
|
|
var bindPat: *Pattern = cur.pattern;
|
|
var guardExpr: *Expr = null as *Expr;
|
|
if cur.pattern != null as *Pattern && cur.pattern.kind == pkGuarded {
|
|
bindPat = cur.pattern.patChild1;
|
|
guardExpr = cur.pattern.patGuardExpr;
|
|
}
|
|
// Bindings before guard/body so renames are active and allocas precede uses
|
|
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
|
|
var guardHirEarly: *HirNode = null as *HirNode;
|
|
if guardExpr != null as *Expr {
|
|
guardHirEarly = Lcx_LowerExpr(ctx, guardExpr);
|
|
}
|
|
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
|
|
// Pop this arm's renames (nested matches already restored themselves)
|
|
ctx.patMapCount = savedMapCount;
|
|
|
|
// store result = body
|
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
storeNode.kind = hStore;
|
|
storeNode.line = line;
|
|
storeNode.column = col;
|
|
let resVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
resVar.kind = hVar;
|
|
resVar.strValue = resultName;
|
|
storeNode.child1 = resVar;
|
|
var bodyPrefix: *HirNode = null as *HirNode;
|
|
if Lcx_IsMatchYield(bodyHir) {
|
|
storeNode.child2 = Lcx_YieldVarOf(bodyHir);
|
|
bodyHir.strValue = "";
|
|
bodyPrefix = bodyHir.child1;
|
|
} else {
|
|
storeNode.child2 = bodyHir;
|
|
}
|
|
|
|
// found = true
|
|
let foundSet: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundSet.kind = hStore;
|
|
foundSet.line = line;
|
|
foundSet.column = col;
|
|
let foundVar1: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundVar1.kind = hVar;
|
|
foundVar1.strValue = foundName;
|
|
foundSet.child1 = foundVar1;
|
|
foundSet.child2 = Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
|
|
storeNode.child3 = foundSet;
|
|
|
|
// success block: bodyPrefix → store → found=true
|
|
let successBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
successBlock.kind = hBlock;
|
|
successBlock.line = line;
|
|
successBlock.column = col;
|
|
if bodyPrefix != null as *HirNode {
|
|
successBlock.child1 = bodyPrefix;
|
|
var sbt: *HirNode = bodyPrefix;
|
|
while sbt.child3 != null as *HirNode { sbt = sbt.child3; }
|
|
sbt.child3 = storeNode;
|
|
} else {
|
|
successBlock.child1 = storeNode;
|
|
}
|
|
|
|
// Optional guard wraps success (already lowered with renames active)
|
|
var afterBinds: *HirNode = successBlock;
|
|
if guardHirEarly != null as *HirNode {
|
|
let guardIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
guardIf.kind = hIf;
|
|
guardIf.line = line;
|
|
guardIf.column = col;
|
|
guardIf.child1 = guardHirEarly;
|
|
guardIf.child2 = successBlock;
|
|
afterBinds = guardIf;
|
|
}
|
|
|
|
// armInner: binds → afterBinds
|
|
let armInner: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
armInner.kind = hBlock;
|
|
armInner.line = line;
|
|
armInner.column = col;
|
|
if bindHead != null as *HirNode {
|
|
armInner.child1 = bindHead;
|
|
var ibt: *HirNode = bindHead;
|
|
while ibt.child3 != null as *HirNode { ibt = ibt.child3; }
|
|
ibt.child3 = afterBinds;
|
|
} else {
|
|
armInner.child1 = afterBinds;
|
|
}
|
|
|
|
// Optional pattern condition
|
|
let cond: *HirNode = Lcx_PatternCond(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
|
|
var armBody: *HirNode = armInner;
|
|
if cond != null as *HirNode {
|
|
let condIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
condIf.kind = hIf;
|
|
condIf.line = line;
|
|
condIf.column = col;
|
|
condIf.child1 = cond;
|
|
condIf.child2 = armInner;
|
|
armBody = condIf;
|
|
}
|
|
|
|
// if (!found) armBody
|
|
let foundLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
foundLoad.kind = hVar;
|
|
foundLoad.strValue = foundName;
|
|
foundLoad.typeName = "bool";
|
|
let notFound: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
notFound.kind = hUnary;
|
|
notFound.line = line;
|
|
notFound.column = col;
|
|
notFound.intValue = tkBang;
|
|
notFound.child1 = foundLoad;
|
|
|
|
let tryIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tryIf.kind = hIf;
|
|
tryIf.line = line;
|
|
tryIf.column = col;
|
|
tryIf.child1 = notFound;
|
|
tryIf.child2 = armBody;
|
|
|
|
tail.child3 = tryIf;
|
|
tail = tryIf;
|
|
cur = cur.next;
|
|
}
|
|
|
|
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
block.kind = hBlock;
|
|
block.line = line;
|
|
block.column = col;
|
|
block.child1 = allocaNode;
|
|
// Yield marker: strValue = result var name for last-expr return / let init
|
|
block.strValue = resultName;
|
|
block.typeName = typeName;
|
|
return block;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Expression lowering
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|
if expr == null as *Expr { return null as *HirNode; }
|
|
|
|
let line: uint32 = expr.line;
|
|
let col: uint32 = expr.column;
|
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
n.kind = hBlock;
|
|
n.line = line;
|
|
n.column = col;
|
|
|
|
let kind: int = expr.kind;
|
|
|
|
// Match expression
|
|
if kind == ekMatch {
|
|
return Lcx_LowerMatch(ctx, expr);
|
|
}
|
|
|
|
// String interpolation: desugar to String_Concat + String_FromInt/Bool/Float
|
|
// Parts in callArgs are interleaved text lits and expressions.
|
|
if kind == ekStringInterp {
|
|
var result: *HirNode = null as *HirNode;
|
|
var part: *ExprList = expr.callArgs;
|
|
while part != null as *ExprList {
|
|
let pe: *Expr = part.expr;
|
|
var piece: *HirNode = null as *HirNode;
|
|
if pe != null as *Expr && pe.kind == ekLiteral && pe.tokKind == tkStringLiteral {
|
|
piece = Lcx_LowerExpr(ctx, pe);
|
|
} else {
|
|
let lowered: *HirNode = Lcx_LowerExpr(ctx, pe);
|
|
// Convert non-string to String
|
|
var needConv: bool = true;
|
|
var convName: String = "String_FromInt";
|
|
if pe != null as *Expr && pe.refType != null as *TypeExpr {
|
|
let tn: String = pe.refType.typeName;
|
|
if String_Eq(tn, "String") || String_Eq(tn, "str") {
|
|
needConv = false;
|
|
} else if String_Eq(tn, "bool") {
|
|
convName = "String_FromBool";
|
|
} else if String_Eq(tn, "float64") || String_Eq(tn, "float") || String_Eq(tn, "float32") {
|
|
convName = "String_FromFloat";
|
|
} else {
|
|
convName = "String_FromInt";
|
|
}
|
|
}
|
|
if needConv {
|
|
let callN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callN.kind = hCall;
|
|
callN.line = line;
|
|
callN.column = col;
|
|
callN.strValue = convName;
|
|
callN.child1 = lowered;
|
|
piece = callN;
|
|
} else {
|
|
piece = lowered;
|
|
}
|
|
}
|
|
if result == null as *HirNode {
|
|
result = piece;
|
|
} else {
|
|
let cat: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
cat.kind = hCall;
|
|
cat.line = line;
|
|
cat.column = col;
|
|
cat.strValue = "String_Concat";
|
|
cat.child1 = result;
|
|
cat.child2 = piece;
|
|
result = cat;
|
|
}
|
|
part = part.next;
|
|
}
|
|
if result == null as *HirNode {
|
|
// empty f""
|
|
let empty: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
empty.kind = hLit;
|
|
empty.line = line;
|
|
empty.column = col;
|
|
empty.intValue = tkStringLiteral;
|
|
empty.strValue = "\"\"";
|
|
return empty;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Literal
|
|
if kind == ekLiteral {
|
|
n.kind = hLit;
|
|
n.intValue = expr.tokKind;
|
|
n.strValue = expr.tokText;
|
|
return n;
|
|
}
|
|
|
|
// Identifier → variable reference
|
|
if kind == ekIdent {
|
|
// Pattern binding rename: source name → unique C local
|
|
let ren: String = Lcx_PatLookup(ctx, expr.strValue);
|
|
if !String_Eq(ren, "") {
|
|
n.kind = hVar;
|
|
n.strValue = ren;
|
|
let rsym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
|
|
n.typeKind = rsym.typeKind;
|
|
if rsym.typeName != null as String && !String_Eq(rsym.typeName, "") {
|
|
n.typeName = rsym.typeName;
|
|
}
|
|
return n;
|
|
}
|
|
// Capture rewriting: if inside closure body and this ident is captured,
|
|
// emit field access on env instance instead of bare variable
|
|
if ctx.closureDepth > 0 && ctx.currentClosureExpr != null as *Expr && !String_Eq(ctx.envInstanceName, "") {
|
|
let capCount: int = ctx.currentClosureExpr.captureCount;
|
|
var ci: int = 0;
|
|
var isCaptured: bool = false;
|
|
var capType: int = 0;
|
|
while ci < capCount {
|
|
var capName: String = "";
|
|
if ci == 0 { capName = ctx.currentClosureExpr.captureName0; capType = ctx.currentClosureExpr.captureType0; }
|
|
else if ci == 1 { capName = ctx.currentClosureExpr.captureName1; capType = ctx.currentClosureExpr.captureType1; }
|
|
else if ci == 2 { capName = ctx.currentClosureExpr.captureName2; capType = ctx.currentClosureExpr.captureType2; }
|
|
else if ci == 3 { capName = ctx.currentClosureExpr.captureName3; capType = ctx.currentClosureExpr.captureType3; }
|
|
else if ci == 4 { capName = ctx.currentClosureExpr.captureName4; capType = ctx.currentClosureExpr.captureType4; }
|
|
else if ci == 5 { capName = ctx.currentClosureExpr.captureName5; capType = ctx.currentClosureExpr.captureType5; }
|
|
else if ci == 6 { capName = ctx.currentClosureExpr.captureName6; capType = ctx.currentClosureExpr.captureType6; }
|
|
else if ci == 7 { capName = ctx.currentClosureExpr.captureName7; capType = ctx.currentClosureExpr.captureType7; }
|
|
if String_Eq(capName, expr.strValue) {
|
|
isCaptured = true;
|
|
}
|
|
ci = ci + 1;
|
|
}
|
|
if isCaptured {
|
|
n.kind = hFieldAccess;
|
|
n.strValue = expr.strValue;
|
|
let baseNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
baseNode.kind = hVar;
|
|
baseNode.strValue = ctx.envInstanceName;
|
|
n.child1 = baseNode;
|
|
n.typeKind = capType;
|
|
n.typeName = Lcx_TypeKindToName(capType);
|
|
return n;
|
|
}
|
|
}
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
|
|
// Named function used as a value → fat pointer via __adapt_ wrapper
|
|
if sym.kind == skFunc {
|
|
var fatName: String = "BuxFn_int_int";
|
|
if sym.refType != null as *TypeExpr && sym.refType.kind == tekFunc {
|
|
fatName = Lcx_BuildFuncTypeName(sym.refType);
|
|
} else if !String_Eq(sym.typeName, "") && String_StartsWith(sym.typeName, "BuxFn_") {
|
|
fatName = sym.typeName;
|
|
}
|
|
n.kind = hStructInit;
|
|
n.strValue = fatName;
|
|
n.typeKind = tyFunc;
|
|
n.typeName = fatName;
|
|
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
codeField.kind = hBlock;
|
|
codeField.strValue = "code";
|
|
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
codeVal.kind = hVar;
|
|
codeVal.strValue = String_Concat("__adapt_", expr.strValue);
|
|
codeField.child1 = codeVal;
|
|
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
envField.kind = hBlock;
|
|
envField.strValue = "env";
|
|
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
nullEnv.kind = hCast;
|
|
nullEnv.typeName = "void*";
|
|
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
zero.kind = hLit;
|
|
zero.intValue = tkIntLiteral;
|
|
zero.strValue = "0";
|
|
nullEnv.child1 = zero;
|
|
envField.child1 = nullEnv;
|
|
codeField.child3 = envField;
|
|
n.child1 = codeField;
|
|
return n;
|
|
}
|
|
n.kind = hVar;
|
|
n.strValue = expr.strValue;
|
|
n.typeKind = sym.typeKind;
|
|
|
|
if expr.refType != null as *TypeExpr {
|
|
n.typeName = expr.refType.typeName;
|
|
}
|
|
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
|
|
n.typeName = sym.typeName;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// self → variable reference named "self"
|
|
if kind == ekSelf {
|
|
n.kind = hVar;
|
|
n.strValue = "self";
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, "self");
|
|
n.typeKind = sym.typeKind;
|
|
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
|
|
n.typeName = sym.typeName;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Binary
|
|
if kind == ekBinary {
|
|
// Assignment operator → use hAssign
|
|
if expr.intValue == tkAssign {
|
|
n.kind = hAssign;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
|
return n;
|
|
}
|
|
|
|
// Operator overloading: try method call
|
|
var opMethodName: String = "";
|
|
if expr.intValue == tkPlus { opMethodName = "operator_add"; }
|
|
else if expr.intValue == tkMinus { opMethodName = "operator_sub"; }
|
|
else if expr.intValue == tkStar { opMethodName = "operator_mul"; }
|
|
else if expr.intValue == tkSlash { opMethodName = "operator_div"; }
|
|
else if expr.intValue == tkPercent { opMethodName = "operator_mod"; }
|
|
else if expr.intValue == tkEq { opMethodName = "operator_eq"; }
|
|
else if expr.intValue == tkNe { opMethodName = "operator_ne"; }
|
|
else if expr.intValue == tkLt { opMethodName = "operator_lt"; }
|
|
else if expr.intValue == tkLe { opMethodName = "operator_le"; }
|
|
else if expr.intValue == tkGt { opMethodName = "operator_gt"; }
|
|
else if expr.intValue == tkGe { opMethodName = "operator_ge"; }
|
|
else if expr.intValue == tkAmp { opMethodName = "operator_bitand"; }
|
|
else if expr.intValue == tkPipe { opMethodName = "operator_bitor"; }
|
|
else if expr.intValue == tkCaret { opMethodName = "operator_xor"; }
|
|
else if expr.intValue == tkShl { opMethodName = "operator_shl"; }
|
|
else if expr.intValue == tkShr { opMethodName = "operator_shr"; }
|
|
|
|
if !String_Eq(opMethodName, "") {
|
|
var receiverTypeName: String = "";
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
let refTe: *TypeExpr = expr.child1.refType;
|
|
if refTe.kind == tekNamed {
|
|
if refTe.typeName != null as String { receiverTypeName = refTe.typeName; }
|
|
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
|
|
if refTe.pointerPointee.typeName != null as String {
|
|
receiverTypeName = refTe.pointerPointee.typeName;
|
|
}
|
|
}
|
|
}
|
|
// Note: String_Eq(null, "") is false — must also reject null type names
|
|
if receiverTypeName != null as String && !String_Eq(receiverTypeName, "") {
|
|
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName);
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
|
if sym.kind == skFunc && sym.decl != null as *Decl {
|
|
n.kind = hCall;
|
|
n.strValue = funcName;
|
|
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
// If method expects pointer/reference but receiver is a value, add &
|
|
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr {
|
|
let paramKind: int = sym.decl.param0.refParamType.kind;
|
|
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
|
|
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = recv;
|
|
n.child1 = addrNode;
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Overflow checking: in @[Checked] mode, lower +, -, * on signed integers to checked calls
|
|
if ctx.checkedFunc && !ctx.releaseFunc {
|
|
var opKind: int = expr.intValue;
|
|
var isArithOp: bool = opKind == tkPlus || opKind == tkMinus || opKind == tkStar;
|
|
var isSignedInt: bool = false;
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
let lhsKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
|
|
isSignedInt = Type_IsSigned(lhsKind);
|
|
}
|
|
if isArithOp && isSignedInt {
|
|
var checkedFunc: String = "";
|
|
if opKind == tkPlus { checkedFunc = "bux_add_i64_checked"; }
|
|
else if opKind == tkMinus { checkedFunc = "bux_sub_i64_checked"; }
|
|
else if opKind == tkStar { checkedFunc = "bux_mul_i64_checked"; }
|
|
if !String_Eq(checkedFunc, "") {
|
|
n.kind = hCall;
|
|
n.strValue = checkedFunc;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
n.kind = hBinary;
|
|
n.intValue = expr.intValue; // operator
|
|
let leftHir: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
let rightHir: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
|
// If either side is a match yield block, expand to:
|
|
// match stmts...; int __binop_N = leftVal op rightVal; yield __binop_N
|
|
if Lcx_IsMatchYield(leftHir) || Lcx_IsMatchYield(rightHir) {
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
let tmpName: String = String_Concat("__binop_", String_FromInt(ctx.varCounter as int64));
|
|
var leftVal: *HirNode = leftHir;
|
|
var rightVal: *HirNode = rightHir;
|
|
let outer: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
outer.kind = hBlock;
|
|
outer.line = line;
|
|
outer.column = col;
|
|
outer.strValue = tmpName;
|
|
outer.typeName = "int";
|
|
var first: *HirNode = null as *HirNode;
|
|
if Lcx_IsMatchYield(leftHir) {
|
|
leftVal = Lcx_YieldVarOf(leftHir);
|
|
leftHir.strValue = "";
|
|
first = leftHir;
|
|
}
|
|
if Lcx_IsMatchYield(rightHir) {
|
|
rightVal = Lcx_YieldVarOf(rightHir);
|
|
rightHir.strValue = "";
|
|
if first == null as *HirNode {
|
|
first = rightHir;
|
|
} else {
|
|
Lcx_AppendToChain(first, rightHir);
|
|
}
|
|
}
|
|
let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tmpAlloca.kind = hAlloca;
|
|
tmpAlloca.line = line;
|
|
tmpAlloca.column = col;
|
|
tmpAlloca.strValue = tmpName;
|
|
tmpAlloca.typeName = "int";
|
|
let binNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
binNode.kind = hBinary;
|
|
binNode.line = line;
|
|
binNode.column = col;
|
|
binNode.intValue = expr.intValue;
|
|
binNode.child1 = leftVal;
|
|
binNode.child2 = rightVal;
|
|
let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tmpStore.kind = hStore;
|
|
tmpStore.line = line;
|
|
tmpStore.column = col;
|
|
let tmpVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
tmpVar.kind = hVar;
|
|
tmpVar.strValue = tmpName;
|
|
tmpStore.child1 = tmpVar;
|
|
tmpStore.child2 = binNode;
|
|
tmpAlloca.child3 = tmpStore;
|
|
if first == null as *HirNode {
|
|
outer.child1 = tmpAlloca;
|
|
} else {
|
|
outer.child1 = first;
|
|
Lcx_AppendToChain(first, tmpAlloca);
|
|
}
|
|
return outer;
|
|
}
|
|
n.child1 = leftHir;
|
|
n.child2 = rightHir;
|
|
return n;
|
|
}
|
|
|
|
// Unary
|
|
if kind == ekUnary {
|
|
// Overflow checking: in @[Checked] mode, lower negation on signed integers to checked call
|
|
if ctx.checkedFunc && !ctx.releaseFunc && expr.intValue == tkMinus {
|
|
var isSignedInt: bool = false;
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
let operandKind: int = Lcx_ResolveTypeKind(expr.child1.refType);
|
|
isSignedInt = Type_IsSigned(operandKind);
|
|
}
|
|
if isSignedInt {
|
|
n.kind = hCall;
|
|
n.strValue = "bux_neg_i64_checked";
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
return n;
|
|
}
|
|
}
|
|
n.kind = hUnary;
|
|
n.intValue = expr.intValue;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
if expr.intValue == tkAmp {
|
|
n.typeKind = tyPointer;
|
|
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekFunc {
|
|
n.typeName = Lcx_BuildFuncTypeName(expr.child1.refType);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Call
|
|
if kind == ekCall {
|
|
// Method call desugaring: obj.method(args) → Type_method(obj, args)
|
|
if expr.child1 != null as *Expr && expr.child1.kind == ekField {
|
|
n.kind = hCall;
|
|
let methodName: String = expr.child1.strValue;
|
|
var receiverTypeName: String = "";
|
|
var receiverRefType: *TypeExpr = null as *TypeExpr;
|
|
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
|
|
receiverTypeName = sym.typeName;
|
|
receiverRefType = sym.refType;
|
|
}
|
|
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
|
|
receiverTypeName = expr.child1.child1.refType.typeName;
|
|
receiverRefType = expr.child1.child1.refType;
|
|
}
|
|
|
|
var methodDecl: *Decl = null as *Decl;
|
|
if !String_Eq(receiverTypeName, "") {
|
|
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
|
|
var baseName: String = receiverTypeName;
|
|
let len: int = bux_strlen(baseName) as int;
|
|
if len > 0 {
|
|
let lastChar: String = bux_str_slice(baseName, (len - 1) as uint, 1);
|
|
if String_Eq(lastChar, "*") {
|
|
baseName = bux_str_slice(baseName, 0, (len - 1) as uint);
|
|
}
|
|
}
|
|
n.strValue = String_Concat(baseName, "_");
|
|
n.strValue = String_Concat(n.strValue, methodName);
|
|
|
|
// Generic method monomorphization: Box_Get<T> on Box<int> -> Box_Get_int
|
|
var genericRecvType: *TypeExpr = receiverRefType;
|
|
if genericRecvType != null as *TypeExpr && genericRecvType.kind == tekPointer && genericRecvType.pointerPointee != null as *TypeExpr {
|
|
genericRecvType = genericRecvType.pointerPointee;
|
|
}
|
|
if genericRecvType != null as *TypeExpr && genericRecvType.typeArgCount > 0 {
|
|
let baseTypeName: String = Lcx_StripTypeArgs(genericRecvType.typeName, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
|
|
let baseMethodName: String = String_Concat(String_Concat(baseTypeName, "_"), methodName);
|
|
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, baseMethodName);
|
|
if genDecl != null as *Decl {
|
|
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
|
|
n.strValue = mangled;
|
|
methodDecl = genDecl;
|
|
}
|
|
}
|
|
}
|
|
// Lower receiver as first argument
|
|
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
|
|
// Find method decl if not already found (non-generic case)
|
|
if methodDecl == null as *Decl {
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, n.strValue);
|
|
if sym.kind == skFunc && sym.decl != null as *Decl {
|
|
methodDecl = sym.decl;
|
|
}
|
|
}
|
|
// Auto-address if method expects pointer/reference but receiver is a value
|
|
if methodDecl != null as *Decl && methodDecl.paramCount > 0 && methodDecl.param0.refParamType != null as *TypeExpr {
|
|
let paramKind: int = methodDecl.param0.refParamType.kind;
|
|
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
|
|
if expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr &&
|
|
expr.child1.child1.refType.kind != tekPointer && expr.child1.child1.refType.kind != tekRef && expr.child1.child1.refType.kind != tekMutRef {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = recv;
|
|
n.child1 = addrNode;
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
// Lower remaining arguments from linked list
|
|
var arg: *ExprList = expr.callArgs;
|
|
var argIdx: int = 0;
|
|
while arg != null as *ExprList {
|
|
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
|
|
if argIdx == 0 {
|
|
n.child2 = lowered;
|
|
} else if argIdx == 1 {
|
|
// Third argument — start linked list
|
|
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
firstExtra.node = lowered;
|
|
firstExtra.next = null as *HirArgList;
|
|
n.extraData = firstExtra as *void;
|
|
n.extraCount = 1;
|
|
} else {
|
|
// Additional args — append to linked list
|
|
var cur: *HirArgList = n.extraData as *HirArgList;
|
|
while cur.next != null as *HirArgList {
|
|
cur = cur.next;
|
|
}
|
|
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
newNode.node = lowered;
|
|
newNode.next = null as *HirArgList;
|
|
cur.next = newNode;
|
|
n.extraCount = n.extraCount + 1;
|
|
}
|
|
arg = arg.next;
|
|
argIdx = argIdx + 1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Decide direct vs indirect call
|
|
var isDirectFunc: bool = false;
|
|
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
|
|
if sym.kind == skFunc {
|
|
isDirectFunc = true;
|
|
}
|
|
}
|
|
|
|
if isDirectFunc {
|
|
n.kind = hCall;
|
|
n.strValue = expr.child1.strValue;
|
|
|
|
// Generic call monomorphization (explicit / inferred type args)
|
|
if expr.child1 != null as *Expr {
|
|
var argc: int = expr.child1.genericTypeArgCount;
|
|
var typeArg0: String = expr.child1.genericTypeArg0;
|
|
var typeArg1: String = expr.child1.genericTypeArg1;
|
|
// Fallback: infer T from first *Array/*Iter arg when sema left count=0
|
|
if argc == 0 {
|
|
let genTry: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
|
|
if genTry != null as *Decl && genTry.typeParamCount > 0 {
|
|
if expr.callArgs != null as *ExprList && expr.callArgs.expr != null as *Expr {
|
|
var a0: *Expr = expr.callArgs.expr;
|
|
var te: *TypeExpr = a0.refType;
|
|
if te == null as *TypeExpr && a0.kind == ekUnary && a0.intValue == tkAmp {
|
|
if a0.child1 != null as *Expr { te = a0.child1.refType; }
|
|
}
|
|
if te != null as *TypeExpr {
|
|
// Unwrap pointer
|
|
if (te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef)
|
|
&& te.pointerPointee != null as *TypeExpr {
|
|
te = te.pointerPointee;
|
|
}
|
|
var elem: String = "";
|
|
if te.typeArgCount > 0 {
|
|
elem = te.typeArgName0;
|
|
} else {
|
|
// Mangled Array_int / Iter_String
|
|
elem = Lcx_ExtractElemFromName(te.typeName);
|
|
}
|
|
if !String_Eq(elem, "") {
|
|
typeArg0 = elem;
|
|
argc = 1;
|
|
// Second type arg from func-typed second argument if needed
|
|
if genTry.typeParamCount >= 2 && expr.callArgs.next != null as *ExprList {
|
|
let a1e: *Expr = expr.callArgs.next.expr;
|
|
if a1e != null as *Expr && a1e.kind == ekIdent {
|
|
let fsym: Symbol = Scope_Lookup(ctx.scope, a1e.strValue);
|
|
if fsym.kind == skFunc && fsym.decl != null as *Decl
|
|
&& fsym.decl.retType != null as *TypeExpr
|
|
&& fsym.decl.retType.kind == tekNamed {
|
|
typeArg1 = fsym.decl.retType.typeName;
|
|
argc = 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if argc > 0 {
|
|
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
|
|
if genDecl != null as *Decl {
|
|
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
|
|
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
|
|
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
|
|
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
|
|
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, argc);
|
|
n.strValue = mangled;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lower arguments into child1/child2/extraData
|
|
var arg: *ExprList = expr.callArgs;
|
|
var argIdx: int = 0;
|
|
while arg != null as *ExprList {
|
|
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
|
|
if argIdx == 0 {
|
|
n.child1 = lowered;
|
|
} else if argIdx == 1 {
|
|
n.child2 = lowered;
|
|
} else if argIdx == 2 {
|
|
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
firstExtra.node = lowered;
|
|
firstExtra.next = null as *HirArgList;
|
|
n.extraData = firstExtra as *void;
|
|
n.extraCount = 1;
|
|
} else {
|
|
var cur: *HirArgList = n.extraData as *HirArgList;
|
|
while cur.next != null as *HirArgList {
|
|
cur = cur.next;
|
|
}
|
|
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
newNode.node = lowered;
|
|
newNode.next = null as *HirArgList;
|
|
cur.next = newNode;
|
|
n.extraCount = n.extraCount + 1;
|
|
}
|
|
arg = arg.next;
|
|
argIdx = argIdx + 1;
|
|
}
|
|
} else {
|
|
n.kind = hCallIndirect;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
// Lower arguments into child2/child3/extraData (child1 is callee)
|
|
var arg: *ExprList = expr.callArgs;
|
|
var argIdx: int = 0;
|
|
while arg != null as *ExprList {
|
|
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
|
|
if argIdx == 0 {
|
|
n.child2 = lowered;
|
|
} else if argIdx == 1 {
|
|
n.child3 = lowered;
|
|
} else if argIdx == 2 {
|
|
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
firstExtra.node = lowered;
|
|
firstExtra.next = null as *HirArgList;
|
|
n.extraData = firstExtra as *void;
|
|
n.extraCount = 1;
|
|
} else {
|
|
var cur: *HirArgList = n.extraData as *HirArgList;
|
|
while cur.next != null as *HirArgList {
|
|
cur = cur.next;
|
|
}
|
|
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
newNode.node = lowered;
|
|
newNode.next = null as *HirArgList;
|
|
cur.next = newNode;
|
|
n.extraCount = n.extraCount + 1;
|
|
}
|
|
arg = arg.next;
|
|
argIdx = argIdx + 1;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Sizeof
|
|
if kind == ekSizeOf {
|
|
n.kind = hSizeOf;
|
|
if expr.refType != null as *TypeExpr {
|
|
let substTe: *TypeExpr = Lcx_SubstituteType(ctx, expr.refType);
|
|
if substTe != null as *TypeExpr {
|
|
n.typeName = substTe.typeName;
|
|
} else {
|
|
n.typeName = expr.refType.typeName;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Field access
|
|
if kind == ekField {
|
|
// Check if this is enum variant access: Color::Green
|
|
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
|
|
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
|
|
// Emit as variable reference: Color_Green
|
|
n.kind = hVar;
|
|
n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue);
|
|
return n;
|
|
}
|
|
}
|
|
// Simple enum .tag is the enum value itself
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekNamed && String_Eq(expr.strValue, "tag") {
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.refType.typeName);
|
|
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
|
|
var hasData: bool = false;
|
|
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { hasData = true; }
|
|
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { hasData = true; }
|
|
if !hasData {
|
|
return Lcx_LowerExpr(ctx, expr.child1);
|
|
}
|
|
}
|
|
}
|
|
n.kind = hFieldPtr;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
n.strValue = expr.strValue;
|
|
|
|
// Get struct type from base expr refType
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
n.typeName = expr.child1.refType.typeName;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// spawn Callee(args)
|
|
if kind == ekSpawn {
|
|
n.kind = hSpawn;
|
|
n.boolValue = expr.boolValue;
|
|
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
|
n.strValue = expr.child1.strValue;
|
|
}
|
|
if expr.child2 != null as *Expr {
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// expr.await
|
|
if kind == ekAwait {
|
|
n.kind = hAwait;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
return n;
|
|
}
|
|
|
|
// Index: arr[idx]
|
|
if kind == ekIndex {
|
|
// In @[Checked] functions, Array access goes through Array_Get with bounds check
|
|
if ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) {
|
|
let elemType: String = Lcx_GetArrayElemType(expr.child1.refType);
|
|
if !String_Eq(elemType, "") {
|
|
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
|
|
|
var isPtr: bool = false;
|
|
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekPointer {
|
|
isPtr = true;
|
|
}
|
|
|
|
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callNode.kind = hCall;
|
|
callNode.strValue = Lcx_MangleName("Array_Get", elemType, "", 1);
|
|
callNode.line = line;
|
|
callNode.column = col;
|
|
|
|
let genGet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Get");
|
|
if genGet != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genGet, elemType, "", 1);
|
|
}
|
|
|
|
if isPtr {
|
|
callNode.child1 = baseNode;
|
|
} else {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = baseNode;
|
|
callNode.child1 = addrNode;
|
|
}
|
|
callNode.child2 = idxNode;
|
|
callNode.extraCount = 0;
|
|
callNode.extraData = null as *void;
|
|
|
|
return callNode;
|
|
}
|
|
}
|
|
|
|
// For Array<T> or *Array<T>, desugar arr[idx] → arr.data[idx]
|
|
if expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) {
|
|
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
|
|
|
let fieldPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fieldPtr.kind = hFieldPtr;
|
|
fieldPtr.line = line;
|
|
fieldPtr.column = col;
|
|
fieldPtr.strValue = "data";
|
|
fieldPtr.child1 = baseNode;
|
|
|
|
n.kind = hIndexPtr;
|
|
n.child1 = fieldPtr;
|
|
n.child2 = idxNode;
|
|
return n;
|
|
}
|
|
|
|
// Operator overloading: try operator_index_get
|
|
if expr.child1 != null as *Expr {
|
|
var receiverTypeName: String = "";
|
|
if expr.child1.refType != null as *TypeExpr {
|
|
let refTe: *TypeExpr = expr.child1.refType;
|
|
if refTe.kind == tekNamed {
|
|
receiverTypeName = refTe.typeName;
|
|
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
|
|
receiverTypeName = refTe.pointerPointee.typeName;
|
|
}
|
|
}
|
|
if !String_Eq(receiverTypeName, "") {
|
|
var funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get");
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
|
var didMono: bool = false;
|
|
// Generic monomorphization: if not found or is generic, monomorphize
|
|
if sym.kind != skFunc || sym.decl == null as *Decl || sym.decl.typeParamCount > 0 {
|
|
var typeArg0: String = "";
|
|
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
|
|
let refTe2: *TypeExpr = expr.child1.refType;
|
|
if refTe2.kind == tekNamed && refTe2.typeArgCount > 0 {
|
|
typeArg0 = refTe2.typeArgName0;
|
|
} else if refTe2.kind == tekPointer && refTe2.pointerPointee != null as *TypeExpr && refTe2.pointerPointee.typeArgCount > 0 {
|
|
typeArg0 = refTe2.pointerPointee.typeArgName0;
|
|
}
|
|
}
|
|
if !String_Eq(typeArg0, "") {
|
|
let genericFuncName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get");
|
|
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, genericFuncName);
|
|
if genDecl != null as *Decl {
|
|
funcName = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, "", 1);
|
|
didMono = true;
|
|
}
|
|
}
|
|
}
|
|
let sym2: Symbol = Scope_Lookup(ctx.scope, funcName);
|
|
let targetDecl: *Decl = sym2.decl;
|
|
if didMono {
|
|
targetDecl = sym.decl;
|
|
}
|
|
if sym2.kind == skFunc && sym2.decl != null as *Decl || didMono {
|
|
n.kind = hCall;
|
|
n.strValue = funcName;
|
|
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
|
// If method expects pointer/reference but receiver is a value, add &
|
|
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
|
|
let paramKind: int = targetDecl.param0.refParamType.kind;
|
|
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
|
|
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = recv;
|
|
n.child1 = addrNode;
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
} else {
|
|
n.child1 = recv;
|
|
}
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
n.kind = hIndexPtr;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
|
return n;
|
|
}
|
|
|
|
// Assign: target = value
|
|
if kind == ekAssign {
|
|
// Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val)
|
|
// Only for plain assignment (=), not compound operators (+=, -=, etc.)
|
|
if expr.intValue == tkAssign && ctx.checkedFunc && !ctx.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.child1.refType) {
|
|
let elemType: String = Lcx_GetArrayElemType(expr.child1.child1.refType);
|
|
if !String_Eq(elemType, "") {
|
|
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
|
|
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child2);
|
|
let valNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
|
|
|
var isPtr: bool = false;
|
|
if expr.child1.child1.refType != null as *TypeExpr && expr.child1.child1.refType.kind == tekPointer {
|
|
isPtr = true;
|
|
}
|
|
|
|
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callNode.kind = hCall;
|
|
callNode.strValue = Lcx_MangleName("Array_Set", elemType, "", 1);
|
|
callNode.line = line;
|
|
callNode.column = col;
|
|
|
|
let genSet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Set");
|
|
if genSet != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genSet, elemType, "", 1);
|
|
}
|
|
|
|
if isPtr {
|
|
callNode.child1 = baseNode;
|
|
} else {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = baseNode;
|
|
callNode.child1 = addrNode;
|
|
}
|
|
callNode.child2 = idxNode;
|
|
|
|
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
extra.node = valNode;
|
|
extra.next = null as *HirArgList;
|
|
callNode.extraData = extra as *void;
|
|
callNode.extraCount = 1;
|
|
|
|
return callNode;
|
|
}
|
|
}
|
|
|
|
// Operator overloading: try operator_index_set
|
|
if expr.intValue == tkAssign && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr {
|
|
var receiverTypeName: String = "";
|
|
let objExpr: *Expr = expr.child1.child1;
|
|
if objExpr.refType != null as *TypeExpr {
|
|
let refTe: *TypeExpr = objExpr.refType;
|
|
if refTe.kind == tekNamed {
|
|
receiverTypeName = refTe.typeName;
|
|
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
|
|
receiverTypeName = refTe.pointerPointee.typeName;
|
|
}
|
|
}
|
|
if !String_Eq(receiverTypeName, "") {
|
|
var funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set");
|
|
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
|
|
var didMono: bool = false;
|
|
// Generic monomorphization: if not found or is generic, monomorphize
|
|
if sym.kind != skFunc || sym.decl == null as *Decl || sym.decl.typeParamCount > 0 {
|
|
var typeArg0: String = "";
|
|
if objExpr != null as *Expr && objExpr.refType != null as *TypeExpr {
|
|
let refTe2: *TypeExpr = objExpr.refType;
|
|
if refTe2.kind == tekNamed && refTe2.typeArgCount > 0 {
|
|
typeArg0 = refTe2.typeArgName0;
|
|
} else if refTe2.kind == tekPointer && refTe2.pointerPointee != null as *TypeExpr && refTe2.pointerPointee.typeArgCount > 0 {
|
|
typeArg0 = refTe2.pointerPointee.typeArgName0;
|
|
}
|
|
}
|
|
if !String_Eq(typeArg0, "") {
|
|
let genericFuncName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set");
|
|
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, genericFuncName);
|
|
if genDecl != null as *Decl {
|
|
funcName = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, "", 1);
|
|
didMono = true;
|
|
}
|
|
}
|
|
}
|
|
let sym2: Symbol = Scope_Lookup(ctx.scope, funcName);
|
|
let targetDecl: *Decl = sym2.decl;
|
|
if didMono {
|
|
targetDecl = sym.decl;
|
|
}
|
|
if sym2.kind == skFunc && sym2.decl != null as *Decl || didMono {
|
|
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callNode.kind = hCall;
|
|
callNode.strValue = funcName;
|
|
callNode.line = line;
|
|
callNode.column = col;
|
|
let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr);
|
|
// If method expects pointer/reference but receiver is a value, add &
|
|
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
|
|
let paramKind: int = targetDecl.param0.refParamType.kind;
|
|
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
|
|
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer && objExpr.refType.kind != tekRef && objExpr.refType.kind != tekMutRef {
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = recv;
|
|
callNode.child1 = addrNode;
|
|
} else {
|
|
callNode.child1 = recv;
|
|
}
|
|
} else {
|
|
callNode.child1 = recv;
|
|
}
|
|
} else {
|
|
callNode.child1 = recv;
|
|
}
|
|
callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2);
|
|
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
extra.node = Lcx_LowerExpr(ctx, expr.child2);
|
|
extra.next = null as *HirArgList;
|
|
callNode.extraData = extra as *void;
|
|
callNode.extraCount = 1;
|
|
return callNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
n.kind = hAssign;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
|
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
|
|
return n;
|
|
}
|
|
|
|
// Tuple expression (a, b, ...) → struct init Tuple_int_int { ._0 = a, ._1 = b }
|
|
if kind == ekTuple {
|
|
var tname: String = "Tuple";
|
|
var ti: int = 0;
|
|
while ti < expr.callArgCount {
|
|
tname = String_Concat(tname, "_int");
|
|
ti = ti + 1;
|
|
}
|
|
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
|
|
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
|
|
tname = expr.refType.typeName;
|
|
}
|
|
n.kind = hStructInit;
|
|
n.strValue = tname;
|
|
n.typeKind = tyNamed;
|
|
n.typeName = tname;
|
|
var firstField: *HirNode = null as *HirNode;
|
|
var lastField: *HirNode = null as *HirNode;
|
|
var tcur: *ExprList = expr.callArgs;
|
|
var tidx: int = 0;
|
|
while tcur != null as *ExprList {
|
|
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fNode.kind = hBlock;
|
|
fNode.strValue = String_Concat("_", String_FromInt(tidx as int64));
|
|
fNode.child1 = Lcx_LowerExpr(ctx, tcur.expr);
|
|
if firstField == null as *HirNode {
|
|
firstField = fNode;
|
|
lastField = fNode;
|
|
} else {
|
|
lastField.child3 = fNode;
|
|
lastField = fNode;
|
|
}
|
|
tcur = tcur.next;
|
|
tidx = tidx + 1;
|
|
}
|
|
n.child1 = firstField;
|
|
return n;
|
|
}
|
|
|
|
// Closure: fat function pointer (multi-instance via heap env + maker)
|
|
if kind == ekClosure {
|
|
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
|
|
var fatName: String = "BuxFn_int_int";
|
|
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
|
|
fatName = Lcx_BuildFuncTypeName(expr.refType);
|
|
} else if f.paramCount >= 2 {
|
|
// thunk has __env + user params; approximate from ret + user arity
|
|
fatName = "BuxFn_int_int";
|
|
if f.paramCount == 3 { fatName = "BuxFn_int_int_int"; }
|
|
if f.paramCount == 1 { fatName = "BuxFn_int_void"; }
|
|
}
|
|
if f.captureCount > 0 {
|
|
// Call __make_<closure>(captures...) which heap-allocs env
|
|
n.kind = hCall;
|
|
n.strValue = String_Concat("__make_", f.name);
|
|
n.typeKind = tyFunc;
|
|
n.typeName = fatName;
|
|
var ci: int = 0;
|
|
while ci < f.captureCount {
|
|
var capName: String = "";
|
|
if ci == 0 { capName = f.captureName0; }
|
|
else if ci == 1 { capName = f.captureName1; }
|
|
else if ci == 2 { capName = f.captureName2; }
|
|
else if ci == 3 { capName = f.captureName3; }
|
|
else if ci == 4 { capName = f.captureName4; }
|
|
else if ci == 5 { capName = f.captureName5; }
|
|
else if ci == 6 { capName = f.captureName6; }
|
|
else if ci == 7 { capName = f.captureName7; }
|
|
let capVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
capVar.kind = hVar;
|
|
capVar.strValue = capName;
|
|
if ci == 0 { n.child1 = capVar; }
|
|
else if ci == 1 { n.child2 = capVar; }
|
|
else if ci == 2 {
|
|
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
firstExtra.node = capVar;
|
|
firstExtra.next = null as *HirArgList;
|
|
n.extraData = firstExtra as *void;
|
|
n.extraCount = 1;
|
|
} else {
|
|
var cur: *HirArgList = n.extraData as *HirArgList;
|
|
while cur.next != null as *HirArgList { cur = cur.next; }
|
|
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
|
newNode.node = capVar;
|
|
newNode.next = null as *HirArgList;
|
|
cur.next = newNode;
|
|
n.extraCount = n.extraCount + 1;
|
|
}
|
|
ci = ci + 1;
|
|
}
|
|
return n;
|
|
}
|
|
// Capture-less: compound literal fat pointer with NULL env
|
|
n.kind = hStructInit;
|
|
n.strValue = fatName;
|
|
n.typeKind = tyFunc;
|
|
n.typeName = fatName;
|
|
let codeField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
codeField.kind = hBlock;
|
|
codeField.strValue = "code";
|
|
let codeVal: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
codeVal.kind = hVar;
|
|
codeVal.strValue = f.name;
|
|
codeField.child1 = codeVal;
|
|
let envField: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
envField.kind = hBlock;
|
|
envField.strValue = "env";
|
|
let nullEnv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
nullEnv.kind = hCast;
|
|
nullEnv.typeName = "void*";
|
|
let zero: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
zero.kind = hLit;
|
|
zero.intValue = tkIntLiteral;
|
|
zero.strValue = "0";
|
|
nullEnv.child1 = zero;
|
|
envField.child1 = nullEnv;
|
|
codeField.child3 = envField;
|
|
n.child1 = codeField;
|
|
return n;
|
|
}
|
|
|
|
// Cast
|
|
if kind == ekCast {
|
|
n.kind = hCast;
|
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
|
if expr.refType != null as *TypeExpr {
|
|
let substTe: *TypeExpr = Lcx_SubstituteType(ctx, expr.refType);
|
|
if substTe == null as *TypeExpr { substTe = expr.refType; }
|
|
let resolvedKind: int = Lcx_ResolveTypeKind(substTe);
|
|
n.typeKind = resolvedKind;
|
|
// For pointer types, construct "PointeeType*"
|
|
if substTe.kind == tekPointer && substTe.pointerPointee != null as *TypeExpr {
|
|
n.typeName = String_Concat(substTe.pointerPointee.typeName, "*");
|
|
} else if !String_Eq(substTe.typeName, "") {
|
|
n.typeName = substTe.typeName;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Struct init: TypeName { field: value, ... }
|
|
if kind == ekStructInit {
|
|
// Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
|
|
let enumSym: Symbol = Scope_Lookup(ctx.scope, expr.structName);
|
|
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
|
|
var hasData: bool = false;
|
|
if enumSym.decl.variantCount > 0 && enumSym.decl.variant0.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 1 && enumSym.decl.variant1.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 2 && enumSym.decl.variant2.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 3 && enumSym.decl.variant3.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 4 && enumSym.decl.variant4.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 5 && enumSym.decl.variant5.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 6 && enumSym.decl.variant6.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 7 && enumSym.decl.variant7.fieldCount > 0 { hasData = true; }
|
|
if enumSym.decl.variantCount > 8 && enumSym.decl.variant8.fieldCount > 0 { hasData = true; }
|
|
if !hasData && expr.child1 != null as *Expr && String_Eq(expr.child1.strValue, "tag") {
|
|
return Lcx_LowerExpr(ctx, expr.child1.child1);
|
|
}
|
|
}
|
|
n.kind = hStructInit;
|
|
var structName: String = expr.structName;
|
|
// Generic struct monomorphization
|
|
if expr.genericTypeArgCount > 0 {
|
|
let genDecl: *Decl = Lcx_FindGenericStruct(ctx, expr.structName);
|
|
if genDecl != null as *Decl {
|
|
var typeArg0: String = expr.genericTypeArg0;
|
|
var typeArg1: String = expr.genericTypeArg1;
|
|
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
|
|
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
|
|
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
|
|
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
|
|
structName = Lcx_GenerateStructInstance(ctx, genDecl, typeArg0, typeArg1, expr.genericTypeArgCount);
|
|
}
|
|
}
|
|
n.strValue = structName;
|
|
// Lower each field (fields are chained via child3 on synthetic ekField exprs)
|
|
var field: *Expr = expr.child1;
|
|
var firstField: *HirNode = null as *HirNode;
|
|
var lastField: *HirNode = null as *HirNode;
|
|
while field != null as *Expr {
|
|
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
fNode.kind = hBlock; // placeholder, field is identified by name+value
|
|
fNode.line = expr.line;
|
|
fNode.column = expr.column;
|
|
fNode.strValue = field.strValue; // field name
|
|
fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value
|
|
if firstField == null as *HirNode {
|
|
firstField = fNode;
|
|
lastField = fNode;
|
|
} else {
|
|
lastField.child3 = fNode;
|
|
lastField = fNode;
|
|
}
|
|
field = field.child3;
|
|
}
|
|
n.child1 = firstField;
|
|
return n;
|
|
}
|
|
|
|
// Block expression (boolValue = true means unsafe block)
|
|
// retTypeKind -2 → yield last expression as block value
|
|
if kind == ekBlock {
|
|
if expr.refBlock != null as *Block {
|
|
if expr.boolValue {
|
|
let oldChecked: bool = ctx.checkedFunc;
|
|
let oldRelease: bool = ctx.releaseFunc;
|
|
ctx.checkedFunc = false;
|
|
ctx.releaseFunc = false;
|
|
let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -2);
|
|
ctx.checkedFunc = oldChecked;
|
|
ctx.releaseFunc = oldRelease;
|
|
return blockNode;
|
|
} else {
|
|
return Lcx_LowerBlock(ctx, expr.refBlock, -2);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Statement lowering
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|
if stmt == null as *Stmt { return null as *HirNode; }
|
|
|
|
let line: uint32 = stmt.line;
|
|
let col: uint32 = stmt.column;
|
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
n.kind = hBlock;
|
|
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);
|
|
|
|
// Match (or other multi-stmt yield) as let initializer:
|
|
// match stmts...; Type x = __match_N;
|
|
// instead of illegal `Type x = <block>;`
|
|
if Lcx_IsMatchYield(init) {
|
|
let yieldName: String = init.strValue;
|
|
init.strValue = "";
|
|
let yieldVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
yieldVar.kind = hVar;
|
|
yieldVar.strValue = yieldName;
|
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
storeNode.kind = hStore;
|
|
storeNode.line = line;
|
|
storeNode.column = col;
|
|
storeNode.child1 = alloca;
|
|
storeNode.child2 = yieldVar;
|
|
Lcx_AppendToChain(init, storeNode);
|
|
return init;
|
|
}
|
|
|
|
// store the init value
|
|
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
storeNode.kind = hStore;
|
|
storeNode.child1 = alloca;
|
|
storeNode.child2 = init;
|
|
|
|
// Auto-Drop for @[Drop] types and heap-allocated stdlib types
|
|
var deferNode: *HirNode = null as *HirNode;
|
|
if !String_Eq(alloca.typeName, "") {
|
|
let typeName: String = alloca.typeName;
|
|
let freeName: String = Lcx_BuildAutoDropFree(ctx, typeName);
|
|
if !String_Eq(freeName, "") {
|
|
let varRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
varRef.kind = hVar;
|
|
varRef.strValue = stmt.strValue;
|
|
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrNode.kind = hUnary;
|
|
addrNode.intValue = tkAmp;
|
|
addrNode.child1 = varRef;
|
|
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callNode.kind = hCall;
|
|
callNode.strValue = freeName;
|
|
callNode.child1 = addrNode;
|
|
deferNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
deferNode.kind = hDefer;
|
|
deferNode.child1 = callNode;
|
|
}
|
|
}
|
|
|
|
// Capturing closures allocate env via __make_* at ekClosure site.
|
|
// Wrap with defer if present
|
|
if deferNode != null as *HirNode {
|
|
storeNode.child3 = deferNode;
|
|
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
blockNode.kind = hBlock;
|
|
blockNode.line = line;
|
|
blockNode.column = col;
|
|
blockNode.child1 = storeNode;
|
|
return blockNode;
|
|
}
|
|
return storeNode;
|
|
}
|
|
|
|
// Return
|
|
if kind == skReturn {
|
|
if stmt.child1 != null as *Expr {
|
|
let retVal: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
|
// `return match { ... }` / other multi-stmt yields: expand stmts then return result var
|
|
if retVal != null as *HirNode && Lcx_IsMatchYield(retVal) {
|
|
let retVar: *HirNode = Lcx_YieldVarOf(retVal);
|
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
retNode.kind = hReturn;
|
|
retNode.line = line;
|
|
retNode.column = col;
|
|
retNode.child1 = retVar;
|
|
retVal.strValue = "";
|
|
var lastIn: *HirNode = retVal.child1;
|
|
if lastIn == null as *HirNode {
|
|
retVal.child1 = retNode;
|
|
} else {
|
|
while lastIn.child3 != null as *HirNode {
|
|
lastIn = lastIn.child3;
|
|
}
|
|
lastIn.child3 = retNode;
|
|
}
|
|
return retVal;
|
|
}
|
|
n.kind = hReturn;
|
|
n.child1 = retVal;
|
|
return n;
|
|
}
|
|
n.kind = hReturn;
|
|
return n;
|
|
}
|
|
|
|
// Expression statement
|
|
if kind == skExpr && stmt.child1 != null as *Expr {
|
|
return Lcx_LowerExpr(ctx, stmt.child1);
|
|
}
|
|
|
|
// If
|
|
if kind == skIf {
|
|
n.kind = hIf;
|
|
n.child1 = Lcx_LowerExpr(ctx, stmt.child1); // condition
|
|
if stmt.refStmtBlock != null as *Block {
|
|
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
|
|
}
|
|
if stmt.refStmtElse != null as *Block {
|
|
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// While
|
|
if kind == skWhile {
|
|
n.kind = hWhile;
|
|
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
|
|
if stmt.refStmtBlock != null as *Block {
|
|
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Loop
|
|
if kind == skLoop {
|
|
n.kind = hLoop;
|
|
if stmt.refStmtBlock != null as *Block {
|
|
n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// For
|
|
if kind == skFor {
|
|
let iterExpr: *Expr = stmt.child1;
|
|
let varName: String = stmt.strValue;
|
|
let body: *Block = stmt.refStmtBlock;
|
|
|
|
// Range-based for: for i in lo..hi { body }
|
|
// (selfhost parses .. as ekBinary; bootstrap parses as ekRange)
|
|
let isRangeExpr: bool = iterExpr != null as *Expr && (iterExpr.kind == ekRange || (iterExpr.kind == ekBinary && (iterExpr.intValue == tkDotDot || iterExpr.intValue == tkDotDotEqual)));
|
|
if isRangeExpr {
|
|
let lo: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child1);
|
|
let hi: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child2);
|
|
var inclusive: bool = iterExpr.boolValue;
|
|
if iterExpr.kind == ekBinary && iterExpr.intValue == tkDotDotEqual {
|
|
inclusive = true;
|
|
}
|
|
|
|
let varTypeKind: int = tyInt;
|
|
let varTypeName: String = "int";
|
|
|
|
// alloca for loop variable
|
|
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
alloca.kind = hAlloca;
|
|
alloca.line = line;
|
|
alloca.column = col;
|
|
alloca.strValue = varName;
|
|
alloca.typeName = varTypeName;
|
|
alloca.typeKind = varTypeKind;
|
|
|
|
// store init value
|
|
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
store.kind = hStore;
|
|
store.child1 = alloca;
|
|
store.child2 = lo;
|
|
|
|
// var node for reading in condition
|
|
let varRead: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
varRead.kind = hVar;
|
|
varRead.strValue = varName;
|
|
varRead.typeName = varTypeName;
|
|
varRead.typeKind = varTypeKind;
|
|
|
|
// condition: var < hi (or <=)
|
|
let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
cond.kind = hBinary;
|
|
if inclusive {
|
|
cond.intValue = tkLe;
|
|
} else {
|
|
cond.intValue = tkLt;
|
|
}
|
|
cond.child1 = varRead;
|
|
cond.child2 = hi;
|
|
|
|
// Build while body: original body + increment
|
|
let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1);
|
|
|
|
// increment: var = var + 1
|
|
let varRead2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
varRead2.kind = hVar;
|
|
varRead2.strValue = varName;
|
|
varRead2.typeName = varTypeName;
|
|
varRead2.typeKind = varTypeKind;
|
|
|
|
let one: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
one.kind = hLit;
|
|
one.intValue = tkIntLiteral;
|
|
one.strValue = "1";
|
|
one.typeKind = varTypeKind;
|
|
|
|
let inc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
inc.kind = hBinary;
|
|
inc.intValue = tkPlus;
|
|
inc.child1 = varRead2;
|
|
inc.child2 = one;
|
|
|
|
let storeInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
storeInc.kind = hStore;
|
|
// Use hVar (not alloca) for assignment to existing variable
|
|
let varForInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
varForInc.kind = hVar;
|
|
varForInc.strValue = varName;
|
|
varForInc.typeName = varTypeName;
|
|
varForInc.typeKind = varTypeKind;
|
|
storeInc.child1 = varForInc;
|
|
storeInc.child2 = inc;
|
|
|
|
// Append storeInc to body block chain
|
|
if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock {
|
|
if bodyBlock.child1 != null as *HirNode {
|
|
var last: *HirNode = bodyBlock.child1;
|
|
while last.child3 != null as *HirNode {
|
|
last = last.child3;
|
|
}
|
|
last.child3 = storeInc;
|
|
} else {
|
|
bodyBlock.child1 = storeInc;
|
|
}
|
|
} else if bodyBlock != null as *HirNode {
|
|
// Wrap single node into a block
|
|
let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
wrapBlock.kind = hBlock;
|
|
wrapBlock.child1 = bodyBlock;
|
|
var last: *HirNode = bodyBlock;
|
|
while last.child3 != null as *HirNode {
|
|
last = last.child3;
|
|
}
|
|
last.child3 = storeInc;
|
|
bodyBlock = wrapBlock;
|
|
} else {
|
|
let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
wrapBlock.kind = hBlock;
|
|
wrapBlock.child1 = storeInc;
|
|
bodyBlock = wrapBlock;
|
|
}
|
|
|
|
// while node
|
|
let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
whileNode.kind = hWhile;
|
|
whileNode.child1 = cond;
|
|
whileNode.child2 = bodyBlock;
|
|
|
|
// Chain: store (contains alloca as child1) -> while
|
|
// C backend emits hStore with hAlloca child1 as "Type x = value;"
|
|
store.child3 = whileNode;
|
|
|
|
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
blockNode.kind = hBlock;
|
|
blockNode.line = line;
|
|
blockNode.column = col;
|
|
blockNode.child1 = store;
|
|
return blockNode;
|
|
}
|
|
|
|
// Collection-based for: for x in arr { body }
|
|
// Desugar to:
|
|
// let __iter = Array_Iter_T(&arr);
|
|
// while Iter_HasNext_T(&__iter) {
|
|
// let x = Iter_Next_T(&__iter);
|
|
// body
|
|
// }
|
|
if iterExpr != null as *Expr {
|
|
let collTypeExpr: *TypeExpr = iterExpr.refType;
|
|
if collTypeExpr == null as *TypeExpr && iterExpr.kind == ekIdent {
|
|
// Fallback: try scope lookup (may have substituted type)
|
|
let collSym: Symbol = Scope_Lookup(ctx.scope, iterExpr.strValue);
|
|
collTypeExpr = collSym.refType;
|
|
}
|
|
if collTypeExpr != null as *TypeExpr && collTypeExpr.kind == tekNamed {
|
|
let collTypeName: String = collTypeExpr.typeName;
|
|
var elemTypeName: String = "";
|
|
var elemTypeKind: int = tyInt;
|
|
if collTypeExpr.typeArgCount > 0 {
|
|
elemTypeName = collTypeExpr.typeArgName0;
|
|
elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName);
|
|
}
|
|
|
|
// Handle already-monomorphized types like Array_int, Iter_string
|
|
var isArray: bool = String_Eq(collTypeName, "Array");
|
|
var isIter: bool = String_Eq(collTypeName, "Iter");
|
|
var isChannel: bool = String_Eq(collTypeName, "Channel");
|
|
|
|
// Also check for mangled names like Array_int, Iter_string
|
|
if !isArray && !isIter && !isChannel {
|
|
if String_StartsWith(collTypeName, "Array_") {
|
|
isArray = true;
|
|
let prefixLen: uint = 6; // len("Array_")
|
|
let totalLen: uint = bux_strlen(collTypeName);
|
|
if totalLen > prefixLen {
|
|
elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen);
|
|
elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName);
|
|
}
|
|
} else if String_StartsWith(collTypeName, "Iter_") {
|
|
isIter = true;
|
|
let prefixLen: uint = 5; // len("Iter_")
|
|
let totalLen: uint = bux_strlen(collTypeName);
|
|
if totalLen > prefixLen {
|
|
elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen);
|
|
elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName);
|
|
}
|
|
} else if String_StartsWith(collTypeName, "Channel_") {
|
|
isChannel = true;
|
|
let prefixLen: uint = 8; // len("Channel_")
|
|
let totalLen: uint = bux_strlen(collTypeName);
|
|
if totalLen > prefixLen {
|
|
elemTypeName = bux_str_slice(collTypeName, prefixLen, totalLen - prefixLen);
|
|
elemTypeKind = Lcx_ResolveTypeKindFromName(elemTypeName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !String_Eq(elemTypeName, "") {
|
|
if isChannel {
|
|
// Channel-based for: for x in ch { body }
|
|
// Desugar to:
|
|
// while true {
|
|
// let x: T;
|
|
// if !Channel_Recv_Ok_T(&ch, &x) { break; }
|
|
// body
|
|
// }
|
|
let recvOkFuncName: String = Lcx_MangleName("Channel_Recv_Ok", elemTypeName, "", 1);
|
|
let genRecvOk: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Recv_Ok");
|
|
if genRecvOk != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genRecvOk, elemTypeName, "", 1);
|
|
}
|
|
|
|
let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1);
|
|
|
|
// alloca for x
|
|
let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
xAlloca.kind = hAlloca;
|
|
xAlloca.line = line;
|
|
xAlloca.column = col;
|
|
xAlloca.strValue = varName;
|
|
xAlloca.typeName = elemTypeName;
|
|
xAlloca.typeKind = elemTypeKind;
|
|
|
|
// call Channel_Recv_Ok_T(&ch, &x)
|
|
let recvOkCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
recvOkCall.kind = hCall;
|
|
recvOkCall.strValue = recvOkFuncName;
|
|
let addrCh: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrCh.kind = hUnary;
|
|
addrCh.intValue = tkAmp;
|
|
if iterExpr.kind == ekIdent {
|
|
let chVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
chVar.kind = hVar;
|
|
chVar.strValue = iterExpr.strValue;
|
|
addrCh.child1 = chVar;
|
|
} else {
|
|
addrCh.child1 = Lcx_LowerExpr(ctx, iterExpr);
|
|
}
|
|
let addrX: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrX.kind = hUnary;
|
|
addrX.intValue = tkAmp;
|
|
let xVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
xVar.kind = hVar;
|
|
xVar.strValue = varName;
|
|
addrX.child1 = xVar;
|
|
recvOkCall.child1 = addrCh;
|
|
recvOkCall.child2 = addrX;
|
|
|
|
// if !recvOk { break; }
|
|
let notRecvOk: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
notRecvOk.kind = hUnary;
|
|
notRecvOk.intValue = tkBang;
|
|
notRecvOk.child1 = recvOkCall;
|
|
let breakNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
breakNode.kind = hBreak;
|
|
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
ifNode.kind = hIf;
|
|
ifNode.child1 = notRecvOk;
|
|
ifNode.child2 = breakNode;
|
|
|
|
// Build while body block: ifNode -> bodyBlock
|
|
let whileBody: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
whileBody.kind = hBlock;
|
|
whileBody.child1 = xAlloca;
|
|
xAlloca.child3 = ifNode;
|
|
if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock {
|
|
ifNode.child3 = bodyBlock.child1;
|
|
} else if bodyBlock != null as *HirNode {
|
|
ifNode.child3 = bodyBlock;
|
|
}
|
|
|
|
// while true
|
|
let trueLit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
trueLit.kind = hLit;
|
|
trueLit.intValue = tkBoolLiteral;
|
|
trueLit.strValue = "true";
|
|
let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
whileNode.kind = hWhile;
|
|
whileNode.child1 = trueLit;
|
|
whileNode.child2 = whileBody;
|
|
|
|
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
blockNode.kind = hBlock;
|
|
blockNode.line = line;
|
|
blockNode.column = col;
|
|
blockNode.child1 = whileNode;
|
|
return blockNode;
|
|
}
|
|
if isArray || isIter {
|
|
let iterVarName: String = String_Concat("__iter_", varName);
|
|
let iterTypeName: String = Lcx_MangleName("Iter", elemTypeName, "", 1);
|
|
|
|
// Ensure struct instances exist
|
|
let iterGenStruct: *Decl = Lcx_FindGenericStruct(ctx, "Iter");
|
|
if iterGenStruct != null as *Decl {
|
|
Lcx_GenerateStructInstance(ctx, iterGenStruct, elemTypeName, "", 1);
|
|
}
|
|
|
|
// Ensure function instances exist
|
|
let genIter: *Decl = Lcx_FindGenericFunc(ctx, "Array_Iter");
|
|
let genHasNext: *Decl = Lcx_FindGenericFunc(ctx, "Iter_HasNext");
|
|
let genNext: *Decl = Lcx_FindGenericFunc(ctx, "Iter_Next");
|
|
let iterFuncName: String = Lcx_MangleName("Array_Iter", elemTypeName, "", 1);
|
|
let hasNextFuncName: String = Lcx_MangleName("Iter_HasNext", elemTypeName, "", 1);
|
|
let nextFuncName: String = Lcx_MangleName("Iter_Next", elemTypeName, "", 1);
|
|
if genIter != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genIter, elemTypeName, "", 1);
|
|
}
|
|
if genHasNext != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genHasNext, elemTypeName, "", 1);
|
|
}
|
|
if genNext != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genNext, elemTypeName, "", 1);
|
|
}
|
|
|
|
// alloca for __iter
|
|
let iterAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
iterAlloca.kind = hAlloca;
|
|
iterAlloca.line = line;
|
|
iterAlloca.column = col;
|
|
iterAlloca.strValue = iterVarName;
|
|
iterAlloca.typeName = iterTypeName;
|
|
iterAlloca.typeKind = tyNamed;
|
|
|
|
// __iter = Array_Iter_T(&arr) or just copy if already Iter
|
|
var iterInit: *HirNode = null as *HirNode;
|
|
var collStore: *HirNode = null as *HirNode;
|
|
if isArray {
|
|
let callIter: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
callIter.kind = hCall;
|
|
callIter.strValue = iterFuncName;
|
|
let addrArr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrArr.kind = hUnary;
|
|
addrArr.intValue = tkAmp;
|
|
if iterExpr.kind == ekIdent {
|
|
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
arrVar.kind = hVar;
|
|
arrVar.strValue = iterExpr.strValue;
|
|
addrArr.child1 = arrVar;
|
|
} else {
|
|
// Non-identifier: create temp variable for collection
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
let tmpName: String = String_Concat("__tmp_coll_", String_FromInt(ctx.varCounter));
|
|
// Ensure Array<T> struct instance exists and get mangled name
|
|
let arrayGenStruct: *Decl = Lcx_FindGenericStruct(ctx, "Array");
|
|
var arrayMangledName: String = collTypeName;
|
|
if arrayGenStruct != null as *Decl {
|
|
arrayMangledName = Lcx_GenerateStructInstance(ctx, arrayGenStruct, elemTypeName, "", 1);
|
|
}
|
|
let collAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
collAlloca.kind = hAlloca;
|
|
collAlloca.strValue = tmpName;
|
|
collAlloca.typeName = arrayMangledName;
|
|
collAlloca.typeKind = tyNamed;
|
|
collStore = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
collStore.kind = hStore;
|
|
collStore.child1 = collAlloca;
|
|
collStore.child2 = Lcx_LowerExpr(ctx, iterExpr);
|
|
let collVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
collVar.kind = hVar;
|
|
collVar.strValue = tmpName;
|
|
addrArr.child1 = collVar;
|
|
}
|
|
callIter.child1 = addrArr;
|
|
iterInit = callIter;
|
|
} else {
|
|
// Already an iterator: __iter = arr
|
|
if iterExpr.kind == ekIdent {
|
|
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
arrVar.kind = hVar;
|
|
arrVar.strValue = iterExpr.strValue;
|
|
iterInit = arrVar;
|
|
} else {
|
|
iterInit = Lcx_LowerExpr(ctx, iterExpr);
|
|
}
|
|
}
|
|
|
|
let iterStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
iterStore.kind = hStore;
|
|
iterStore.child1 = iterAlloca;
|
|
iterStore.child2 = iterInit;
|
|
|
|
// Chain collStore -> iterStore if temp was created
|
|
if collStore != null as *HirNode {
|
|
collStore.child3 = iterStore;
|
|
}
|
|
|
|
// condition: Iter_HasNext_T(&__iter)
|
|
let condCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
condCall.kind = hCall;
|
|
condCall.strValue = hasNextFuncName;
|
|
let addrIter: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrIter.kind = hUnary;
|
|
addrIter.intValue = tkAmp;
|
|
let iterVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
iterVar.kind = hVar;
|
|
iterVar.strValue = iterVarName;
|
|
addrIter.child1 = iterVar;
|
|
condCall.child1 = addrIter;
|
|
|
|
// while body: alloca x + store x = Iter_Next_T(&__iter) + original body
|
|
let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1);
|
|
|
|
// alloca for x
|
|
let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
xAlloca.kind = hAlloca;
|
|
xAlloca.line = line;
|
|
xAlloca.column = col;
|
|
xAlloca.strValue = varName;
|
|
xAlloca.typeName = elemTypeName;
|
|
xAlloca.typeKind = elemTypeKind;
|
|
|
|
// x = Iter_Next_T(&__iter)
|
|
let nextCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
nextCall.kind = hCall;
|
|
nextCall.strValue = nextFuncName;
|
|
let addrIter2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
addrIter2.kind = hUnary;
|
|
addrIter2.intValue = tkAmp;
|
|
let iterVar2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
iterVar2.kind = hVar;
|
|
iterVar2.strValue = iterVarName;
|
|
addrIter2.child1 = iterVar2;
|
|
nextCall.child1 = addrIter2;
|
|
|
|
let xStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
xStore.kind = hStore;
|
|
xStore.child1 = xAlloca;
|
|
xStore.child2 = nextCall;
|
|
|
|
// Build while body block: xStore -> bodyBlock
|
|
let whileBody: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
whileBody.kind = hBlock;
|
|
whileBody.child1 = xStore;
|
|
if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock {
|
|
xStore.child3 = bodyBlock.child1;
|
|
} else if bodyBlock != null as *HirNode {
|
|
xStore.child3 = bodyBlock;
|
|
}
|
|
|
|
// while node
|
|
let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
whileNode.kind = hWhile;
|
|
whileNode.child1 = condCall;
|
|
whileNode.child2 = whileBody;
|
|
|
|
// Chain: iterStore -> whileNode
|
|
iterStore.child3 = whileNode;
|
|
|
|
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
blockNode.kind = hBlock;
|
|
blockNode.line = line;
|
|
blockNode.column = col;
|
|
if collStore != null as *HirNode {
|
|
blockNode.child1 = collStore;
|
|
} else {
|
|
blockNode.child1 = iterStore;
|
|
}
|
|
return blockNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: infinite loop
|
|
n.kind = hLoop;
|
|
if stmt.refStmtBlock != null as *Block {
|
|
n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Break
|
|
if kind == skBreak {
|
|
n.kind = hBreak;
|
|
return n;
|
|
}
|
|
|
|
// Continue
|
|
if kind == skContinue {
|
|
n.kind = hContinue;
|
|
return n;
|
|
}
|
|
|
|
// Defer
|
|
if kind == skDefer {
|
|
n.kind = hDefer;
|
|
if stmt.child1 != null as *Expr {
|
|
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Switch — desugar to if-else chain
|
|
if kind == skSwitch {
|
|
let subject: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
|
var current: *HirNode = null as *HirNode;
|
|
// Default first (bottom of chain)
|
|
if stmt.refStmtElse != null as *Block {
|
|
current = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1);
|
|
}
|
|
// Cases in reverse order (from caseBlock)
|
|
if stmt.refStmtBlock != null as *Block {
|
|
let caseBlock: *Block = stmt.refStmtBlock;
|
|
var caseCount: int = caseBlock.stmtCount;
|
|
// Collect cases into array for reverse iteration
|
|
var c0: *Stmt = null as *Stmt;
|
|
var c1: *Stmt = null as *Stmt;
|
|
var c2: *Stmt = null as *Stmt;
|
|
var c3: *Stmt = null as *Stmt;
|
|
var c4: *Stmt = null as *Stmt;
|
|
var c5: *Stmt = null as *Stmt;
|
|
var c6: *Stmt = null as *Stmt;
|
|
var c7: *Stmt = null as *Stmt;
|
|
var ci: int = 0;
|
|
var cs: *Stmt = caseBlock.firstStmt;
|
|
while cs != null as *Stmt && ci < 8 {
|
|
if ci == 0 { c0 = cs; }
|
|
if ci == 1 { c1 = cs; }
|
|
if ci == 2 { c2 = cs; }
|
|
if ci == 3 { c3 = cs; }
|
|
if ci == 4 { c4 = cs; }
|
|
if ci == 5 { c5 = cs; }
|
|
if ci == 6 { c6 = cs; }
|
|
if ci == 7 { c7 = cs; }
|
|
ci = ci + 1;
|
|
cs = cs.nextStmt;
|
|
}
|
|
while caseCount > 0 {
|
|
caseCount = caseCount - 1;
|
|
var c: *Stmt = null as *Stmt;
|
|
if caseCount == 0 { c = c0; }
|
|
if caseCount == 1 { c = c1; }
|
|
if caseCount == 2 { c = c2; }
|
|
if caseCount == 3 { c = c3; }
|
|
if caseCount == 4 { c = c4; }
|
|
if caseCount == 5 { c = c5; }
|
|
if caseCount == 6 { c = c6; }
|
|
if caseCount == 7 { c = c7; }
|
|
if c != null as *Stmt {
|
|
let caseVal: *HirNode = Lcx_LowerExpr(ctx, c.child1);
|
|
let caseBody: *HirNode = Lcx_LowerBlock(ctx, c.refStmtBlock, -1);
|
|
let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
cond.kind = hBinary;
|
|
cond.intValue = 74; // tkEq
|
|
cond.child1 = subject;
|
|
cond.child2 = caseVal;
|
|
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
ifNode.kind = hIf;
|
|
ifNode.child1 = cond;
|
|
ifNode.child2 = caseBody;
|
|
ifNode.child3 = current;
|
|
current = ifNode;
|
|
}
|
|
}
|
|
}
|
|
return current;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Block lowering
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
|
|
if block == null as *Block { return null as *HirNode; }
|
|
if block.stmtCount == 0 { return null as *HirNode; }
|
|
|
|
// retTypeKind:
|
|
// >= 0 → function body; last skExpr becomes return
|
|
// -1 → statement block (if/while/for); no return, no yield
|
|
// -2 → block-as-expression (match arm / let { ... }); yield last skExpr
|
|
let asExpr: bool = retTypeKind == -2;
|
|
var yieldName: String = "";
|
|
if asExpr {
|
|
ctx.varCounter = ctx.varCounter + 1;
|
|
yieldName = String_Concat("__blk_", String_FromInt(ctx.varCounter as int64));
|
|
}
|
|
|
|
// Build a linked list of HirNodes via child3
|
|
var firstNode: *HirNode = null as *HirNode;
|
|
var prevNode: *HirNode = null as *HirNode;
|
|
var stmt: *Stmt = block.firstStmt;
|
|
while stmt != null as *Stmt {
|
|
let isLast: bool = stmt.nextStmt == null as *Stmt;
|
|
var lowered: *HirNode = null as *HirNode;
|
|
|
|
// Last expression statement in a non-void function → implicit return
|
|
if isLast && !asExpr && retTypeKind != tyVoid && retTypeKind != tyUnknown && retTypeKind >= 0
|
|
&& stmt.kind == skExpr && stmt.child1 != null as *Expr {
|
|
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
|
if exprNode != null as *HirNode {
|
|
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
|
|
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
retVar.kind = hVar;
|
|
retVar.strValue = exprNode.strValue;
|
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
retNode.kind = hReturn;
|
|
retNode.line = stmt.line;
|
|
retNode.column = stmt.column;
|
|
retNode.child1 = retVar;
|
|
var lastInBlock: *HirNode = exprNode.child1;
|
|
if lastInBlock == null as *HirNode {
|
|
exprNode.child1 = retNode;
|
|
} else {
|
|
while lastInBlock.child3 != null as *HirNode {
|
|
lastInBlock = lastInBlock.child3;
|
|
}
|
|
lastInBlock.child3 = retNode;
|
|
}
|
|
exprNode.strValue = "";
|
|
lowered = exprNode;
|
|
} else if exprNode.kind == hReturn {
|
|
lowered = exprNode;
|
|
} else {
|
|
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
retNode.kind = hReturn;
|
|
retNode.line = stmt.line;
|
|
retNode.column = stmt.column;
|
|
retNode.child1 = exprNode;
|
|
lowered = retNode;
|
|
}
|
|
}
|
|
} else if isLast && asExpr && stmt.kind == skExpr && stmt.child1 != null as *Expr {
|
|
// Block-as-expression: last expr is the yield value
|
|
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
|
|
// alloca yield temp
|
|
let allocaN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
allocaN.kind = hAlloca;
|
|
allocaN.line = stmt.line;
|
|
allocaN.column = stmt.column;
|
|
allocaN.strValue = yieldName;
|
|
allocaN.typeName = "int";
|
|
if exprNode != null as *HirNode && exprNode.typeName != null as String
|
|
&& !String_Eq(exprNode.typeName, "") {
|
|
allocaN.typeName = exprNode.typeName;
|
|
}
|
|
// store: yield = value (handle nested match yield)
|
|
let storeN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
storeN.kind = hStore;
|
|
storeN.line = stmt.line;
|
|
storeN.column = stmt.column;
|
|
let yv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
yv.kind = hVar;
|
|
yv.strValue = yieldName;
|
|
storeN.child1 = yv;
|
|
if exprNode != null as *HirNode && Lcx_IsMatchYield(exprNode) {
|
|
// Expand nested match stmts then store its result var
|
|
let yvar: *HirNode = Lcx_YieldVarOf(exprNode);
|
|
storeN.child2 = yvar;
|
|
exprNode.strValue = "";
|
|
// chain: exprNode stmts → alloca → store
|
|
var lastIn: *HirNode = exprNode.child1;
|
|
if lastIn == null as *HirNode {
|
|
exprNode.child1 = allocaN;
|
|
allocaN.child3 = storeN;
|
|
lowered = exprNode;
|
|
} else {
|
|
while lastIn.child3 != null as *HirNode {
|
|
lastIn = lastIn.child3;
|
|
}
|
|
lastIn.child3 = allocaN;
|
|
allocaN.child3 = storeN;
|
|
lowered = exprNode;
|
|
}
|
|
} else {
|
|
storeN.child2 = exprNode;
|
|
allocaN.child3 = storeN;
|
|
lowered = allocaN;
|
|
}
|
|
} else {
|
|
lowered = Lcx_LowerStmt(ctx, stmt);
|
|
}
|
|
if lowered != null as *HirNode {
|
|
if firstNode == null as *HirNode {
|
|
firstNode = lowered;
|
|
prevNode = lowered;
|
|
} else {
|
|
// Walk to end of chain (lowered may itself be a multi-node chain)
|
|
prevNode.child3 = lowered;
|
|
prevNode = lowered;
|
|
while prevNode.child3 != null as *HirNode {
|
|
prevNode = prevNode.child3;
|
|
}
|
|
}
|
|
}
|
|
stmt = stmt.nextStmt;
|
|
}
|
|
|
|
// Wrap in an hBlock node with child1 = first statement in chain
|
|
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
|
n.kind = hBlock;
|
|
n.line = block.line;
|
|
n.column = block.column;
|
|
n.boolValue = true;
|
|
n.child1 = firstNode;
|
|
if asExpr && !String_Eq(yieldName, "") {
|
|
n.strValue = yieldName;
|
|
n.typeName = "int";
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Param → HirParam conversion
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) {
|
|
out.name = p.name;
|
|
var te: *TypeExpr = p.refParamType;
|
|
if ctx != null as *LowerCtx {
|
|
te = Lcx_SubstituteType(ctx, te);
|
|
}
|
|
if te != null as *TypeExpr {
|
|
out.typeKind = Lcx_ResolveTypeKind(te);
|
|
// Function type: build C function-pointer syntax
|
|
if te.kind == tekFunc {
|
|
out.typeName = Lcx_BuildFuncTypeName(te);
|
|
} else if !String_Eq(te.typeName, "") {
|
|
out.typeName = te.typeName;
|
|
} else if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
out.typeName = String_Concat(te.pointerPointee.typeName, "*");
|
|
} else {
|
|
out.typeName = "";
|
|
}
|
|
} else {
|
|
out.typeKind = 0;
|
|
out.typeName = "";
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Function lowering
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
|
let oldChecked: bool = ctx.checkedFunc;
|
|
ctx.checkedFunc = decl.isChecked != 0;
|
|
let oldRelease: bool = ctx.releaseFunc;
|
|
ctx.releaseFunc = decl.isRelease != 0;
|
|
|
|
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
|
|
f.name = decl.strValue;
|
|
f.isPublic = decl.isPublic;
|
|
f.checkedFunc = ctx.checkedFunc;
|
|
f.sourceFile = decl.sourceFile;
|
|
f.paramCount = decl.paramCount;
|
|
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param0, &decl.param0, ctx);
|
|
f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param1, &decl.param1, ctx);
|
|
f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param2, &decl.param2, ctx);
|
|
f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param3, &decl.param3, ctx);
|
|
f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param4, &decl.param4, ctx);
|
|
f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param5, &decl.param5, ctx);
|
|
f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param6, &decl.param6, ctx);
|
|
f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param7, &decl.param7, ctx);
|
|
f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
Lcx_LowerParam(f.param8, &decl.param8, ctx);
|
|
|
|
let retTe: *TypeExpr = Lcx_SubstituteType(ctx, decl.retType);
|
|
if retTe != null as *TypeExpr {
|
|
f.retTypeKind = Lcx_ResolveTypeKind(retTe);
|
|
if retTe.kind == tekFunc {
|
|
f.retTypeName = Lcx_BuildFuncTypeName(retTe);
|
|
} else if !String_Eq(retTe.typeName, "") {
|
|
f.retTypeName = retTe.typeName;
|
|
} else if retTe.kind == tekPointer && retTe.pointerPointee != null as *TypeExpr {
|
|
f.retTypeName = String_Concat(retTe.pointerPointee.typeName, "*");
|
|
} else {
|
|
f.retTypeName = "";
|
|
}
|
|
} else {
|
|
f.retTypeName = "";
|
|
f.retTypeKind = 0;
|
|
}
|
|
|
|
// Create function scope as child of current scope
|
|
var funcScope: Scope = Scope_NewChild(ctx.scope);
|
|
|
|
// Add parameters to function scope for field offset lookups
|
|
var pi: int = 0;
|
|
while pi < decl.paramCount {
|
|
var p: *Param = null as *Param;
|
|
if pi == 0 { p = &decl.param0; }
|
|
else if pi == 1 { p = &decl.param1; }
|
|
else if pi == 2 { p = &decl.param2; }
|
|
else if pi == 3 { p = &decl.param3; }
|
|
else if pi == 4 { p = &decl.param4; }
|
|
else if pi == 5 { p = &decl.param5; }
|
|
else if pi == 6 { p = &decl.param6; }
|
|
else if pi == 7 { p = &decl.param7; }
|
|
else if pi == 8 { p = &decl.param8; }
|
|
if p != null as *Param && p.refParamType != null as *TypeExpr {
|
|
let pTe: *TypeExpr = Lcx_SubstituteType(ctx, p.refParamType);
|
|
var sym: Symbol;
|
|
sym.kind = skVar;
|
|
sym.name = p.name;
|
|
sym.typeKind = Lcx_ResolveTypeKind(pTe);
|
|
sym.refType = pTe;
|
|
// Build typeName same as Lcx_LowerParam
|
|
if pTe.kind == tekFunc {
|
|
sym.typeName = Lcx_BuildFuncTypeName(pTe);
|
|
} else if !String_Eq(pTe.typeName, "") {
|
|
sym.typeName = pTe.typeName;
|
|
} else if pTe.kind == tekPointer && pTe.pointerPointee != null as *TypeExpr {
|
|
sym.typeName = String_Concat(pTe.pointerPointee.typeName, "*");
|
|
} else {
|
|
sym.typeName = "";
|
|
}
|
|
sym.isMutable = false;
|
|
sym.isPublic = false;
|
|
sym.decl = null as *Decl;
|
|
discard Scope_Define(&funcScope, sym);
|
|
}
|
|
pi = pi + 1;
|
|
}
|
|
|
|
// Lower body with function scope active (pass ret kind for last-expr return)
|
|
let prevScope: *Scope = ctx.scope;
|
|
ctx.scope = &funcScope;
|
|
if decl.refBody != null as *Block {
|
|
f.body = Lcx_LowerBlock(ctx, decl.refBody, f.retTypeKind);
|
|
} 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;
|
|
// Fat-func ABI: leading void* __env, then user params
|
|
var userCount: int = 0;
|
|
if params != null as *Decl { userCount = params.paramCount; }
|
|
f.paramCount = userCount + 1;
|
|
f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam;
|
|
f.param0.name = "__env";
|
|
f.param0.typeKind = tyPointer;
|
|
f.param0.typeName = "void*";
|
|
if userCount > 0 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param0, ctx); }
|
|
if userCount > 1 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param1, ctx); }
|
|
if userCount > 2 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param2, ctx); }
|
|
if userCount > 3 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param3, ctx); }
|
|
if userCount > 4 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param4, ctx); }
|
|
if userCount > 5 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param5, ctx); }
|
|
if userCount > 6 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param6, ctx); }
|
|
if userCount > 7 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param7, ctx); }
|
|
|
|
if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc {
|
|
// Return type of the *thunk* is the closure's return type (not the fat type)
|
|
if expr.refType.funcRet != null as *TypeExpr {
|
|
f.retTypeName = expr.refType.funcRet.typeName;
|
|
if String_Eq(f.retTypeName, "") { f.retTypeName = "int"; }
|
|
f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet);
|
|
} else {
|
|
f.retTypeName = "void";
|
|
f.retTypeKind = tyVoid;
|
|
}
|
|
} else {
|
|
f.retTypeName = "int";
|
|
f.retTypeKind = tyInt;
|
|
}
|
|
|
|
// Copy capture metadata from AST
|
|
f.captureCount = expr.captureCount;
|
|
f.captureName0 = expr.captureName0;
|
|
f.captureName1 = expr.captureName1;
|
|
f.captureName2 = expr.captureName2;
|
|
f.captureName3 = expr.captureName3;
|
|
f.captureName4 = expr.captureName4;
|
|
f.captureName5 = expr.captureName5;
|
|
f.captureName6 = expr.captureName6;
|
|
f.captureName7 = expr.captureName7;
|
|
f.captureType0 = expr.captureType0;
|
|
f.captureType1 = expr.captureType1;
|
|
f.captureType2 = expr.captureType2;
|
|
f.captureType3 = expr.captureType3;
|
|
f.captureType4 = expr.captureType4;
|
|
f.captureType5 = expr.captureType5;
|
|
f.captureType6 = expr.captureType6;
|
|
f.captureType7 = expr.captureType7;
|
|
|
|
// Generate env struct and instance names if there are captures
|
|
var envStructName: String = "";
|
|
var envInstanceName: String = "";
|
|
if f.captureCount > 0 {
|
|
envStructName = String_Concat("__closure_env_", numStr);
|
|
envInstanceName = String_Concat("__closure_env_instance_", numStr);
|
|
f.envStructName = envStructName;
|
|
f.envInstanceName = envInstanceName;
|
|
|
|
// Env struct is emitted directly by C backend from func capture metadata
|
|
}
|
|
|
|
// Create function scope
|
|
var funcScope: Scope = Scope_NewChild(ctx.scope);
|
|
var pi: int = 0;
|
|
while pi < params.paramCount {
|
|
var p: *Param = null as *Param;
|
|
if pi == 0 { p = ¶ms.param0; }
|
|
else if pi == 1 { p = ¶ms.param1; }
|
|
else if pi == 2 { p = ¶ms.param2; }
|
|
else if pi == 3 { p = ¶ms.param3; }
|
|
else if pi == 4 { p = ¶ms.param4; }
|
|
else if pi == 5 { p = ¶ms.param5; }
|
|
else if pi == 6 { p = ¶ms.param6; }
|
|
else if pi == 7 { p = ¶ms.param7; }
|
|
else if pi == 8 { p = ¶ms.param8; }
|
|
if p != null as *Param && p.refParamType != null as *TypeExpr {
|
|
let pTe: *TypeExpr = Lcx_SubstituteType(ctx, p.refParamType);
|
|
var sym: Symbol;
|
|
sym.kind = skVar;
|
|
sym.name = p.name;
|
|
sym.typeKind = Lcx_ResolveTypeKind(pTe);
|
|
sym.refType = pTe;
|
|
if pTe.kind == tekFunc {
|
|
sym.typeName = Lcx_BuildFuncTypeName(pTe);
|
|
} else if !String_Eq(pTe.typeName, "") {
|
|
sym.typeName = pTe.typeName;
|
|
} else if pTe.kind == tekPointer && pTe.pointerPointee != null as *TypeExpr {
|
|
sym.typeName = String_Concat(pTe.pointerPointee.typeName, "*");
|
|
} else {
|
|
sym.typeName = "";
|
|
}
|
|
sym.isMutable = false;
|
|
sym.isPublic = false;
|
|
sym.decl = null as *Decl;
|
|
discard Scope_Define(&funcScope, sym);
|
|
}
|
|
pi = pi + 1;
|
|
}
|
|
|
|
let prevScope: *Scope = ctx.scope;
|
|
let prevClosureDepth: int = ctx.closureDepth;
|
|
let prevClosureExpr: *Expr = ctx.currentClosureExpr;
|
|
let prevEnvInstanceName: String = ctx.envInstanceName;
|
|
ctx.scope = &funcScope;
|
|
ctx.closureDepth = ctx.closureDepth + 1;
|
|
ctx.currentClosureExpr = expr;
|
|
ctx.envInstanceName = envInstanceName;
|
|
if expr.refBlock != null as *Block {
|
|
f.body = Lcx_LowerBlock(ctx, expr.refBlock, -1);
|
|
} else {
|
|
f.body = null as *HirNode;
|
|
}
|
|
ctx.scope = prevScope;
|
|
ctx.closureDepth = prevClosureDepth;
|
|
ctx.currentClosureExpr = prevClosureExpr;
|
|
ctx.envInstanceName = prevEnvInstanceName;
|
|
|
|
// Add to module functions
|
|
ctx.funcs[ctx.funcCount] = *f;
|
|
ctx.funcCount = ctx.funcCount + 1;
|
|
|
|
return f;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Compile-Time Function Execution (CTFE) — constant expression evaluator
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const CTFE_MAX_LOCALS: int = 64;
|
|
|
|
struct CtfeLocal {
|
|
name: String,
|
|
value: int,
|
|
}
|
|
|
|
struct CtfeEnv {
|
|
locals: *CtfeLocal,
|
|
count: int,
|
|
}
|
|
|
|
struct CtVal {
|
|
value: int,
|
|
isReturn: bool,
|
|
}
|
|
|
|
func CtfeEnv_New() -> CtfeEnv {
|
|
let locals: *CtfeLocal = bux_alloc(CTFE_MAX_LOCALS as uint * sizeof(CtfeLocal)) as *CtfeLocal;
|
|
return CtfeEnv { locals: locals, count: 0 };
|
|
}
|
|
|
|
func CtfeEnv_Get(env: *CtfeEnv, name: String) -> int {
|
|
var i: int = env.count - 1;
|
|
while i >= 0 {
|
|
if String_Eq(env.locals[i].name, name) {
|
|
return env.locals[i].value;
|
|
}
|
|
i = i - 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
func CtfeEnv_Set(env: *CtfeEnv, name: String, value: int) {
|
|
if env.count >= CTFE_MAX_LOCALS { return; }
|
|
env.locals[env.count].name = name;
|
|
env.locals[env.count].value = value;
|
|
env.count = env.count + 1;
|
|
}
|
|
|
|
func Lcx_FindConstFunc(ctx: *LowerCtx, name: String) -> *Decl {
|
|
var decl: *Decl = ctx.module.firstItem;
|
|
while decl != null as *Decl {
|
|
if decl.kind == dkFunc && decl.isConst == 1 && String_Eq(decl.strValue, name) {
|
|
return decl;
|
|
}
|
|
decl = decl.childDecl2;
|
|
}
|
|
return null as *Decl;
|
|
}
|
|
|
|
func Lcx_ParamName(fd: *Decl, idx: int) -> String {
|
|
if fd == null as *Decl { return ""; }
|
|
if idx == 0 { return fd.param0.name; }
|
|
if idx == 1 { return fd.param1.name; }
|
|
if idx == 2 { return fd.param2.name; }
|
|
if idx == 3 { return fd.param3.name; }
|
|
if idx == 4 { return fd.param4.name; }
|
|
if idx == 5 { return fd.param5.name; }
|
|
if idx == 6 { return fd.param6.name; }
|
|
if idx == 7 { return fd.param7.name; }
|
|
if idx == 8 { return fd.param8.name; }
|
|
return "";
|
|
}
|
|
|
|
func CtVal_Make(value: int) -> CtVal {
|
|
return CtVal { value: value, isReturn: false };
|
|
}
|
|
|
|
func CtVal_Return(value: int) -> CtVal {
|
|
return CtVal { value: value, isReturn: true };
|
|
}
|
|
|
|
func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
|
|
if expr == null as *Expr {
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
// Literal integer
|
|
if expr.kind == ekLiteral {
|
|
return CtVal_Make(expr.intValue);
|
|
}
|
|
|
|
// Reference to local or another constant
|
|
if expr.kind == ekIdent {
|
|
let localVal: int = CtfeEnv_Get(env, expr.strValue);
|
|
// Local takes precedence; 0 from Get could mean not found, but global const 0
|
|
// will still be looked up below if needed. For Factorial parameters this works
|
|
// because params are always set in env.
|
|
if localVal != 0 {
|
|
return CtVal_Make(localVal);
|
|
}
|
|
let name: String = expr.strValue;
|
|
var i: int = 0;
|
|
while i < ctx.hm.constCount {
|
|
if String_Eq(ctx.hm.consts[i].name, name) {
|
|
return CtVal_Make(ctx.hm.consts[i].value);
|
|
}
|
|
i = i + 1;
|
|
}
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
// Unary operators
|
|
if expr.kind == ekUnary {
|
|
let operand: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
|
|
let op: int = expr.intValue;
|
|
if op == tkMinus { return CtVal_Make(-operand.value); }
|
|
if op == tkBang { return CtVal_Make((operand.value == 0) as int); }
|
|
if op == tkTilde { return CtVal_Make(~operand.value); }
|
|
return CtVal_Make(operand.value);
|
|
}
|
|
|
|
// Binary operators
|
|
if expr.kind == ekBinary {
|
|
let left: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
|
|
let right: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child2, env);
|
|
let op: int = expr.intValue;
|
|
if op == tkPlus { return CtVal_Make(left.value + right.value); }
|
|
if op == tkMinus { return CtVal_Make(left.value - right.value); }
|
|
if op == tkStar { return CtVal_Make(left.value * right.value); }
|
|
if op == tkSlash {
|
|
if right.value == 0 { return CtVal_Make(0); }
|
|
return CtVal_Make(left.value / right.value);
|
|
}
|
|
if op == tkPercent {
|
|
if right.value == 0 { return CtVal_Make(0); }
|
|
return CtVal_Make(left.value % right.value);
|
|
}
|
|
if op == tkLt { return CtVal_Make((left.value < right.value) as int); }
|
|
if op == tkLe { return CtVal_Make((left.value <= right.value) as int); }
|
|
if op == tkGt { return CtVal_Make((left.value > right.value) as int); }
|
|
if op == tkGe { return CtVal_Make((left.value >= right.value) as int); }
|
|
if op == tkEq { return CtVal_Make((left.value == right.value) as int); }
|
|
if op == tkNe { return CtVal_Make((left.value != right.value) as int); }
|
|
if op == tkAmp { return CtVal_Make(left.value & right.value); }
|
|
if op == tkPipe { return CtVal_Make(left.value | right.value); }
|
|
if op == tkCaret { return CtVal_Make(left.value ^ right.value); }
|
|
if op == tkShl { return CtVal_Make(left.value << right.value); }
|
|
if op == tkShr { return CtVal_Make(left.value >> right.value); }
|
|
if op == tkAmpAmp { return CtVal_Make((left.value != 0 && right.value != 0) as int); }
|
|
if op == tkPipePipe { return CtVal_Make((left.value != 0 || right.value != 0) as int); }
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
// Ternary operator
|
|
if expr.kind == ekTernary {
|
|
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
|
|
if cond.value != 0 {
|
|
return Lcx_EvalConstExprEnv(ctx, expr.child2, env);
|
|
} else {
|
|
return Lcx_EvalConstExprEnv(ctx, expr.child3, env);
|
|
}
|
|
}
|
|
|
|
// Cast — evaluate operand (types don't affect integer values)
|
|
if expr.kind == ekCast {
|
|
return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
|
|
}
|
|
|
|
// Const function call
|
|
if expr.kind == ekCall {
|
|
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
|
let funcName: String = expr.child1.strValue;
|
|
let fd: *Decl = Lcx_FindConstFunc(ctx, funcName);
|
|
if fd != null as *Decl && fd.refBody != null as *Block {
|
|
// Evaluate arguments
|
|
var argExpr: *ExprList = expr.callArgs;
|
|
var ai: int = 0;
|
|
var argVals: *int = bux_alloc(fd.paramCount as uint * sizeof(int)) as *int;
|
|
while ai < fd.paramCount {
|
|
argVals[ai] = 0;
|
|
ai = ai + 1;
|
|
}
|
|
ai = 0;
|
|
while argExpr != null as *ExprList && ai < fd.paramCount {
|
|
let av: CtVal = Lcx_EvalConstExprEnv(ctx, argExpr.expr, env);
|
|
argVals[ai] = av.value;
|
|
argExpr = argExpr.next;
|
|
ai = ai + 1;
|
|
}
|
|
// Set up call environment
|
|
let callEnv: CtfeEnv = CtfeEnv_New();
|
|
var pi: int = 0;
|
|
while pi < fd.paramCount {
|
|
CtfeEnv_Set(&callEnv, Lcx_ParamName(fd, pi), argVals[pi]);
|
|
pi = pi + 1;
|
|
}
|
|
let result: CtVal = Lcx_EvalConstBlock(ctx, fd.refBody, &callEnv);
|
|
return CtVal_Make(result.value);
|
|
}
|
|
}
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
|
|
if block == null as *Block {
|
|
return CtVal_Make(0);
|
|
}
|
|
var stmt: *Stmt = block.firstStmt;
|
|
while stmt != null as *Stmt {
|
|
if stmt.kind == skLet && stmt.child1 != null as *Expr {
|
|
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
|
|
CtfeEnv_Set(env, stmt.strValue, val.value);
|
|
} else if stmt.kind == skIf {
|
|
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
|
|
if cond.value != 0 {
|
|
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtBlock, env);
|
|
if r.isReturn { return r; }
|
|
} else if stmt.refStmtElse != null as *Block {
|
|
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtElse, env);
|
|
if r.isReturn { return r; }
|
|
}
|
|
} else if stmt.kind == skReturn {
|
|
if stmt.child1 != null as *Expr {
|
|
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
|
|
return CtVal_Return(val.value);
|
|
}
|
|
return CtVal_Return(0);
|
|
} else if stmt.kind == skExpr {
|
|
discard Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
|
|
}
|
|
stmt = stmt.nextStmt;
|
|
}
|
|
return CtVal_Make(0);
|
|
}
|
|
|
|
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
|
|
let env: CtfeEnv = CtfeEnv_New();
|
|
let result: CtVal = Lcx_EvalConstExprEnv(ctx, expr, &env);
|
|
return result.value;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Auto-Drop: build the Free function name for a given type
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String {
|
|
if String_StartsWith(typeName, "Array_") {
|
|
let elemType: String = bux_str_slice(typeName, 6, bux_strlen(typeName) - 6);
|
|
// Ensure inner free is also monomorphized since Drop calls Free
|
|
let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Array_Free");
|
|
if genFree != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1);
|
|
}
|
|
let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Array_Drop");
|
|
if genDrop != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1);
|
|
}
|
|
return String_Concat("Array_Drop_", elemType);
|
|
}
|
|
if String_StartsWith(typeName, "Channel_") {
|
|
let elemType: String = bux_str_slice(typeName, 8, bux_strlen(typeName) - 8);
|
|
let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Free");
|
|
if genFree != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1);
|
|
}
|
|
let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Channel_Drop");
|
|
if genDrop != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1);
|
|
}
|
|
return String_Concat("Channel_Drop_", elemType);
|
|
}
|
|
if String_StartsWith(typeName, "Set_") {
|
|
let elemType: String = bux_str_slice(typeName, 4, bux_strlen(typeName) - 4);
|
|
let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Set_Free");
|
|
if genFree != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genFree, elemType, "", 1);
|
|
}
|
|
let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Set_Drop");
|
|
if genDrop != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genDrop, elemType, "", 1);
|
|
}
|
|
return String_Concat("Set_Drop_", elemType);
|
|
}
|
|
if String_StartsWith(typeName, "Map_") {
|
|
let rest: String = bux_str_slice(typeName, 4, bux_strlen(typeName) - 4);
|
|
// Find underscore separator between K and V
|
|
var ki: int = 0;
|
|
var klen: int = bux_strlen(rest) as int;
|
|
while ki < klen {
|
|
if (rest[ki] as int) == ('_' as int) {
|
|
break;
|
|
}
|
|
ki = ki + 1;
|
|
}
|
|
if ki < klen {
|
|
let kType: String = bux_str_slice(rest, 0, ki);
|
|
let vType: String = bux_str_slice(rest, ki + 1, klen - ki - 1);
|
|
let genFree: *Decl = Lcx_FindGenericFunc(ctx, "Map_Free");
|
|
if genFree != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genFree, kType, vType, 2);
|
|
}
|
|
let genDrop: *Decl = Lcx_FindGenericFunc(ctx, "Map_Drop");
|
|
if genDrop != null as *Decl {
|
|
Lcx_GenerateFuncInstance(ctx, genDrop, kType, vType, 2);
|
|
}
|
|
return String_Concat(String_Concat("Map_Drop_", kType), String_Concat("_", vType));
|
|
}
|
|
}
|
|
// User-defined types with @[Drop] OR an explicit TypeName_Drop method
|
|
let typeSym: Symbol = Scope_Lookup(ctx.scope, typeName);
|
|
if typeSym.kind == skType && typeSym.decl != null as *Decl {
|
|
let dropName: String = String_Concat(typeName, "_Drop");
|
|
let dropSym: Symbol = Scope_Lookup(ctx.scope, dropName);
|
|
let hasAttr: bool = typeSym.decl.isDrop != 0;
|
|
let hasMethod: bool = dropSym.decl != null as *Decl;
|
|
if hasAttr || hasMethod {
|
|
return dropName;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Module lowering — main entry point
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
|
|
ctx.module = mod;
|
|
ctx.scope = sema.scope;
|
|
ctx.funcs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc;
|
|
ctx.funcCount = 0;
|
|
ctx.externFuncs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc;
|
|
ctx.externCount = 0;
|
|
ctx.varCounter = 0;
|
|
ctx.genFuncCount = 0;
|
|
ctx.genFuncs = bux_alloc(256 as uint * sizeof(Decl)) as *Decl;
|
|
ctx.genStructCount = 0;
|
|
ctx.genStructs = bux_alloc(256 as uint * sizeof(Decl)) as *Decl;
|
|
ctx.substParam0 = "";
|
|
ctx.substArg0 = "";
|
|
ctx.substParam1 = "";
|
|
ctx.substArg1 = "";
|
|
|
|
let hm: *HirModule = bux_alloc(sizeof(HirModule)) as *HirModule;
|
|
hm.funcCount = 0;
|
|
hm.funcs = ctx.funcs;
|
|
hm.structCount = 0;
|
|
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
|
|
hm.enumCount = 0;
|
|
hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum;
|
|
hm.constCount = 0;
|
|
hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst;
|
|
ctx.hm = hm;
|
|
|
|
// First pass: count structs (to allocate field arrays later)
|
|
// Second pass: actually collect them
|
|
// For simplicity, do single pass with pre-allocated field arrays
|
|
|
|
// Pass 1: collect generic declarations for monomorphization
|
|
var decl: *Decl = mod.firstItem;
|
|
while decl != null as *Decl {
|
|
if decl.kind == dkFunc && decl.typeParamCount > 0 {
|
|
ctx.genFuncs[ctx.genFuncCount] = *decl;
|
|
ctx.genFuncCount = ctx.genFuncCount + 1;
|
|
}
|
|
if decl.kind == dkStruct && decl.typeParamCount > 0 {
|
|
ctx.genStructs[ctx.genStructCount] = *decl;
|
|
ctx.genStructCount = ctx.genStructCount + 1;
|
|
}
|
|
// Generic impl/extend blocks: methods inherit the impl's type params
|
|
if decl.kind == dkImpl && decl.typeParamCount > 0 {
|
|
let implTypeName: String = decl.strValue;
|
|
var implDecl: *Decl = decl.childDecl1;
|
|
while implDecl != null as *Decl {
|
|
if implDecl.kind == dkFunc {
|
|
let renamed: String = String_Concat(String_Concat(implTypeName, "_"), implDecl.strValue);
|
|
var copy: Decl = *implDecl;
|
|
copy.strValue = renamed;
|
|
copy.typeParam0 = decl.typeParam0;
|
|
copy.typeParam1 = decl.typeParam1;
|
|
copy.typeParamCount = decl.typeParamCount;
|
|
ctx.genFuncs[ctx.genFuncCount] = copy;
|
|
ctx.genFuncCount = ctx.genFuncCount + 1;
|
|
}
|
|
implDecl = implDecl.childDecl2;
|
|
}
|
|
}
|
|
decl = decl.childDecl2;
|
|
}
|
|
|
|
// Pass 2: lower all declarations
|
|
decl = mod.firstItem;
|
|
while decl != null as *Decl {
|
|
if decl.kind == dkStruct && decl.typeParamCount == 0 && !String_Eq(decl.strValue, "") {
|
|
// Collect struct definition for C codegen
|
|
let si: int = hm.structCount;
|
|
hm.structs[si].name = decl.strValue;
|
|
hm.structs[si].fieldCount = decl.fieldCount;
|
|
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
|
var fi: int = 0;
|
|
while fi < decl.fieldCount {
|
|
var fname: String = "";
|
|
var ftype: *TypeExpr = null as *TypeExpr;
|
|
fname = decl.fields[fi].name;
|
|
ftype = decl.fields[fi].refFieldType;
|
|
// Skip empty field names
|
|
if String_Eq(fname, "") {
|
|
fi = fi + 1;
|
|
continue;
|
|
}
|
|
hm.structs[si].fields[fi].name = fname;
|
|
if ftype != null as *TypeExpr {
|
|
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
|
|
// Pointer type: emit "TypeName*"
|
|
if !String_Eq(ftype.pointerPointee.typeName, "") {
|
|
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
|
|
}
|
|
} else if !String_Eq(ftype.typeName, "") {
|
|
// Monomorphize container fields: Array<int> → Array_int
|
|
// so C backend does not skip the parent struct as "generic".
|
|
var tn: String = ftype.typeName;
|
|
if ftype.typeArgCount >= 1 && !String_Eq(ftype.typeArgName0, "") {
|
|
if String_Eq(tn, "Array") || String_Eq(tn, "Set") ||
|
|
String_Eq(tn, "Channel") || String_Eq(tn, "Iter") {
|
|
tn = String_Concat(String_Concat(tn, "_"), ftype.typeArgName0);
|
|
} else if String_Eq(tn, "Map") && ftype.typeArgCount >= 2 {
|
|
tn = String_Concat(String_Concat(String_Concat("Map_", ftype.typeArgName0), "_"), ftype.typeArgName1);
|
|
}
|
|
}
|
|
hm.structs[si].fields[fi].typeName = tn;
|
|
}
|
|
}
|
|
fi = fi + 1;
|
|
}
|
|
hm.structCount = hm.structCount + 1;
|
|
}
|
|
if decl.kind == dkFunc && decl.refBody != null as *Block && decl.typeParamCount == 0 {
|
|
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
|
|
ctx.funcs[ctx.funcCount] = *f;
|
|
ctx.funcCount = ctx.funcCount + 1;
|
|
}
|
|
if decl.kind == dkImpl {
|
|
let implTypeName: String = decl.strValue;
|
|
var implDecl: *Decl = decl.childDecl1;
|
|
while implDecl != null as *Decl {
|
|
if implDecl.kind == dkFunc && implDecl.refBody != null as *Block {
|
|
// Generic impl methods are monomorphized on demand; skip direct lowering
|
|
if decl.typeParamCount > 0 {
|
|
implDecl = implDecl.childDecl2;
|
|
continue;
|
|
}
|
|
let mangled: String = String_Concat(implTypeName, "_");
|
|
implDecl.strValue = String_Concat(mangled, implDecl.strValue);
|
|
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
|
|
ctx.funcs[ctx.funcCount] = *f;
|
|
ctx.funcCount = ctx.funcCount + 1;
|
|
}
|
|
implDecl = implDecl.childDecl2;
|
|
}
|
|
}
|
|
if decl.kind == dkExternFunc {
|
|
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
|
|
ctx.externFuncs[ctx.externCount] = *f;
|
|
ctx.externCount = ctx.externCount + 1;
|
|
}
|
|
// Pass 1: collect const names (expressions evaluated in Pass 2)
|
|
if decl.kind == dkConst && hm.constCount < 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 {
|
|
// Positional field names: Variant_0, Variant_1 (matches data.Variant_i / nested struct)
|
|
hm.enums[ei].variants[vi].fieldName0 = String_Concat(v.name, "_0");
|
|
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
|
|
hm.enums[ei].variants[vi].fieldTypeName0 = v.fieldTypeName0;
|
|
}
|
|
if v.fieldCount > 1 {
|
|
hm.enums[ei].variants[vi].fieldName1 = String_Concat(v.name, "_1");
|
|
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
|
|
hm.enums[ei].variants[vi].fieldTypeName1 = v.fieldTypeName1;
|
|
}
|
|
}
|
|
vi = vi + 1;
|
|
}
|
|
hm.enumCount = hm.enumCount + 1;
|
|
}
|
|
decl = decl.childDecl2;
|
|
}
|
|
|
|
|
|
// Pass 2: evaluate all const expressions (multiple passes for forward refs)
|
|
var changed: bool = true;
|
|
var maxPasses: int = 10;
|
|
var pass: int = 0;
|
|
while changed && pass < maxPasses {
|
|
changed = false;
|
|
decl = mod.firstItem;
|
|
var ci2: int = 0;
|
|
while decl != null as *Decl && ci2 < hm.constCount {
|
|
if decl.kind == dkConst {
|
|
if decl.constValue != null as *Expr {
|
|
let newVal: int = Lcx_EvalConstExpr(ctx, decl.constValue);
|
|
if newVal != hm.consts[ci2].value {
|
|
hm.consts[ci2].value = newVal;
|
|
changed = true;
|
|
}
|
|
}
|
|
ci2 = ci2 + 1;
|
|
}
|
|
decl = decl.childDecl2;
|
|
}
|
|
pass = pass + 1;
|
|
}
|
|
|
|
hm.funcCount = ctx.funcCount;
|
|
hm.funcs = ctx.funcs;
|
|
hm.externCount = ctx.externCount;
|
|
hm.externFuncs = ctx.externFuncs;
|
|
|
|
|
|
return hm;
|
|
}
|
|
|
|
}
|