Skip to content
Ben Rosenberg edited this page Feb 17, 2025 · 2 revisions

Durtle documentation

Table of contents

Overview

Durtle is a turtle-oriented language, meaning that its primary focus is drawing programmatically.

Limitations

Durtle has some (intentional) limitations:

  • Angle movement is limited to multiples of 90 degrees $\left(\frac{\pi}{2}\right)$
  • There is only one variable available for modification/storage
  • There are (only?) 25 color options (one for each alphabetical character except v)

The angle limitation is due to the way Durtle is written - movement is restricted to the cardinal directions.

The single-variable constraint is meant as a challenge - how complex of a program can you write using only one variable at a time? The idea is to force users to rely on passing colors to functions and/or using the pixels colored by the turtle as memory.

The last limitation is due to Durtle's syntax, which uses the letter v for the "move down" command.

Syntax

The syntax of Durtle is best described with the help message (viewable using ??):

Help for durtle
---------------

  Notes:
    X is anything that evalues to a number
    C is a color (any lowercase letter besides 'v')
    N is a number (integer)
    Functions cannot be defined inside a function definition

  Movement and direction:
    >X    Move X units right
    <X    Move X units left
    ^X    Move X units up
    vX    Move X units down
    |X    Add X * 90 to the angle in degrees

  Pen and color:
    .    Pen down
    ,    Pen up
    C    Select color
    !    Set color to color at current location

  Variable:
    :X    Set variable to the numerical value of X
    +X    The value of the variable plus X
    -X    The value of the variable minus X
    *X    The value of the variable times X
    /X    The value of the variable divided by X
    @X    The value of the variable raised to the power X
    $X    The value of X raised to the power of the variable
    %X    The value of the variable mod X
    _     The current variable value

  Control flow:
    [...]X          Loop X times
    [...]C          Loop until current color is C
    ~X;...;...\     If/Then/Else with guard "variable == X"
    ~C;...;...\     If/Then/Else with guard "current color == C"
    ~X;...\         If/Then with guard "variable == X"
    ~C;...\         If/Then with guard "current color == C"

  Output:
    ?;    Toggle printing style (default is no line breaks)
    ??    Print this help message
    ?!    Print the current color name
    ?_    Print the variable value
    ?:    Print the variable's ASCII character
    ?|    Print the current heading as a multiple of 90 degrees

  Functions:
    (...)N    Define a function with ID N
    {CX}N     Call function N with parameters C and X

  Miscellaneous:
    `     Begin/end a comment

Examples

The below examples illustrate some fractals created using recursion. You can see the images for these in the README. Try running them in the REPL!

Sierpinski triangle

(~4;[>1^2]_[>1v2]_<*2;{#/2}1,>_.{#/2}1,</2^_.{#/2}1,</2v_.\)1.{g512}1

Cantor set fractal

(~1;>_;v_>_<_{#/3}1>/3{#/3}1^_\)1.{a243}1

Simple grid of squares

(~4;.>_v_<_^_,;{#/2}1>/2{#/2}1v/2{#/2}1</2{#/2}1^/2\)1{b128}1

Hilbert curve

(~0;_;~a;|1;|3\~a;{r-1}1;{a-1}1\^2~a;|3;|1\~a;{a-1}1;{r-1}1\^2~a;{a-1}1;{r-1}1\~a;|3;|1\^2~a;{r-1}1;{a-1}1\~a;|1;|3\\)1.{a8}1

Color mapping

There is a color for each letter of the alphabet, except v (because v is used as the "move down" command).

Durtle's 25 colors from a to z

The background color is white by default. The initial pen color is also white (so drawing will not have any effect until a color is selected).

Other than that, colors come from the xkcd list. Here is the mapping from character to color name:

const string[char] colorNames = [
    'a': "azure", 'b': "black", 'c': "cyan",
    'd': "denim", 'e': "eggplant", 'f': "fawn",
    'g': "green", 'h': "heliotrope", 'i': "indigo",
    'j': "jade", 'k': "kiwi", 'l': "lichen",
    'm': "magenta", 'n': "navy", 'o': "orange",
    'p': "pink", 'q': "turquoise", 'r': "red",
    's': "silver", 't': "teal", 'u': "umber",
    'w': "white", 'x': "bordeaux",
    'y': "yellow", 'z': "maize"
];

And here is the mapping from character to RGB color:

const Color[char] colorMap = [
    'a': Color(6, 154, 243), 'b': Color(0, 0, 0),
    'c': Color(0, 255, 255), 'd': Color(59, 99, 140),
    'e': Color(56, 8, 53), 'f': Color(207, 175, 123),
    'g': Color(21, 176, 26), 'h': Color(217, 79, 245),
    'i': Color(56, 2, 130), 'j': Color(31, 167, 116),
    'k': Color(156, 239, 67), 'l': Color(143, 182, 123),
    'm': Color(194, 0, 120), 'n': Color(1, 21, 62),
    'o': Color(249, 115, 6), 'p': Color(255, 129, 192),
    'q': Color(6, 194, 172), 'r': Color(229, 0, 0),
    's': Color(197, 201, 199), 't': Color(2, 147, 135),
    'u': Color(178, 101, 0), 'w': Color(255, 255, 255),
    'x': Color(123, 0, 43), 'y': Color(255, 255, 20),
    'z': Color(244, 209, 84)
];

Log levels

There are currently 5 different log levels:

const enum LogLevel {
    // log everything
    LOGGING_ALL = 0,
    // log everything except movement instructions
    LOGGING_NO_MOVEMENT = 1,
    // log only the generated parse tree
    LOGGING_PARSETREE = 2,
    // the REPL default is to just log the parse tree
    LOGGING_REPL = 2,
    // log nothing
    LOGGING_DISABLED = 3
}

Additionally, the runProgram function takes a boolean parameter logToFile, which will write the logs to a file log/log_<iso datetime string>.txt instead of stdout.

REPL (Read-Evaluate-Print-Loop)

Calling runRepl will open a REPL session in the shell, as well as a corresponding raylib window running in another thread. Currently, the REPL treats each entry as a completely new program and the raylib window is wiped each time; that may be changed in the future.

The main benefit of using the REPL is that it is much faster than building Durtle with a hardcoded program - the raylib window only opens once, and is then listening for pixel array updates passed by the main thread. This is very useful when trying to get a program to work.

The input is printed again at the end of any parse output, so that the user can easily copy it to modify it if their shell does not support command history.

To exit nicely from the REPL and close the UI window, type either exit or quit in the REPL and hit Enter.

Sample REPL session screenshot:

Sample REPL session with graphics

UI controls

The raylib UI has some minimal controls to move around and zoom in/zoom out:

  • Zoom in: Up arrow key
  • Zoom out: Down arrow key
  • Pan up/left/down/right: WASD keys
  • Quit: Escape key

Grammar

Durtle is parsed using a parsing expression grammar (PEG), which is a specification similar to BNF but can be used directly by a PEG parser (such as Pegged, the parser used by Durtle) to parse a program without having to write a lexer or parser manually.

Here is the PEG specification for Durtle:

Turtle:
    Terms       < (Instruction)+
    Instruction < Right / Left / Up / Down
                  / Loop / Color / Set / Get
                  / Add / Sub / Mul / Div / Pow
                  / RPow / Mod
                  / PenDown / PenUp / PrintCmd
                  / Conditional / FuncDef
                  / FuncCall / Comment / Exp
                  / AngleChange / ColorCond
    AngleChange < "|" Exp
    Comment     < "`" (!"`" .)* "`"
    FuncDef     < "(" FuncTerms ")" Number
    FuncTerms   < FuncTerm+
    FuncTerm    < Right / Left / Up / Down
                  / FuncLoop / ColorExp / Set / Get
                  / Add / Sub / Mul / Div / Pow / RPow / Mod
                  / PenDown / PenUp / PrintCmd
                  / FuncCond / FFuncCall / FuncExp / Comment
                  / FColorCond / AngleChange
    FuncLoop    < "[" FuncTerms "]" (FuncExp / ColorExp)
    FuncCond    < FuncIfElse / FuncIf / FColorIfElse / FColorIf
    FuncIfElse  < "~" FuncExp ";" FuncTerms ";" FuncTerms "\\"
    FuncIf      < "~" FuncExp ";" FuncTerms "\\"
    FFuncCall   < "{" ColorExp FuncExp "}" Number
    FuncCall    < "{" ColorExp Exp "}" Number
    Conditional < IfElse / If
    ColorCond   < ColorIfElse / ColorIf
    FColorCond  < FColorIfElse / FColorIf
    IfElse      < "~" Exp ";" Terms ";" Terms "\\"
    If          < "~" Exp ";" Terms "\\"
    ColorIfElse < "~" ColorExp ";" Terms ";" Terms "\\"
    ColorIf     < "~" ColorExp ";" Terms "\\"
    FColorIfElse< "~" ColorExp ";" FuncTerms ";" FuncTerms "\\"
    FColorIf    < "~" ColorExp ";" FuncTerms "\\"
    ColorExp    < "#" / Color
    PrintCmd    < "?_" / "??" / "?!" / "?:" / "?;" / "?|"
    PrintToggle < ";"
    Right       < ">" Exp
    Left        < "<" Exp
    Up          < "^" Exp
    Down        < "v" Exp
    Loop        < "[" Terms "]" (Exp / ColorExp)
    Color       < [a-z]
    Set         < ":" Exp
    Get         < "!"
    PenDown     < "."
    PenUp       < ","
    MathExp     < Add / Sub / Mul / Div / Pow / RPow / Mod
    Add         < "+" Exp
    Sub         < "-" Exp
    Mul         < "*" Exp
    Div         < "/" Exp
    Pow         < "@" Exp
    RPow        < "$" Exp
    Mod         < "%" Exp
    Exp         < Number / Var / MathExp / FuncCall
                  / Conditional / ColorCond
    FuncExp     < Number / Var / MathExp / FuncCall
                  / FuncCond / FColorCond
    Var         < "_"
    Number      < ~([0-9]+)