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.
This commit is contained in:
@@ -483,6 +483,63 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
||||||
return n;
|
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.kind = hBinary;
|
||||||
n.intValue = expr.intValue; // operator
|
n.intValue = expr.intValue; // operator
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
@@ -775,6 +832,44 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
|||||||
return n;
|
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.kind = hIndexPtr;
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
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.kind = hAssign;
|
||||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
|
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
|
||||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
|
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
|
||||||
|
|||||||
+104
@@ -491,6 +491,50 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
|
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
|
||||||
return left;
|
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
|
// Comparison operators return bool
|
||||||
if op >= tkEq && op <= tkGe { return tyBool; }
|
if op >= tkEq && op <= tkGe { return tyBool; }
|
||||||
// Logical operators return bool
|
// Logical operators return bool
|
||||||
@@ -645,6 +689,33 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
|||||||
if !Sema_IsNumeric(idx) && idx != tyUnknown {
|
if !Sema_IsNumeric(idx) && idx != tyUnknown {
|
||||||
Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
|
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;
|
return tyUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1174,6 +1245,39 @@ func Sema_CollectGlobals(sema: *Sema) {
|
|||||||
|
|
||||||
decl = decl.childDecl2;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user