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 tutorial on building a parser for the classic LOGO turtle graphics language, demonstrating how to create a Domain-Specific Language (DSL) with the ExpressionTransformer.
This project is a fun, hands-on tutorial that demonstrates how to build a parser for a simple but classic programming language: LOGO. We'll create a Domain-Specific Language (DSL) that understands "turtle graphics" commands like FD 100 (forward 100) and RT 90 (right turn 90), and even handles loops with REPEAT.
This is a perfect real-world example of how uCalc's ExpressionTransformer and expression parser work together to create a custom language interpreter from scratch.
We want our uCalc instance to understand a script like this, which draws a square:
PD // Pen DownREPEAT 4 [ FD 100 // Forward 100 pixels RT 90 // Right turn 90 degrees]PU // Pen UpThe core idea is to use the ExpressionTransformer to translate our LOGO commands into standard uCalc expressions before they are evaluated. For example, RT 90 will be rewritten to angle = angle - 90. For movement, we'll create a native callback to handle the trigonometry, demonstrating how a DSL can be integrated with high-performance host application code.
First, we need to define the variables that will represent our turtle's state: its position (x, y), its heading (angle), and whether its pen is down (pen_down).
csharp
// Initial state of our "turtle"
uc.DefineVariable("x = 0.0");
uc.DefineVariable("y = 0.0");
uc.DefineVariable("angle = 90.0"); // Start facing up (90 degrees)
uc.DefineVariable("pen_down = false");
uc.DefineConstant("PI = 3.1415926535");
We also define helper functions to work with degrees, as LOGO uses degrees while uCalc's trigonometric functions use radians.csharp
uc.DefineFunction("CosD(a) = Cos(a * PI / 180)");
uc.DefineFunction("SinD(a) = Sin(a * PI / 180)");
To keep our transformation rules clean, we'll encapsulate the complex movement logic in a single native callback function called Move. This function will calculate the new x and y coordinates and simulate drawing a line if the pen is down.
csharp
static void MoveTurtle(uCalc.Callback cb) {
var dist = cb.Arg(1);
var uc_inst = cb.uCalc;
// Get current state
var x = uc_inst.ItemOf("x").Value();
var y = uc_inst.ItemOf("y").Value();
var angle = uc_inst.ItemOf("angle").Value();
var pen_down = uc_inst.ItemOf("pen_down").ValueBool();
// Calculate new position
var new_x = x + dist * uc_inst.Eval("CosD(" + (angle).ToString() + ")");
var new_y = y + dist * uc_inst.Eval("SinD(" + (angle).ToString() + ")");
if (pen_down) {
Console.WriteLine($"Drawing line from ({x},{y}) to ({new_x},{new_y})");
} else {
Console.WriteLine($"Moving from ({x},{y}) to ({new_x},{new_y})");
}
// Update state
uc_inst.ItemOf("x").Value(new_x);
uc_inst.ItemOf("y").Value(new_y);
}
// Define the uCalc function that bridges to our native code
uc.DefineFunction("Move(dist)", MoveTurtle);
Now we define the transformation rules on the ExpressionTransformer. Each rule finds a LOGO command and replaces it with a standard uCalc expression.
csharp
var t = uc.ExpressionTransformer;
// Movement commands call our native callback
t.FromTo("FD {@Number:dist}", "Move({dist})");
t.FromTo("BK {@Number:dist}", "Move(-{dist})");
// Turning commands modify the angle variable
t.FromTo("RT {@Number:deg}", "angle = angle - {deg}");
t.FromTo("LT {@Number:deg}", "angle = angle + {deg}");
// Pen commands modify the boolean flag
t.FromTo("PU", "pen_down = false");
t.FromTo("PD", "pen_down = true");
REPEATThe REPEAT command is the most interesting. We'll transform it into a uCalc ForLoop function. The key here is setting RewindOnChange(true). This tells the transformer to re-scan the text inside the loop's body, ensuring that commands like FD and RT inside the REPEAT block are also transformed.
csharp
// The body is captured as a literal string and passed to ForLoop
// RewindOnChange is crucial for processing the commands inside the body
t.FromTo("REPEAT {@Number:n} '[' {body} ']'").RewindOnChange = true;
With our state, callback, and rules defined, we can now execute a LOGO script. The EvalStr method will automatically apply the ExpressionTransformer rules before evaluating the resulting expressions.
REPEAT blocks, and an interpreter. This would be hundreds of lines of complex code. uCalc reduces this to a handful of declarative rules.CIRCLE, you would need to modify a grammar file and recompile. With uCalc, you can add a new FromTo rule at runtime, making the language extensible.ID: 1409
using uCalcSoftware;
var uc = new uCalc();
static void MoveTurtle(uCalc.Callback cb) {
var dist = cb.Arg(1);
var uc_inst = cb.uCalc;
var x = uc_inst.ItemOf("x").Value();
var y = uc_inst.ItemOf("y").Value();
var angle = uc_inst.ItemOf("angle").Value();
var pen_down = uc_inst.ItemOf("pen_down").ValueBool();
int new_x = Convert.ToInt32(x + dist * uc_inst.Eval("CosD(" + (angle).ToString() + ")"));
int new_y = Convert.ToInt32(y + dist * uc_inst.Eval("SinD(" + (angle).ToString() + ")"));
if (pen_down) {
Console.WriteLine($"Drawing line to ({new_x},{new_y})");
} else {
Console.WriteLine($"Moving to ({new_x},{new_y})");
}
uc_inst.ItemOf("x").Value(new_x);
uc_inst.ItemOf("y").Value(new_y);
}
// --- Setup ---
uc.DefineVariable("x = 0.0");
uc.DefineVariable("y = 0.0");
uc.DefineVariable("angle = 90.0");
uc.DefineVariable("pen_down = false");
uc.DefineConstant("PI = 3.1415926535");
uc.DefineFunction("CosD(a) = Cos(a * PI / 180)");
uc.DefineFunction("SinD(a) = Sin(a * PI / 180)");
uc.DefineFunction("Move(dist)", MoveTurtle);
var t = uc.ExpressionTransformer;
t.FromTo("FD {@Number:dist}", "Move({dist})");
t.FromTo("RT {@Number:deg}", "angle = angle - {deg}");
t.FromTo("PD", "pen_down = true");
// --- Script ---
var script = """
PD
FD 100
RT 90
FD 50
""";
uc.EvalStr(script);
Console.WriteLine($"Final Position: ({uc.Eval("x")}, {uc.Eval("y")})");
Drawing line to (0,100)
Drawing line to (50,100)
Final Position: (50, 100) using uCalcSoftware; var uc = new uCalc(); static void MoveTurtle(uCalc.Callback cb) { var dist = cb.Arg(1); var uc_inst = cb.uCalc; var x = uc_inst.ItemOf("x").Value(); var y = uc_inst.ItemOf("y").Value(); var angle = uc_inst.ItemOf("angle").Value(); var pen_down = uc_inst.ItemOf("pen_down").ValueBool(); int new_x = Convert.ToInt32(x + dist * uc_inst.Eval("CosD(" + (angle).ToString() + ")")); int new_y = Convert.ToInt32(y + dist * uc_inst.Eval("SinD(" + (angle).ToString() + ")")); if (pen_down) { Console.WriteLine($"Drawing line to ({new_x},{new_y})"); } else { Console.WriteLine($"Moving to ({new_x},{new_y})"); } uc_inst.ItemOf("x").Value(new_x); uc_inst.ItemOf("y").Value(new_y); } // --- Setup --- uc.DefineVariable("x = 0.0"); uc.DefineVariable("y = 0.0"); uc.DefineVariable("angle = 90.0"); uc.DefineVariable("pen_down = false"); uc.DefineConstant("PI = 3.1415926535"); uc.DefineFunction("CosD(a) = Cos(a * PI / 180)"); uc.DefineFunction("SinD(a) = Sin(a * PI / 180)"); uc.DefineFunction("Move(dist)", MoveTurtle); var t = uc.ExpressionTransformer; t.FromTo("FD {@Number:dist}", "Move({dist})"); t.FromTo("RT {@Number:deg}", "angle = angle - {deg}"); t.FromTo("PD", "pen_down = true"); // --- Script --- var script = """ PD FD 100 RT 90 FD 50 """; uc.EvalStr(script); Console.WriteLine($"Final Position: ({uc.Eval("x")}, {uc.Eval("y")})");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call MoveTurtle(uCalcBase::Callback cb) {
auto dist = cb.Arg(1);
auto uc_inst = cb.uCalc();
auto x = uc_inst.ItemOf("x").Value();
auto y = uc_inst.ItemOf("y").Value();
auto angle = uc_inst.ItemOf("angle").Value();
auto pen_down = uc_inst.ItemOf("pen_down").ValueBool();
int new_x = x + dist * uc_inst.Eval("CosD(" + to_string(angle) + ")");
int new_y = y + dist * uc_inst.Eval("SinD(" + to_string(angle) + ")");
if (pen_down) {
cout << "Drawing line to (" << new_x << "," << new_y << ")" << endl;
} else {
cout << "Moving to (" << new_x << "," << new_y << ")" << endl;
}
uc_inst.ItemOf("x").Value(new_x);
uc_inst.ItemOf("y").Value(new_y);
}
int main() {
uCalc uc;
// --- Setup ---
uc.DefineVariable("x = 0.0");
uc.DefineVariable("y = 0.0");
uc.DefineVariable("angle = 90.0");
uc.DefineVariable("pen_down = false");
uc.DefineConstant("PI = 3.1415926535");
uc.DefineFunction("CosD(a) = Cos(a * PI / 180)");
uc.DefineFunction("SinD(a) = Sin(a * PI / 180)");
uc.DefineFunction("Move(dist)", MoveTurtle);
auto t = uc.ExpressionTransformer();
t.FromTo("FD {@Number:dist}", "Move({dist})");
t.FromTo("RT {@Number:deg}", "angle = angle - {deg}");
t.FromTo("PD", "pen_down = true");
// --- Script ---
auto script = R"(
PD
FD 100
RT 90
FD 50
)";
uc.EvalStr(script);
cout << "Final Position: (" << uc.Eval("x") << ", " << uc.Eval("y") << ")" << endl;
}
Drawing line to (0,100)
Drawing line to (50,100)
Final Position: (50, 100) #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call MoveTurtle(uCalcBase::Callback cb) { auto dist = cb.Arg(1); auto uc_inst = cb.uCalc(); auto x = uc_inst.ItemOf("x").Value(); auto y = uc_inst.ItemOf("y").Value(); auto angle = uc_inst.ItemOf("angle").Value(); auto pen_down = uc_inst.ItemOf("pen_down").ValueBool(); int new_x = x + dist * uc_inst.Eval("CosD(" + to_string(angle) + ")"); int new_y = y + dist * uc_inst.Eval("SinD(" + to_string(angle) + ")"); if (pen_down) { cout << "Drawing line to (" << new_x << "," << new_y << ")" << endl; } else { cout << "Moving to (" << new_x << "," << new_y << ")" << endl; } uc_inst.ItemOf("x").Value(new_x); uc_inst.ItemOf("y").Value(new_y); } int main() { uCalc uc; // --- Setup --- uc.DefineVariable("x = 0.0"); uc.DefineVariable("y = 0.0"); uc.DefineVariable("angle = 90.0"); uc.DefineVariable("pen_down = false"); uc.DefineConstant("PI = 3.1415926535"); uc.DefineFunction("CosD(a) = Cos(a * PI / 180)"); uc.DefineFunction("SinD(a) = Sin(a * PI / 180)"); uc.DefineFunction("Move(dist)", MoveTurtle); auto t = uc.ExpressionTransformer(); t.FromTo("FD {@Number:dist}", "Move({dist})"); t.FromTo("RT {@Number:deg}", "angle = angle - {deg}"); t.FromTo("PD", "pen_down = true"); // --- Script --- auto script = R"( PD FD 100 RT 90 FD 50 )"; uc.EvalStr(script); cout << "Final Position: (" << uc.Eval("x") << ", " << uc.Eval("y") << ")" << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub MoveTurtle(ByVal cb As uCalc.Callback)
Dim dist = cb.Arg(1)
Dim uc_inst = cb.uCalc
Dim x = uc_inst.ItemOf("x").Value()
Dim y = uc_inst.ItemOf("y").Value()
Dim angle = uc_inst.ItemOf("angle").Value()
Dim pen_down = uc_inst.ItemOf("pen_down").ValueBool()
Dim new_x As Integer = Convert.ToInt32(x + dist * uc_inst.Eval("CosD(" + (angle).ToString() + ")"))
Dim new_y As Integer = Convert.ToInt32(y + dist * uc_inst.Eval("SinD(" + (angle).ToString() + ")"))
If pen_down Then
Console.WriteLine($"Drawing line to ({new_x},{new_y})")
Else
Console.WriteLine($"Moving to ({new_x},{new_y})")
End If
uc_inst.ItemOf("x").Value(new_x)
uc_inst.ItemOf("y").Value(new_y)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// --- Setup ---
uc.DefineVariable("x = 0.0")
uc.DefineVariable("y = 0.0")
uc.DefineVariable("angle = 90.0")
uc.DefineVariable("pen_down = false")
uc.DefineConstant("PI = 3.1415926535")
uc.DefineFunction("CosD(a) = Cos(a * PI / 180)")
uc.DefineFunction("SinD(a) = Sin(a * PI / 180)")
uc.DefineFunction("Move(dist)", AddressOf MoveTurtle)
Dim t = uc.ExpressionTransformer
t.FromTo("FD {@Number:dist}", "Move({dist})")
t.FromTo("RT {@Number:deg}", "angle = angle - {deg}")
t.FromTo("PD", "pen_down = true")
'// --- Script ---
Dim script = "
PD
FD 100
RT 90
FD 50
"
uc.EvalStr(script)
Console.WriteLine($"Final Position: ({uc.Eval("x")}, {uc.Eval("y")})")
End Sub
End Module
Drawing line to (0,100)
Drawing line to (50,100)
Final Position: (50, 100) Imports System Imports uCalcSoftware Public Module Program Public Sub MoveTurtle(ByVal cb As uCalc.Callback) Dim dist = cb.Arg(1) Dim uc_inst = cb.uCalc Dim x = uc_inst.ItemOf("x").Value() Dim y = uc_inst.ItemOf("y").Value() Dim angle = uc_inst.ItemOf("angle").Value() Dim pen_down = uc_inst.ItemOf("pen_down").ValueBool() Dim new_x As Integer = Convert.ToInt32(x + dist * uc_inst.Eval("CosD(" + (angle).ToString() + ")")) Dim new_y As Integer = Convert.ToInt32(y + dist * uc_inst.Eval("SinD(" + (angle).ToString() + ")")) If pen_down Then Console.WriteLine($"Drawing line to ({new_x},{new_y})") Else Console.WriteLine($"Moving to ({new_x},{new_y})") End If uc_inst.ItemOf("x").Value(new_x) uc_inst.ItemOf("y").Value(new_y) End Sub Public Sub Main() Dim uc As New uCalc() '// --- Setup --- uc.DefineVariable("x = 0.0") uc.DefineVariable("y = 0.0") uc.DefineVariable("angle = 90.0") uc.DefineVariable("pen_down = false") uc.DefineConstant("PI = 3.1415926535") uc.DefineFunction("CosD(a) = Cos(a * PI / 180)") uc.DefineFunction("SinD(a) = Sin(a * PI / 180)") uc.DefineFunction("Move(dist)", AddressOf MoveTurtle) Dim t = uc.ExpressionTransformer t.FromTo("FD {@Number:dist}", "Move({dist})") t.FromTo("RT {@Number:deg}", "angle = angle - {deg}") t.FromTo("PD", "pen_down = true") '// --- Script --- Dim script = " PD FD 100 RT 90 FD 50 " uc.EvalStr(script) Console.WriteLine($"Final Position: ({uc.Eval("x")}, {uc.Eval("y")})") End Sub End Module