0% found this document useful (0 votes)
59 views

The Pico Lisp Machine

This document provides an introduction and overview of the Pico Lisp programming language. It discusses that Pico Lisp was designed to explore what constitutes a minimal but useful virtual machine architecture. The document then summarizes that Pico Lisp emphasizes simplicity, being unlimited, dynamic behavior, and practical usability. It proceeds to describe the basic data structure of Pico Lisp, called a cell, and how different data types like numbers, symbols, and lists are constructed from cells. The document provides examples of how properties are represented in symbols and concludes by describing the different types of symbols in Pico Lisp.

Uploaded by

chintuleo
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views

The Pico Lisp Machine

This document provides an introduction and overview of the Pico Lisp programming language. It discusses that Pico Lisp was designed to explore what constitutes a minimal but useful virtual machine architecture. The document then summarizes that Pico Lisp emphasizes simplicity, being unlimited, dynamic behavior, and practical usability. It proceeds to describe the basic data structure of Pico Lisp, called a cell, and how different data types like numbers, symbols, and lists are constructed from cells. The document provides examples of how properties are represented in symbols and concludes by describing the different types of symbols in Pico Lisp.

Uploaded by

chintuleo
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 20

The Pico Lisp Reference

Introduction

Pico Lisp is the result of a language design study, trying to answer the question "What is
a minimal but useful architecture for a virtual machine?". Because opinions differ about
what is meant by "minimal" and "useful", there are many answers to that question, and
people might consider other solutions more "minimal" or more "useful". But from a
practical point of view, Pico Lisp proved to be a valuable answer to that question.

First of all, Pico Lisp is a virtual machine architecture, and then a programming language.
It was designed in a "bottom up" way, and "bottom up" is also the most natural way to
understand and to use it: Form Follows Function.

Pico Lisp was used in several commercial and research programming projects since 1988.
Its internal structures are simple enough, allowing an experienced programmer always to
fully understand what's going on under the hood, and its language features, efficiency and
extensibility make it suitable for almost any practical programming task.

In a nutshell, emphasis was put on four design objectives. The Pico Lisp system should
be

Simple
The internal data structure should be as simple as possible. Only one single data
structure is used to build all higher level constructs.
Unlimited
There are no limits imposed upon the language due to limitations of the virtual
machine architecture. That is, there is no upper bound in symbol name length,
number digit counts, stack depth, or data structure and buffer sizes, except for the
total memory size of the host machine.
Dynamic
Behavior should be as dynamic as possible ("run"-time vs. "compile"-time). All
decisions are delayed till runtime where possible. This involves matters like
memory management, dynamic symbol binding, and late method binding.
Practical
Pico Lisp is not just a toy of theoretical value. It is used since 1988 in actual
application development, research and production.

The Pico Lisp Machine

An important point in the Pico Lisp philosophy is the knowledge about the architecture
and data structures of the internal machinery. The high-level constructs of the
programming language directly map to that machinery, making the whole system both
understandable and predictable.

1
This is similar to assembly language programming, where the programmer has complete
control over the machine.

The Cell

The Pico Lisp virtual machine is both simpler and more powerful than most current
(hardware) processors. At the lowest level, it is constructed from a single data structure
called "cell":

+-----+-----+
| CAR | CDR |
+-----+-----+

A cell is a pair of machine words, which traditionally are called CAR and CDR in the Lisp
terminology. These words can represent either a numeric value (scalar) or the address of
another cell (pointer). All higher level data structures are built out of cells.

The type information of higher level data is contained in the pointers to these data.
Assuming the implementation on a byte-addressed physical machine, and a pointer size
of typically 4 bytes, each cell has a size of 8 bytes. Therefore, the pointer to a cell must
point to an 8-byte boundary, and its bit-representation will look like:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx000

(the 'x' means "don't care"). For the individual data types, the pointer is adjusted to point
to other parts of a cell, in effect setting some of the lower three bits to non-zero values.
These bits are then used by the interpreter to determine the data type.

In any case, bit(0) - the least significant of these bits - is reserved as a mark bit for
garbage collection.

