feat: bux test runner, bitwise-not parsing, stdlib discovery hardening\n\n- Add 'bux test' runner: discovers tests/*.bux, builds each in a temp package, runs and reports PASS/FAIL.\n- Fix unary ~ parsing and C emission in self-hosted compiler.\n- Harden stdlib discovery (require Fs.bux, avoid system /lib) and lstat in directory traversal.\n- Selfhost loop passes: buxc2 builds src/ and produces identical C output + stripped binary.\n- Update PLAN.md with completed milestones.

This commit is contained in:
2026-06-12 22:19:13 +03:00
parent a668127721
commit 84df4bba9d
18 changed files with 21917 additions and 20942 deletions
+512 -66
View File
@@ -349,6 +349,26 @@ func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String,
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
// ---------------------------------------------------------------------------
@@ -521,14 +541,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
// 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;
}
@@ -569,13 +594,18 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
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;
@@ -588,10 +618,52 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
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);
n.child1 = recv;
// 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;
@@ -743,6 +815,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
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;
@@ -757,6 +848,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// 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;
}
@@ -877,14 +969,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
// 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;
}
@@ -999,14 +1096,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
callNode.line = line;
callNode.column = col;
let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr);
// If method expects pointer but receiver is not pointer, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
callNode.child1 = addrNode;
// 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;
}
@@ -1068,6 +1170,23 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// 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
@@ -1129,6 +1248,157 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// 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;
@@ -2169,87 +2439,240 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
// Compile-Time Function Execution (CTFE) — constant expression evaluator
// ---------------------------------------------------------------------------
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
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 0;
return CtVal_Make(0);
}
// Literal integer
if expr.kind == ekLiteral {
return expr.intValue;
return CtVal_Make(expr.intValue);
}
// Reference to another constant
// 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;
let i: int = 0;
var i: int = 0;
while i < ctx.hm.constCount {
if String_Eq(ctx.hm.consts[i].name, name) {
return ctx.hm.consts[i].value;
return CtVal_Make(ctx.hm.consts[i].value);
}
i = i + 1;
}
return 0;
return CtVal_Make(0);
}
// Unary operators
if expr.kind == ekUnary {
let operand: int = Lcx_EvalConstExpr(ctx, expr.child1);
let operand: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
let op: int = expr.intValue;
if op == tkMinus { return -operand; }
if op == tkBang { return (operand == 0) as int; }
if op == tkTilde { return ~operand; }
return operand;
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: int = Lcx_EvalConstExpr(ctx, expr.child1);
let right: int = Lcx_EvalConstExpr(ctx, expr.child2);
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 left + right; }
if op == tkMinus { return left - right; }
if op == tkStar { return left * right; }
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 == 0 { return 0; }
return left / right;
if right.value == 0 { return CtVal_Make(0); }
return CtVal_Make(left.value / right.value);
}
if op == tkPercent {
if right == 0 { return 0; }
return left % right;
if right.value == 0 { return CtVal_Make(0); }
return CtVal_Make(left.value % right.value);
}
if op == tkLt { return (left < right) as int; }
if op == tkLe { return (left <= right) as int; }
if op == tkGt { return (left > right) as int; }
if op == tkGe { return (left >= right) as int; }
if op == tkEq { return (left == right) as int; }
if op == tkNe { return (left != right) as int; }
if op == tkAmp { return left & right; }
if op == tkPipe { return left | right; }
if op == tkCaret { return left ^ right; }
if op == tkShl { return left << right; }
if op == tkShr { return left >> right; }
if op == tkAmpAmp { return (left != 0 && right != 0) as int; }
if op == tkPipePipe { return (left != 0 || right != 0) as int; }
return 0;
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: int = Lcx_EvalConstExpr(ctx, expr.child1);
if cond != 0 {
return Lcx_EvalConstExpr(ctx, expr.child2);
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
if cond.value != 0 {
return Lcx_EvalConstExprEnv(ctx, expr.child2, env);
} else {
return Lcx_EvalConstExpr(ctx, expr.child3);
return Lcx_EvalConstExprEnv(ctx, expr.child3, env);
}
}
// Cast — evaluate operand (types don't affect integer values)
if expr.kind == ekCast {
return Lcx_EvalConstExpr(ctx, expr.child1);
return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
}
return 0;
// 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;
}
// ---------------------------------------------------------------------------
@@ -2379,6 +2802,24 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
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;
}
@@ -2427,6 +2868,11 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
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);