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.
Creating Custom Functions
Product:
Class:
Learn how to extend uCalc's expression engine by creating your own functions, either as simple inline expressions or by binding to powerful native code callbacks.
Remarks
🛠️ Creating Custom Functions
One of uCalc's most powerful features is its runtime extensibility. You can teach the engine new functions on the fly, tailoring it to your application's domain without ever needing to recompile. This is fundamental for building Domain-Specific Languages (DSLs), creating reusable calculation libraries, or simplifying complex formulas for end-users.
There are two primary ways to create a custom function:
- Inline Definition: Define the entire function as a single string. This is the quickest and easiest method for simple mathematical or string-based logic.
- Callback Binding: Link a function signature to a function in your host application (C#, C++, etc.). This is the most powerful method, used for complex logic, I/O operations, or integrating with existing code.
1. Inline Definitions (The Easy Way)
For functions that can be expressed as a single line of logic, an inline definition is the best choice. You provide the entire signature and expression body to the DefineFunction method.
Syntax
The definition string follows a simple pattern: FunctionName(param1, param2) = expression.
// Define a function to convert inches to centimeters.uc.DefineFunction("InToCm(inches) = inches * 2.54");// Now you can use it immediately in any expression.Console.WriteLine(uc.Eval("InToCm(10)")); // Output: 25.4This dynamic capability is a major advantage over compiled languages. You can load these function definitions at runtime from a configuration file or even allow end-users to define their own helpers.
2. Callback Binding (The Powerful Way)
When your function's logic is too complex for a single expression or needs to interact with the host application (e.g., to read a file, query a database, or update a UI), you bind it to a native callback function.
In this model, you provide a signature to DefineFunction and the address of a callback. When uCalc evaluates your function, it calls your native code, passing a special Callback object (cb) that you use to get arguments and set the return value.
static void LogMessage(uCalc.Callback cb) { // Get the first argument as a string. string msg = cb.ArgStr(1); // In a real app, this would write to a file or log service. Console.WriteLine($"[LOG]: {msg}");}// Link the uCalc function "Log" to our native "LogMessage" callback.uc.DefineFunction("Log(message As String)", LogMessage);uc.Eval("Log('System initialized')");This creates a seamless bridge between the flexible uCalc scripting environment and your high-performance native code.
✨ Advanced Argument Passing
uCalc's callback system goes beyond simple value passing, enabling powerful metaprogramming techniques with special argument modifiers.
ByRef: Passes a variable by reference, allowing your callback to modify its value in the caller's scope (creating an in-out parameter).ByExpr: Passes the argument as an unevaluated Expression object. This is the key to lazy evaluation, allowing you to build custom control structures (like the built-in IIf function) that only execute the branches they need.ByHandle: Passes the argument's underlying metadata object, allowing your callback to inspect its name, data type, or original expression text before deciding how to process it.
Examples
Succinct: A simple inline function to convert inches to centimeters.
using uCalcSoftware;
var uc = new uCalc();
uc.DefineFunction("InToCm(inches) = inches * 2.54");
Console.WriteLine(uc.Eval("InToCm(10)"));
25.4 using uCalcSoftware; var uc = new uCalc(); uc.DefineFunction("InToCm(inches) = inches * 2.54"); Console.WriteLine(uc.Eval("InToCm(10)"));
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
uc.DefineFunction("InToCm(inches) = inches * 2.54");
cout << uc.Eval("InToCm(10)") << endl;
}
25.4 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; uc.DefineFunction("InToCm(inches) = inches * 2.54"); cout << uc.Eval("InToCm(10)") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
uc.DefineFunction("InToCm(inches) = inches * 2.54")
Console.WriteLine(uc.Eval("InToCm(10)"))
End Sub
End Module
25.4 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() uc.DefineFunction("InToCm(inches) = inches * 2.54") Console.WriteLine(uc.Eval("InToCm(10)")) End Sub End Module
Practical: A callback-based logging function that prints a message to the console.
using uCalcSoftware;
var uc = new uCalc();
static void LogMessage(uCalc.Callback cb) {
string msg = cb.ArgStr(1);
// In a real app, this would write to a file or log service.
Console.WriteLine("[LOG]: " + msg);
}
uc.DefineFunction("Log(message As String)", LogMessage);
uc.Eval("Log('System initialized')");
uc.Eval("Log('User logged in')");
[LOG]: System initialized
[LOG]: User logged in using uCalcSoftware; var uc = new uCalc(); static void LogMessage(uCalc.Callback cb) { string msg = cb.ArgStr(1); // In a real app, this would write to a file or log service. Console.WriteLine("[LOG]: " + msg); } uc.DefineFunction("Log(message As String)", LogMessage); uc.Eval("Log('System initialized')"); uc.Eval("Log('User logged in')");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call LogMessage(uCalcBase::Callback cb) {
string msg = cb.ArgStr(1);
// In a real app, this would write to a file or log service.
cout << "[LOG]: " + msg << endl;
}
int main() {
uCalc uc;
uc.DefineFunction("Log(message As String)", LogMessage);
uc.Eval("Log('System initialized')");
uc.Eval("Log('User logged in')");
}
[LOG]: System initialized
[LOG]: User logged in #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call LogMessage(uCalcBase::Callback cb) { string msg = cb.ArgStr(1); // In a real app, this would write to a file or log service. cout << "[LOG]: " + msg << endl; } int main() { uCalc uc; uc.DefineFunction("Log(message As String)", LogMessage); uc.Eval("Log('System initialized')"); uc.Eval("Log('User logged in')"); }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub LogMessage(ByVal cb As uCalc.Callback)
Dim msg As String = cb.ArgStr(1)
'// In a real app, this would write to a file or log service.
Console.WriteLine("[LOG]: " + msg)
End Sub
Public Sub Main()
Dim uc As New uCalc()
uc.DefineFunction("Log(message As String)", AddressOf LogMessage)
uc.Eval("Log('System initialized')")
uc.Eval("Log('User logged in')")
End Sub
End Module
[LOG]: System initialized
[LOG]: User logged in Imports System Imports uCalcSoftware Public Module Program Public Sub LogMessage(ByVal cb As uCalc.Callback) Dim msg As String = cb.ArgStr(1) '// In a real app, this would write to a file or log service. Console.WriteLine("[LOG]: " + msg) End Sub Public Sub Main() Dim uc As New uCalc() uc.DefineFunction("Log(message As String)", AddressOf LogMessage) uc.Eval("Log('System initialized')") uc.Eval("Log('User logged in')") End Sub End Module
Internal Test: A custom `IIf` implementation using `ByExpr` to test lazy evaluation. The branches containing division by zero are never executed, preventing errors.
using uCalcSoftware;
var uc = new uCalc();
static void CustomIIf(uCalc.Callback cb) {
bool condition = cb.ArgBool(1);
var truePart = cb.ArgExpr(2);
var falsePart = cb.ArgExpr(3);
if (condition) {
cb.Return(truePart.Evaluate());
} else {
cb.Return(falsePart.Evaluate());
}
}
uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", CustomIIf);
// The 'else' branch contains a division by zero, but it should NOT be evaluated
// because the condition (1 < 2) is true.
var result = uc.Eval("MyIIf(1 < 2, 100, 1/0)");
Console.Write("Result 1: ");
Console.WriteLine(result);
// Now test the false branch. The 'then' branch with the error is skipped.
result = uc.Eval("MyIIf(1 > 2, 1/0, 200)");
Console.Write("Result 2: ");
Console.WriteLine(result);
Result 1: 100
Result 2: 200 using uCalcSoftware; var uc = new uCalc(); static void CustomIIf(uCalc.Callback cb) { bool condition = cb.ArgBool(1); var truePart = cb.ArgExpr(2); var falsePart = cb.ArgExpr(3); if (condition) { cb.Return(truePart.Evaluate()); } else { cb.Return(falsePart.Evaluate()); } } uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", CustomIIf); // The 'else' branch contains a division by zero, but it should NOT be evaluated // because the condition (1 < 2) is true. var result = uc.Eval("MyIIf(1 < 2, 100, 1/0)"); Console.Write("Result 1: "); Console.WriteLine(result); // Now test the false branch. The 'then' branch with the error is skipped. result = uc.Eval("MyIIf(1 > 2, 1/0, 200)"); Console.Write("Result 2: "); Console.WriteLine(result);
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call CustomIIf(uCalcBase::Callback cb) {
bool condition = cb.ArgBool(1);
auto truePart = cb.ArgExpr(2);
auto falsePart = cb.ArgExpr(3);
if (condition) {
cb.Return(truePart.Evaluate());
} else {
cb.Return(falsePart.Evaluate());
}
}
int main() {
uCalc uc;
uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", CustomIIf);
// The 'else' branch contains a division by zero, but it should NOT be evaluated
// because the condition (1 < 2) is true.
auto result = uc.Eval("MyIIf(1 < 2, 100, 1/0)");
cout << "Result 1: ";
cout << result << endl;
// Now test the false branch. The 'then' branch with the error is skipped.
result = uc.Eval("MyIIf(1 > 2, 1/0, 200)");
cout << "Result 2: ";
cout << result << endl;
}
Result 1: 100
Result 2: 200 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call CustomIIf(uCalcBase::Callback cb) { bool condition = cb.ArgBool(1); auto truePart = cb.ArgExpr(2); auto falsePart = cb.ArgExpr(3); if (condition) { cb.Return(truePart.Evaluate()); } else { cb.Return(falsePart.Evaluate()); } } int main() { uCalc uc; uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", CustomIIf); // The 'else' branch contains a division by zero, but it should NOT be evaluated // because the condition (1 < 2) is true. auto result = uc.Eval("MyIIf(1 < 2, 100, 1/0)"); cout << "Result 1: "; cout << result << endl; // Now test the false branch. The 'then' branch with the error is skipped. result = uc.Eval("MyIIf(1 > 2, 1/0, 200)"); cout << "Result 2: "; cout << result << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub CustomIIf(ByVal cb As uCalc.Callback)
Dim condition As Boolean = cb.ArgBool(1)
Dim truePart = cb.ArgExpr(2)
Dim falsePart = cb.ArgExpr(3)
If condition Then
cb.Return(truePart.Evaluate())
Else
cb.Return(falsePart.Evaluate())
End If
End Sub
Public Sub Main()
Dim uc As New uCalc()
uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", AddressOf CustomIIf)
'// The 'else' branch contains a division by zero, but it should NOT be evaluated
'// because the condition (1 < 2) is true.
Dim result = uc.Eval("MyIIf(1 < 2, 100, 1/0)")
Console.Write("Result 1: ")
Console.WriteLine(result)
'// Now test the false branch. The 'then' branch with the error is skipped.
result = uc.Eval("MyIIf(1 > 2, 1/0, 200)")
Console.Write("Result 2: ")
Console.WriteLine(result)
End Sub
End Module
Result 1: 100
Result 2: 200 Imports System Imports uCalcSoftware Public Module Program Public Sub CustomIIf(ByVal cb As uCalc.Callback) Dim condition As Boolean = cb.ArgBool(1) Dim truePart = cb.ArgExpr(2) Dim falsePart = cb.ArgExpr(3) If condition Then cb.Return(truePart.Evaluate()) Else cb.Return(falsePart.Evaluate()) End If End Sub Public Sub Main() Dim uc As New uCalc() uc.DefineFunction("MyIIf(condition As Bool, ByExpr thenExpr, ByExpr elseExpr)", AddressOf CustomIIf) '// The 'else' branch contains a division by zero, but it should NOT be evaluated '// because the condition (1 < 2) is true. Dim result = uc.Eval("MyIIf(1 < 2, 100, 1/0)") Console.Write("Result 1: ") Console.WriteLine(result) '// Now test the false branch. The 'then' branch with the error is skipped. result = uc.Eval("MyIIf(1 > 2, 1/0, 200)") Console.Write("Result 2: ") Console.WriteLine(result) End Sub End Module