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: 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:

The 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 REPEAT blocks, 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 new FromTo rule 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)
				
					#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;
}
				
			
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
				
			
Drawing line to (0,100)
Drawing line to (50,100)
Final Position: (50, 100)