uCalc API Version: 5.7.0-preview.1 Released: 7/30/2026
Warning
uCalc API Preview Release Notice:This preview documentation describes the intended behavior of the API. It is not fully accurate or complete.The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.Use of the preview version in your production code is not recommended.
A step-by-step guide to building a simple, readable Domain-Specific Language (DSL) for unit conversions using the ExpressionTransformer.
This tutorial will guide you through building a simple but powerful Domain-Specific Language (DSL) for handling unit conversions. By leveraging uCalc's ExpressionTransformer, you can transform complex, hard-to-read formulas into an intuitive, human-readable syntax.
Instead of writing verbose code like (value - 32) * 5 / 9 or calling functions like ConvertFtoC(value), we want to enable a natural language syntax directly within our expressions:
10 in to cm98.6 F to C100 km to milesThis approach makes expressions more readable, maintainable, and easier for non-programmers to use.
The quickest way to build our DSL is to define a specific rule for each conversion pair using the ExpressionTransformer. This pre-processes the expression string, rewriting our custom syntax into a standard mathematical formula before the main parser evaluates it.
csharp
// Get the transformer that pre-processes all expressions.
var t = uc.ExpressionTransformer;
// Rule for Inches to Centimeters
// The pattern `{@Number:val} in to cm` finds a number followed by the anchors "in to cm".
// The replacement uses {@Eval} to perform the calculation on the captured value.
// Note: Captured values are text, so we use Double() to convert 'val' to a number.
t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})");
// Rule for Fahrenheit to Celsius
t.FromTo("{@Number:val} F to C", "({@Eval: (Double(val) - 32) * 5 / 9})");
// Now we can use the new syntax directly in any expression.
Console.WriteLine($"10 inches is {uc.EvalStr("10 in to cm")} cm.");
Console.WriteLine($"98.6 Fahrenheit is {uc.EvalStr("98.6 F to C")} Celsius.");
While this works perfectly, it requires a separate rule for every single conversion (cm to in, ft to m, etc.), which can become difficult to maintain.
A more scalable and maintainable solution is to create a single, generic rule that can handle any unit conversion by looking up conversion factors stored in variables. This turns our transformer into a flexible conversion engine.
First, we store our conversion factors as variables in the uCalc instance. We'll use a consistent naming convention, like fromUnit_to_toUnit.
csharp
// Define one-way conversion factors.
uc.DefineVariable("in_to_cm = 2.54");
uc.DefineVariable("ft_to_m = 0.3048");
uc.DefineVariable("km_to_miles = 0.621371");
Next, we'll create a single callback function, Convert, that can handle any conversion. This function will dynamically construct the name of the factor variable (e.g., "in_to_cm") and use it to perform the calculation. The logic will even be smart enough to handle inverse conversions (e.g., cm to in) automatically.
Finally, we create one generic rule in the ExpressionTransformer that captures the value and the two units, then calls our Convert function.
csharp
var t = uc.ExpressionTransformer;
t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')");
The "Practical (Real World)" example below provides the complete implementation for this advanced approach, including the callback logic for the Convert function. This pattern is the key to building powerful, data-driven DSLs with uCalc.
ID: 1354
using uCalcSoftware;
var uc = new uCalc();
// Get the transformer that pre-processes expressions.
var t = uc.ExpressionTransformer;
// Define a rule to find "X in to cm" and replace it with the calculated value.
// Note: 'val' is captured as text and must be converted to a number with Double().
t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})");
// Use the new syntax directly in an expression.
Console.WriteLine(uc.EvalStr("10 in to cm"));
25.4 using uCalcSoftware; var uc = new uCalc(); // Get the transformer that pre-processes expressions. var t = uc.ExpressionTransformer; // Define a rule to find "X in to cm" and replace it with the calculated value. // Note: 'val' is captured as text and must be converted to a number with Double(). t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})"); // Use the new syntax directly in an expression. Console.WriteLine(uc.EvalStr("10 in to cm"));
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
// Get the transformer that pre-processes expressions.
auto t = uc.ExpressionTransformer();
// Define a rule to find "X in to cm" and replace it with the calculated value.
// Note: 'val' is captured as text and must be converted to a number with Double().
t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})");
// Use the new syntax directly in an expression.
cout << uc.EvalStr("10 in to cm") << endl;
}
25.4 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; // Get the transformer that pre-processes expressions. auto t = uc.ExpressionTransformer(); // Define a rule to find "X in to cm" and replace it with the calculated value. // Note: 'val' is captured as text and must be converted to a number with Double(). t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})"); // Use the new syntax directly in an expression. cout << uc.EvalStr("10 in to cm") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
'// Get the transformer that pre-processes expressions.
Dim t = uc.ExpressionTransformer
'// Define a rule to find "X in to cm" and replace it with the calculated value.
'// Note: 'val' is captured as text and must be converted to a number with Double().
t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})")
'// Use the new syntax directly in an expression.
Console.WriteLine(uc.EvalStr("10 in to cm"))
End Sub
End Module
25.4 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() '// Get the transformer that pre-processes expressions. Dim t = uc.ExpressionTransformer '// Define a rule to find "X in to cm" and replace it with the calculated value. '// Note: 'val' is captured as text and must be converted to a number with Double(). t.FromTo("{@Number:val} in to cm", "({@Eval: Double(val) * 2.54})") '// Use the new syntax directly in an expression. Console.WriteLine(uc.EvalStr("10 in to cm")) End Sub End Module
ID: 1355
using uCalcSoftware;
var uc = new uCalc();
static void ConvertUnits(uCalc.Callback cb) {
var value = cb.Arg(1);
var fromUnit = cb.ArgStr(2);
var toUnit = cb.ArgStr(3);
// Construct the variable names for direct and inverse factors
var factorName = fromUnit + "_to_" + toUnit;
var inverseFactorName = toUnit + "_to_" + fromUnit;
var uc_instance = cb.uCalc;
// Try to find the direct conversion factor
var factorItem = uc_instance.ItemOf(factorName);
if (factorItem.NotEmpty()) {
cb.Return(Math.Round(value * factorItem.Value(), 4));
return;
}
// If not found, try to find the inverse factor and use its reciprocal
var inverseFactorItem = uc_instance.ItemOf(inverseFactorName);
if (inverseFactorItem.NotEmpty()) {
cb.Return(value / inverseFactorItem.Value());
return;
}
// If no factor is found, raise an error
cb.Error.Raise("Conversion factor not found for " + fromUnit + " to " + toUnit);
}
// Define the conversion factors as variables
uc.DefineVariable("in_to_cm = 2.54");
uc.DefineVariable("km_to_miles = 0.621371");
// Define a custom function for temperature, since it's not a simple multiplication
uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0");
// Register our generic conversion callback
uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", ConvertUnits);
// Create generic rules in the expression transformer
var t = uc.ExpressionTransformer;
t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')");
// A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last)
t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})");
Console.WriteLine($"10 in to cm = {uc.Eval("10 in to cm")}");
Console.WriteLine($"100 km to miles = {uc.Eval("100 km to miles")}");
// Test the inverse conversion, which the callback handles automatically
Console.WriteLine($"254 cm to in = {uc.Eval("254 cm to in")}");
Console.WriteLine($"98.6 F to C = {uc.Eval("98.6 F to C")}");
10 in to cm = 25.4
100 km to miles = 62.1371
254 cm to in = 100
98.6 F to C = 37 using uCalcSoftware; var uc = new uCalc(); static void ConvertUnits(uCalc.Callback cb) { var value = cb.Arg(1); var fromUnit = cb.ArgStr(2); var toUnit = cb.ArgStr(3); // Construct the variable names for direct and inverse factors var factorName = fromUnit + "_to_" + toUnit; var inverseFactorName = toUnit + "_to_" + fromUnit; var uc_instance = cb.uCalc; // Try to find the direct conversion factor var factorItem = uc_instance.ItemOf(factorName); if (factorItem.NotEmpty()) { cb.Return(Math.Round(value * factorItem.Value(), 4)); return; } // If not found, try to find the inverse factor and use its reciprocal var inverseFactorItem = uc_instance.ItemOf(inverseFactorName); if (inverseFactorItem.NotEmpty()) { cb.Return(value / inverseFactorItem.Value()); return; } // If no factor is found, raise an error cb.Error.Raise("Conversion factor not found for " + fromUnit + " to " + toUnit); } // Define the conversion factors as variables uc.DefineVariable("in_to_cm = 2.54"); uc.DefineVariable("km_to_miles = 0.621371"); // Define a custom function for temperature, since it's not a simple multiplication uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0"); // Register our generic conversion callback uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", ConvertUnits); // Create generic rules in the expression transformer var t = uc.ExpressionTransformer; t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')"); // A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last) t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})"); Console.WriteLine($"10 in to cm = {uc.Eval("10 in to cm")}"); Console.WriteLine($"100 km to miles = {uc.Eval("100 km to miles")}"); // Test the inverse conversion, which the callback handles automatically Console.WriteLine($"254 cm to in = {uc.Eval("254 cm to in")}"); Console.WriteLine($"98.6 F to C = {uc.Eval("98.6 F to C")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call ConvertUnits(uCalcBase::Callback cb) {
auto value = cb.Arg(1);
auto fromUnit = cb.ArgStr(2);
auto toUnit = cb.ArgStr(3);
// Construct the variable names for direct and inverse factors
auto factorName = fromUnit + "_to_" + toUnit;
auto inverseFactorName = toUnit + "_to_" + fromUnit;
auto uc_instance = cb.uCalc();
// Try to find the direct conversion factor
auto factorItem = uc_instance.ItemOf(factorName);
if (factorItem.NotEmpty()) {
cb.Return(value * factorItem.Value());
return;
}
// If not found, try to find the inverse factor and use its reciprocal
auto inverseFactorItem = uc_instance.ItemOf(inverseFactorName);
if (inverseFactorItem.NotEmpty()) {
cb.Return(value / inverseFactorItem.Value());
return;
}
// If no factor is found, raise an error
cb.Error().Raise("Conversion factor not found for " + fromUnit + " to " + toUnit);
}
int main() {
uCalc uc;
// Define the conversion factors as variables
uc.DefineVariable("in_to_cm = 2.54");
uc.DefineVariable("km_to_miles = 0.621371");
// Define a custom function for temperature, since it's not a simple multiplication
uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0");
// Register our generic conversion callback
uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", ConvertUnits);
// Create generic rules in the expression transformer
auto t = uc.ExpressionTransformer();
t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')");
// A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last)
t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})");
cout << "10 in to cm = " << uc.Eval("10 in to cm") << endl;
cout << "100 km to miles = " << uc.Eval("100 km to miles") << endl;
// Test the inverse conversion, which the callback handles automatically
cout << "254 cm to in = " << uc.Eval("254 cm to in") << endl;
cout << "98.6 F to C = " << uc.Eval("98.6 F to C") << endl;
}
10 in to cm = 25.4
100 km to miles = 62.1371
254 cm to in = 100
98.6 F to C = 37 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call ConvertUnits(uCalcBase::Callback cb) { auto value = cb.Arg(1); auto fromUnit = cb.ArgStr(2); auto toUnit = cb.ArgStr(3); // Construct the variable names for direct and inverse factors auto factorName = fromUnit + "_to_" + toUnit; auto inverseFactorName = toUnit + "_to_" + fromUnit; auto uc_instance = cb.uCalc(); // Try to find the direct conversion factor auto factorItem = uc_instance.ItemOf(factorName); if (factorItem.NotEmpty()) { cb.Return(value * factorItem.Value()); return; } // If not found, try to find the inverse factor and use its reciprocal auto inverseFactorItem = uc_instance.ItemOf(inverseFactorName); if (inverseFactorItem.NotEmpty()) { cb.Return(value / inverseFactorItem.Value()); return; } // If no factor is found, raise an error cb.Error().Raise("Conversion factor not found for " + fromUnit + " to " + toUnit); } int main() { uCalc uc; // Define the conversion factors as variables uc.DefineVariable("in_to_cm = 2.54"); uc.DefineVariable("km_to_miles = 0.621371"); // Define a custom function for temperature, since it's not a simple multiplication uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0"); // Register our generic conversion callback uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", ConvertUnits); // Create generic rules in the expression transformer auto t = uc.ExpressionTransformer(); t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')"); // A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last) t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})"); cout << "10 in to cm = " << uc.Eval("10 in to cm") << endl; cout << "100 km to miles = " << uc.Eval("100 km to miles") << endl; // Test the inverse conversion, which the callback handles automatically cout << "254 cm to in = " << uc.Eval("254 cm to in") << endl; cout << "98.6 F to C = " << uc.Eval("98.6 F to C") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub ConvertUnits(ByVal cb As uCalc.Callback)
Dim value = cb.Arg(1)
Dim fromUnit = cb.ArgStr(2)
Dim toUnit = cb.ArgStr(3)
'// Construct the variable names for direct and inverse factors
Dim factorName = fromUnit + "_to_" + toUnit
Dim inverseFactorName = toUnit + "_to_" + fromUnit
Dim uc_instance = cb.uCalc
'// Try to find the direct conversion factor
Dim factorItem = uc_instance.ItemOf(factorName)
If factorItem.NotEmpty() Then
cb.Return(Math.Round(value * factorItem.Value(), 4))
return
End If
'// If not found, try to find the inverse factor and use its reciprocal
Dim inverseFactorItem = uc_instance.ItemOf(inverseFactorName)
If inverseFactorItem.NotEmpty() Then
cb.Return(value / inverseFactorItem.Value())
return
End If
'// If no factor is found, raise an error
cb.Error.Raise("Conversion factor not found for " + fromUnit + " to " + toUnit)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// Define the conversion factors as variables
uc.DefineVariable("in_to_cm = 2.54")
uc.DefineVariable("km_to_miles = 0.621371")
'// Define a custom function for temperature, since it's not a simple multiplication
uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0")
'// Register our generic conversion callback
uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", AddressOf ConvertUnits)
'// Create generic rules in the expression transformer
Dim t = uc.ExpressionTransformer
t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')")
'// A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last)
t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})")
Console.WriteLine($"10 in to cm = {uc.Eval("10 in to cm")}")
Console.WriteLine($"100 km to miles = {uc.Eval("100 km to miles")}")
'// Test the inverse conversion, which the callback handles automatically
Console.WriteLine($"254 cm to in = {uc.Eval("254 cm to in")}")
Console.WriteLine($"98.6 F to C = {uc.Eval("98.6 F to C")}")
End Sub
End Module
10 in to cm = 25.4
100 km to miles = 62.1371
254 cm to in = 100
98.6 F to C = 37 Imports System Imports uCalcSoftware Public Module Program Public Sub ConvertUnits(ByVal cb As uCalc.Callback) Dim value = cb.Arg(1) Dim fromUnit = cb.ArgStr(2) Dim toUnit = cb.ArgStr(3) '// Construct the variable names for direct and inverse factors Dim factorName = fromUnit + "_to_" + toUnit Dim inverseFactorName = toUnit + "_to_" + fromUnit Dim uc_instance = cb.uCalc '// Try to find the direct conversion factor Dim factorItem = uc_instance.ItemOf(factorName) If factorItem.NotEmpty() Then cb.Return(Math.Round(value * factorItem.Value(), 4)) return End If '// If not found, try to find the inverse factor and use its reciprocal Dim inverseFactorItem = uc_instance.ItemOf(inverseFactorName) If inverseFactorItem.NotEmpty() Then cb.Return(value / inverseFactorItem.Value()) return End If '// If no factor is found, raise an error cb.Error.Raise("Conversion factor not found for " + fromUnit + " to " + toUnit) End Sub Public Sub Main() Dim uc As New uCalc() '// Define the conversion factors as variables uc.DefineVariable("in_to_cm = 2.54") uc.DefineVariable("km_to_miles = 0.621371") '// Define a custom function for temperature, since it's not a simple multiplication uc.DefineFunction("ConvertTempFToC(val) = (val - 32) * 5.0/9.0") '// Register our generic conversion callback uc.DefineFunction("Convert(val, fromUnit As String, toUnit As String)", AddressOf ConvertUnits) '// Create generic rules in the expression transformer Dim t = uc.ExpressionTransformer t.FromTo("{@Number:val} {@Alpha:from} to {@Alpha:to}", "Convert({val}, '{from}', '{to}')") '// A specific rule for Fahrenheit to Celsius since it's more complex (higher precedence because it's defined last) t.FromTo("{@Number:val} F to C", "ConvertTempFToC({val})") Console.WriteLine($"10 in to cm = {uc.Eval("10 in to cm")}") Console.WriteLine($"100 km to miles = {uc.Eval("100 km to miles")}") '// Test the inverse conversion, which the callback handles automatically Console.WriteLine($"254 cm to in = {uc.Eval("254 cm to in")}") Console.WriteLine($"98.6 F to C = {uc.Eval("98.6 F to C")}") End Sub End Module