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.

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.00

This 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
				
					#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;
}
				
			
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
				
			
Final Balance: 600
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
				
					#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;
}
				
			
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
				
			
Initial Balance: 0
Processing script...
Final Balance: 753.99625
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
				
					#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;
}
				
			
--- 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
				
			
--- 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