// sema.bux — Semantic analysis (type checker, ported from sema.nim) // Validates types, resolves identifiers, checks function calls. module Sema { // --------------------------------------------------------------------------- // Sema context // --------------------------------------------------------------------------- struct Sema { module: *Module; scope: *Scope; typeTable: *void; methodTable: *void; diagCount: int; diags: *SemaDiag; hasError: bool; currentRetType: int; // return type of the function being checked } struct SemaDiag { line: uint32; column: uint32; message: String; } // --------------------------------------------------------------------------- // Diagnostics // --------------------------------------------------------------------------- func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) { if sema.diagCount < 256 { sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg }; sema.diagCount = sema.diagCount + 1; } sema.hasError = true; } // --------------------------------------------------------------------------- // Symbol zero-init helper (bootstrap C backend does not zero-init structs) // --------------------------------------------------------------------------- func Sema_ZeroInitSymbol(sym: *Symbol) { sym.kind = 0; sym.name = ""; sym.typeKind = 0; sym.typeName = ""; sym.isMutable = false; sym.isPublic = false; sym.decl = null as *Decl; } // --------------------------------------------------------------------------- // Type resolution from TypeExpr → Type constants // --------------------------------------------------------------------------- func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int { if te == null as *TypeExpr { return tyUnknown; } let name: String = te.typeName; if te.kind == tekPointer { return tyPointer; } if String_Eq(name, "void") { return tyVoid; } if String_Eq(name, "bool") { return tyBool; } if String_Eq(name, "bool8") { return tyBool8; } if String_Eq(name, "bool16") { return tyBool16; } if String_Eq(name, "bool32") { return tyBool32; } if String_Eq(name, "char8") { return tyChar8; } if String_Eq(name, "char16") { return tyChar16; } if String_Eq(name, "char32") { return tyChar32; } if String_Eq(name, "String") { return tyStr; } if String_Eq(name, "str") { return tyStr; } if String_Eq(name, "int8") { return tyInt8; } if String_Eq(name, "int16") { return tyInt16; } if String_Eq(name, "int32") { return tyInt32; } if String_Eq(name, "int64") { return tyInt64; } if String_Eq(name, "int") { return tyInt; } if String_Eq(name, "uint8") { return tyUInt8; } if String_Eq(name, "uint16") { return tyUInt16; } if String_Eq(name, "uint32") { return tyUInt32; } if String_Eq(name, "uint64") { return tyUInt64; } if String_Eq(name, "uint") { return tyUInt; } if String_Eq(name, "float32") { return tyFloat32; } if String_Eq(name, "float64") { return tyFloat64; } if String_Eq(name, "float") { return tyFloat64; } // Check type table for user-defined types (StringMap not yet supported) // TODO: re-enable when StringMap generic is available // if sema.typeTable != null as *void { ... } return tyNamed; // assume named type } // --------------------------------------------------------------------------- // Type predicates // --------------------------------------------------------------------------- func Sema_IsNumeric(kind: int) -> bool { if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; } if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; } if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; } if kind == tyFloat32 || kind == tyFloat64 { return true; } return false; } func Sema_IsBool(kind: int) -> bool { return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32; } func Sema_TypeName(kind: int) -> String { if kind == tyUnknown { return "?"; } if kind == tyVoid { return "void"; } if kind == tyBool { return "bool"; } if kind == tyInt{ return "int"; } if kind == tyInt64 { return "int64"; } if kind == tyUInt { return "uint"; } if kind == tyFloat64 { return "float64"; } if kind == tyStr { return "String"; } if kind == tyPointer { return "*ptr"; } if kind == tyNamed { return "user-type"; } if kind == tyTypeParam { return "type-param"; } return "?"; } // --------------------------------------------------------------------------- // Block checking helper // --------------------------------------------------------------------------- func Sema_CheckBlock(sema: *Sema, block: *Block) { if block == null as *Block { return; } var blockScope: Scope = Scope_NewChild(sema.scope); let prevScope: *Scope = sema.scope; sema.scope = &blockScope; var stmt: *Stmt = block.firstStmt; while stmt != null as *Stmt { Sema_CheckStmt(sema, stmt); stmt = stmt.nextStmt; } sema.scope = prevScope; } // --------------------------------------------------------------------------- // Expression type checking // --------------------------------------------------------------------------- func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { if expr == null as *Expr { return tyUnknown; } let kind: int = expr.kind; // Debug: print expr kind // Print("Sema_CheckExpr kind="); // PrintInt(kind as int64); // PrintLine(""); // Literal if kind == ekLiteral { let tk: int = expr.tokKind; if tk == tkIntLiteral { return tyInt; } if tk == tkFloatLiteral { return tyFloat64; } if tk == tkStringLiteral { return tyStr; } if tk == tkBoolLiteral { return tyBool; } if tk == tkCharLiteral { return tyChar32; } if tk == tkNull { return tyPointer; } return tyUnknown; } // Identifier — look up in scope if kind == ekIdent { let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue); if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) { Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier"); return tyUnknown; } if sym.typeName != null as String && !String_Eq(sym.typeName, "") { let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; te.kind = tekNamed; te.typeName = sym.typeName; expr.refType = te; } return sym.typeKind; } // self if kind == ekSelf { let sym: Symbol = Scope_Lookup(sema.scope, "self"); if sym.kind == 0 && !String_Eq(sym.name, "self") { Sema_EmitError(sema, expr.line, expr.column, "self outside method"); return tyUnknown; } return sym.typeKind; } // Binary if kind == ekBinary { let left: int = Sema_CheckExpr(sema, expr.child1); let right: int = Sema_CheckExpr(sema, expr.child2); let op: int = expr.intValue; // Assignment operators return target type if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) { return left; } // Comparison operators return bool if op >= tkEq && op <= tkGe { return tyBool; } // Logical operators return bool if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; } // Arithmetic returns wider type if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) { Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands"); } if left == tyFloat64 || right == tyFloat64 { return tyFloat64; } return tyInt; } // Unary if kind == ekUnary { let operand: int = Sema_CheckExpr(sema, expr.child1); let op: int = expr.intValue; if op == tkBang { return tyBool; } if op == tkStar { return tyUnknown; } // dereference — resolve pointee if op == tkAmp { return tyPointer; } return operand; } // Assign if kind == ekAssign { let target: int = Sema_CheckExpr(sema, expr.child1); let value: int = Sema_CheckExpr(sema, expr.child2); // Basic type compatibility (permissive for now) return target; } // Call if kind == ekCall { discard Sema_CheckExpr(sema, expr.child1); var arg: *ExprList = expr.callArgs; while arg != null as *ExprList { discard Sema_CheckExpr(sema, arg.expr); arg = arg.next; } // Try to resolve return type from function declaration if expr.child1.kind == ekIdent { let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue); if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr { return Sema_ResolveType(sema, sym.decl.retType); } } return tyUnknown; } // Ternary if kind == ekTernary { return Sema_CheckExpr(sema, expr.child2); // then type } // Cast — return target type if kind == ekCast { if expr.refType != null as *TypeExpr { return Sema_ResolveType(sema, expr.refType); } return tyUnknown; } // Try (?) if kind == ekTry { let inner: int = Sema_CheckExpr(sema, expr.child1); return inner; // simplified } // Struct init: TypeName { field: value, ... } if kind == ekStructInit { return tyNamed; } // Field access if kind == ekField { discard Sema_CheckExpr(sema, expr.child1); return tyUnknown; } // Index if kind == ekIndex { let obj: int = Sema_CheckExpr(sema, expr.child1); let idx: int = Sema_CheckExpr(sema, expr.child2); if !Sema_IsNumeric(idx) && idx != tyUnknown { Sema_EmitError(sema, expr.line, expr.column, "index must be integer"); } return tyUnknown; } // Block expression if kind == ekBlock { if expr.refBlock != null as *Block { Sema_CheckBlock(sema, expr.refBlock); } return tyVoid; } return tyUnknown; } // --------------------------------------------------------------------------- // Statement checking // --------------------------------------------------------------------------- func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { if stmt == null as *Stmt { return; } let kind: int = stmt.kind; // Let/var if kind == skLet { let initType: int = Sema_CheckExpr(sema, stmt.child1); // Register variable in scope var sym: Symbol; sym.kind = skVar; sym.name = stmt.strValue; sym.typeKind = initType; sym.typeName = ""; if stmt.refStmtType != null as *TypeExpr { sym.typeName = stmt.refStmtType.typeName; } sym.isMutable = stmt.boolValue; sym.isPublic = false; sym.decl = null as *Decl; discard Scope_Define(sema.scope, sym); return; } // Return if kind == skReturn { if stmt.child1 != null as *Expr { let retType: int = Sema_CheckExpr(sema, stmt.child1); if sema.currentRetType != tyUnknown && retType != tyUnknown { if retType != sema.currentRetType { // Be permissive: allow numeric widening, pointer compatibility if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) { // Allow pointer-type compatibility (String <-> *T, *T <-> *U) let retIsPtr: bool = retType == tyPointer || retType == tyStr; let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr; if !retIsPtr || !expectedIsPtr { Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch"); } } } } } else { if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown { Sema_EmitError(sema, stmt.line, stmt.column, "missing return value"); } } return; } // If if kind == skIf { let condType: int = Sema_CheckExpr(sema, stmt.child1); if !Sema_IsBool(condType) && condType != tyUnknown { Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool"); } Sema_CheckBlock(sema, stmt.refStmtBlock); Sema_CheckBlock(sema, stmt.refStmtElse); return; } // While if kind == skWhile { let condType: int = Sema_CheckExpr(sema, stmt.child1); if !Sema_IsBool(condType) && condType != tyUnknown { Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool"); } Sema_CheckBlock(sema, stmt.refStmtBlock); return; } // Do-while if kind == skDoWhile { Sema_CheckBlock(sema, stmt.refStmtBlock); let condType: int = Sema_CheckExpr(sema, stmt.child1); if !Sema_IsBool(condType) && condType != tyUnknown { Sema_EmitError(sema, stmt.line, stmt.column, "do-while condition must be bool"); } return; } // Loop if kind == skLoop { Sema_CheckBlock(sema, stmt.refStmtBlock); return; } // For if kind == skFor { discard Sema_CheckExpr(sema, stmt.child1); var forScope: Scope = Scope_NewChild(sema.scope); var loopSym: Symbol; loopSym.kind = skVar; loopSym.name = stmt.strValue; loopSym.typeKind = tyUnknown; loopSym.typeName = ""; loopSym.isMutable = true; loopSym.isPublic = false; loopSym.decl = null as *Decl; discard Scope_Define(&forScope, loopSym); let prevScope: *Scope = sema.scope; sema.scope = &forScope; Sema_CheckBlock(sema, stmt.refStmtBlock); sema.scope = prevScope; return; } // Match if kind == skMatch { discard Sema_CheckExpr(sema, stmt.child1); return; } // Break / Continue if kind == skBreak || kind == skContinue { return; } // Expression statement if kind == skExpr && stmt.child1 != null as *Expr { discard Sema_CheckExpr(sema, stmt.child1); return; } // Decl (nested) if kind == skDecl { if stmt.refStmtDecl != null as *Decl && stmt.refStmtDecl.kind == dkFunc { Sema_EmitError(sema, stmt.line, stmt.column, "nested functions not yet supported"); } return; } } // --------------------------------------------------------------------------- // Collect globals (register functions, structs, enums in scope) // --------------------------------------------------------------------------- func Sema_CollectGlobals(sema: *Sema) { var decl: *Decl = sema.module.firstItem; while decl != null as *Decl { let dk: int = decl.kind; // Function if dk == dkFunc { var sym: Symbol; Sema_ZeroInitSymbol(&sym); sym.kind = skFunc; sym.name = decl.strValue; sym.typeKind = tyFunc; sym.isPublic = decl.isPublic; sym.decl = decl; discard Scope_Define(sema.scope, sym); } // Struct if dk == dkStruct { var sym: Symbol; Sema_ZeroInitSymbol(&sym); sym.kind = skType; sym.name = decl.strValue; sym.typeKind = tyNamed; sym.isPublic = decl.isPublic; sym.decl = decl; discard Scope_Define(sema.scope, sym); } // Enum if dk == dkEnum { var sym: Symbol; Sema_ZeroInitSymbol(&sym); sym.kind = skType; sym.name = decl.strValue; sym.typeKind = tyNamed; sym.isPublic = decl.isPublic; sym.decl = decl; discard Scope_Define(sema.scope, sym); // Register enum variants as constants var vi: int = 0; while vi < decl.variantCount && vi < 8 { var v: EnumVariant; if vi == 0 { v = decl.variant0; } else if vi == 1 { v = decl.variant1; } else if vi == 2 { v = decl.variant2; } else if vi == 3 { v = decl.variant3; } else if vi == 4 { v = decl.variant4; } else if vi == 5 { v = decl.variant5; } else if vi == 6 { v = decl.variant6; } else if vi == 7 { v = decl.variant7; } let variantName: String = String_Concat(decl.strValue, "_"); let variantName2: String = String_Concat(variantName, v.name); var vSym: Symbol; vSym.kind = skConst; vSym.name = variantName2; vSym.typeKind = tyNamed; vSym.typeName = String_Concat(decl.strValue, "_Tag"); vSym.isMutable = false; vSym.isPublic = decl.isPublic; vSym.decl = decl; discard Scope_Define(sema.scope, vSym); vi = vi + 1; } } // Use (import) if dk == dkUse { if decl.useKind == 2 { // Multi-import: names are comma-separated in useNames // For now, register the whole useNames string as a single func name // (permissive fallback — real fix needs string split) let namesStr: String = decl.useNames; if !String_Eq(namesStr, "") { // Simple split by comma using available string functions var start: uint = 0; var pos: uint = 0; let totalLen: uint = String_Len(namesStr); while pos <= totalLen { let atEnd: bool = pos == totalLen; let isComma: bool = false; if pos < totalLen { // Check if character at pos is comma let chStr: String = bux_str_slice(namesStr, pos, 1); isComma = String_Eq(chStr, ","); } if atEnd || isComma { let nameLen: uint = pos - start; if nameLen > 0 { let name: String = bux_str_slice(namesStr, start, nameLen); var sym: Symbol; sym.kind = skFunc; sym.name = name; sym.typeKind = tyUnknown; sym.typeName = ""; sym.isMutable = false; sym.isPublic = true; sym.decl = null as *Decl; discard Scope_Define(sema.scope, sym); } start = pos + 1; } pos = pos + 1; } } } else { // Single import or glob — add last path segment let path: String = decl.usePath; if !String_Eq(path, "") { // Find last :: segment var lastSeg: String = path; let containsColons: int = bux_str_contains(path, "::"); if containsColons != 0 { // Try to get last segment by finding last :: // Use a simple heuristic: slice from various positions let pathLen: uint = String_Len(path); var tryPos: uint = 0; while tryPos < pathLen { let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos); if String_StartsWith(slice, "::") { lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2); } tryPos = tryPos + 1; } } var sym: Symbol; sym.kind = skFunc; sym.name = lastSeg; sym.typeKind = tyUnknown; sym.typeName = ""; sym.isMutable = false; sym.isPublic = true; sym.decl = null as *Decl; discard Scope_Define(sema.scope, sym); } } } // Const if dk == dkConst { var sym: Symbol; Sema_ZeroInitSymbol(&sym); sym.kind = skConst; sym.name = decl.strValue; sym.typeKind = tyUnknown; sym.isPublic = decl.isPublic; sym.decl = decl; discard Scope_Define(sema.scope, sym); } // Extern function if dk == dkExternFunc { var sym: Symbol; Sema_ZeroInitSymbol(&sym); sym.kind = skFunc; sym.name = decl.strValue; sym.typeKind = tyFunc; sym.isPublic = true; sym.decl = decl; discard Scope_Define(sema.scope, sym); } decl = decl.childDecl2; } } // --------------------------------------------------------------------------- // Analyze — main entry point // --------------------------------------------------------------------------- func Sema_Analyze(mod: *Module) -> *Sema { let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema; s.module = mod; s.scope = bux_alloc(sizeof(Scope)) as *Scope; s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol; s.scope.count = 0; s.scope.parent = null as *Scope; s.hasError = false; s.diagCount = 0; s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag; s.typeTable = null as *void; s.methodTable = null as *void; s.currentRetType = tyVoid; // First pass: collect globals Sema_CollectGlobals(s); // Second pass: check function bodies var decl: *Decl = mod.firstItem; while decl != null as *Decl { if decl.kind == dkFunc && decl.refBody != null as *Block { // Create function scope (child of global) var funcScope: Scope = Scope_NewChild(s.scope); // Add type params to scope if decl.typeParamCount >= 1 { var tpSym: Symbol; Sema_ZeroInitSymbol(&tpSym); tpSym.kind = skType; tpSym.name = decl.typeParam0; tpSym.typeKind = tyTypeParam; tpSym.decl = null as *Decl; discard Scope_Define(&funcScope, tpSym); } if decl.typeParamCount >= 2 { var tpSym2: Symbol; Sema_ZeroInitSymbol(&tpSym2); tpSym2.kind = skType; tpSym2.name = decl.typeParam1; tpSym2.typeKind = tyTypeParam; tpSym2.decl = null as *Decl; discard Scope_Define(&funcScope, tpSym2); } // Add parameters to scope var i: int = 0; while i < decl.paramCount { var p: *Param = null as *Param; if i == 0 { p = &decl.param0; } else if i == 1 { p = &decl.param1; } else if i == 2 { p = &decl.param2; } else if i == 3 { p = &decl.param3; } else if i == 4 { p = &decl.param4; } else if i == 5 { p = &decl.param5; } else if i == 6 { p = &decl.param6; } else if i == 7 { p = &decl.param7; } else if i == 8 { p = &decl.param8; } var pSym: Symbol; Sema_ZeroInitSymbol(&pSym); pSym.kind = skVar; if p != null as *Param && p.refParamType != null as *TypeExpr { pSym.typeKind = Sema_ResolveType(s, p.refParamType); pSym.typeName = p.refParamType.typeName; } else { pSym.typeKind = tyInt; pSym.typeName = ""; } pSym.isMutable = false; pSym.isPublic = false; pSym.decl = null as *Decl; if i == 0 { pSym.name = decl.param0.name; } else if i == 1 { pSym.name = decl.param1.name; } else if i == 2 { pSym.name = decl.param2.name; } else if i == 3 { pSym.name = decl.param3.name; } else if i == 4 { pSym.name = decl.param4.name; } else if i == 5 { pSym.name = decl.param5.name; } else if i == 6 { pSym.name = decl.param6.name; } else if i == 7 { pSym.name = decl.param7.name; } else if i == 8 { pSym.name = decl.param8.name; } discard Scope_Define(&funcScope, pSym); i = i + 1; } // Switch to function scope and check body statements let prevScope: *Scope = s.scope; s.scope = &funcScope; // Set current function return type if decl.retType != null as *TypeExpr { s.currentRetType = Sema_ResolveType(s, decl.retType); } else { s.currentRetType = tyVoid; } // Check body statements var stmt: *Stmt = decl.refBody.firstStmt; while stmt != null as *Stmt { Sema_CheckStmt(s, stmt); stmt = stmt.nextStmt; } s.scope = prevScope; } decl = decl.childDecl2; } return s; } func Sema_HasError(sema: *Sema) -> bool { return sema.hasError; } func Sema_DiagCount(sema: *Sema) -> int { return sema.diagCount; } func Sema_Free(sema: *Sema) { bux_free(sema.scope.symbols as *void); bux_free(sema.scope as *void); bux_free(sema.diags as *void); bux_free(sema as *void); } }