If you are impatient, you might want to go directly to the Short reference.
The Lisp2k command line looks like so:
windowsill [ -debug ] progfile
(The name of the Lisp2k interpreter is windowsill
. There is currently
no compiler available.) This executes the program in progfile. If
you give the -debug flag to windowsill, the evaluator will dump
out tracing information as it evaluates. You will probably find this
valuable if you do anything non-trivial in Lisp2k.
In Lisp2k, a program is a sequence (list) of instructions. The sequence might contain embedded sub-sequences. Effectively, this forms a tree-like structure which represents the program.
The tree structure is reflected in the source file by using white space. The toplevel sequence is written in the source file, line by line, from the beginning of the file downwards; you can make embedded sequences by inducing extra indentation. For example, the following source code:
x y z b wcorresponds to the following tree structure: x,y,(z,b),w. For a more complicated example:
a b c d e f g hcorresponds to the following tree structure: a,(b),(c,d,(e)),(f),g,(h). As you can see, a sequence with one item is a distinct object from an item by itself. By the way, the amount of indentation does not matter; the structure is decided by the relations of the indentations.
Data that are not sequences (lists) are called atoms. The Lisp2k model for atoms is particularly simple, there being only one type of them, the symbol. A symbol is like a name: it has textual outward appearance and identity. You can (and must) use symbols in any way you like.
There is one more shorthand: you can write a flat list on a single line, items separated by whitespace. Writing multiple items on a single line always induces a list, so if you also use indentation, you will end with two levels of embedding. For example:
ghee amma omma dohis equivalent to the tree structure ghee,((amma,omma),doh).
As in many Lisp-like languages, evaluation in Lisp2k proceeds by executing instructions in the top-level sequence. These instructions, in turn, might cause the evaluation of sub-sequences, etc. However, there are some big differences with Lisp:
The execution of the sequence of instructions is called evaluation. In Lisp2k, any operation might produce zero, one or multiple results. These results are gathered in a list as the evaluation proceeds. Often this result list is just thrown away; at other times, it is re-inspected, particularly by evaluating it again.
In addition to the result of evaluation, operations might have side effects. The only kinds of side effects in Lisp2k are I/O and changing symbol bindings. More about those in the following section.
Every Lisp2k program consists of the Lisp2k primitives, all of which are special forms. Some primitives require some specific type / kind of parameters and will terminate the program if not given them. At a later stage, some polymorphism might be introduced to the basic operations, as it becomes clearer what kind of primitives would benefit the users most.
Lisp2k supports many programming styles, which are conveniently defined by the primitives used in each. The sets of operations are, by programming style:
The primitives are as follows:
pattern) to the second one (the
value) to bind values thus:
matchand are errors.
By the way, the result of evaluating a symbol is its value. That is, if the Lisp2k evaluator encounters a symbol, it produces its value in the output. Symbol replacement always yields a single value (remember, operations in Lisp2k might yield any number of results.) Symbols that have no assigned value are replaced by a special symbol, nil.
quoteoperation. The q primitive takes one argument and produces a singleton sequence[1] from it as a result. This is a kind of quoting, because sequences, in turn, evaluate to their contents.
[1] a sequence with
only one element
That is, if the evaluator processes, for example, the list q,a, then the result is (a) which, if re-evaluated, expands to a. Because sequences evaluate to their contents and might be of any length, evaluating a sequence might yield any number of results. (The sequences formed by q are always singleton, however, so you can count on them returning a single result when evaluated.)
For example, if you concatenate the three-item sequence a,(b,c),d and the four-item sequence g,h,i,j, you get the seven-item sequence a,(b,c),d,g,h,i,j. If you concatenate the symbol a and the empty sequence (), you get the one-item sequence containing a.
executebut is really the equivalent of Lisp's (eval). Unlike Lisp, its (single) argument must always be a sequence. (Symbol arguments to x might provide an interface to first-class stoppinguations in future versions.)
What x does is a little bit difficult to explain. Basically, x takes the result of the evaluation of its argument sequence (remember evaluation always yields a sequence of results), and re-evaluates it. This technique, used with different combinations of other primitives, can be used to form code at runtime — the famous subject of pride of Lisp aficionados.
For example, if you evaluate the structure x,((q,b),c,((d,e))), x double-evaluates the sequence (q,b),c,((d,e)), producing on the first run q,b,nil,(d,e),[2] and on the second run, (b),nil,d,e. Note how the q instruction got evaluated on the second go.
[2] actually c is the concatenation
operation, so this would produce a runtime error because of the
missing operand
[3] It is interesting to note that (eval) and
(apply) make a pretty complete language by themselves if we
augment (apply) a little, as we are doing here.
First, the value is matched against the pattern using the same rules as for the d primitive. But the resulting bindings are not put in the global symbol table — instead, they are used only to fill in the template, where occurrences of symbols bound by the match are substituted with the symbol's value. The resulting template is returned (apply always returns a single value).
For example, if you apply the value xebboo with the pattern X to the template X,(X,X), you get the result xebboo,(xebboo,xebboo). If you apply the value 1,2,3 with the pattern X,Y to the template X,Y,Y, you get the result 1,(2,3),(2,3).
[4] Actually, I'm very happy if someone bothers to shed
light on this.
ipperopp provides the means for structural programming in
two ways: first, it allows programs to use local variables,
which makes recursion safe and easy
; second, it allows
programs to build different kinds of execution environments
which, in turn, can be used as a sophisticated means of
parameter passing.
The input/output facilities are:
butt inin the peaceful pastimes of the poor user. Input is a relatively high-level facility akin to Lisp's (read), and always produces full-fledged values. Indentation is handled similarly to source files, but inbüt only reads one line of text per invocation. inbüt may yield any number of results.
Unlike most programming languages, Lisp2k treats empty lines as values, namely symbols. You can e.g. print an empty line, which causes a newline in the output, by writing pr followed by an empty line. Or you can define a value for empty lines.
Comments are supposed to work by putting a semicolon (;) somewhere. Comments extend to the end of line, and are treated as (non-significant) whitespace, except that a comment near the left edge of the screen affects Lisp2k's idea of how much indentation the line has.
Program structure and whitespace:
; this is comment (but it might not work) ; this is too, but it might cause a(n empty) subsequence x ; <- that instruction is on the top level d foo bar ; <- this is double-embedded seq: once for indent, ; <- second time for having multiple atoms Heppa ; <- this is a symbol /Heppa ; <- this is a list (a slash is a separate symbol) Heppa ; <- this is also a list (because of indentation) Heppa ; <- this is another list on the same level Heppa ; <- this is the second item of that list Heppa ; <- this is an embedded list ; The previous line is a symbol, the empty line
Evaluation:
form | expands to | side effects |
sym | nil or value | none |
(list of a b c) | list of a b c | none |
d foo bar | nothing | sets the value of foo to bar |
d foo (quux) | nothing | sets the value of foo to (quux) |
d (h t) (a b c) | nothing | sets h to a, t to (b c) |
d (x (y z)) ((a) (b)) | nothing | sets x to (a), y to b and z to () |
q zoq | (zoq) | none |
c (a b) (c e) | (a b c e) | none |
c a (b t) | (a b t) | none |
c u ö | (u ö) | none |
c () foo | (foo) | none |
x (foo) | foo => (quux) => quux | none unless induced by evaluation |
apply (1 2 3) (h t) h | 1 | none |
apply zuc z (z (z z) z) | (zuc (zuc zuc) zuc) | none |
apply ((y)) ((X Y) Z) Z | () | none |
ipperopp | nothing | creates a local variable enrironment |
pr foo | nothing | prints foo |
pr (foo bar) | nothing | prints foo barand a newline |
inbüt | anything | reads input from user |
debug | nothing | dumps symbol table contents |