Initially, all cells in the memory are unused (free), and linked together to form a "free
list". To create higher level data types at runtime, cells are taken from that free list, and
returned by the garbage collector when they are no longer needed. All memory
management is done via that free list; there are no additional buffers, string spaces or
special memory areas (With two exceptions: A certain fixed area of memory is set aside
to contain the executable code and global variables of the interpreter itself, and a standard
push down stack for return addresses and temporary storage. Both are not directly
accessible by the programmer).

Data Types

2
On the virtual machine level, Pico Lisp supports exactly three base data types (Numbers,
Symbols and Cons Pairs (Lists)), the three scope variations of symbols (Internal,
Transient and External), and the special symbol NIL. They are all built from the single
cell data structure, and all runtime data cannot consist of any other types than these three.

The following diagram shows the complete data type hierarchy, consisting of the three
base types and the symbol variations:

cell
|
+--------+--------+
| | |
Number Symbol List
|
+--------+--------+--------+
| | | |
NIL Internal Transient External

Numbers

A number can represent a signed integral value of arbitrary size. The CAR of one or more
cells hold the number's "digits" (each in the machine's word size), to store the number's
binary representation.

Number
|
V
+-----+-----+ +-----+-----+ +-----+-----+
|'DIG'| ---+---> |'DIG'| ---+---> |'DIG'| / |
+-----+-----+ +-----+-----+ +-----+-----+

The first cell holds the least significant digit. The least significant bit of that digit
represents the sign.

The pointer to a number points into the middle of the CAR, with an offset of 2 from the
cell's start address. Therefore, the bit pattern of a number will be:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx010

Thus, a number is recognized by the interpreter when bit(1) is non-zero.

Symbols

A symbol is more complex than a number. Each symbol has a value, and optionally a
name and an arbitrary number of properties. The CAR of a symbol cell is also called VAL,
and the CDR points to the symol's tail. As a minimum, a symbol consists of a single cell,
and has no name or properties:

3
Symbol
|
V
+-----+-----+
| VAL | / |
+-----+-----+

That is, the symbol's tail is empty (points to NIL, as indicated by the '/' character).

The pointer to a symbol points to the CDR of the cell, with an offset of 4 from the cell's
start address. Therefore, the bit pattern of a symbol will be:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx100

Thus, a symbol is recognized by the interpreter when bit(2) is non-zero. In addition, it is


possible that bit(1) is also set for a symbol (This is the case for external symbols).

A property is a key-value-pair, represented as a cell in the symbol's tail. This is called a


"property list". The property list may be terminated by a number representing the
symbol's name. In the following example, a symbol with the name "abc" has three
properties:

Symbol
|
V
+-----+-----+
| VAL | ---+---+
+-----+-----+ | tail
|
+------------+
|
V name
+-----+-----+ +-----+-----+ +-----+-----+ +-----
+-----+
| | | ---+---> | KEY | ---+---> | | | ---+---> |'cba'|
/ |
+--+--+-----+ +-----+-----+ +--+--+-----+ +-----
+-----+
| |
V V
+-----+-----+ +-----+-----+
| VAL | KEY | | VAL | KEY |
+-----+-----+ +-----+-----+

Each property in a symbol's tail is either a symbol (then it represents a boolean value), or
a cell with the property key in its CDR and the property value in its CAR. In both cases, the
key should be a symbol, because searches in the property list are performed using pointer
comparisons.

4
The name of a symbol is stored as a number at the end of the tail. It contains the
characters of the name in UTF-8 encoding, using between one and three 8-bit-bytes per
character. The first byte of the first character is stored in the lowest 8 bits of the number.

All symbols have the above structure, but depending on scope and accessibility there are
actually four types of symbols:

NIL

NIL is a special symbol which exists exactly once in the whole system. It is used

• as an end-of-list marker
• to represent the empty list
• to represent the boolean value "false"
• to represent a string of length zero
• to represent the value "Not a Number"
• as the root of all class hierarchies

For that, NIL has a special structure:

NIL: /
|
V
+-----+-----+-----+-----+
| / | / | / | / |
+-----+--+--+-----+-----+

The reason for that structure is NIL's dual nature both as a symbol and as a list:

