Defines a custom operator to extend or modify the expression syntax at runtime.
Product:
Class:
Warning
uCalc API Preview Release Notice:The uCalc engine has successfully transitioned to modern cross-platform environments.The next phase envolves some structural changes, performance optimizations, and API refinements.The API is subject to breaking changes prior to the stable release. Please evaluate the preview version thoroughly before production use.
An Item object representing the newly defined operator, which can be used to modify or release it later.
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.
You can define three kinds of operators:
x + y).-x).x!).Operands in the definition string must be enclosed in curly braces {}.
using uCalcSoftware;
var uc = new uCalc();
// Infix operator with an alphanumeric name
uc.DefineOperator("{x} Plus {y} = x + y");
// Prefix operator with a symbolic name
uc.DefineOperator("neg {x} = -x");
// Postfix operator with a symbolic name
uc.DefineOperator("{x}% = x / 100");
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).
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.
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().
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:
using uCalcSoftware;
var uc = new uCalc();
// 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);
When operators of the same precedence appear together, associativity determines their grouping. The default is LeftToRight.
a - b - c is parsed as (a - b) - c.a = b = c is parsed as a = (b = c).This is controlled by the associativity parameter, using the Associativity enum.
You can embed the precedence and associativity directly into the definition string, which will override the method parameters:
using uCalcSoftware;
var uc = new uCalc();
// Define 'MyOp' with precedence 123 and default LeftToRight associativity
uc.DefineOperator("123 {a} MyOp {b} = a + b");
// Define 'MyOp' with precedence 123 and RightToLeft (RL) associativity
uc.DefineOperator("123:RL {a} MyOp {b} = a + b");
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:
+, -, *, ==, etc.). You cannot invent new ones, and the definitions are baked into the compiled binary.+ will always have lower precedence than *.uCalc's DefineOperator is fundamentally different and more powerful:
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.
ID: 64
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
ID: 66
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
This page last modified on: