DEV Community

Cover image for A very first PicoLisp program
Mia
Mia

Posted on • Edited on • Originally published at picolisp-blog.hashnode.dev

A very first PicoLisp program

To conclude our "PicoLisp for Beginners" Series, we should talk about naming conventions, and finally create our first own little program!


Naming Conventions

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 https://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 car:

: (de max-speed (car)
   (.. (get car 'speeds) ..) )
-> max-speed
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
   ...
Enter fullscreen mode Exit fullscreen mode

Scripting

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")
Enter fullscreen mode Exit fullscreen mode

Let's check it:

: (prinl "Hello " Name)
Hello Mia
Enter fullscreen mode Exit fullscreen mode

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 "!")
Enter fullscreen mode Exit fullscreen mode

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 !  
Enter fullscreen mode Exit fullscreen mode

[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 "!")
Enter fullscreen mode Exit fullscreen mode

After calling it:

Hello! Who are you?
Mia                                                              
Hello Mia!  
:
Enter fullscreen mode Exit fullscreen mode

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 "!") )
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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
    
  2. 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!

  3. 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
    
  4. 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.


Adding arguments

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 "!"))
Enter fullscreen mode Exit fullscreen mode

Then we need to pass our command line argument to the global variable *Name by

(setq *Name (opt)) 
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Now we can pass the name to our script by ./greetings.l Mia.

$ ./greetings.l Mia
Hello Mia!
Enter fullscreen mode Exit fullscreen mode

It works!

But what if we don't pass any argument? Then the name just stays blank:

$ ./greetings.l
Hello !
Enter fullscreen mode Exit fullscreen mode

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) )
Enter fullscreen mode Exit fullscreen mode

Now let's try again:

$ ./greeting-with-args.l 
Please specify a name!

$ ./greeting-with-args.l Mia
Hello Mia!
Enter fullscreen mode Exit fullscreen mode

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.


Sources

Gitlab links:

Top comments (0)