uCalc API Version: 2.1.3-preview.2 Released: 6/17/2026

Warning

uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.

DefineOperator

Method

Product: 

Fast Math Parser

Class: 

uCalcBase

Defines a custom operator to extend or modify the expression syntax at runtime.

Syntax

DefineOperator(string, int, Associativity, OpCallback, DataType, bool, bool)

Parameters

definition
string
The operator's signature and body, e.g., `{x} op {y} = x + y`.
precedence
int
The operator's precedence level, which determines its binding strength relative to other operators.
associativity
Associativity
(Default = Associativity::LeftToRight)
The operator's associativity, either left-to-right or right-to-left.
callbackAddress
OpCallback
(Default = 0)
Optional address of a native callback function to implement the operator's logic.
returnType
DataType
(Default = Empty)
Optional return data type for the operator. If omitted, the type is often inferred from the expression body.
bootstrap
bool
(Default = false)
If true, allows redefining an operator using its previous definition, useful for extending functionality.
overwrite
bool
(Default = false)
If true, this definition replaces the previous definition of the same operator, affecting already-parsed expressions.

Return

Item

An Item object representing the newly defined operator, which can be used to modify or release it later.

Remarks

The DefineOperator method allows you to create new operators, giving you the power to define custom syntax for your expressions. This is a cornerstone feature for building Domain-Specific Languages (DSLs) within uCalc.

Operator definitions share many concepts with function definitions (see DefineFunction), but this topic focuses on their unique characteristics.

Operator Anatomy (Prefix, Infix, Postfix)

You can define three kinds of operators:

  • Infix (Binary): Sits between two operands (e.g., x + y).
  • Prefix (Unary): Comes before a single operand (e.g., -x).
  • Postfix (Unary): Comes after a single operand (e.g., x!).

Operands in the definition string must be enclosed in curly braces {}.

// Infix operator with an alphanumeric nameuc.DefineOperator("{x} Plus {y} = x + y");// Prefix operator with a symbolic nameuc.DefineOperator("neg {x} = -x");// Postfix operator with a symbolic nameuc.DefineOperator("{x}% = x / 100");

Data type placement

To specify a data type for a parameter, add As [type] next to the operand within curly braces. You can also have a return type (e.g., {x As Int} + {y As Int} As Int = x + y).


Key Concepts

Operator Names

