Operator Overloading in BHL

May 21, 2026 ยท View on GitHub

Operator overloading lets you define the behaviour of built-in operators for your own classes. Operators are defined as static functions whose name is the operator symbol.

Supported Operators

  • Arithmetic: +, -, *, /
  • Comparison: ==, !=, >, >=, <, <=
  • Logical: &&, ||, !
  • Bitwise: &, |

Basic Rules

Operator overloads must:

  • Be declared static
  • Take exactly two arguments (one for unary !)
  • Return a non-void type

Defining operators in BHL

class Vector2 {
  float x
  float y

  static func Vector2 +(Vector2 a, Vector2 b) {
    Vector2 r
    r.x = a.x + b.x
    r.y = a.y + b.y
    return r
  }

  static func bool ==(Vector2 a, Vector2 b) {
    return a.x == b.x && a.y == b.y
  }
}

func test() {
  Vector2 v1 = {x: 1, y: 2}
  Vector2 v2 = {x: 3, y: 4}
  Vector2 v3 = v1 + v2   // calls the overloaded +
  bool eq    = v1 == v2   // calls the overloaded ==
}

Defining operators via C# bindings

When the class is defined in C#, add the operator as a static FuncSymbolNative and define it on the class before calling Setup():

var cl = new ClassSymbolNative(new Origin(), "Vector2", null,
    delegate(VM.ExecState exec, ref Val v, IType type)
    {
        v.SetObj(new Vector2(), type);
    }
);

// + operator
cl.Define(new FuncSymbolNative(new Origin(), "+", FuncAttrib.Static, types.T("Vector2"), 0,
    delegate(VM.ExecState exec, FuncArgsInfo args_info)
    {
        var b = (Vector2)exec.stack.PopFast().obj;
        var a = (Vector2)exec.stack.PopFast().obj;
        exec.stack.Push(Val.NewObj(exec.vm, new Vector2(a.x + b.x, a.y + b.y), cl));
        return null;
    },
    new FuncArgSymbol("a", types.T("Vector2")),
    new FuncArgSymbol("b", types.T("Vector2"))
));

// == operator
cl.Define(new FuncSymbolNative(new Origin(), "==", FuncAttrib.Static, Types.Bool, 0,
    delegate(VM.ExecState exec, FuncArgsInfo args_info)
    {
        var b = (Vector2)exec.stack.PopFast().obj;
        var a = (Vector2)exec.stack.PopFast().obj;
        exec.stack.Push(a.x == b.x && a.y == b.y);
        return null;
    },
    new FuncArgSymbol("a", types.T("Vector2")),
    new FuncArgSymbol("b", types.T("Vector2"))
));

types.ns.Define(cl);
cl.Setup();

Operator precedence

Overloaded operators follow normal precedence rules:

Vector2 v3 = v1 + v2 * 2  // equivalent to v1 + (v2 * 2)