A very first PicoLisp program

Search for a command to run...

Very good.
This is for users who want to start with PicoLisp from scratch. This series will cover installation, basic concepts, syntax, how to use the REPL, create simple scripts, read the docs and debug.
A person who won't read has no advantage over one who can't read. (Mark Twain)
In our previous post, we introduced how to utilize Discrete Event Simulation (DES) in PicoLisp. Now, let's explore another application: a railroad model simulation that includes tracks and trains. The visualization is ASCII-based, so feel free to unl...

The Dining Philosophers

In this post, we will introduce the concept of co-routines and how they are handled in PicoLisp. This will be the foundation for the subsequent posts on Discrete Event Simulation and finally a couple of examples, including an ASCII model railway simu...

One of the many nice things of PicoLisp is that it comes with a built-in database. This makes it easy to collect and store our own data, for example via a mobile app. But not all data is best represented in tables and lists: sometimes a colorful litt...

In the last two posts, we have built a little app that shows the current location and displays the nearest ice cream shops: https://picolisp-explored.com/an-openstreetmap-app-written-in-picolisp-pt-1 https://picolisp-explored.com/an-openstreetmap-ap...

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:
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 https://software-lab.de/doc/ref.html#conv.
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 car:
: (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 function.
(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.
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:
$ 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:
$ locate "bin/picolisp"
/home/user/pil21/bin/picolisp
Step 2: Add the library file lib.l
The interpreter alone is not enough, we also need the library called lib.l. For global installations, we will usually find it in the /usr/lib folder.
$ 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 /usr/bin and /usr/lib:
$ 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:
$ ./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 *Name by
(setq *Name (opt))
where 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.
$ ./greetings.l Mia
Hello Mia!
It works!
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.
https://software-lab.de/doc/index.html
https://software-lab.de/doc/tut.html
https://gitlab.com/picolisp-blog/single-plage-scripts/-/blob/main/beginners-tutorial/greeting-script.l
https://gitlab.com/picolisp-blog/single-plage-scripts/-/blob/main/beginners-tutorial/greeting-executable.l
https://gitlab.com/picolisp-blog/single-plage-scripts/-/blob/main/beginners-tutorial/greeting-with-args.l