A Logo-to-Python Transpiler

I remember very fondly some classes I had in elementary school that taught us Logo. Some days ago I got melancholic while learning of turtledemo and decided to create this toy project with no practical applications whatsoever, but one that I had a lot of fun with – a transpiler called chelodina1.

At the moment it can turn Logo code like this:

TO triangle :length
  REPEAT 3 [ FORWARD :length RIGHT 120 ]
END

TO flower :length :count
  REPEAT 150 [
    triangle :length
    RIGHT 360 / :count
  ]
END

TO web
  REPEAT 6 [ flower 150 18 ]
END

web

Into Python like this:

import turtle

def triangle(p_length):
    for _ in range(3):
        turtle.forward(p_length)
        turtle.right(120.0)

def flower(p_length, p_count):
    for _ in range(150):
        triangle(p_length)
        turtle.right(360.0 / p_count)

def web():
    for _ in range(6):
        flower(150.0, 18.0)

web()
turtle.done()

And it can compile the python code directly with chelodina --input file --run to look at the result:

You will find more details about its usage on Github.

Notes on its development and PLY

For chelodina I wanted to use a Yacc-like parser in Python, so I went with the sure-fire solution that is PLY – David Beazley has been maintaining this project since 20012.

Positive notes:

  • PLY is as tested as they come, once you get the hang of it, the lexer and parser error handling is very helpful along the way.
  • PLYs documentation is amazing – it's so detailed it feels like someone is walking you through it.
  • Python's documentation for the ast module is not particularly helpful, thankfully Green Tree Snakes more than makes up for it.

Things I didn't enjoy:

  • PLY behaves more like a framework than a library, I disliked some assumptions that it makes. It is just a hassle to try and figure out what magic is going under the hood.
    • Looking for variables and methods prefixed t_ in the lexer and p_ in the parser.
    • Uses docstrings for regex definitions in the lexer and BNF grammar in the parser.
    • Not following conventions for the slicing operator on special vars, in some cases like [-1] behaves differently.
    • Reserved words.
  • I'd rather use EBNF than BNF, but that's a nitpick.

For a future project, I would probably look into a more pythonic library like Lark. It's harder to develop when the library is not being explicit, as you're always second-guessing if it was a problem with your code or you just broke another magic rule you didn't know about.

This is the current BNF grammar. I have only focused on making the examples work so it still has ways to go for a complete implementation.

program : statements

statements : statements statement
           | statement

statement : expression
          | funcdef
          | repeat

expression : NAME terms
           | NAME

terms : terms PLUS terms
      | terms MINUS terms
      | terms MULT terms
      | terms DIV terms
      | terms NUMBER
      | terms PARAM
      | NUMBER
      | PARAM

funcdef : TO NAME terms statements END
        | TO NAME statements END

repeat : REPEAT NUMBER LBRACKET expression RBRACKET

I plan to work on it sparingly –with UCBLogo as the target implementation– since it's been a fun project.

Footnotes

  1. That's funny because snake-necked turtles!

  2. And actively maintaining it, not just letting it sit pretty.