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: Parsing LOGO Turtle Graphics Commands
Product:
Class:
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.
Remarks
🐢 Project: Parsing LOGO Turtle Graphics Commands
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.
The Goal: Our LOGO DSL
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 Strategy: Transforming LOGO into uCalc Expressions
The 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.
Step 1: The Turtle's State
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).
// 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.
uc.DefineFunction("CosD(a) = Cos(a * PI / 180)");uc.DefineFunction("SinD(a) = Sin(a * PI / 180)");Step 2: The Core Movement Logic (A Native Callback)
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.
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 codeuc.DefineFunction("Move(dist)", MoveTurtle);Step 3: Defining the DSL Rules
Now we define the transformation rules on the ExpressionTransformer. Each rule finds a LOGO command and replaces it with a standard uCalc expression.
var t = uc.ExpressionTransformer;// Movement commands call our native callbackt.FromTo("FD {@Number:dist}", "Move({dist})");t.FromTo("BK {@Number:dist}", "Move(-{dist})");// Turning commands modify the angle variablet.FromTo("RT {@Number:deg}", "angle = angle - {deg}");t.FromTo("LT {@Number:deg}", "angle = angle + {deg}");// Pen commands modify the boolean flagt.FromTo("PU", "pen_down = false");t.FromTo("PD", "pen_down = true");Step 4: Handling Loops with REPEAT
The 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.
// The body is captured as a literal string and passed to ForLoop// RewindOnChange is crucial for processing the commands inside the bodyt.FromTo("REPEAT {@Number:n} '[' {body} ']'").RewindOnChange = true;Step 5: Putting It All Together
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.
⚖️ Why uCalc? (Comparative Analysis)
- vs. Manual Parsing: Writing a parser for LOGO from scratch would require a tokenizer, a state machine to handle
REPEATblocks, and an interpreter. This would be hundreds of lines of complex code. uCalc reduces this to a handful of declarative rules. - vs. Parser Generators (ANTLR): Tools like ANTLR are static. To add a new command like
CIRCLE, you would need to modify a grammar file and recompile. With uCalc, you can add a newFromTorule at runtime, making the language extensible. - Integration: The seamless integration of the ExpressionTransformer (for the DSL syntax) and the Expression Parser (for the math and state changes) is the key. The ability to bridge to high-performance native code via callbacks provides the best of both worlds.
Examples
A succinct example demonstrating basic forward and turn commands.
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