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.
Project: Creating a Simple DSL for Financial Transactions
Product:Â
Class:Â
A step-by-step guide to building a simple, readable Domain-Specific Language (DSL) for processing financial transactions using the ExpressionTransformer.
Remarks
📈 Project: Creating a Simple DSL for Financial Transactions
A Domain-Specific Language (DSL) is a mini-language tailored to a specific task. By creating a DSL, you can make complex operations more readable, maintainable, and accessible to non-programmers. This project will guide you through building a simple yet powerful DSL for processing a list of financial transactions using uCalc's ExpressionTransformer.
The Goal: A Readable Transaction Script
Instead of writing complex C# or C++ code to process transactions, we want our application to understand a simple, human-readable script like this:
DEPOSIT 1000.00WITHDRAW 50.75INTEREST 0.5WITHDRAW 200.00This script is far more intuitive and can be easily created, modified, or stored in configuration files or a database.
The Strategy: Transforming DSL into uCalc Expressions
The key to building our DSL is to teach the uCalc engine to understand our new keywords (DEPOSIT, WITHDRAW, INTEREST). We won't modify the core parser; instead, we'll use the ExpressionTransformer to transform our DSL syntax into standard mathematical expressions before they are evaluated.
For example, the line DEPOSIT 1000.00 will be automatically rewritten to balance = balance + 1000.00.
Step 1: Setting Up the Environment
First, we need a variable within our uCalc instance to hold the account balance. We'll create it using DefineVariable.
// This variable will store the running total.uc.DefineVariable("balance = 0.0");Step 2: Defining the DSL Syntax
Next, we'll get the ExpressionTransformer and add rules for each of our keywords using FromTo(). We use the {@Number} category matcher to capture the numeric amount for each transaction.
// Get the transformer that pre-processes expressions.var t = uc.ExpressionTransformer;// Rule for DEPOSITt.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}");// Rule for WITHDRAWt.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}");// Rule for INTEREST (rate is a percentage)t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)");Step 3: Processing the Script
With our transformation rules in place, we can now pass the entire multi-line script to EvalStr(). Because newlines are treated as statement separators by default, uCalc will automatically pre-process and execute each line sequentially.
EvalStr() returns the result of the last statement executed. The other statements simply modify the balance variable as a side effect. To see the final result, we make a final call to evaluate the balance variable itself.
Conclusion
You've successfully created a simple but functional DSL! This approach offers several advantages over hardcoding logic:
- Readability: The transaction script is clean and self-documenting.
- Maintainability: You can change the logic behind a keyword (e.g., add a transaction fee to
WITHDRAW) by modifying a single rule, without touching your application's compiled code. - Flexibility: The script can be generated, stored, and loaded from anywhere, making your application highly configurable.
Examples
A "Hello World" example showing how to define a single DSL command and process a one-line script.
using uCalcSoftware;
var uc = new uCalc();
// 1. Define the account balance variable
uc.DefineVariable("balance = 100.0");
// 2. Get the expression transformer and define a single rule
var t = uc.ExpressionTransformer;
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}");
// 3. Process a single-line script.
// This gets transformed to "balance = balance + 500" and executed.
uc.EvalStr("DEPOSIT 500");
// 4. Print the final balance
Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}");
Final Balance: 600 using uCalcSoftware; var uc = new uCalc(); // 1. Define the account balance variable uc.DefineVariable("balance = 100.0"); // 2. Get the expression transformer and define a single rule var t = uc.ExpressionTransformer; t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}"); // 3. Process a single-line script. // This gets transformed to "balance = balance + 500" and executed. uc.EvalStr("DEPOSIT 500"); // 4. Print the final balance Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
// 1. Define the account balance variable
uc.DefineVariable("balance = 100.0");
// 2. Get the expression transformer and define a single rule
auto t = uc.ExpressionTransformer();
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}");
// 3. Process a single-line script.
// This gets transformed to "balance = balance + 500" and executed.
uc.EvalStr("DEPOSIT 500");
// 4. Print the final balance
cout << "Final Balance: " << uc.EvalStr("balance") << endl;
}
Final Balance: 600 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; // 1. Define the account balance variable uc.DefineVariable("balance = 100.0"); // 2. Get the expression transformer and define a single rule auto t = uc.ExpressionTransformer(); t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}"); // 3. Process a single-line script. // This gets transformed to "balance = balance + 500" and executed. uc.EvalStr("DEPOSIT 500"); // 4. Print the final balance cout << "Final Balance: " << uc.EvalStr("balance") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
'// 1. Define the account balance variable
uc.DefineVariable("balance = 100.0")
'// 2. Get the expression transformer and define a single rule
Dim t = uc.ExpressionTransformer
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}")
'// 3. Process a single-line script.
'// This gets transformed to "balance = balance + 500" and executed.
uc.EvalStr("DEPOSIT 500")
'// 4. Print the final balance
Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}")
End Sub
End Module
Final Balance: 600 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() '// 1. Define the account balance variable uc.DefineVariable("balance = 100.0") '// 2. Get the expression transformer and define a single rule Dim t = uc.ExpressionTransformer t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}") '// 3. Process a single-line script. '// This gets transformed to "balance = balance + 500" and executed. uc.EvalStr("DEPOSIT 500") '// 4. Print the final balance Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}") End Sub End Module
The complete financial DSL processing a multi-line script of deposits, withdrawals, and interest calculation.
using uCalcSoftware;
var uc = new uCalc();
// 1. Initialize the account balance
uc.DefineVariable("balance = 0.0");
// 2. Define the DSL rules on the expression transformer
var t = uc.ExpressionTransformer;
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}");
t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}");
t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)");
// 3. Define the multi-line transaction script
var script = """
DEPOSIT 1000.00
WITHDRAW 50.75
INTEREST 0.5
WITHDRAW 200.00
""";
Console.WriteLine($"Initial Balance: {uc.Eval("balance")}");
Console.WriteLine("Processing script...");
// 4. Evaluate the entire script.
// Note: EvalStr returns the result of the LAST line.
// The intermediate lines modify the 'balance' variable.
uc.EvalStr(script);
// 5. Get the final balance
Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}");
Initial Balance: 0
Processing script...
Final Balance: 753.99625 using uCalcSoftware; var uc = new uCalc(); // 1. Initialize the account balance uc.DefineVariable("balance = 0.0"); // 2. Define the DSL rules on the expression transformer var t = uc.ExpressionTransformer; t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}"); t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}"); t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)"); // 3. Define the multi-line transaction script var script = """ DEPOSIT 1000.00 WITHDRAW 50.75 INTEREST 0.5 WITHDRAW 200.00 """; Console.WriteLine($"Initial Balance: {uc.Eval("balance")}"); Console.WriteLine("Processing script..."); // 4. Evaluate the entire script. // Note: EvalStr returns the result of the LAST line. // The intermediate lines modify the 'balance' variable. uc.EvalStr(script); // 5. Get the final balance Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
// 1. Initialize the account balance
uc.DefineVariable("balance = 0.0");
// 2. Define the DSL rules on the expression transformer
auto t = uc.ExpressionTransformer();
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}");
t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}");
t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)");
// 3. Define the multi-line transaction script
auto script = R"(
DEPOSIT 1000.00
WITHDRAW 50.75
INTEREST 0.5
WITHDRAW 200.00
)";
cout << "Initial Balance: " << uc.Eval("balance") << endl;
cout << "Processing script..." << endl;
// 4. Evaluate the entire script.
// Note: EvalStr returns the result of the LAST line.
// The intermediate lines modify the 'balance' variable.
uc.EvalStr(script);
// 5. Get the final balance
cout << "Final Balance: " << uc.EvalStr("balance") << endl;
}
Initial Balance: 0
Processing script...
Final Balance: 753.99625 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; // 1. Initialize the account balance uc.DefineVariable("balance = 0.0"); // 2. Define the DSL rules on the expression transformer auto t = uc.ExpressionTransformer(); t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}"); t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}"); t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)"); // 3. Define the multi-line transaction script auto script = R"( DEPOSIT 1000.00 WITHDRAW 50.75 INTEREST 0.5 WITHDRAW 200.00 )"; cout << "Initial Balance: " << uc.Eval("balance") << endl; cout << "Processing script..." << endl; // 4. Evaluate the entire script. // Note: EvalStr returns the result of the LAST line. // The intermediate lines modify the 'balance' variable. uc.EvalStr(script); // 5. Get the final balance cout << "Final Balance: " << uc.EvalStr("balance") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
'// 1. Initialize the account balance
uc.DefineVariable("balance = 0.0")
'// 2. Define the DSL rules on the expression transformer
Dim t = uc.ExpressionTransformer
t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}")
t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}")
t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)")
'// 3. Define the multi-line transaction script
Dim script = "
DEPOSIT 1000.00
WITHDRAW 50.75
INTEREST 0.5
WITHDRAW 200.00
"
Console.WriteLine($"Initial Balance: {uc.Eval("balance")}")
Console.WriteLine("Processing script...")
'// 4. Evaluate the entire script.
'// Note: EvalStr returns the result of the LAST line.
'// The intermediate lines modify the 'balance' variable.
uc.EvalStr(script)
'// 5. Get the final balance
Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}")
End Sub
End Module
Initial Balance: 0
Processing script...
Final Balance: 753.99625 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() '// 1. Initialize the account balance uc.DefineVariable("balance = 0.0") '// 2. Define the DSL rules on the expression transformer Dim t = uc.ExpressionTransformer t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}") t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}") t.FromTo("INTEREST {@Number:rate}", "balance = balance * (1 + {rate} / 100.0)") '// 3. Define the multi-line transaction script Dim script = " DEPOSIT 1000.00 WITHDRAW 50.75 INTEREST 0.5 WITHDRAW 200.00 " Console.WriteLine($"Initial Balance: {uc.Eval("balance")}") Console.WriteLine("Processing script...") '// 4. Evaluate the entire script. '// Note: EvalStr returns the result of the LAST line. '// The intermediate lines modify the 'balance' variable. uc.EvalStr(script) '// 5. Get the final balance Console.WriteLine($"Final Balance: {uc.EvalStr("balance")}") End Sub End Module
Internal Test: An advanced example that adds logging to each transaction to trace the balance changes, showing how DSLs can be combined with custom functions.
using uCalcSoftware;
var uc = new uCalc();
static void LogBalance(uCalc.Callback cb) {
// Get the message and the current balance to log
var msg = cb.ArgStr(1);
var bal = cb.uCalc.Eval("balance");
Console.WriteLine($"[LOG] {msg} | New Balance: {bal}");
}
// 1. Initialize the balance and the logger function
uc.DefineVariable("balance = 0");
uc.DefineFunction("LOG(msg As String)", LogBalance);
// 2. Define DSL rules that also call the LOG function
var t = uc.ExpressionTransformer;
// Each rule now has a side effect: logging its action.
t.FromTo("DEPOSIT {@Number:amount}",
"balance = balance + {amount}; LOG('Deposited {amount}')");
t.FromTo("WITHDRAW {@Number:amount}",
"balance = balance - {amount}; LOG('Withdrew {amount}')");
// 3. A longer script to test the log
var script = """
DEPOSIT 500
WITHDRAW 100
DEPOSIT 250
WITHDRAW 75
""";
Console.WriteLine("--- Transaction Log ---");
uc.EvalStr(script);
Console.WriteLine("-----------------------");
Console.WriteLine($"Final Balance: {uc.Eval("balance")}");
--- Transaction Log ---
[LOG] Deposited 500 | New Balance: 500
[LOG] Withdrew 100 | New Balance: 400
[LOG] Deposited 250 | New Balance: 650
[LOG] Withdrew 75 | New Balance: 575
-----------------------
Final Balance: 575 using uCalcSoftware; var uc = new uCalc(); static void LogBalance(uCalc.Callback cb) { // Get the message and the current balance to log var msg = cb.ArgStr(1); var bal = cb.uCalc.Eval("balance"); Console.WriteLine($"[LOG] {msg} | New Balance: {bal}"); } // 1. Initialize the balance and the logger function uc.DefineVariable("balance = 0"); uc.DefineFunction("LOG(msg As String)", LogBalance); // 2. Define DSL rules that also call the LOG function var t = uc.ExpressionTransformer; // Each rule now has a side effect: logging its action. t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}; LOG('Deposited {amount}')"); t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}; LOG('Withdrew {amount}')"); // 3. A longer script to test the log var script = """ DEPOSIT 500 WITHDRAW 100 DEPOSIT 250 WITHDRAW 75 """; Console.WriteLine("--- Transaction Log ---"); uc.EvalStr(script); Console.WriteLine("-----------------------"); Console.WriteLine($"Final Balance: {uc.Eval("balance")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call LogBalance(uCalcBase::Callback cb) {
// Get the message and the current balance to log
auto msg = cb.ArgStr(1);
auto bal = cb.uCalc().Eval("balance");
cout << "[LOG] " << msg << " | New Balance: " << bal << endl;
}
int main() {
uCalc uc;
// 1. Initialize the balance and the logger function
uc.DefineVariable("balance = 0");
uc.DefineFunction("LOG(msg As String)", LogBalance);
// 2. Define DSL rules that also call the LOG function
auto t = uc.ExpressionTransformer();
// Each rule now has a side effect: logging its action.
t.FromTo("DEPOSIT {@Number:amount}",
"balance = balance + {amount}; LOG('Deposited {amount}')");
t.FromTo("WITHDRAW {@Number:amount}",
"balance = balance - {amount}; LOG('Withdrew {amount}')");
// 3. A longer script to test the log
auto script = R"(
DEPOSIT 500
WITHDRAW 100
DEPOSIT 250
WITHDRAW 75
)";
cout << "--- Transaction Log ---" << endl;
uc.EvalStr(script);
cout << "-----------------------" << endl;
cout << "Final Balance: " << uc.Eval("balance") << endl;
}
--- Transaction Log ---
[LOG] Deposited 500 | New Balance: 500
[LOG] Withdrew 100 | New Balance: 400
[LOG] Deposited 250 | New Balance: 650
[LOG] Withdrew 75 | New Balance: 575
-----------------------
Final Balance: 575 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call LogBalance(uCalcBase::Callback cb) { // Get the message and the current balance to log auto msg = cb.ArgStr(1); auto bal = cb.uCalc().Eval("balance"); cout << "[LOG] " << msg << " | New Balance: " << bal << endl; } int main() { uCalc uc; // 1. Initialize the balance and the logger function uc.DefineVariable("balance = 0"); uc.DefineFunction("LOG(msg As String)", LogBalance); // 2. Define DSL rules that also call the LOG function auto t = uc.ExpressionTransformer(); // Each rule now has a side effect: logging its action. t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}; LOG('Deposited {amount}')"); t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}; LOG('Withdrew {amount}')"); // 3. A longer script to test the log auto script = R"( DEPOSIT 500 WITHDRAW 100 DEPOSIT 250 WITHDRAW 75 )"; cout << "--- Transaction Log ---" << endl; uc.EvalStr(script); cout << "-----------------------" << endl; cout << "Final Balance: " << uc.Eval("balance") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub LogBalance(ByVal cb As uCalc.Callback)
'// Get the message and the current balance to log
Dim msg = cb.ArgStr(1)
Dim bal = cb.uCalc.Eval("balance")
Console.WriteLine($"[LOG] {msg} | New Balance: {bal}")
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// 1. Initialize the balance and the logger function
uc.DefineVariable("balance = 0")
uc.DefineFunction("LOG(msg As String)", AddressOf LogBalance)
'// 2. Define DSL rules that also call the LOG function
Dim t = uc.ExpressionTransformer
'// Each rule now has a side effect: logging its action.
t.FromTo("DEPOSIT {@Number:amount}",
"balance = balance + {amount}; LOG('Deposited {amount}')")
t.FromTo("WITHDRAW {@Number:amount}",
"balance = balance - {amount}; LOG('Withdrew {amount}')")
'// 3. A longer script to test the log
Dim script = "
DEPOSIT 500
WITHDRAW 100
DEPOSIT 250
WITHDRAW 75
"
Console.WriteLine("--- Transaction Log ---")
uc.EvalStr(script)
Console.WriteLine("-----------------------")
Console.WriteLine($"Final Balance: {uc.Eval("balance")}")
End Sub
End Module
--- Transaction Log ---
[LOG] Deposited 500 | New Balance: 500
[LOG] Withdrew 100 | New Balance: 400
[LOG] Deposited 250 | New Balance: 650
[LOG] Withdrew 75 | New Balance: 575
-----------------------
Final Balance: 575 Imports System Imports uCalcSoftware Public Module Program Public Sub LogBalance(ByVal cb As uCalc.Callback) '// Get the message and the current balance to log Dim msg = cb.ArgStr(1) Dim bal = cb.uCalc.Eval("balance") Console.WriteLine($"[LOG] {msg} | New Balance: {bal}") End Sub Public Sub Main() Dim uc As New uCalc() '// 1. Initialize the balance and the logger function uc.DefineVariable("balance = 0") uc.DefineFunction("LOG(msg As String)", AddressOf LogBalance) '// 2. Define DSL rules that also call the LOG function Dim t = uc.ExpressionTransformer '// Each rule now has a side effect: logging its action. t.FromTo("DEPOSIT {@Number:amount}", "balance = balance + {amount}; LOG('Deposited {amount}')") t.FromTo("WITHDRAW {@Number:amount}", "balance = balance - {amount}; LOG('Withdrew {amount}')") '// 3. A longer script to test the log Dim script = " DEPOSIT 500 WITHDRAW 100 DEPOSIT 250 WITHDRAW 75 " Console.WriteLine("--- Transaction Log ---") uc.EvalStr(script) Console.WriteLine("-----------------------") Console.WriteLine($"Final Balance: {uc.Eval("balance")}") End Sub End Module