• As a symbol, it should give NIL for its VAL, and be without properties
• For the empty list, NIL should give NIL both for its CAR and for its CDR

These requirements are fulfilled by the above structure.

Internal Symbols

Internal Symbols are all those "normal" symbols, as they are used for function definitions
and variable names. They are "interned" into a hashed list structure, so that it is possible
to find an internal symbol by searching for its name.

5
There cannot be two different internal symbols with the same name.

Initially, a new internal symbol's VAL is NIL.

Transient Symbols

Transient symbols are only interned into a hashed list structure for a certain time (e.g.
while reading the current source file), and are released after that. That means, a transient
symbol cannot be accessed then by its name, and there may be several transient symbols
in the system having the same name.

Transient symbols are used

• as text strings
• as identifiers with a limited access scope (like, for example, static identifiers in
the C language family)
• as anonymous, dynamically created objects (without a name)

Initially, a new transient symbol's VAL is that symbol itself.

External Symbols

External symbols reside in a database file, and are loaded into memory - and written back
to the file - dynamically as needed, and transparent to the programmer.

The interpreter recognizes external symbols, because in addition to the symbol bit(2),
also bit(1) is set:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx110

There cannot be two different external symbols with the same name. External symbols
are maintained in hash structures while they are loaded into memory, and have their
external location (disk block, network URL, etc.) directly coded into their names.Initially,
a new external symbol's VAL is NIL, unless otherwise specified at creation time.

Lists

A list is a sequence of one or more cells, holding numbers, symbols, or lists. Lists are
used in Pico Lisp to emulate composite data structures like arrays, trees, stacks or queues.

In contrast to lists, numbers and symbols are collectively called "Atoms".

6
Typically, the CDR of each cell in a list points to the following cell, except for the last cell
which points NIL. If, however, the CDR of the last cell points to an atom, this cell is called
a "dotted pair" (because of its I/O syntax with a dot '.' between the two values).

Memory Management

The Pico Lisp interpreter has complete knowledge of all data in the system, due to the
type information associated with every pointer. Therefore, an efficient garbage collector
mechanism can easily be implemented. Pico Lisp employs a simple but fast mark-and-
sweep garbage collector.

As the collection process is very fast (in the order of milliseconds per megabyte), it was
not necessary to develop more complicated, time-consuming and error-prone garbage
collection algorithms (e.g. incremental collection). A compacting garbage collector is
also not necessary, because the single cell data type cannot cause heap fragmentation.

Invocation

When Pico Lisp is invoked from the command line, an arbitrary number of arguments
may follow the command name.

By default, each argument is the name of a file to be executed by the interpreter. If,
however, the argument's first character is a hyphen '-', then the rest of that argument is
taken as a function call (without the surrounding parentheses). A hyphen by itself as an
argument stops evaluation of the rest of the command line (it may be processed later
using the argv function). This mechanism corresponds to calling (load T).

As a convention, Pico Lisp source files have the extension ".l".

Note that the Pico Lisp executable itself does not expect or accept any command line
flags or options. They are reserved for application programs.

The simplest and shortest invocation of Pico Lisp does nothing, and exits immediately by
calling bye:

$ bin/pico -bye
$

In interactive mode, the Pico Lisp interpreter (see load) will also exit when an empty line
is entered:

$ bin/pico
:

7
$

To start up the standard Pico Lisp environment, several files should be loaded. The most
commonly used things are in "lib.l" and in a bunch of other files, which are in turn loaded
by "ext.l". Thus, a typical call would be:

$ bin/pico lib.l ext.l

The recommended way, however, is to call the "p" shell script, which includes "lib.l" and
"ext.l". Given that your current project is loaded by some file "myProject.l" and your
startup function is main, your invocation would look like:

$ ./p myProject.l -main

For interactive development and debugging it is recommended also to load "dbg.l", to get
the vi-style command line editor, single-stepping, tracing and other debugging utilities.

$ ./p dbg.l myProject.l -main

In any case, the path name to the first command line file argument is remembered
internally as the Pico Lisp Home Directory. This path is later automatically substituted
for any leading "@" character in file name arguments to I/O functions.

Input/Output