Unlike functions, an operator name can be either fully alphanumeric (e.g., Plus, times) or consist entirely of non-alphanumeric symbols (e.g., +, ++, &&, @#%). A mix of character types is not allowed.

Reducible Tokens

When the parser encounters a sequence of symbols, it greedily matches the longest defined operator. This is known as using reducible tokens. For example, if you have operators for - (prefix and infix) and -- (postfix), an expression like a---b is parsed as a-- - b because a-- is the first and longest valid match.

The set of characters that can form a symbolic operator is configurable. You can inspect the current definition via uc.ItemOf("_Token_Reducible").Regex().

Precedence

Every operator requires a precedence level. This numeric value determines the order of operations. Higher numbers bind more tightly. For example, * has a higher precedence than +, so 2 + 3 * 4 is 2 + (3 * 4).

Rather than using arbitrary numbers, it's best practice to base new operator precedence on existing ones:

// Give 'times' the same precedence as the multiplication operator '*'var prec = uc.ItemOf("*", ItemIs.Infix).Precedence();uc.DefineOperator("{a} times {b} = a * b", prec);// Place a new operator between '+' and '*'var prec_between = (uc.ItemOf("+").Precedence() + uc.ItemOf("*").Precedence()) / 2;uc.DefineOperator("{a} custom {b} = ...", prec_between);

Associativity

When operators of the same precedence appear together, associativity determines their grouping. The default is LeftToRight.

  • LeftToRight: a - b - c is parsed as (a - b) - c.
  • RightToLeft: a = b = c is parsed as a = (b = c).

This is controlled by the associativity parameter, using the Associativity enum.


Definition Shortcuts

You can embed the precedence and associativity directly into the definition string, which will override the method parameters:

// Define 'MyOp' with precedence 123 and default LeftToRight associativityuc.DefineOperator("123 {a} MyOp {b} = a + b");// Define 'MyOp' with precedence 123 and RightToLeft (RL) associativityuc.DefineOperator("123:RL {a} MyOp {b} = a + b");

💡 Comparative Analysis: uCalc vs. Native Operator Overloading

Many compiled languages like C++ and C# support operator overloading, which allows developers to redefine how operators work for their custom classes. However, this has key limitations:

  1. Compile-Time Only: You can only overload a fixed set of existing operators (+, -, *, ==, etc.). You cannot invent new ones, and the definitions are baked into the compiled binary.
  2. Fixed Precedence: You cannot change the precedence or associativity of operators. + will always have lower precedence than *.

uCalc's DefineOperator is fundamentally different and more powerful:

  • Runtime Definition: Operators can be defined, redefined, or removed dynamically at runtime. This allows an application to adapt its syntax based on user input, configuration files, or different modes of operation.
  • New Syntax: You can invent entirely new operators (e.g., within, matches, $$) with custom precedence and associativity, enabling the creation of highly readable, domain-specific languages (DSLs) without an external compiler or pre-processor.

This makes uCalc an ideal engine for applications requiring configurable or user-extendable logic.

Examples

Operator definitions; infix, prefix, postfix, data types, precedence
				
					using uCalcSoftware;

var uc = new uCalc();
uc.DefineOperator("{x} MyOp {y} = x + y", 0); // Infix operator with alphanumeric name
uc.DefineOperator("@@ {number} = number * 2", 0); // Prefix operator with symbolic name
uc.DefineOperator("{val} % = val / 100", 0); // Postfix operator with symbolic name
uc.DefineOperator("{a} times {b} = a * b", uc.ItemOf("*").Precedence); // Specifying precedence
uc.DefineOperator("{TextA As String} concat {TextB As String} As String = TextA + TextB", 0); // Specifying types

Console.WriteLine(uc.Eval("5 MyOp 4"));
Console.WriteLine(uc.Eval("@@5"));
Console.WriteLine(uc.Eval("5 %"));
Console.WriteLine(uc.Eval("3 times 5"));
Console.WriteLine(uc.EvalStr("'Hello' concat ' world!'"));
				
			
9
10
0.05
15
Hello world!
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

int main() {
   uCalc uc;
   uc.DefineOperator("{x} MyOp {y} = x + y", 0); // Infix operator with alphanumeric name
   uc.DefineOperator("@@ {number} = number * 2", 0); // Prefix operator with symbolic name
   uc.DefineOperator("{val} % = val / 100", 0); // Postfix operator with symbolic name
   uc.DefineOperator("{a} times {b} = a * b", uc.ItemOf("*").Precedence()); // Specifying precedence
   uc.DefineOperator("{TextA As String} concat {TextB As String} As String = TextA + TextB", 0); // Specifying types

   cout << uc.Eval("5 MyOp 4") << endl;
   cout << uc.Eval("@@5") << endl;
   cout << uc.Eval("5 %") << endl;
   cout << uc.Eval("3 times 5") << endl;
   cout << uc.EvalStr("'Hello' concat ' world!'") << endl;
}
				
			
9
10
0.05
15
Hello world!
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      uc.DefineOperator("{x} MyOp {y} = x + y", 0) '// Infix operator with alphanumeric name
      uc.DefineOperator("@@ {number} = number * 2", 0) '// Prefix operator with symbolic name
      uc.DefineOperator("{val} % = val / 100", 0) '// Postfix operator with symbolic name
      uc.DefineOperator("{a} times {b} = a * b", uc.ItemOf("*").Precedence) '// Specifying precedence
      uc.DefineOperator("{TextA As String} concat {TextB As String} As String = TextA + TextB", 0) '// Specifying types
      
      Console.WriteLine(uc.Eval("5 MyOp 4"))
      Console.WriteLine(uc.Eval("@@5"))
      Console.WriteLine(uc.Eval("5 %"))
      Console.WriteLine(uc.Eval("3 times 5"))
      Console.WriteLine(uc.EvalStr("'Hello' concat ' world!'"))
   End Sub
End Module
				
			
9
10
0.05
15
Hello world!
Operator ByRef, AnyType, SameTypeAs, Precedence, RightToLeft, callback
				
					using uCalcSoftware;

var uc = new uCalc();

static void AssignValueA(uCalc.Callback cb) {
   cb.uCalc.DataTypeOf(BuiltInType.Integer_64).SetScalar(cb.ArgPtr(1), cb.ArgAddr(2));
   // C++ can do it with pointers instead like the commented line below:
   // *(int64_t *)cb.ArgInt64(1) = cb.ArgInt64(2);
}
static void AssignValueB(uCalc.Callback cb) {
   if (cb.ArgItem(1).DataType.BuiltInTypeEnum == BuiltInType.String) {
      cb.ArgItem(1).ValueStr(cb.ArgItem(2).ValueStr());
   } else {
      cb.ArgItem(1).DataType.SetScalar(cb.ArgItem(1).ValueAddr(), cb.ArgItem(2).ValueAddr());
   }
}


// ByRef approach (only for primitive types only, like double, int, etc., not composite types like strings)
Console.WriteLine("-- ByRef approach --");
uc.DefineOperator("{ByRef variable As AnyType} SetValA {value As SameTypeAs:0} As SameTypeAs:0", uc.ItemOf("=").Precedence, Associativity.RightToLeft, AssignValueA);
uc.DefineVariable("MyDbl As Double");
uc.DefineVariable("MyInt As Int");
uc.DefineVariable("MyStr As String");

uc.Eval("MyDbl SetValA 3.14");
uc.Eval("MyInt SetValA Int(3.14 * 10)");

Console.WriteLine("MyDbl: " + uc.EvalStr("MyDbl"));
Console.WriteLine("MyInt: " + uc.EvalStr("MyInt"));

// ByHandle approach
Console.WriteLine("-- ByHandle approach --");
uc.DefineOperator("{ByHandle variable As AnyType} SetValB {ByHandle val As SameTypeAs:0}", uc.ItemOf("=").Precedence, Associativity.RightToLeft, AssignValueB);
uc.Eval("MyDbl SetValB 123.456");
uc.Eval("MyInt SetValB Int(555.123)");
uc.Eval("MyStr SetValB 'Hello World'");

Console.WriteLine("MyDbl: " + uc.EvalStr("MyDbl"));
Console.WriteLine("MyInt: " + uc.EvalStr("MyInt"));
Console.WriteLine("MyStr: " + uc.EvalStr("MyStr"));

				
			
-- ByRef approach --
MyDbl: 3.14
MyInt: 31
-- ByHandle approach --
MyDbl: 123.456
MyInt: 555
MyStr: Hello World
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

void ucalc_call AssignValueA(uCalcBase::Callback cb) {
   cb.uCalc().DataTypeOf(BuiltInType::Integer_64).SetScalar(cb.ArgPtr(1), cb.ArgAddr(2));
   // C++ can do it with pointers instead like the commented line below:
   // *(int64_t *)cb.ArgInt64(1) = cb.ArgInt64(2);
}
void ucalc_call AssignValueB(uCalcBase::Callback cb) {
   if (cb.ArgItem(1).DataType().BuiltInTypeEnum() == BuiltInType::String) {
      cb.ArgItem(1).ValueStr(cb.ArgItem(2).ValueStr());
   } else {
      cb.ArgItem(1).DataType().SetScalar(cb.ArgItem(1).ValueAddr(), cb.ArgItem(2).ValueAddr());
   }
}
int main() {
   uCalc uc;

   // ByRef approach (only for primitive types only, like double, int, etc., not composite types like strings)
   cout << "-- ByRef approach --" << endl;
   uc.DefineOperator("{ByRef variable As AnyType} SetValA {value As SameTypeAs:0} As SameTypeAs:0", uc.ItemOf("=").Precedence(), Associativity::RightToLeft, AssignValueA);
   uc.DefineVariable("MyDbl As Double");
   uc.DefineVariable("MyInt As Int");
   uc.DefineVariable("MyStr As String");

   uc.Eval("MyDbl SetValA 3.14");
   uc.Eval("MyInt SetValA Int(3.14 * 10)");

   cout << "MyDbl: " + uc.EvalStr("MyDbl") << endl;
   cout << "MyInt: " + uc.EvalStr("MyInt") << endl;

   // ByHandle approach
   cout << "-- ByHandle approach --" << endl;
   uc.DefineOperator("{ByHandle variable As AnyType} SetValB {ByHandle val As SameTypeAs:0}", uc.ItemOf("=").Precedence(), Associativity::RightToLeft, AssignValueB);
   uc.Eval("MyDbl SetValB 123.456");
   uc.Eval("MyInt SetValB Int(555.123)");
   uc.Eval("MyStr SetValB 'Hello World'");

   cout << "MyDbl: " + uc.EvalStr("MyDbl") << endl;
   cout << "MyInt: " + uc.EvalStr("MyInt") << endl;
   cout << "MyStr: " + uc.EvalStr("MyStr") << endl;

}
				
			
-- ByRef approach --
MyDbl: 3.14
MyInt: 31
-- ByHandle approach --
MyDbl: 123.456
MyInt: 555
MyStr: Hello World
				
					Imports System
Imports uCalcSoftware
Public Module Program
   
   Public Sub AssignValueA(ByVal cb As uCalc.Callback)
      cb.uCalc.DataTypeOf(BuiltInType.Integer_64).SetScalar(cb.ArgPtr(1), cb.ArgAddr(2))
      '// C++ can do it with pointers instead like the commented line below:
      '// *(int64_t *)cb.ArgInt64(1) = cb.ArgInt64(2);
   End Sub
   Public Sub AssignValueB(ByVal cb As uCalc.Callback)
      If cb.ArgItem(1).DataType.BuiltInTypeEnum = BuiltInType.String Then
         cb.ArgItem(1).ValueStr(cb.ArgItem(2).ValueStr())
      Else
         cb.ArgItem(1).DataType.SetScalar(cb.ArgItem(1).ValueAddr(), cb.ArgItem(2).ValueAddr())
      End If
   End Sub
   Public Sub Main()
      Dim uc As New uCalc()
      
      '// ByRef approach (only for primitive types only, like double, int, etc., not composite types like strings)
      Console.WriteLine("-- ByRef approach --")
      uc.DefineOperator("{ByRef variable As AnyType} SetValA {value As SameTypeAs:0} As SameTypeAs:0", uc.ItemOf("=").Precedence, Associativity.RightToLeft, AddressOf AssignValueA)
      uc.DefineVariable("MyDbl As Double")
      uc.DefineVariable("MyInt As Int")
      uc.DefineVariable("MyStr As String")
      
      uc.Eval("MyDbl SetValA 3.14")
      uc.Eval("MyInt SetValA Int(3.14 * 10)")
      
      Console.WriteLine("MyDbl: " + uc.EvalStr("MyDbl"))
      Console.WriteLine("MyInt: " + uc.EvalStr("MyInt"))
      
      '// ByHandle approach
      Console.WriteLine("-- ByHandle approach --")
      uc.DefineOperator("{ByHandle variable As AnyType} SetValB {ByHandle val As SameTypeAs:0}", uc.ItemOf("=").Precedence, Associativity.RightToLeft, AddressOf AssignValueB)
      uc.Eval("MyDbl SetValB 123.456")
      uc.Eval("MyInt SetValB Int(555.123)")
      uc.Eval("MyStr SetValB 'Hello World'")
      
      Console.WriteLine("MyDbl: " + uc.EvalStr("MyDbl"))
      Console.WriteLine("MyInt: " + uc.EvalStr("MyInt"))
      Console.WriteLine("MyStr: " + uc.EvalStr("MyStr"))
      
   End Sub
End Module
				
			
-- ByRef approach --
MyDbl: 3.14
MyInt: 31
-- ByHandle approach --
MyDbl: 123.456
MyInt: 555
MyStr: Hello World