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.
Advanced Argument Passing
Product:Â
Class:Â
Explores advanced callback parameter modifiers like ByRef, ByExpr, and ByHandle for metaprogramming and lazy evaluation.
Remarks
🚀 Advanced Argument Passing: ByRef, ByExpr, and ByHandle
While most functions operate on simple values passed ByVal, uCalc's real power comes from its advanced argument passing modifiers. These special keywords, used in function signatures defined with DefineFunction, allow you to build functions that can modify variables in place, delay execution for performance, and inspect the very structure of the code they are given. They transform a simple Callback from a calculator into an intelligent, context-aware meta-program.
This tutorial explores the three advanced modifiers that make this possible.
1. ByRef: Modifying Caller Variables
The ByRef modifier allows you to pass a variable by reference. This creates an in-out parameter, meaning your callback function receives a direct link to the original variable and can modify its value in the caller's scope.
💡 Why uCalc? (Comparative Analysis)
This is conceptually similar to C#'s ref keyword or C++'s & references. The key difference is uCalc's dynamic nature. You are defining this reference behavior at runtime within a string-based script engine, enabling you to create functions with side effects that are not hardcoded at compile time. It provides the power of native reference passing within a flexible, sandboxed environment.
2. ByExpr: Lazy Evaluation for Control Structures
The ByExpr modifier is the key to lazy evaluation. Instead of evaluating an argument and passing its result, uCalc passes the argument as a complete, unevaluated Expression object. Your callback function then has full control over if, when, and how many times that expression is executed.
This is the mechanism used to build custom control structures. The built-in IIf function, for example, uses ByExpr for its then and else parts to ensure that only the chosen branch is ever evaluated, preventing errors and side effects in the other.
💡 Why uCalc? (Comparative Analysis)
Passing an expression is similar to passing a Func<T> delegate in C# or a lambda in C++. Both pass a piece of code to be executed later. However, uCalc's approach is superior for dynamic environments because:
- The expression is defined in a string at runtime.
- [The callback receives a rich Expression object that can be inspected (e.g., getting its original text) before execution, enabling metaprogramming.]
3. ByHandle: Introspection and Metaprogramming
The ByHandle modifier is the most powerful tool for introspection. It passes the argument's underlying Item object, giving your callback access to a wealth of metadata about the argument before its value is even considered.
Inside the callback, you can use the Item object to inspect:
- The argument's original name (
item.Name). - Its data type (
item.DataType). - Whether it was a literal, a variable, or another function call.
- Its original definition string (
item.Text)).
💡 Why uCalc? (Comparative Analysis)
This is uCalc's equivalent of a full-fledged reflection API. While C# reflection operates on compiled types, uCalc's ByHandle provides this power at runtime on symbols defined within its dynamic scripting environment. It allows you to write functions that analyze the code they are given, a feature typically found only in highly dynamic languages like LISP.
Summary Table
| Modifier | What is Passed | Primary Use Case |
|---|---|---|
ByRef | A direct reference to a variable's memory. | Creating functions with side effects (in-out parameters). |
ByExpr | An unevaluated Expression object. | Lazy evaluation; building custom control structures like loops and conditionals. |
ByHandle | The argument's underlying Item metadata object. | Introspection and metaprogramming; analyzing an argument's name, type, and source. |
Examples
A practical example of `ByRef` to create a classic `Swap` function that modifies its arguments in the caller's scope.
using uCalcSoftware;
var uc = new uCalc();
static void SwapValues(uCalc.Callback cb) {
// Get the item handles for the two variables passed by reference
var item1 = cb.ArgItem(1);
var item2 = cb.ArgItem(2);
// Use the item's DataType object to perform a highly efficient, pointer-based swap
item1.DataType.SwapScalarValues(item1.ValueAddr(), item2.ValueAddr());
}
// Define the Swap function with ByRef parameters
uc.DefineFunction("Swap(ByHandle a, ByHandle b)", SwapValues);
// Define the variables to be swapped
uc.DefineVariable("x = 100");
uc.DefineVariable("y = 200");
Console.WriteLine($"Before: x = {uc.Eval("x")}, y = {uc.Eval("y")}");
// Call the swap function
uc.Eval("Swap(x, y)");
Console.WriteLine($"After: x = {uc.Eval("x")}, y = {uc.Eval("y")}");
Before: x = 100, y = 200
After: x = 200, y = 100 using uCalcSoftware; var uc = new uCalc(); static void SwapValues(uCalc.Callback cb) { // Get the item handles for the two variables passed by reference var item1 = cb.ArgItem(1); var item2 = cb.ArgItem(2); // Use the item's DataType object to perform a highly efficient, pointer-based swap item1.DataType.SwapScalarValues(item1.ValueAddr(), item2.ValueAddr()); } // Define the Swap function with ByRef parameters uc.DefineFunction("Swap(ByHandle a, ByHandle b)", SwapValues); // Define the variables to be swapped uc.DefineVariable("x = 100"); uc.DefineVariable("y = 200"); Console.WriteLine($"Before: x = {uc.Eval("x")}, y = {uc.Eval("y")}"); // Call the swap function uc.Eval("Swap(x, y)"); Console.WriteLine($"After: x = {uc.Eval("x")}, y = {uc.Eval("y")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call SwapValues(uCalcBase::Callback cb) {
// Get the item handles for the two variables passed by reference
auto item1 = cb.ArgItem(1);
auto item2 = cb.ArgItem(2);
// Use the item's DataType object to perform a highly efficient, pointer-based swap
item1.DataType().SwapScalarValues(item1.ValueAddr(), item2.ValueAddr());
}
int main() {
uCalc uc;
// Define the Swap function with ByRef parameters
uc.DefineFunction("Swap(ByHandle a, ByHandle b)", SwapValues);
// Define the variables to be swapped
uc.DefineVariable("x = 100");
uc.DefineVariable("y = 200");
cout << "Before: x = " << uc.Eval("x") << ", y = " << uc.Eval("y") << endl;
// Call the swap function
uc.Eval("Swap(x, y)");
cout << "After: x = " << uc.Eval("x") << ", y = " << uc.Eval("y") << endl;
}
Before: x = 100, y = 200
After: x = 200, y = 100 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call SwapValues(uCalcBase::Callback cb) { // Get the item handles for the two variables passed by reference auto item1 = cb.ArgItem(1); auto item2 = cb.ArgItem(2); // Use the item's DataType object to perform a highly efficient, pointer-based swap item1.DataType().SwapScalarValues(item1.ValueAddr(), item2.ValueAddr()); } int main() { uCalc uc; // Define the Swap function with ByRef parameters uc.DefineFunction("Swap(ByHandle a, ByHandle b)", SwapValues); // Define the variables to be swapped uc.DefineVariable("x = 100"); uc.DefineVariable("y = 200"); cout << "Before: x = " << uc.Eval("x") << ", y = " << uc.Eval("y") << endl; // Call the swap function uc.Eval("Swap(x, y)"); cout << "After: x = " << uc.Eval("x") << ", y = " << uc.Eval("y") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub SwapValues(ByVal cb As uCalc.Callback)
'// Get the item handles for the two variables passed by reference
Dim item1 = cb.ArgItem(1)
Dim item2 = cb.ArgItem(2)
'// Use the item's DataType object to perform a highly efficient, pointer-based swap
item1.DataType.SwapScalarValues(item1.ValueAddr(), item2.ValueAddr())
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// Define the Swap function with ByRef parameters
uc.DefineFunction("Swap(ByHandle a, ByHandle b)", AddressOf SwapValues)
'// Define the variables to be swapped
uc.DefineVariable("x = 100")
uc.DefineVariable("y = 200")
Console.WriteLine($"Before: x = {uc.Eval("x")}, y = {uc.Eval("y")}")
'// Call the swap function
uc.Eval("Swap(x, y)")
Console.WriteLine($"After: x = {uc.Eval("x")}, y = {uc.Eval("y")}")
End Sub
End Module
Before: x = 100, y = 200
After: x = 200, y = 100 Imports System Imports uCalcSoftware Public Module Program Public Sub SwapValues(ByVal cb As uCalc.Callback) '// Get the item handles for the two variables passed by reference Dim item1 = cb.ArgItem(1) Dim item2 = cb.ArgItem(2) '// Use the item's DataType object to perform a highly efficient, pointer-based swap item1.DataType.SwapScalarValues(item1.ValueAddr(), item2.ValueAddr()) End Sub Public Sub Main() Dim uc As New uCalc() '// Define the Swap function with ByRef parameters uc.DefineFunction("Swap(ByHandle a, ByHandle b)", AddressOf SwapValues) '// Define the variables to be swapped uc.DefineVariable("x = 100") uc.DefineVariable("y = 200") Console.WriteLine($"Before: x = {uc.Eval("x")}, y = {uc.Eval("y")}") '// Call the swap function uc.Eval("Swap(x, y)") Console.WriteLine($"After: x = {uc.Eval("x")}, y = {uc.Eval("y")}") End Sub End Module
Demonstrates `ByExpr` to create a custom `Assert` function where the error message is only evaluated if the assertion fails, improving performance.
using uCalcSoftware;
var uc = new uCalc();
static void Assert(uCalc.Callback cb) {
var condition = cb.ArgBool(1);
// If the condition is false, then we evaluate the message expression
if (condition == false) {
var errorMessage = cb.ArgExpr(2);
Console.WriteLine($"Assertion failed: {errorMessage.EvaluateStr()}");
}
}
uc.DefineVariable("x = 50");
// The message is passed as an unevaluated expression
uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", Assert);
// This will do nothing because the condition is true
uc.Eval("Assert(10 < 20, 'This will not be evaluated')");
// This will trigger the assertion and evaluate the message expression
uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')");
Assertion failed: x (50) is not greater than 100 using uCalcSoftware; var uc = new uCalc(); static void Assert(uCalc.Callback cb) { var condition = cb.ArgBool(1); // If the condition is false, then we evaluate the message expression if (condition == false) { var errorMessage = cb.ArgExpr(2); Console.WriteLine($"Assertion failed: {errorMessage.EvaluateStr()}"); } } uc.DefineVariable("x = 50"); // The message is passed as an unevaluated expression uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", Assert); // This will do nothing because the condition is true uc.Eval("Assert(10 < 20, 'This will not be evaluated')"); // This will trigger the assertion and evaluate the message expression uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call Assert(uCalcBase::Callback cb) {
auto condition = cb.ArgBool(1);
// If the condition is false, then we evaluate the message expression
if (condition == false) {
auto errorMessage = cb.ArgExpr(2);
cout << "Assertion failed: " << errorMessage.EvaluateStr() << endl;
}
}
int main() {
uCalc uc;
uc.DefineVariable("x = 50");
// The message is passed as an unevaluated expression
uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", Assert);
// This will do nothing because the condition is true
uc.Eval("Assert(10 < 20, 'This will not be evaluated')");
// This will trigger the assertion and evaluate the message expression
uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')");
}
Assertion failed: x (50) is not greater than 100 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call Assert(uCalcBase::Callback cb) { auto condition = cb.ArgBool(1); // If the condition is false, then we evaluate the message expression if (condition == false) { auto errorMessage = cb.ArgExpr(2); cout << "Assertion failed: " << errorMessage.EvaluateStr() << endl; } } int main() { uCalc uc; uc.DefineVariable("x = 50"); // The message is passed as an unevaluated expression uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", Assert); // This will do nothing because the condition is true uc.Eval("Assert(10 < 20, 'This will not be evaluated')"); // This will trigger the assertion and evaluate the message expression uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')"); }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Assert(ByVal cb As uCalc.Callback)
Dim condition = cb.ArgBool(1)
'// If the condition is false, then we evaluate the message expression
If condition = false Then
Dim errorMessage = cb.ArgExpr(2)
Console.WriteLine($"Assertion failed: {errorMessage.EvaluateStr()}")
End If
End Sub
Public Sub Main()
Dim uc As New uCalc()
uc.DefineVariable("x = 50")
'// The message is passed as an unevaluated expression
uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", AddressOf Assert)
'// This will do nothing because the condition is true
uc.Eval("Assert(10 < 20, 'This will not be evaluated')")
'// This will trigger the assertion and evaluate the message expression
uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')")
End Sub
End Module
Assertion failed: x (50) is not greater than 100 Imports System Imports uCalcSoftware Public Module Program Public Sub Assert(ByVal cb As uCalc.Callback) Dim condition = cb.ArgBool(1) '// If the condition is false, then we evaluate the message expression If condition = false Then Dim errorMessage = cb.ArgExpr(2) Console.WriteLine($"Assertion failed: {errorMessage.EvaluateStr()}") End If End Sub Public Sub Main() Dim uc As New uCalc() uc.DefineVariable("x = 50") '// The message is passed as an unevaluated expression uc.DefineFunction("Assert(condition As Bool, ByExpr message As String)", AddressOf Assert) '// This will do nothing because the condition is true uc.Eval("Assert(10 < 20, 'This will not be evaluated')") '// This will trigger the assertion and evaluate the message expression uc.Eval("Assert(x > 100, 'x (' + Str(x) + ') is not greater than 100')") End Sub End Module
A practical example of `ByHandle` to create a `TypeOf` function that introspects an argument and returns its data type name as a string.
using uCalcSoftware;
var uc = new uCalc();
static void GetTypeOf(uCalc.Callback cb) {
// Get the Item object for the argument
var item = cb.ArgItem(1);
// Get the item's DataType, then its name, and return it as a string
cb.ReturnStr(item.DataType.Name);
}
// The ByHandle modifier passes the argument's metadata (Item) instead of its value
uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", GetTypeOf);
uc.DefineVariable("myInt As Int = 10");
uc.DefineVariable("myStr As String = 'hello'");
uc.DefineVariable("myDbl = 3.14"); // Type is inferred as double
Console.WriteLine($"Type of myInt: {uc.EvalStr("TypeOf(myInt)")}");
Console.WriteLine($"Type of myStr: {uc.EvalStr("TypeOf(myStr)")}");
Console.WriteLine($"Type of myDbl: {uc.EvalStr("TypeOf(myDbl)")}");
Type of myInt: int
Type of myStr: string
Type of myDbl: double using uCalcSoftware; var uc = new uCalc(); static void GetTypeOf(uCalc.Callback cb) { // Get the Item object for the argument var item = cb.ArgItem(1); // Get the item's DataType, then its name, and return it as a string cb.ReturnStr(item.DataType.Name); } // The ByHandle modifier passes the argument's metadata (Item) instead of its value uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", GetTypeOf); uc.DefineVariable("myInt As Int = 10"); uc.DefineVariable("myStr As String = 'hello'"); uc.DefineVariable("myDbl = 3.14"); // Type is inferred as double Console.WriteLine($"Type of myInt: {uc.EvalStr("TypeOf(myInt)")}"); Console.WriteLine($"Type of myStr: {uc.EvalStr("TypeOf(myStr)")}"); Console.WriteLine($"Type of myDbl: {uc.EvalStr("TypeOf(myDbl)")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call GetTypeOf(uCalcBase::Callback cb) {
// Get the Item object for the argument
auto item = cb.ArgItem(1);
// Get the item's DataType, then its name, and return it as a string
cb.ReturnStr(item.DataType().Name());
}
int main() {
uCalc uc;
// The ByHandle modifier passes the argument's metadata (Item) instead of its value
uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", GetTypeOf);
uc.DefineVariable("myInt As Int = 10");
uc.DefineVariable("myStr As String = 'hello'");
uc.DefineVariable("myDbl = 3.14"); // Type is inferred as double
cout << "Type of myInt: " << uc.EvalStr("TypeOf(myInt)") << endl;
cout << "Type of myStr: " << uc.EvalStr("TypeOf(myStr)") << endl;
cout << "Type of myDbl: " << uc.EvalStr("TypeOf(myDbl)") << endl;
}
Type of myInt: int
Type of myStr: string
Type of myDbl: double #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call GetTypeOf(uCalcBase::Callback cb) { // Get the Item object for the argument auto item = cb.ArgItem(1); // Get the item's DataType, then its name, and return it as a string cb.ReturnStr(item.DataType().Name()); } int main() { uCalc uc; // The ByHandle modifier passes the argument's metadata (Item) instead of its value uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", GetTypeOf); uc.DefineVariable("myInt As Int = 10"); uc.DefineVariable("myStr As String = 'hello'"); uc.DefineVariable("myDbl = 3.14"); // Type is inferred as double cout << "Type of myInt: " << uc.EvalStr("TypeOf(myInt)") << endl; cout << "Type of myStr: " << uc.EvalStr("TypeOf(myStr)") << endl; cout << "Type of myDbl: " << uc.EvalStr("TypeOf(myDbl)") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub GetTypeOf(ByVal cb As uCalc.Callback)
'// Get the Item object for the argument
Dim item = cb.ArgItem(1)
'// Get the item's DataType, then its name, and return it as a string
cb.ReturnStr(item.DataType.Name)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// The ByHandle modifier passes the argument's metadata (Item) instead of its value
uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", AddressOf GetTypeOf)
uc.DefineVariable("myInt As Int = 10")
uc.DefineVariable("myStr As String = 'hello'")
uc.DefineVariable("myDbl = 3.14") '// Type is inferred as double
Console.WriteLine($"Type of myInt: {uc.EvalStr("TypeOf(myInt)")}")
Console.WriteLine($"Type of myStr: {uc.EvalStr("TypeOf(myStr)")}")
Console.WriteLine($"Type of myDbl: {uc.EvalStr("TypeOf(myDbl)")}")
End Sub
End Module
Type of myInt: int
Type of myStr: string
Type of myDbl: double Imports System Imports uCalcSoftware Public Module Program Public Sub GetTypeOf(ByVal cb As uCalc.Callback) '// Get the Item object for the argument Dim item = cb.ArgItem(1) '// Get the item's DataType, then its name, and return it as a string cb.ReturnStr(item.DataType.Name) End Sub Public Sub Main() Dim uc As New uCalc() '// The ByHandle modifier passes the argument's metadata (Item) instead of its value uc.DefineFunction("TypeOf(ByHandle arg As AnyType) As String", AddressOf GetTypeOf) uc.DefineVariable("myInt As Int = 10") uc.DefineVariable("myStr As String = 'hello'") uc.DefineVariable("myDbl = 3.14") '// Type is inferred as double Console.WriteLine($"Type of myInt: {uc.EvalStr("TypeOf(myInt)")}") Console.WriteLine($"Type of myStr: {uc.EvalStr("TypeOf(myStr)")}") Console.WriteLine($"Type of myDbl: {uc.EvalStr("TypeOf(myDbl)")}") End Sub End Module