In Lisp, each internal data structure has a well-defined external representation in human-
readable format. All kinds of data can be written to a file, and restored later to their
orignial form by reading that file.

In normal operation, the Pico Lisp interpreter continuously executes an infinite "read-
eval-print loop". It reads one expression at a time, evaluates it, and prints the result to the
console. Any input into the system, like data structures and function definitions, is done
in a consistent way no matter whether it is entered at the console or read from a file.

Comments can be embedded in the input stream with the hash # character. Everything up
to the end of that line will be ignored by the reader.

: (* 1 2 3) # This is a comment
-> 6

Here is the I/O syntax for the individual Pico Lisp data types:

8
Numbers

A number consists of an arbitrary number of digits ('0' through '9'), optionally


preceeded by a sign character ('+' or '-'). Legal number input is:

: 7
-> 7
: -12345678901245678901234567890
-> -12345678901245678901234567890

Fixed-point numbers can be input by embedding a decimal point '.', and setting the
global variable *Scl appropriately:

: *Scl
-> 0

: 123.45
-> 123
: 456.78
-> 457

: (setq *Scl 3)
-> 3
: 123.45
-> 123450
: 456.78
-> 456780

Thus, fixed-point input simply scales the number to an integer value corresponding to the
number of digits in *Scl.

Formatted output of scaled fixed-point values can be done with the format function:

: (format 1234567890 2)
-> "12345678.90"
: (format 1234567890 2 "." ",")
-> "12,345,678.90"

Symbols

The reader is able to recognize the individual symbol types from their syntactic form. A
symbol name should - of course - not look like a legal number (see above).

In general, symbol names are case-sensitive. car is not the same as CAR.

NIL

9
Besides for standard normal form, NIL is also recognized as (), [], "" or {}.

: NIL
-> NIL
: ()
-> NIL
: ""
-> NIL

Output will always be as NIL.

Internal Symbols

Internal symbol names can consist of any printable (non-whitespace) character, except for
the following meta characters:

" # ' ( ) [ ] ` ~

As a rule, anything not recognized by the reader as another data type will be returned as
an internal symbol.

Transient Symbols

A transient symbol is anything surrounded by double quotes '"'. With that, it looks - and
can be used - like a string constant in other languages. However, it is a real symbol, and
may be assigned a value or a function definition, and properties. Initially, a transient
symbol's value is that symbol itself, so that it does not need to be quoted for evaluation:

: "This is a string"
-> "This is a string"

However, care must be taken when assigning a value to a transient symbol. This may
cause unexpected behavior:

: (setq "This is a string" 12345)


-> 12345
: "This is a string"
-> 12345

The name of a transient symbol can contain any character. A double quote character can
be escaped with a backslash '\', and a backslash itself has to be escaped with another
backslash. Control characters can be written with a preceding hat '^' character.

10
: "We^Ird\\Str\"ing"
-> "We^Ird\\Str\"ing"
: (chop @)
-> ("W" "e" "^I" "r" "d" "\\" "S" "t" "r" "\"" "i" "n" "g")

The hash table for transient symbols is cleared automatically before and after loading a
source file, or it can be reset explicitly with the ==== function. With that mechanism, it is
possible to create symbols with a local access scope, not accessible from other parts of
the program.

A special case of transient symbols are anonymous symbols, symbols without name (see
box or new). They print as a dollar sign ($) followed by a decimal digit string (actually
their machine address).

External Symbols

External symbol names are surrounded by braces ('{' and '}'). The characters of the
symbol's name itself identify the physical location of the external object. This is currently
the number of the starting block in the database file, encoded in base-64 notation
(characters '0' through '9', ':' through ';', 'A' through 'Z' and 'a' through 'z'). Later versions
might include other formats like Internet URL's.

Lists

Lists are surrounded by parentheses ('(' and ')').

(A) is a list consisting of a single cell, with the symbol A in its CAR, and NIL in its CDR.

(A B C) is a list consisting of three cells, with the symbols A, B and C respectively in


their CAR, and NIL in the last cell's CDR.

(A . B) is a "dotted pair", a list consisting of a single cell, with the symbol A in its CAR,
and B in its CDR.

