A very first PicoLisp program
7 min read
To conclude our "PicoLisp for Beginners" Series, we should talk about naming conventions, and finally create our first own little program!
PicoLisp has a few naming conventions that should be followed, in order to introduce name conflicts and keep the code readable for other programmers. Here are some of the most important ones:
- Global variables start with an asterisk "*"
- Global constants may be written all-uppercase
- Functions and other global symbols start with a lower case letter
- Locally bound symbols start with an upper case letter (for example, arguments in a function declaration)
Note: This list is not exhaustive, since some concepts were left out that we did not talk about yet (for example, classes). For the complete list of conventions refer to software-lab.de/doc/ref.html#conv.
What do you mean by "name conflicts"?
For example, let's take the built-in PicoLisp function
car. Since data and functions are basically the same thing (see "Concepts and Data Types of PicoLisp"), a local variable "car" could overshadow the function definition of
: (de max-speed (car) (.. (get car 'speeds) ..) ) -> max-speed
Inside the body of
max-speed (and all other functions called during that execution) the kernel function
car is redefined to some other value, and will surely crash if something like
(car Lst) is executed. Instead, it is safe to write:
: (de max-speed (Car) # 'Car' with upper case first letter (.. (get Car 'speeds) ..) ) -> max-speed
Another example of a name conflict: The symbols T and NIL are global constants, so care should be taken not to bind them to some other value by mistake:
(de foo (R S T) ...
Obviously it is hard to only rely on the REPL if the programs get more complex. Let's now write a minimal program to illustrate how a basic PicoLisp program should look like: a little "greeting"-program that asks the user for a name and prints it.
For best learning results, consider to code along while reading. If you want to skip the explanations, you can find the link to the final scripts at the end.
As you might remember from the Input-Output section of the "60 basic functions" series, we can read in a line using the
line function. Let's try to store it in a variable called "Name". We use the REPL to test our idea:
: (setq Name (line)) Mia -> ("M" "i" "a")
Let's check it:
: (prinl "Hello " Name) Hello Mia
Okay, this seems to work. So, as second step, let's open a text file called greeting.l (.l is the ending for PicoLisp scripts) and paste the two lines inside.
# greeting.l (prinl "Hello! Who are you?") (setq Name (line)) (prinl "Hello " Name "!")
We can execute the script using the interpreter by typing
pil greeting.l from the console. However, unfortunately our output looks kind of strange.
$ pil greeting.l Hello! Who are you? Hello !
[The $ symbolizes a shell command and doesn't need to be typed.]
It seems our
(line) command was skipped! Why is that?
According to the documentation,
line "reads a line of characters from the current input channel". It seems we need to specify that we want to read from the console, because during the execution of a file, the current "input channel" is the file itself.
After searching the documentation, we find that we can define that by the function
in NIL (as opposite to
in <filename>, to read in from a file).
So we change it and the modified script looks like this:
# greeting.l (prinl "Hello! Who are you?") (setq name (in NIL (line))) (prinl "Hello " name "!")
After calling it:
Hello! Who are you? Mia Hello Mia! :
It works! After execution the REPL is still open, as we can see by the colon in the next line. Therefore we add
(bye) as last line of the function to close it.
Now, for the sake of demonstration, let's give the greeting its own function. We remember that a function is defined using the
(de greeting (Name) (prinl "Hello " Name "!") )
The function argument "Name" is spelled with an uppercase letter according to the conventions that we saw above. Also, note that the parentheses in the second line have some space in between:
) ). This is to visualize that the first bracket is closing the bracket from the same line, but the second one comes from the line above.
Of course, the
greeting function needs to be defined before it gets called, otherwise the interpreter will not know what to do with it. The complete program now looks like this:
# execute this script by calling "pil greeting.l" (de greeting (Name) (prinl "Hello " Name "!") ) (prinl "Hello! Who are you?") (setq Name (in NIL (line))) (greeting Name) (bye)
The complete example can be download it here.
Creating an executable program
It can be a little bit unconvenient to use the interpreter for calling the program, especially if it gets more complex. Let's check the tutorial how to create an executable file:
It is better to write a single executable file using the mechanisms of "interpreter files". If the first two characters in an executable file are "#!", the operating system kernel will pass this file to an interpreter program whose pathname is given in the first line (optionally followed by a single argument). This is fast and efficient, because the overhead of a subshell is avoided.
Step 1: Find the path to the PicoLisp interpreter.
If PicoLisp is installed globally, for example using a package manager, it is most probably in the folder
/usr/bin. We could verify this using the shell command
$ which picolisp /usr/bin/picolisp
If the program is installed only locally, the path to the "picolisp" interpreter will be in the
bin/folder of the installation folder:
<Path to Folder>/pil21/lib. We can find the path using the shell command
$ locate "bin/picolisp" /home/user/pil21/bin/picolisp
Step 2: Add the library file
The interpreter alone is not enough, we also need the library called
lib.l. For global installations, we will usually find it in the
$ locate "picolisp/lib.l" /usr/lib/picolisp/lib.l
For local installations, it is in the root of the installation folder.
$ locate "lib.l" /home/user/pil21/lib.l
Fine! Now back to our script!
Step 3: Adding the path to the interpreter and the library: This should be the first line in your script.
#! /usr/bin/picolisp /usr/lib/picolisp/lib.l
Replace these folders by your local folders if PicoLisp is not installed globally. Alternatively, you can also set soft links to
$ ln -s /home/foo/pil21 /usr/lib/picolisp $ ln -s /usr/lib/picolisp/bin/picolisp /usr/bin
Step 4: Making the script executable
As a last step, let's make our script executable by
chmod +x greetings.l. Now we can execute the script by
$ ./greeting.l Hello World! Who are you? Mia Hello Mia!
That's it! The final script can be downloaded here.
Sometimes we want to add the arguments directly at the execution of the script, instead of typing it after it started.
To do this, we modify our
greeting function so that it prints a global variable called
*Name (according to the convention that global variables should start with an asterisk "*").
(de greeting () (prinl "Hello " *Name "!"))
Then we need to pass our command line argument to the global variable
(setq *Name (opt))
opt is a pre-defined functiont hat retrieves the next command line option.
The full program so far:
#! /usr/bin/picolisp /usr/lib/picolisp/lib.l (de greeting () (prinl "Hello " *Name "!")) (setq *Name (opt)) (greeting) (bye)
Now we can pass the name to our script by
$ ./greetings.l Mia Hello Mia!
But what if we don't pass any argument? Then the name just stays blank:
$ ./greetings.l Hello !
It would be nice if the program would tell us about the name option. Let's write a little check to see if the global variable
*Name is empty. If yes, let's print out a warning, otherwise the
greeting function should be called. For this we can use a simple
ifn ("if not") statement:
(ifn *Name (prinl "Please specify a name!") (greeting) )
Now let's try again:
$ ./greeting-with-args.l Please specify a name! $ ./greeting-with-args.l Mia Hello Mia!
The full script can be downloaded here.
In the last part of the beginner's series, we will talk about the documentation and debugging functions of PicoLisp.