AOTLogoSharp.Drawing
1.4.0
dotnet add package AOTLogoSharp.Drawing --version 1.4.0
NuGet\Install-Package AOTLogoSharp.Drawing -Version 1.4.0
<PackageReference Include="AOTLogoSharp.Drawing" Version="1.4.0" />
<PackageVersion Include="AOTLogoSharp.Drawing" Version="1.4.0" />
<PackageReference Include="AOTLogoSharp.Drawing" />
paket add AOTLogoSharp.Drawing --version 1.4.0
#r "nuget: AOTLogoSharp.Drawing, 1.4.0"
#:package AOTLogoSharp.Drawing@1.4.0
#addin nuget:?package=AOTLogoSharp.Drawing&version=1.4.0
#tool nuget:?package=AOTLogoSharp.Drawing&version=1.4.0
AOTLogoSharp
Logo programming language for managed world.
What's Logo?
Logo is a programming language that controls a turtle on the screen to draw amazing pictures, like below:

More beautiful pictures drawn by AOTLogoSharp:
Box Picture

Beautiful Flower

Square Flower

Web

Normal distribution curve

t-distribution curve

Logo is widely used in computer education for kids, as it is simple and interesting. AOTLogoSharp is a .NET implementation of the Logo programming language which is based on a hand-written recursive descent parser.
Why AOTLogoSharp?
AOTLogoSharp is fully compatible with Native AOT and trimming. You can publish your application as a single native executable with minimal size and fast startup, without any JIT overhead. This makes it ideal for scenarios where deployment size and startup speed matter, such as command-line tools, educational apps, or embedded drawing pipelines.
The assembly names remain LogoSharp.dll and LogoSharp.Drawing.dll, and the namespace stays as LogoSharp / LogoSharp.Drawing, so existing code that references the original LogoSharp can migrate by simply swapping the NuGet package.
How to use AOTLogoSharp?
It is very simple and straightforward to use AOTLogoSharp in your .NET projects that is either based on .NET Framework 4.8, .NET Standard 2.0, or .NET 10. This means that you can make your AOTLogoSharp app working with .NET Core and thereby provides the cross-platform capability.
- Install AOTLogoSharp NuGet Package, for example:
Install-Package AOTLogoSharp -Version 1.4.0
- Write your first app:
using LogoSharp;
static void Main(string[] args)
{
var logo = new Logo();
logo.Forward += (s, e)
=> Console.WriteLine($"Forwarded {e.Steps} steps.");
logo.Execute("FD 102");
}
- Run your app, you should get the message
Forwarded 102 steps.on your console
Basically, a Logo program code is provided to the Execute method of the logo instance, AOTLogoSharp will execute the code and emit the events. Therefore, event handlers that subscribe to a particular event will be hit once the execution of the code emits the subscribed event.
The Logo class exposes the HeadingMode property to control the semantics of SETH / SETHEADING:
"logo"(default): 0° = north, clockwise. This matches the standard Logo language specification."standard": 0° = east, counter-clockwise. This matches the mathematical polar coordinate convention.
var logo = new Logo();
logo.HeadingMode = "standard"; // switch to mathematical heading semantics
logo.Execute("SETH 0"); // now points east instead of north
The Logo class also exposes the WaitMode property to control the unit of the WAIT command:
"logo"(default): 1 tick = 1/60 second (~16.67 ms), following traditional Logo specifications."ms"/"standard": 1 tick = 1 millisecond.
var logo = new Logo();
logo.WaitMode = "logo"; // WAIT 1 ≈ 16.67 ms
logo.Execute("WAIT 60"); // waits ~1 second
PRINT Command and Print Event
AOTLogoSharp supports the PRINT command with multiple forms:
PRINT "text— output a string literal (everything after"up to the next whitespace).PRINT 42— output a numeric value.PRINT [word1 word2 ...]— output a list literal as a space-separated sentence.PRINT (expr)— output an expression result.PRINT ([text] :var)— enhanced: list + variable concatenation with spaces.PRINT ([text] + :var)— enhanced: list + variable concatenation without spaces (+joins).PRINT (SE 1 2 3)— enhanced: multi-arg SE/WORD/LIST function call.
Example:
MAKE "x 42
PRINT ([The answer is] :x) ; The answer is 42
PRINT ([Answer:] + :x) ; Answer:42
PRINT ([COS 45=] + {COS 45}) ; COS 45=0.707106781186548
PRINT (SE 1 2 3) ; 1 2 3
PRINT (WORD A B C) ; ABC
Each PRINT invocation fires the Print event, which is exposed as EventHandler<PrintEventArgs>. The Text property carries the rendered text. String literal contents preserve the user's original casing, so PRINT "Hello outputs Hello exactly as written, even though keywords such as PRINT are matched case-insensitively.
using LogoSharp;
var logo = new Logo();
logo.Print += (s, e) => Console.WriteLine($"[print] {e.Text}");
logo.Execute(@"
PRINT [Test OK]
PRINT ""Hello World
PRINT 3.14
");
Error Reporting
Runtime errors (for example, calling an undefined procedure) are reported with a precise [line:column] location in the source program. When multiple errors occur in a single Execute call, the engine aggregates them into a single RuntimeException whose Message contains every error on its own line, so a host UI can display each error as a separate list item.
STOP Command and Recursion/Loop Limits
STOP is a built-in keyword that immediately exits the innermost running procedure. It does not fire any event — instead it throws an internal StopException that the procedure dispatcher swallows, so a host UI does not see a crash. STOP outside any procedure has no effect (the exception escapes without anything to catch it and is rethrown as a normal RuntimeException).
The engine guards against runaway Logo programs with two public properties on Logo:
MaxRepeatIterations(default10000) — caps the count of a singleREPEATand the per-step iterations of aWHILEloop. Exceeding it throwsRuntimeExceptionwith aStackOverflowExceptionprefix.MaxCallStackDepth(default500) — caps the recursion depth of user-defined procedure calls.
var logo = new Logo
{
MaxRepeatIterations = 1000000, // allow long-running drawings
MaxCallStackDepth = 2000 // allow deeper recursion
};
logo.Execute("REPEAT 1000000 [FD 1]");
For an additional process-level safety net, the static LogoSharp.MemoryGuard helper watches the process's private memory and exits the process as soon as it crosses a threshold. This catches scenarios that pure language-level limits cannot (e.g. runaway AST growth from pathological input):
using LogoSharp;
// Start a 1 GiB watchdog (defaults: 1024 MB threshold, 1000 ms check interval).
MemoryGuard.Start(thresholdMB: 1024, checkIntervalMs: 1000);
// ... run Logo code ...
MemoryGuard.Stop();
For more information about how to use AOTLogoSharp, please refer to the LogoSharp.Drawing project.
Using AOTLogoSharp.Drawing
AOTLogoSharp.Drawing provides a Turtle class that renders Logo programs to images. It is also available as the AOTLogoSharp.Drawing NuGet package.
- Install AOTLogoSharp.Drawing NuGet Package, for example:
Install-Package AOTLogoSharp.Drawing -Version 1.4.0
- Write your first app:
using LogoSharp;
using LogoSharp.Drawing;
using SkiaSharp;
static void Main(string[] args)
{
var turtle = new Turtle();
var logo = new Logo();
// Wire Logo events to the Turtle
logo.TurnLeft += (s, e) => turtle.Left(e.Angle);
logo.TurnRight += (s, e) => turtle.Right(e.Angle);
logo.SetHeading += (s, e) => turtle.Angle = e.Angle;
logo.Forward += (s, e) => turtle.MoveForward(e.Steps);
logo.Backward += (s, e) => turtle.MoveBackward(e.Steps);
logo.PenUp += (s, e) => turtle.PenStatus = PenStatus.Up;
logo.PenDown += (s, e) => turtle.PenStatus = PenStatus.Down;
logo.SetPenColor += (s, e) =>
{
turtle.SetPenColor(e.R, e.G, e.B);
var color = Color.FromRgb(
(byte)Math.Clamp(e.R, 0, 255),
(byte)Math.Clamp(e.G, 0, 255),
(byte)Math.Clamp(e.B, 0, 255));
// Key: UI actions must in UI thread
Dispatcher.UIThread.Invoke(() =>
{
PenColor = new SolidColorBrush(color);
StrokeColor = color;
});
};
logo.SetPenSize += (s, e) => turtle.SetPenWidth(e.Width);
logo.Delay += (s, e) => turtle.DelayMilliseconds = e.Milliseconds;
logo.Wait += (s, e) =>
{
turtle.WaitMode = logo.WaitMode;
turtle.WaitOneShot(e.Ticks);
};
logo.ClearScreen += (s, e) => turtle.Clear();
logo.GoHome += (s, e) => turtle.Reset();
logo.ShowTurtle += (s, e) => turtle.ShowTurtle();
logo.HideTurtle += (s, e) => turtle.HideTurtle();
logo.PenErase += (s, e) => turtle.PenErase();
logo.PenNormal += (s, e) => turtle.PenNormal();
logo.SetX += (s, e) => turtle.SetX(e.X);
logo.SetY += (s, e) => turtle.SetY(e.Y);
logo.SetXY += (s, e) => turtle.SetXY(e.X, e.Y);
logo.Execute(@"
REPEAT 4 [
FD 100
RT 90
]
");
turtle.Save("output.png");
}
The Turtle class exposes the following APIs:
SetCanvasSize(int width, int height)- set the canvas sizeShowTurtle()/HideTurtle()- toggle turtle visibilitySave(string fileName)- save the rendered image to a fileGetLineSegments()- get the list of drawn line segments for custom renderingGetTurtleState()- get the current turtle position, angle, and visibilityDelayMilliseconds- control the delay between drawing steps. The delay only applies to drawing operations (PENDOWN / PENERASE). Non-drawing moves (e.g.SetX/SetY/SetXY/MoveForwardwhile PENUP) are instantaneous, so repositioning the turtle with the pen up does not stall the program.Delay behavior:
0ms: instantaneous drawing, no sleep overhead.1–19ms: batches multiple segments into a single sleep to amortize OS scheduling overhead. For example, at 1 ms delay, 20 segments are drawn before a single 20 ms sleep; at 10 ms, 2 segments before a 20 ms sleep.20ms or higher: one segment per sleep, matching the requested delay exactly.
On Windows 10 (1803+) and above, .NET 10.0/.NET Framework 4.8, delays use QuickTickLib for high-precision timing via IO Completion Ports. On older Windows versions or unsupported platforms, it falls back to
Thread.Sleep.
Key Features
AOTLogoSharp provides the following commands and features:
- Basic Pen Commands
- PENDOWN/PD
- PENUP/PU
- SETPENCOLOR/SETPC/PC
- SETPENSIZE
- PENERASE/PE
- PENNORMAL/PN
- Basic Drawing Commands
- LEFT/LT (anticlockwise)
- RIGHT/RT (clockwise)
- FORWARD/FD
- BACKWARD/BK/BACK
- DRAW/CLS/CLEARSCR/CLEARSCREEN/CS
- Turtle Control Commands
- HOME
- SHOWTURTLE/ST
- HIDETURTLE/HT
- SETX/SETY/SETXY
- SETH/SETHEADING (Logo semantics: 0° = north, clockwise; set
logo.HeadingMode = "standard"for 0° = east, counter-clockwise)
- Flow Control Commands
- REPEAT and RepCount
- IF/IFELSE
- WHILE
- DELAY
- WAIT
- PRINT (enhanced: mixed concatenation,
+no-space join, multi-arg SE/WORD/LIST) - STOP (exits the innermost running procedure)
- OUTPUT/OP/RETURN
- Language Features
- Variables (The MAKE command)
- Expressions
- Arithmetic:
+,-,*,/,^ - Comparison:
==,<>,<,>,<=,>= - Logic: AND, OR, NOT
- Braced expressions:
{expr}(inline computation, not just named functions)
- Arithmetic:
- Procedures
- Function Calls
- SQRT
- RANDOM
- Trigonometric (degrees): SIN/COS/TAN/COT/SEC/CSC
- Inverse trig (returns degrees): ASIN/ACOS/ATAN/ACOT/ASEC/ACSC, aliases: ARCSIN/ARCCOS/ARCTAN/ARCCOT/ARCSEC/ARCCSC
- Hyperbolic (radians): SINH/COSH/TANH/COTH/SECH/CSCH
- Inverse hyperbolic (returns radians): ASINH/ACOSH/ATANH/ACOTH/ASECH/ACSCH, aliases: ARCSINH/ARCCOSH/ARCTANH/ARCCOTH/ARCSECH/ARCCSCH
- Special functions: TGAMMA (Gamma)/LGAMMA (ln|Γ|)/ERF/ERFC/ERFCX
- ABS
- POWER
- EXP
- LOG (natural) / LOG10, alias: LN
- SE/WORD/LIST (multi-arg via
(SE 1 2 3))
- Inline Comments
- Built-in Constants
- PI
- E
Important: All function calls (both built-in and TO...END user-defined procedures) MUST be wrapped in curly braces
{ }. For example,{SQRT 2},{tpdf 1 5}. Without curly braces, the parser cannot disambiguate and will report "Expected expression". User-defined procedures are no exception: define withTO procname :a :b ... END, then call with{procname 1 2}. When calling user-defined procedures inside SETXY or other commands, always use curly braces:SETXY -100 {catenary 1 2} * 40.
API Breaking Changes (v1.3 → v1.4)
ConstantExpressionNode(publicclass):Valueproperty type changed fromfloattodouble, constructor signature updated accordingly.- All numeric computations now use double precision (aligns with UCBLogo/MSWLogo spec), no more single-precision truncation.
- License changed from MIT to BSD-3-Clause (see
THIRD-PARTY-NOTICE.txtfor NetTopologySuite, SkiaSharp, PolySharp, QuickTickLib licenses).
Limitations
- Code editing doesn't support multi-line format
- Logo commands other than the ones listed above are not supported. More will be added in future
- Function calls (both built-in and user-defined procedures) MUST be wrapped in curly braces, e.g.
{SQRT 2}or{tpdf 1 5} PRINT "textreads the literal up to the next whitespace or delimiter (parenthesis / bracket). To include spaces inside the printed text, use a list literal:PRINT [Hello World]- Quoted identifiers (
"name) and variable/keyword matching are both case-insensitive, but the text inside aPRINT "textliteral is preserved verbatim in its original casing.
License
BSD-3-Clause. See LICENSE. Third-party licenses are listed in THIRD-PARTY-NOTICE.txt.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8
- AOTLogoSharp (>= 1.4.0)
- NetTopologySuite (>= 2.6.0)
- NetTopologySuite.Features (>= 2.2.0)
- QuickTickLib (>= 2.3.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
-
.NETStandard 2.0
- AOTLogoSharp (>= 1.4.0)
- NetTopologySuite (>= 2.6.0)
- NetTopologySuite.Features (>= 2.2.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
-
net10.0
- AOTLogoSharp (>= 1.4.0)
- NetTopologySuite (>= 2.6.0)
- NetTopologySuite.Features (>= 2.2.0)
- QuickTickLib (>= 2.3.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.