Read-Macros

Read-macros in Pico Lisp are special forms that are recognized by the reader, and modify
its behavior. Note that they take effect immediately while reading an expression, and are
not seen by the eval in the main loop.

11
The most prominent read-macro in Lisp is the single quote character ', which expands to
a call of the quote function. Note that the single quote character is also printed instead of
the full function name.

: '(a b c)
-> (a b c)
: '(quote . a)
-> 'a
: (cons 'quote 'a)
-> 'a

A single backquote character ` will cause the reader to evaluate the following expression,
and return the result.

: '(a `(+ 1 2 3) z)
-> (a 6 z)

A tilde character ~ inside a list will cause the reader to evaluate the following expression,
and splice the result into the list.

: '(a b c ~(list 'd 'e 'f) g h i)


-> (a b c d e f g h i)

Brackets ('[' and ']') can be used as super parentheses. A closing bracket will match
the innermost opening bracket, or all currently open parentheses.

: '(a (b (c (d]
-> (a (b (c (d))))
: '(a (b [c (d]))
-> (a (b (c (d))))

Evaluation

Pico Lisp tries to evaluate any expression encountered in the read-eval-print loop.
Basically, it does so by applying the following three rules:

• A number evaluates to itself.


• A symbol evaluates to its value (VAL).
• A list is evaluated as a function call, with the CAR as the function and the CDR the
arguments to that function. These arguments are in turn evaluated according to
these three rules.

: 1234
-> 1234 # Number evaluates to itself
: *Pid

12
-> 22972 # Symbol evalutes to its VAL
: (+ 1 2 3)
-> 6 # List is evaluated as a function call

For the third rule, however, things get a bit more involved. First - as a special case - if the
CAR of the list is a number, the whole list is returned as it is:

: (1 2 3 4 5 6)
-> (1 2 3 4 5 6)

This is not really a function call but just a convenience to avoid having to quote simple
data lists.

Otherwise, if the CAR is a symbol or a list, Pico Lisp tries to obtain an executable function
from that, by either using the symbol's value, or by evaluating the list.

What is an executable function? Or, said in another way, what can be applied to a list of
arguments, to result in a function call? A legal function in Pico Lisp is

either
a number. When a number is used as a function, it is simply taken as a pointer to
executable code that will be called with the list of (unevaluated) arguments as its
single parameter. It is up to that code to evaluate the arguments, or not. Some
functions do not evaluate their arguments (e.g. quote) or evaluate only some of
their arguments (e.g. setq).
or
a lambda expression. A lambda expression is a list, whose CAR is either a symbol
or a list of symbols, and whose CDR is a list of expressions. Note: In contrast to
other Lisp implementations, the symbol LAMBDA itself does not exist in Pico
Lisp but is implied from context.

A few examples should help to understand the practical consequences of these rules. In
the most common case, the CAR will be a symbol defined as a function, like the * in:

: (* 1 2 3) # Call the function '*'


-> 6

Inspecting the VAL of *, however, gives

: * # Get the VAL of the symbol '*'


-> 67291944

The VAL of * is a number. In fact, it is the numeric representation of a C-function pointer,


i.e. a pointer to executable code. This is the case for all built-in functions of Pico Lisp.

13
Other functions in turn are written as Lisp expressions:

: (de foo (X Y) # Define the function 'foo'


(* (+ X Y) (+ X Y)) )
-> foo
: (foo 2 3) # Call the function 'foo'
-> 25
: foo # Get the VAL of the symbol 'foo'
-> ((X Y) (* (+ X Y) (+ X Y)))

The VAL of foo is a list. It is the list that was assigned to foo with the de function. It
would be perfectly legal to use setq instead of de:

