1 Introduction

PicoLisp is a dialect of the Lisp language family. At a first glance, a “hello, world” program written in PicoLisp looks like

(prinl "hello, world")

This calls the built-in ‘prinl’ function, which prints its arguments, followed by a newline. As with any Lisp dialect, a function call is written inside parentheses, with the function itself followed by the arguments.

PicoLisp comes with an interactive interpreter, which can be used to execute the above program. The interpreter is usually invoked by the pil command, if properly installed in the system.

$ pil
: (prinl "hello, world")
hello, world
-> "hello, world"
:

The interactive interpreter gives a ‘:’ prompts for reading an expression, and after evaluating it, the result is printed after ‘->’. Then, it prints another ‘:’, indicating another expression will be read. Thus, the interactive interpreter is also called the REPL (Read-Eval-Print-Loop). To quit, type ‘(bye)’ into the REPL.

We can see that by evaluating the expression ‘(prinl "hello, world")’, the argument is printed to the screen. This is the side effect of the function ‘prinl’. On the other hand, there is also a return value from ‘prinl’, which turns out to be the last argument. As can be seen, functions in PicoLisp are not pure, meaning they may have side effects. Functions are called either for their return value, or their side effects, or both.

1.1 Temperature Conversion Program

In this section we will walk through a larger example to demonstrate various aspects of PicoLisp. Suppose we want a program that prints a table that converts temperature between Celsius and Fahrenheit. First, we will define a function that converts a single value based on the formula

\[F = {9\over 5}C + 32. \]

Instead of typing in the REPL, this time we will write the code in a file, temp.l.

(de c-to-f (C)
   (+ (*/ C 9 5) 32) )

We use ‘de’ to define a function, whose name is ‘c-to-f’, and it takes a single parameter ‘C’. The function body is a single expression, which will be evaluated as the return value.

The expression ‘(*/ C 9 5)’ evaluates to \(C \times 9 \div 5\), and ‘(+ ... 32)’ adds 32 to its first argument. Thus, we have expressed our formula in PicoLisp. As can be seen, an expression can be used wherever a value is needed, and it will be evaluated and replaced by the result.

We can load the file when starting the REPL:

$ pil temp.l
: (c-to-f 15)
-> 59
: (c-to-f 25)
-> 77

Here we called the function we just defined. Now suppose we want a table from 20 to 100 degrees Celsius, in 10-degree increments. A loop comes in handy in this case:

(for C (range 20 100 10)
   (prinl C "^I" (c-to-f C)) )

The ‘for’ loop iterates over a list, which in this case is produced by the ‘range’ function. ‘(range low high step)’ returns a list of numbers, starting from low, in step increments and not exceeding high. In this case, the produced list is ‘(20 30 40 50 60 70 80 90 100)’.

The for-loop will bind the symbol ‘C’ to each element in the given list, and execute the loop body, which is a call to ‘prinl’. As can be seen, ‘prinl’ takes multiple arguments, and prints them together on the screen. ‘^I’ is the caret notation of the tab character.

Add this for-loop to the end of temp.l, and run the program with ‘pil’:

$ pil temp.l
20      68
30      86
40      104
50      122
60      140
70      158
80      176
90      194
100     212
:

1.2 Functional Programming

So far, we have written our program in an imperative way. PicoLisp also allows functional programming, where the program is mostly expressed by composition of functions, without explicit control flow constructs like the for-loop here. To convert a list of Celsius to Fahrenheit, we basically wants to apply the ‘c-to-f’ to each element of the list, and that can be done with the ‘mapcar’ function:

: (setq *Celsius (range 20 100 10))
-> (20 30 40 50 60 70 80 90 100)
: (setq *Fahrenheit (mapcar c-to-f *Celsius))
-> (68 86 104 122 140 158 176 194 212)

