Lispy Logo is a toy programming language, a proof-of-concept implementation of the tiny scripting engine described in a No Time To Play article. You can use it as a model or starting point, for teaching, maybe even embed it.
Why? Because it's easier to reason about a real, working interpreter.
Why Logo? Because I'm familiar with the language, and it's a change of pace from the usual Lisp or Scheme lookalikes.
Why so large? Because working on it gave me all kinds of ideas for things to add. None of it complicates the internals, and it makes a good impression.
To implement Lispy Logo as shown here, you need a host language with a variant data type capable of holding any value, even anonymous functions.
As of May 2021, this project is abandoned; no idea if the D source code even still compiles.
To begin with, try something like:
[sqrt [add [mul 3 3] [mul 4 4]]]
[make "n [parse [prompt "Enter "two "numbers:]]] [make "a [item 0 n]] [make "b [item 1 n]] [ifelse [gt? a b] [alert "First!] [alert "Second!]]
[foreach [list 1 2 3] [alert ?]] [repeat 3 [alert #]]
See the tutorial for details.
As of 7 February 2019, Lispy Logo also supports property lists.
As of 11 February 2019,
pop are available in all implementations, working like their Logo counterparts. The
iffalse control structures were also added. Lispy Logo is now in beta.
As of 13 February 2019, the native port has proper command line options.
As of 15 February 2019, it's safe to compile the interpreter with
-release, as all builtins check their argument count.
Current version as of 15 February 2019 is 1.0.3 beta.
Apart from the online port (embedded above) you can download a source code archive to run locally.
All the code is open source under the MIT license. See inside for details.
Lispy Logo tutorial
A Lispy Logo script is made up of words grouped into lists. Words are separated by whitespace; lists sit in square brackets. A word normally denotes a variable lookup, while a list is a procedure call, with some exceptions:
- a number such as -15.3 is left alone;
- the empty list () is always just that.
You can also be explicit about it: a double-quote sign (") placed in front of a word or list (in short, an expression) causes it to be taken at face value. Beware, double quotes aren't paired like in other languages!
Last but not least, a few words have special status:
repeat. They look like procedures in normal use, but can't be looked up or deleted. These are called primitives instead.
Now let's try something a little more interesting than a "hello, world":
[sqrt [add [mul 3 3] [mul 4 4]]]
Note how all the words are in lowercase. Case matters in Lispy Logo, unlike classic dialects of the language. If you are familiar with those, there have been some changes apart from the obvious:
- arithmetic operations have been renamed to
- comparisons are now called
- other procedures work differently if they are present at all.
If you don't know either Lisp or Logo, don't panic! Lispy Logo is a lot simpler than either. Read on for a gentle introduction.
By itself, Lispy Logo is just a powerful calculator. Simply enter expressions like the first example above to be shown the result. You can do more than one-off calculations however. For example (use
alert instead of
[make "a 15] [ifelse [eq? [modulo a 2] 1] "odd "even] [foreach [list 1 2 3] [print ?]] [repeat 3 [print #]]
Note how the last two examples respond with
undefined) after they're done. That's because
repeat return nothing useful. But what do they do, exactly?
foreachtakes an expression, which must evaluate to a list, followed by any number of templates; for each element of the list, it runs each template with any question marks replaced by that element;
repeatworks much the same way, except it takes a number instead of a list; it runs each template that many times, replacing hash signs with the number of the current iteration, starting from 1;
ifelsetakes three expressions; it evaluates the first one, and depending on its truth value returns either the second or else the third.
For those unfamiliar with Logo,
make is the assignment operation. Note how the variable name has to be quoted. On the plus side, that means you can refer to a variable indirectly!
You can get a list of all constructs recognized by Lispy Logo with
More control structures
Lispy Logo also has more conventional ways to make loops and decisions:
[if [lt? 1 2] [make "a 1] [make "b 2] [make "c 3]] [while [lte? a 3] [print a] [make "a [add a 1]]] [until [gte? b 10] [print b] [make "b [mul b 2]]]
All of them take a condition followed by any number of other expressions. (Unlike
if doesn't have an "else" part.) Moreover, you're not limited to running fixed chunks of code exactly as you typed them. A few procedures help with that:
[run [list "add 1 2]] [apply add [list 1 2]] [invoke add 1 2]
Note that in the first example you can even use primitives! (Those don't work with
invoke.) You can refer to any procedure by name, store it in a variable and recall it later. For instance, to transform or filter a list:
[map sqrt [list 25 100 225]] [filter number? [list 1 "a 3 "b 5]]
All that is possible because Lispy Logo code is composed of the same lists you use to store and manipulate data. And to help you do more with lists, the language comes with about a dozen useful operations, as you'll see in moments.
Working with lists
You've already seen a way of making new lists. Another is simply to quote a literal one, like this:
"[1 2 3]. The difference is, the
list procedure receives its arguments already evaluated, so you can mix the results of calculations among your data. And once you have it, there are a few things you can do with it:
[list? ]tells if the first argument really is one;
[count ]tells you how many elements it has;
[empty? ]returns true if the count is zero, to spare you from typing
[eq? [count ] 0];
[item 2 [list 1 3 5]]returns the list item at the given index, if any (starting from one like in Lua, but unlike most modern languages);
[make "a [list 1 3 5]] [setitem 2 a 7]changes the list element at the given index to a new value.
All of these also work on words. Beware of differences between implementations: the Python interpreter will error out if the list is shorter than the index you give to
(Speaking of which: Logo lacks a concept of "null", or "None", so Lispy makes the
empty? procedure work double duty to detect those.)
Last but not least, a list is a lot more useful when you can add and remove elements at will:
[make "a [list 1 2 3]] [queue a 5] [dequeue a] [push a 0] [pop a]
Beware however that these change the original list. If you'd rather make a new one, use their companions instead:
[fput 0 a] [first a] [butfirst a] [lput 5 a] [last a] [butlast a]
lput, these also work on words. To make a new word however you need
[word "Lo "go].
Before concluding this section, there is another kind of list: the property list. This is what other languages call a dictionary, or map; in (Lispy) Logo they work a bit differently:
[pprop "synonyms "take "get] [gprop "synonyms "take] [remprop "synonyms "take]
For one thing, property lists are automatically created, unless a variable or procedure by that name already exists. And then, if the property list or the key doesn't yet exist,
gprop returns an empty list instead of making it an error. It is an error however if you try to read or set the properties of something that isn't a property list.
One more thing
Until now, all the code examples here have been one-liners. That's because they were intended for the interactive console. But Lispy Logo can run scripts of any length, where expressions extend over as many lines as needed.
For that purpose, get the downloadable interpreter. It knows a few more tricks, including a
load procedure that takes a file name as an argument and runs its contents in-place. These script files can even include comments: any line that begins with a semicolon (;) will be ignored.
That's about it for a starter. Enjoy Lispy Logo!