Starting a wisp project

Wisp combines the beauty of Python with the power of Scheme (a Lisp). If you want the most powerful language while enabling non-programmers to read and edit your code, Wisp could be your best choice. It is implemented on top of GNU Guile and gives you access to all the capabilities of Guile Scheme (the official GNU extension language), including new ones as they are added.

All the while, it can look like this:

import : shakespeare

Enter : Old Hand
        New Star

New Star
    What does it take
    to change the world?

Old Hand
    A wisp and a dream,
    and the persistence
    to make them real.

Here I’ll show you how to start your first wisp project.

Table of Contents

Get Guile and Wisp

Begin by installing Guile 2.0 or later and downloading wisp-0.9.5 or later. Then unpack wisp within your project and get the language folder:

mkdir PROJ
cd PROJ
tar xf wisp*.tar.gz
cp -r wisp*/language language

The wisp REPL

Now your project is prepared. You can already experiement with wisp by calling

guile -L .

Then try the following:

,L wisp
define : hello world
__       cons 'Hello world
hello 'Eris .

That’s it. You just ran your first wisp code. The first line is a meta-command which switches to the language wisp (discovered via ./language/wisp/spec.scm).

But you won’t only want to run interactively. You’ll also want to build tools — to use wisp in your programs.

A Wisp script: hello

To get that, exit the REPL (the interactive read-eval-print-loop you’re running right now) with CTRL-D, then create a text file named hello. To execute it via Guile with wisp via ./hello, you can use shell indirection. Start the file with the following header.1

#!/bin/sh
# -*- wisp -*-
# precompile the wisp spec
guile -L "$(dirname "$0")" \
      -c '(import (language wisp spec))'
# run your file via wisp
exec guile -L "$(dirname "$0")" \
           -x '.w' --language wisp \
           -s "$0" "$@"
# the sh indirection header ends here !#

Then call chmod +x hello. This allows you to run wisp code directly; if you install wisp systemwide, you can leave out the pre-compilation step.

Let’s discuss how the header works.

  • The first line (#!/bin/sh) tells Unix to run the file through the shell.
  • The second line (# -*- wisp -*-) tells your editor to use the wisp mode.
  • The third and fourth line precompile the wisp spec with the scheme language.
  • The fifth and sixth line execute the file as wisp in a new process and switch to that process. The shell won’t see any further lines. We’ll go in depth through that file after finishing the header.
  • The seventh line is never seen by the shell (thanks to exec). It ends in !#, the terminator for a multi-line comment in Guile; the comment started with the hashbang line (#!/bin/sh).

Now for line number 6: The line which runs this script as wisp:

exec guile -L "$(dirname "$0")" \
           -x '.w' --language wisp \
           -s "$0" "$@"
  • exec ends the shell script after calling its arguments.
  • guile uses Guile from the environment.
  • -L "$(dirname "$0")" tells Guile to add the directory with the script “to the front of the module load path” (quoted from guile -h ).
  • -x .w adds ".w" to the front of the load extensions, so guile will consider .w files for loading. This allows writing modules in wisp.
  • --language wisp switches to using wisp as language.
  • -s "\(0" "\)@" runs this script with the arguments given to ./hello.

By adding this header, you can write working scripts like the following:

import : only (srfi srfi-1) second

display "Hello "
display
  if : pair? : cdr : command-line
     second : command-line
     . "World"
display "!"
newline 

Call this file hello, then run ./hello Eris.

A Wisp module: hello.w

If you later want to re-use parts of your scripts, you can move them into modules. Create a file hello.w.

One advantage is that this often starts much faster because it can skip processing the source and jump right to the bytecode.

The module-header looks just like the previous header, except for the following exec-line:2

exec -a "$0" guile -L "$(dirname "$0")" \
                   -L "$(dirname "$0")" \
                   -x '.w' --language wisp \
                   -e '('"$(basename "$0" .w)"')' \
                   -c '' "$@"

The body defines a module and wraps the parts to execute in a main procedure.

define-module : hello
    . #:export : main say-hello

import : only (srfi srfi-1) second

define : say-hello world
    display "Hello "
    display world
    display "!"
    newline 

define : main args
  say-hello
    if : pair? : cdr args
       second args
       . "World"

You can now call this file as ./hello.w Eris, but also import the say-hello procedure from another file. For a version with included module-header, see the complete example: htmlo.w.

You can now import say-hello only by using

import : only (hello) say-hello

or import all exported symbols with

import : hello

A complete Wisp example: htmlo.w

Let’s do that as a final example. Call the file htmlo.w.

#!/bin/sh
# -*- wisp -*-
# precompile the wisp spec
guile -L "$(dirname "$0")" \
      -c '(import (language wisp spec))'
# run your file via wisp
exec -a "$0" guile -L "$(dirname "$0")" \
                   -L "$(dirname "$0")" \
                   -x '.w' --language wisp \
                   -e '('"$(basename "$0" .w)"')' \
                   -c '' "$@"
# the sh indirection header ends here !#

define-module : htmlo
    . #:export : main htmlo

import : hello

define : say-htmlo world
    display "<html><head><title>"
    say-hello world
    display "</title></head><body>"
    say-hello world
    display "</body></html>"
    newline 

;; this main replaces the imported one
define : main args
  say-htmlo
    if : pair? : cdr args
       car : cdr args
       . "World"

And that is it: Your first full-fledged wisp project!

Thank you!

You can find more information about Wisp on its website: http://draketo.de/english/wisp

For day-to-day programming, refer to the Guile Reference manual, either online or via your friendly info reader (in Emacs that’s C-h i m Guile, in the terminal it’s info Guile). Some basic tasks in Guile are explained in Guile basics and py2guile.

Thank you for reading, and Happy Hacking!


With Guise and Guile

License: cc by-sa

Footnotes:

1

This shell indirection combines hashbang syntax with Guile inline comment syntax to allow defining argument handling in the script directly within the header with all the flexibility from bash scripts – without having to implement special handling inside Guile. This reaches a level of technical elegance that has me completely in awe of Guile.

2

Note that this header can be used with different module names, as long as the module with the main procedure to call is named after the file (without extension).

Date: 2017-09-25 Mon 00:00

Author: Arne Babenhauserheide

Created: 2020-05-06 Wed 09:35

Emacs 24.5.1 (Org mode 8.2.6)

Validate