uCalc API Version: 2.1.3-preview.2 Released: 6/16/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:Â
Class:Â
Defines a custom operator to extend or modify the expression syntax at runtime.
Syntax
Parameters
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 - cis parsed as(a - b) - c. - RightToLeft:
a = b = cis parsed asa = (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:
- 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. - 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! 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!'"));
#include
#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! #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; }
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! 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
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 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"));
#include
#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 #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; }
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 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