: (setq foo '((X Y) (* (+ X Y) (+ X Y))))


-> ((X Y) (* (+ X Y) (+ X Y)))
: (foo 2 3)
-> 25
More Examples

: (de foo (X Y Z) # CAR is a list of symbols


(list X Y Z) ) # Return a list of all arguments
-> foo
: (foo (+ 1 2) (+ 3 4) (+ 5 6))
-> (3 7 11) # all arguments are evaluated

: (de foo X # CAR is a single symbol


X ) # Return the argument
-> foo
: (foo (+ 1 2) (+ 3 4) (+ 5 6))
-> ((+ 1 2) (+ 3 4) (+ 5 6)) # the whole unevaluated list is
returned

: (de foo @ # CAR is the symbol `@'


(list (next) (next) (next)) ) # Return the first three arguments
-> foo
: (foo (+ 1 2) (+ 3 4) (+ 5 6))
-> (3 7 11) # all arguments are evaluated

Interrupt

During the evaluation of an expression, the Pico Lisp interpreter can be interrupted at any
time by hitting Ctrl-C.

To come to shell prompt, Press CTRL d.

@ Result
In certain situations, the result of the last evaluation is stored in the VAL of the symbol @.
This can be very convenient, because it often makes the assignment to temporary
variables unnecessary.
load

14
In read-eval loops, the last three results which were printed at the console are
available in @@@, @@ and @, in that order (i.e the latest result is in @).
: (+ 1 2 3)
-> 6
: (/ 128 4)
-> 32
: (- @ @@) # Subtract the last two results
-> 26
Flow functions
Flow- and logic-functions store the result of evaluating their conditional
expression in @.
: (while (read) (println 'got: @))
abc # User input
got: abc # print result
123 # User input
got: 123 # print result
NIL
-> 123

: (setq L (1 2 3 4 5 1 2 3 4 5))
-> (1 2 3 4 5 1 2 3 4 5)
: L
-> (1 2 3 4 5 1 2 3 4 5)

@ is generally local to functions and methods, its value is automatically saved upon
function entry and restored at exit.

Comparing

In Pico Lisp, it is legal to compare data items of arbitrary type. Any two items are either

Identical
They are the same memory object (pointer equality). For example, two internal
symbols with the same name are identical.
Equal
They are equal in every respect (structure equality), but need not to be identical.
Examples are numbers with the same value, transient symbols with the same
name or lists with equal elements.
Or they have a well-defined ordinal relationship
Numbers are comparable by their numeric value, strings by their name, and lists
recursively by their elements (if the CAR's are equal, their CDR's are compared). For
differing types, the following rule applies: Numbers are less than symbols, and
symbols are less than lists. As special cases, NIL is always less than anything else,
and T is always greater than anything else.

To demonstrate this, sort a list of mixed data types:

15
: (sort '("abc" T (d e f) NIL 123 DEF))
-> (NIL 123 DEF "abc" (d e f) T)

[email protected]

A Pico Lisp Tutorial

(c) Software Lab. Alexander Burger

We recommend that you have a terminal window open, and try the examples by yourself.
You may either type them in, directly to the Pico Lisp interpreter, or edit a separate
source file (e.g. "test.l") in a second terminal window and load it into Pico Lisp with

: (load "test.l")
Defining Functions
Most of the time during programming is spent defining functions (or methods). In the
following we will concentrate on functions, but most will be true for methods as well
except for using dm instead of de.

The notorious "Hello world" function must be defined:

: (de hello ()
(prinl "Hello world") )
-> hello

The () in the first line indicates a function without arguments. The body of the function
is in the second line, consisting of a single statement. The last line is the return value of
de. From now on we will omit the return values of examples when they are
unimportant.You'll know that you can call this function as

: (hello)
Hello world

A function with an argument might look this way:

: (de hello (X)

(prinl "Hello " X) )


hello redefined

Pico Lisp informs you that you have just redefined the function. This might be a useful
warning in case you forgot that a bound symbol with that name already existed.

: (hello "world")
Hello world

: (hello "Alex")
Hello Alex

16
Normally, Pico Lisp evaluates the arguments before it passes them to a function:

: (hello (+ 1 2 3))

Hello 6
: (setq A 1 B 2) # Set 'A' to 1 and 'B' to 2
-> 2
: (de foo (X Y) # 'foo' returns the list of its arguments
(list X Y) )
-> foo
: (foo A B) # Now call 'foo' with 'A' and 'B'
-> (1 2) # -> We get a list of 1 and 2, the values of
'A' and 'B'

In some cases you don't want that. For some functions (setq for example) it is better if
the function gets all arguments unevaluated, and can decide for itself what to do with
them. For such cases you do not define the function with a list of parameters, but give it a
single atomic parameter instead. Pico Lisp will then bind all (unevaluated) arguments to
that list.

: (de foo X

(list (car X) (cadr X)) ) # 'foo' lists the first two


arguments

: (foo A B) # Now call it again


-> (A B) # -> We don't get '(1 2)', but '(A
B)'

: (de foo X
(list (car X) (eval (cadr X))) ) # Now evaluate only the second
argument

: (foo A B)
-> (A 2) # -> We get '(A 2)'

As a logical consequence, you can combine these principles. To define a function with 2
evaluated and an arbitrary number of unevaluated arguments:

: (de foo (X Y . Z) # Evaluate only the first two args


(list X Y Z) )

: (foo A B C D E)
-> (1 2 (C D E)) # -> Get the value of 'A' and 'B' and the
remaining list

More common, in fact, is the case where you want to pass an arbitrary number of
evaluated arguments to a function. For that, Pico Lisp recognizes the symbol @ as a single
atomic parameter and remembers all evaluated arguments in an internal frame. This
frame can then be accessed sequentially with the args, next, arg and rest functions.

17
: (de foo @
(list (next) (next)) ) # Get the first two arguments

: (foo A B)
-> (1 2)

Again, this can be combined:

: (de foo (X Y . @)
(list X Y (next) (next)) ) # 'X' and 'Y' are fixed arguments

: (foo A B (+ 3 4) (* 3 4))
-> (1 2 7 12) # All arguments are evaluated

These examples are not very useful, because the advantage of a variable number of
arguments is not used. A function that prints all its evaluated numeric arguments, each on
a line followed by its squared value:

: (de foo @
(while (args)
(println (next) (* (arg) (arg))) ) )

: (foo (+ 2 3) (- 7 1) 1234 (* 9 9))


5 25
6 36
1234 1522756
81 6561
-> 6561

Finally, it is possible to pass all these evaluated argument to another function, using
pass:

: (de foo @

(pass println 9 8 7) # First print all arguments preceded by


9, 8, 7
(pass + 9 8 7) ) # Then add all these values

: (foo (+ 2 3) (- 7 1) 1234 (* 9 9))


9 8 7 5 6 1234 81 # Printing ...
-> 1350 # Return the result
Debugging
At runtime: Tracing can be performed to accomplish debugging.

Tracing means letting functions of interest print their name and arguments when they are
entered, and their name again and the return value when they are exited.

For demonstration, let's define the unavoidable factorial function (or just load the file
"doc/fun.l"):

18
(de fact (N)
(if (=0 N)
1
(* N (fact (- N 1))) ) )

With trace we can put it in trace mode:

: (trace 'fact)
-> fact

Calling fact now will display its execution trace.

: (fact 3)
fact : 3
fact : 2
fact : 1
fact : 0
fact = 1
fact = 1
fact = 2
fact = 6
-> 6

As can be seen here, each level of function call will indent by an additional space. Upon
function entry, the name is separated from the arguments with a colon (:), and upon
function exit with an equals sign (=) from the return value.

Functional I/O
Input and output in Pico Lisp is functional, in the sense that there are not variables
assigned to file descriptors, which need then to be passed to I/O functions for reading,
writing and closing. Instead, these functions operate on implicit input and output
channels, which are created and maintained as dynamic environments.

Standard input and standard output are the default channels. Try reading a single
expression:

: (read)
(a b c) # Console input
-> (a b c)

To read from a file, we redirect the input with in. Note that comments and white space
are automatically skipped by read:

19
: (in "doc/fun.l" (read))
-> (de fact (N) (if (=0 N) 1 (* N (fact (- N 1)))))

Some more examples:

(in "a" # Copy the first 40 Bytes


(out "b" # from file "a" to file "b"
(echo 40) ) )

(in "doc/tut.html" # Show the HTTP-header


(line)
(echo "<body>") )

References

[knuth73] Donald E. Knuth: ``The Art of Computer Programming'', Vol.3, Addison-


Vesley, 1973, p. 392

20

You might also like