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

ModifierWhat is PassedPrimary Use Case
ByRefA direct reference to a variable's memory.Creating functions with side effects (in-out parameters).
ByExprAn unevaluated Expression object.Lazy evaluation; building custom control structures like loops and conditionals.
ByHandleThe 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
				
					#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;
}
				
			
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
				
			
Before: x = 100, y = 200
After:  x = 200, y = 100
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
				
					#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')");
}
				
			
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
				
			
Assertion failed: x (50) is not greater than 100
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
				
					#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;
}
				
			
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
				
			
Type of myInt: int
Type of myStr: string
Type of myDbl: double