We use ‘setq’ to bind the symbol ‘*Celsius’ to the evaluation result of ‘(range 20 100 10)’, i.e., the 9-element list from 20 to 100. Then, the ‘mapcar’ applies ‘c-to-f’ to each element of ‘*Celsius’, and collects the results into a new list, to which we bind the symbol ‘*Fahrenheit’. In PicoLisp, a function is a first-class object, which means it can be passed as values.

To print out as a table, we will use ‘mapc’ together with an anonymous function:

: (mapc '((C F) (prinl C "^I" F)) *Celsius *Fahrenheit)
20      68
30      86
40      104
50      122
60      140
70      158
80      176
90      194
100     212
-> 212

Here, we defined an anonymous function (also known as lambda function), which takes in two arguments, ‘C’ and ‘F’, and evaluates ‘(prinl C "^I" F)’. The ‘mapc’ function will iterate over the lists, taking one element from each at a time, and passed to the anonymous function as arguments.

1.3 Standalone Programs

This time we will implement make a more complete program for printing out a table of temperature conversions, with the following enhancements:

#!/usr/bin/pil

(scl 1)

(de f-to-c (F)
   (*/ (- F 32.0) 5.0 9.0) )

(de temp-line (F C)
  (tab (8 8)
     (format F *Scl)
     (format C *Scl) ) )

(de temp-table (Low High Step)
   (let (F (range Low High Step)
         C (mapcar f-to-c F) )
      (mapc temp-line F C) ) )

(temp-table 20.0 100.0 10.0)

(bye)

The first line starting with ‘#!’ is the shebang line. This is a mechanism to tell the operating system to find the interpreter of this file. In our case, the interpreter is /usr/bin/pil, assuming it is installed there.

In PicoLisp, ‘#’ begins a line of comment, so the shebang line itself is not processed by the interpreter.

With proper permission settings we will be able to run this program from the command line:

$ ./temp.l
    20.0    -6.7
    30.0    -1.1
    40.0     4.4
    50.0    10.0
    60.0    15.6
    70.0    21.1
    80.0    26.7
    90.0    32.2
   100.0    37.8

The ‘scl’ function sets up a global variable named ‘*Scl’, which affects how the numbers are parsed. Any number with a decimal point will be scaled by 10 to the *Scl-th power. In our example, ‘32.0’ gives 320.

When doing arithmetics with scaled numbers, we need to be careful about the scaling factor. ‘(* 2.0 2.0)’ will yield 400, which stands for 40.0 and is not the expected answer because multiplication brings an extra scaling factor, whereas division removes one. However the ‘*/’ operation cancels out the scaling factor, so our calculation is correct.

The scaled numbers can be converted into decimal representation using ‘format’, with ‘*Scl’ passed as its second argument.

We defined a function named ‘temp-line’ that prints out a line of two formatted numbers. This time ‘tab’ is used in place of ‘prinl’, so the two values will each be printed in an 8-character-wide column in a right-aligned manner.

In function ‘temp-table’, we use the construct ‘(let (sym val ...) ...)’ to temporary bind values to symbols. These bindings are visible before the ending parenthesis of ‘let’; after that, the symbols are stored to their previous values.

Finally, we put a ‘(bye)’ at the end of the program, so it does not stay in the REPL when finished.

1.4 List Processing

As one might guess, list is the central data type in any Lisp language. So we will take a closer look at what lists are, and how they are processed in PicoLisp.

Formally, a list is a pair of objects, named CAR (Contents of Address Register) and CDR (Contents of Decrement Register). (These archaic names do not make any sense but survived in Lisp evolution.) The CAR of the list is its first element, whereas the CDR is the list of remaining elements. In memory, a list is referred to by its address, so the CDR is actually a pointer to the list of remaining elements. Below is what the list ‘(20 30 ... 100)’ looks like in the computer memory:

       +-----+-----+    +-----+-----+         +-----+-----+
list-> |  20 |  ---|--->|  30 |  ---|-> ... ->| 100 | NIL |
       +-----+-----+    +-----+-----+         +-----+-----+
         CAR   CDR        CAR   CDR             CAR   CDR

NIL’ is a special value in PicoLisp representing an empty list. For a list with only one element, its CDR is NIL.

PicoLisp provides ‘mapcar’ and ‘mapc’ as built-in functions, which are implemented in assembly language. In fact, they can also be implemented in PicoLisp.

(de mapcar (F Lst)
   (if Lst
      (cons (F (car Lst)) (mapcar F (cdr Lst)))
      NIL ) )

There are many interesting concepts in this example. First there’s a conditional construct: ‘(if condition true-clause false-clause)’, which evaluates true-clause if condition evaluates to non-NIL, or false-clause otherwise. Note, only one clause is evaluated. So this means that:

The built-in function ‘cons’ creates a new pair of its arguments:

: (cons 1 NIL)
-> (1)
: (cons 1 (2 3))
-> (1 2 3)

The functions ‘car’ and ‘cdr’ have the obvious meaning: returning the CAR and CDR, respectively, of the argument.

Back to ‘mapcar’, if ‘Lst’ is not empty, we will get a list whose first element is the evaluation of ‘(F (car Lst))’. In our example, ‘F’ is ‘c-to-f’, so this is the value of the first element in ‘Lst’ converted into Fahrenheit. The rest of the result (the second argument to ‘cons’) will be the evaluation of ‘(mapcar F (cdr Lst))’, which basically applies ‘F’ to the rest of the input list.

This is an example of recursion, where a function calls itself. Solving a problem using recursion usually boils down to:

Recursion can usually result in concise expression of ideas, but it doesn’t have to be used everywhere. It uses stack space proportional to the call depth. In the above ‘mapcar’ implementation, the call depth is about the length of ‘Lst’, which can potentially exhaust stack if the list is too long. It is also possible to program ‘mapcar’ in an imperative manner:

(de mapcar (F Lst)
   (make
      (for X Lst
         (link (F X)) ) ) )

This is about as concise as the recursion version, but it only uses constant stack space, with the help of ‘make’ and ‘link’. The built-in function ‘make’ creates an environment, where a call to ‘link’ will add an item to the tail of the return value of ‘make’. So, in this implementation, we iterate over each element ‘X’ in ‘Lst’, apply ‘F’ to ‘X’, and append the result to the list to be returned.

2 Basic Types

There are 3 fundamental types in PicoLisp: numbers, symbols, and lists. This chapter introduces the lexical convention of these types, their properties, and common operations associated to them.

2.1 Numbers

In PicoLisp, all numbers are signed integers. There is no limit on the absolute value, so the programmer does not need to worry about integer overflow. Numbers evaluate to themselves.

: 3       # positive number
-> 3
: -3      # negative number
-> -3

Numbers can participate in arithmetic operations. Common ones are ‘+’, ‘-’, ‘*’, ‘/’, ‘%’ (modulo), ‘*/’ (multiply-then-divide), and ‘**’ (power).

There is no floating-pointer number. Fixed-point arithmetics can be emulated by setting the ‘*Scl’ global variable.

: (scl 6) # set scaling factor
-> 6
: 1.234
-> 123400 # 1.234 scaled by \(10^6\)
: (*/ 1.234 4.321 1.0)  # 1.234*4.321/1.0
-> 5332114     # 5.332114
: (*/ 1.234 1.0 4.321)
-> 285582      # 0.285582

Scaled numbers can be converted into textual representation using ‘format’ or ‘round’.

: (scl 6)
-> 6
: (format 5332114 *Scl)
-> "5.332114"
: (round 285582 3)
-> "0.286"   # rounded to the 3rd decimal digit

2.2 Symbols

A symbol is a name that can be bound to a value. In PicoLisp, only values are typed, and symbols are not. This mean a symbol can be bound to a value of any type, and may later change its value to one of a different type. This is different from statically-typed languages.

2.2.1 Internal Symbol

Internal symbol is simply spelled out by its name. The name can consist of any printable characters except the following special characters:

" ' ( ) , [ ] ` { } ~

A number cannot be used as a symbol.

2.2.2 Transient Symbol

Transient symbols are written in double quotes. They look like strings in other programming languages. In fact, there is no distinction between symbols and strings in PicoLisp, and transient symbols are usually used where a string is needed.

: (prinl "hello") # use a transient symbol as string
hello
-> "hello"
: (prinl 'hello)  # it's fine to use internal symbols, too
hello
-> hello

Caret notation can be used to denote control characters. For example:

^G

bell (‘\a’ in C)

^H

backspace (‘\b’ in C)

^I

tab (‘\t’ in C)

^J

newline (‘\n’ in C)

^M

carriage return (‘\r’ in C)

^[

escape

^?

delete (ASCII 127)

To denote a literal ‘^’, ‘\’, or ‘"’, prefix that character by a backslash.

The scope of a transient symbol is limited to its source file, which means it never clashes with another symbol with the same name in other source files. This property is sometimes used to get private symbols in a library. However, it can be more conveniently achieved with namespaces.

2.2.3 Binding Values

To bind a symbol to a value, use ‘setq’ or ‘set’.

: (setq X 1)
-> 1
: X  # X bound to 1
-> 1
: Y  # Y unbound, evaluates to NIL
-> NIL
: (set 'Y 2)  # need to quote the symbol in ‘set’
-> 2
: Y
-> 2
: "X"  # "X" is a different symbol from X
-> "X" # and evaluates to itself
: (setq "X" 3)  # we can bind a transient symbol, too
-> 3
: "X"
-> 3

2.2.4 Attributes

A symbol can not only has a value, but also a list of attributes. Each attribute has two components: key and value.

: (put 'X 'name "John")  # ‘put’ sets the attribute of a symbol
-> "John"
: (put 'X 'age 42)
-> 42
: (get 'X 'name)  # ‘get’ retrieves the attribute
-> "John"
: (get 'X 'age)
-> 42
: (show 'X)  # ‘show’ shows the value and attributes
X NIL
   age 42
   name "John"
-> X

2.2.5 External Symbol

External symbols are persistent objects in the file system. The (de)serialization is handled transparently by PicoLisp interpreter, so the programmer can simply use them as normal symbols.

Use ‘pool’ to open a database file where the external symbols will be persisted. External symbols are denoted by ‘{...}’.

$ pil
: (pool "db")
-> T
: (set '{1} 123456)
-> 123456
: (commit)
-> T
: (bye)
# start a new REPL
$ pil
: (pool "db")
-> "db"
: (val '{1})
->123456

External symbols are generally not used directly and is a building block of PicoLisp’s database framework.

2.2.6 Anonymous Symbols

An anonymous symbol does not have a name. It can be created by ‘box’ or ‘new’.

: (setq X (box))  # create a new anonymous and bind ‘X’ to it
-> $177247636565677
: (set X 123)  # X evaluates to $177247636565677, which is set to 123
-> 123
: X  # X evaluates to the anonymous symbol
-> $177247636565677
: $177247636565677  # the anonymous symbol is bound to 123
-> 123
: (val X)
-> 123
: (put X 'name "John")
-> "John"
: (show X)
$177247636565677 123
   name "John"
-> $177247636565677

An anonymous symbol usually represents an object, which itself can be used as a value and has its own attributes. In the above example, the attribute ‘name’ belongs to the anonymous symbol (value of ‘X’), rather than to the symbol ‘X’ itself. When we pass value of ‘X’ to some function, we actually pass the anonymous symbol, including its attributes.

2.3 Lists

5

3 Control Flow

6

3.1 Conditionals

7

3.2 Loops

8

4 Functions

9

4.1 Function Definition

10

4.2 Anonymous Functions

11

4.3 Closures

12

5 Library Functions

13

6 Debugging

14

7 Pilog

15

8 Database Programming

PicoLisp has built-in database functionality.

9 Namespaces


JavaScript license information