The VikiD Truth

Knowledge base / Computing / Lisp family

PicoLisp Chat Program

A Study

This is one of my favourite little PicoLisp programs: a multi-user chat server in about 25 lines. It used to ship in the PicoLisp sources as misc/chat. Current PicoLisp (pil21) no longer includes it, so below is the full source, re-tested on PicoLisp 26.9 in October 2026, with my walk-through.

chat

The source

Save this as chat:

#!/usr/bin/pil
# chat: a tiny multi-user chat server on port 4004
# *Port *Sock *Name

(de chat Lst
   (out *Sock
      (mapc prin Lst)
      (prinl)
      (flush) ) )

(setq *Port (port 4004))

(loop
   (setq *Sock (listen *Port))
   (NIL (fork) (close *Port))
   (close *Sock) )

(out *Sock
   (prin "Please enter your name: ")
   (flush) )
(in *Sock (setq *Name (line T)))

(tell 'chat "+++ " *Name " arrived +++")

(task *Sock
   (in @
      (ifn (eof)
         (tell 'chat *Name "> " (line T))
         (tell 'chat "--- " *Name " left ---")
         (bye) ) ) )
(wait)

Running chat

Make it executable:

$ chmod 755 chat

Line 1, the shebang, tells the shell which interpreter runs the script. In 2017 I had to edit it to something like #!/home/emperor/picoLisp/bin/picolisp /home/emperor/picoLisp/lib.l. With pil21 it is simpler. After a global install (see PicoLisp Installation) /usr/bin/pil exists and the line above just works. If you only built pil21 in your home folder, point it at the pil script there instead, for example #!/home/emperor/pil21/pil.

Start the server:

$ ./chat

Now open two more terminals and connect to port 4004 in each, with telnet or, if telnet isn’t installed (common on modern distros), nc:

$ telnet localhost 4004
$ nc localhost 4004

telnet-1

Type a name in each terminal, and you will see the arrivals announced in the other one. Then chat.

telnet-2

When I tested it on 26.9, “ali” and “bob” connected; bob saw ali> hello bob and then --- ali left ---, and ali saw +++ bob arrived +++. Note that you never see your own messages echoed back.

Exiting telnet can be confusing, because Ctrl-C is passed through to the remote end instead of stopping telnet. Press Ctrl-] and then type quit. With nc, Ctrl-C works normally.

Examining the source

The comment # *Port *Sock *Name lists the globals in use. A leading * on global variables is a PicoLisp convention.

(de chat Lst ...) defines chat with a single atomic parameter, so it receives all its arguments as a list (see The Vikid PicoLisp Tutorial). It prints them to this process’s socket. How it gets called becomes clear below.

(setq *Port (port 4004)) opens a TCP port and returns a socket descriptor for it. So we look out for a listen or accept later in the code.

The loop is the idiomatic PicoLisp shape for a forking server. It is an infinite loop with conditional exits (loop in the reference):

(loop
   (setq *Sock (listen *Port))
   (NIL (fork) (close *Port))
   (close *Sock) )

Asking for a name. out and in temporarily redirect output and input to the socket. (line T) reads one line as a string.

tell is the magic. It sends an expression to all other PicoLisp processes in the same family (the server’s children), and each of them evaluates it. So (tell 'chat "+++ " *Name " arrived +++") makes every other child run chat, which prints to its own user’s socket. That is the broadcast, with no shared memory and no message queue to write.

task installs a background handler on *Sock. Whenever input arrives, the body runs with @ bound to the descriptor. If it isn’t end-of-file, the line is broadcast with the sender’s name. On EOF (the user disconnected) we announce the departure and bye ends this child.

(wait) waits forever, letting the task handlers and incoming tell messages do the work.

It’s amazing what you can achieve with so few lines of code! The same pattern of fork per connection, tell between processes and task for events sits underneath PicoLisp’s web application server. See Application Development and http.l.

Things to try

Sig

Last edited · 24 revisions

Tags: #Computing