From a2236de69aeeb1f90a28aa8e6dce251609f78b7d Mon Sep 17 00:00:00 2001 From: dimgigov Date: Wed, 10 Jun 2026 13:11:46 +0300 Subject: [PATCH] feat(selfhost): add operator overloading support (+,-,==,[], etc.) - Auto-register func Type_Method(self: T, ...) as methods in sema. - Add method table lookup for binary operators in sema.bux ekBinary. - Add method table lookup for operator_index_get in sema.bux ekIndex. - Add HIR lowering for binary operators to method calls in hir_lower.bux. - Add HIR lowering for operator_index_get/set in hir_lower.bux. - Set expr.refType for overloaded operators so chained ops work. --- src/hir_lower.bux | 142 ++++++++++++++++++++++++++++++++++++++++++++++ src/sema.bux | 104 +++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/src/hir_lower.bux b/src/hir_lower.bux index c200d53..e35deac 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -483,6 +483,63 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { n.child2 = Lcx_LowerExpr(ctx, expr.child2); return n; } + + // Operator overloading: try method call + var opMethodName: String = ""; + if expr.intValue == tkPlus { opMethodName = "operator_add"; } + else if expr.intValue == tkMinus { opMethodName = "operator_sub"; } + else if expr.intValue == tkStar { opMethodName = "operator_mul"; } + else if expr.intValue == tkSlash { opMethodName = "operator_div"; } + else if expr.intValue == tkPercent { opMethodName = "operator_mod"; } + else if expr.intValue == tkEq { opMethodName = "operator_eq"; } + else if expr.intValue == tkNe { opMethodName = "operator_ne"; } + else if expr.intValue == tkLt { opMethodName = "operator_lt"; } + else if expr.intValue == tkLe { opMethodName = "operator_le"; } + else if expr.intValue == tkGt { opMethodName = "operator_gt"; } + else if expr.intValue == tkGe { opMethodName = "operator_ge"; } + else if expr.intValue == tkAmp { opMethodName = "operator_bitand"; } + else if expr.intValue == tkPipe { opMethodName = "operator_bitor"; } + else if expr.intValue == tkCaret { opMethodName = "operator_xor"; } + else if expr.intValue == tkShl { opMethodName = "operator_shl"; } + else if expr.intValue == tkShr { opMethodName = "operator_shr"; } + + if !String_Eq(opMethodName, "") { + var receiverTypeName: String = ""; + if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { + let refTe: *TypeExpr = expr.child1.refType; + if refTe.kind == tekNamed { + receiverTypeName = refTe.typeName; + } else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed { + receiverTypeName = refTe.pointerPointee.typeName; + } + } + if !String_Eq(receiverTypeName, "") { + let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName); + let sym: Symbol = Scope_Lookup(ctx.scope, funcName); + if sym.kind == skFunc && sym.decl != null as *Decl { + n.kind = hCall; + n.strValue = funcName; + let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1); + // If method expects pointer 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; + } else { + n.child1 = recv; + } + } else { + n.child1 = recv; + } + n.child2 = Lcx_LowerExpr(ctx, expr.child2); + return n; + } + } + } + n.kind = hBinary; n.intValue = expr.intValue; // operator n.child1 = Lcx_LowerExpr(ctx, expr.child1); @@ -775,6 +832,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { 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, "") { + let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_get"); + 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 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; + } 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); @@ -829,6 +924,53 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { } } + // 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, "") { + let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), "operator_index_set"); + let sym: Symbol = Scope_Lookup(ctx.scope, funcName); + if sym.kind == skFunc && sym.decl != null as *Decl { + 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 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 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; + } 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 diff --git a/src/sema.bux b/src/sema.bux index f6b7828..050787b 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -491,6 +491,50 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) { return left; } + + // Operator overloading: check method table + var opMethodName: String = ""; + if op == tkPlus { opMethodName = "operator_add"; } + else if op == tkMinus { opMethodName = "operator_sub"; } + else if op == tkStar { opMethodName = "operator_mul"; } + else if op == tkSlash { opMethodName = "operator_div"; } + else if op == tkPercent { opMethodName = "operator_mod"; } + else if op == tkEq { opMethodName = "operator_eq"; } + else if op == tkNe { opMethodName = "operator_ne"; } + else if op == tkLt { opMethodName = "operator_lt"; } + else if op == tkLe { opMethodName = "operator_le"; } + else if op == tkGt { opMethodName = "operator_gt"; } + else if op == tkGe { opMethodName = "operator_ge"; } + else if op == tkAmp { opMethodName = "operator_bitand"; } + else if op == tkPipe { opMethodName = "operator_bitor"; } + else if op == tkCaret { opMethodName = "operator_xor"; } + else if op == tkShl { opMethodName = "operator_shl"; } + else if op == tkShr { opMethodName = "operator_shr"; } + + if !String_Eq(opMethodName, "") && left == tyNamed { + var receiverTypeName: String = ""; + if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { + receiverTypeName = expr.child1.refType.typeName; + } + if !String_Eq(receiverTypeName, "") { + var i: int = 0; + while i < sema.methodCount { + if String_Eq(sema.methodEntries[i].typeName, receiverTypeName) && + String_Eq(sema.methodEntries[i].methodName, opMethodName) { + let methodDecl: *Decl = sema.methodEntries[i].decl; + if methodDecl != null as *Decl && methodDecl.paramCount == 2 { + if methodDecl.retType != null as *TypeExpr { + expr.refType = methodDecl.retType; + return Sema_ResolveType(sema, methodDecl.retType); + } + return tyUnknown; + } + } + i = i + 1; + } + } + } + // Comparison operators return bool if op >= tkEq && op <= tkGe { return tyBool; } // Logical operators return bool @@ -645,6 +689,33 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { if !Sema_IsNumeric(idx) && idx != tyUnknown { Sema_EmitError(sema, expr.line, expr.column, "index must be integer"); } + // Operator overloading: check method table for operator_index_get + var receiverTypeName: String = ""; + if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr { + let refTe: *TypeExpr = expr.child1.refType; + if refTe.kind == tekNamed { + receiverTypeName = refTe.typeName; + } else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed { + receiverTypeName = refTe.pointerPointee.typeName; + } + } + if !String_Eq(receiverTypeName, "") { + var i: int = 0; + while i < sema.methodCount { + if String_Eq(sema.methodEntries[i].typeName, receiverTypeName) && + String_Eq(sema.methodEntries[i].methodName, "operator_index_get") { + let methodDecl: *Decl = sema.methodEntries[i].decl; + if methodDecl != null as *Decl && methodDecl.paramCount == 2 { + if methodDecl.retType != null as *TypeExpr { + expr.refType = methodDecl.retType; + return Sema_ResolveType(sema, methodDecl.retType); + } + return tyUnknown; + } + } + i = i + 1; + } + } return tyUnknown; } @@ -1174,6 +1245,39 @@ func Sema_CollectGlobals(sema: *Sema) { decl = decl.childDecl2; } + + // Pass 3: auto-register func Type_Method(self: Type, ...) as methods + decl = sema.module.firstItem; + while decl != null as *Decl { + if decl.kind == dkFunc && decl.paramCount > 0 { + let selfParam: Param = decl.param0; + if String_Eq(selfParam.name, "self") { + var typeName: String = ""; + var i: int = bux_strlen(decl.strValue) as int - 1; + while i > 0 { + if decl.strValue[i] as int == 95 { // '_' + let prefix: String = bux_str_slice(decl.strValue, 0, i as uint); + let typeSym: Symbol = Scope_Lookup(sema.scope, prefix); + if typeSym.kind == skType && typeSym.decl != null as *Decl && typeSym.decl.kind == dkStruct { + typeName = prefix; + break; + } + } + i = i - 1; + } + if !String_Eq(typeName, "") { + let methodName: String = bux_str_slice(decl.strValue, (i + 1) as uint, bux_strlen(decl.strValue) - (i + 1) as uint); + if sema.methodCount < 256 { + sema.methodEntries[sema.methodCount].typeName = typeName; + sema.methodEntries[sema.methodCount].methodName = methodName; + sema.methodEntries[sema.methodCount].decl = decl; + sema.methodCount = sema.methodCount + 1; + } + } + } + } + decl = decl.childDecl2; + } } // ---------------------------------------------------------------------------