New version: draketo.de/software/wisp [1]
» I love the syntax of Python, but crave the simplicity and power of Lisp.«
display "Hello World!" ↦ (display "Hello World!")
define : factorial n (define (factorial n) if : zero? n ↦ (if (zero? n) . 1 1 * n : factorial {n - 1} (* n (factorial {n - 1}))))
hg clone https://hg.sr.ht/~arnebab/wisp [3]
guix install guile guile-wisp
./configure; make install
from the releases.»ArneBab's alternate sexp syntax is best I've seen; pythonesque, hides parens but keeps power« — Christopher Webber in twitter [8], in identi.ca [9] and in his blog: Wisp: Lisp, minus the parentheses [10]
♡ wow ♡
»Wisp allows people to see code how Lispers perceive it. Its structure becomes apparent.« — Ricardo Wurmus in IRC, paraphrasing the wisp statement from his talk at FOSDEM 2019 about Guix for reproducible science in HPC [11].
☺ Yay! ☺
with (open-file "with.w" "r") as port format #t "~a\n" : read portFamiliar with-statement in 25 lines [12].
Update (2020-09-15): Wisp 1.0.3 [13] provides a
wisp
binary to start a wisp repl or run wisp files, builds with Guile 3, and moved to sourcehut for libre hosting: hg.sr.ht/~arnebab/wisp [3].
After installation, just runwisp
to enter a wisp-shell (REPL).
This release also ships wisp-mode 0.2.6 (fewer autoloads), ob-wisp 0.1 (initial support for org-babel), and additional examples. New auxiliary projects include wispserve [14] for experiments with streaming and download-mesh via Guile and wisp in conf [15]:conf new -l wisp PROJNAME
creates an autotools project with wisp whileconf new -l wisp-enter PROJAME
creates a project with natural script writing [16] and guile doctests [17] set up. Both also install a script to run your project with minimal start time: I see 25ms to 130ms for hello world (36ms on average). The name of the script is the name of your project.
For more info about Wisp 1.0.3, see the NEWS file [18].
To test wisp v1.0.3, install Guile 2.0.11 or later [19] and bootstrap wisp:wget https://www.draketo.de/files/wisp-1.0.3.tar_.gz;
tar xf wisp-1.0.3.tar_.gz ; cd wisp-1.0.3/;
./configure; make check;
examples/newbase60.w 123If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.
If you have additional questions, see the Frequently asked Questions (FAQ) and chat in #guile at freenode [21].
That’s it - have fun with wisp syntax [22]!
Update (2019-07-16): wisp-mode 0.2.5 [23] now provides proper indentation support in Emacs: Tab increases indentation and cycles back to zero. Shift-tab decreases indentation via previously defined indentation levels. Return preserves the indentation level (hit tab twice to go to zero indentation).
Update (2019-06-16): In c programming the uncommon way [24], specifically c-indent [25], tantalum is experimenting with combining wisp and sph-sc [26], which compiles scheme-like s-expressions to c. The result is a program written like this:pre-include "stdio.h" define (main argc argv) : int int char** declare i int printf "the number of arguments is %d\n" argc for : (set i 0) (< i argc) (set+ i 1) printf "arg %d is %s\n" (+ i 1) (array-get argv i) return 0 ;; code-snippet under GPLv3+
To me that looks so close to C that it took me a moment to realize that it isn’t just using a parser which allows omitting some special syntax of C, but actually an implementation of a C-generator in Scheme (similar in spirit to cython, which generates C from Python), which results in code that looks like a more regular version of C without superfluous parens. Wisp really completes the round-trip from C over Scheme to something that looks like C but has all the regularity of Scheme, because all things considered, the code example is regular wisp-code. And it is awesome to see tantalum take up the tool I created and use it to experiment with ways to program that I never even imagined! ♡
TLDR: tantalum uses wisp [25] for code that looks like C and compiles to C but has the regularity of Scheme!
Update (2019-06-02): The repository at https://www.draketo.de/proj/wisp/ [27] is stale at the moment, because the staticsite extension [28] I use to update it was broken by API changes and I currently don’t have the time to fix it. Therefore until I get it fixed, the canonical repository for wisp is https://bitbucket.org/ArneBab/wisp/ [29]. I’m sorry for that. I would prefer to self-host it again, but the time to read up what i have to adjust blocks that right now (typically the actual fix only needs a few lines). A pull-request which fixes the staticsite extension [30] for modern Mercurial would be much appreciated!
Update (2019-02-08): wisp v1.0 [31] released as announced at FOSDEM [32]. Wisp the language is complete:display "Hello World!"
↦ (display "Hello World!")
And it achieves its goal:
“Wisp allows people to see code how Lispers perceive it. Its structure becomes apparent.” — Ricardo Wurmus at FOSDEM
Tooling, documentation, and porting of wisp are still work in progress, but before I go on, I want thank the people from the readable lisp project [33]. Without our initial shared path, and without their encouragement, wisp would not be here today. Thank you! You’re awesome!
With this release it is time to put wisp to use. To start your own project, see the tutorial Starting a wisp project [34] and the wisp tutorial [35]. For more info, see the NEWS file [36]. To test wisp v1.0, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-1.0.tar.gz;
tar xf wisp-1.0.tar.gz ; cd wisp-1.0/;
./configure; make check;
examples/newbase60.w 123
If you have additional questions, see the Frequently asked Questions (FAQ) and chat in #guile at freenode [21].
That’s it - have fun with wisp syntax [37]!
Update (2019-01-27): wisp v0.9.9.1 [38] released which includes the emacs support files missed in v0.9.9, but excludes unnecessary files which increased the release size from 500k to 9 MiB (it's now back at about 500k). To start your own wisp-project, see the tutorial Starting a wisp project [34] and the wisp tutorial [35]. For more info, see the NEWS file [39]. To test wisp v0.9.9.1, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.9.1.tar.gz;
tar xf wisp-0.9.9.1.tar.gz ; cd wisp-0.9.9.1/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2019-01-22): wisp v0.9.9 [41] released with support for literal arrays in Guile (needed for doctests), example start times below 100ms, ob-wisp.el for emacs org-mode babel and work on examples: network, securepassword, and downloadmesh. To start your own wisp-project, see the tutorial Starting a wisp project [34] and the wisp tutorial [35]. For more info, see the NEWS file [42]. To test wisp v0.9.9, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.9.tar.gz;
tar xf wisp-0.9.9.tar.gz ; cd wisp-0.9.9/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2018-06-26): There is now a wisp tutorial [35] for beginning programmers: “In this tutorial you will learn to write programs with wisp. It requires no prior knowledge of programming.” — Learn to program with Wisp [35], published in With Guise and Guile [43]
Update (2017-11-10): wisp v0.9.8 [44] released with installation fixes (thanks to benq!). To start your own wisp-project, see the tutorial Starting a wisp project [34]. For more info, see the NEWS file [45]. To test wisp v0.9.8, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.8.tar.gz;
tar xf wisp-0.9.8.tar.gz ; cd wisp-0.9.8/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2017-10-17): wisp v0.9.7 [46] released with bugfixes. To start your own wisp-project, see the tutorial Starting a wisp project [34]. For more info, see the NEWS file [47]. To test wisp v0.9.7, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.7.tar.gz;
tar xf wisp-0.9.7.tar.gz ; cd wisp-0.9.7/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2017-10-08): wisp v0.9.6 [48] released with compatibility for tests on OSX and old autotools, installation toguile/site/(guile version)/language/wisp
for cleaner installation, debugging and warning when using not yet defined lower indentation levels, and withwisp-scheme.scm
moved tolanguage/wisp.scm
. This allows creating a wisp project by simply copyinglanguage/
. A short tutorial for creating a wisp project is available at Starting a wisp project [34] as part of With Guise and Guile [49]. For more info, see the NEWS file [50]. To test wisp v0.9.6, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.6.tar.gz;
tar xf wisp-0.9.6.tar.gz ; cd wisp-0.9.6/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2017-08-19): Thanks to tantalum, wisp is now available as package for Arch Linux [51]: from the Arch User Repository (AUR) as guile-wisp-hg [5]! Instructions for installing the package are provided on the AUR page in the Arch Linux wiki [52]. Thank you, tantalum!
Update (2017-08-20): wisp v0.9.2 [53] released with many additional examples including the proof-of-concept for a minimum ceremony dialog-based game duel.w [54] and the datatype benchmarks in benchmark.w [55]. For more info, see the NEWS file [56]. To test it, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.2.tar.gz;
tar xf wisp-0.9.2.tar.gz ; cd wisp-0.9.2/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2017-03-18): I removed the link to Gozala’s wisp, because it was put in maintenance mode. Quite the opposite of Guile which is taking up speed and just released Guile version 2.2.0 [57], fully compatible with wisp (though wisp helped to find and fix one compiler bug [58], which is something I’m really happy about ☺).
Update (2017-02-05): Allan C. Webber presented my talk Natural script writing with Guile [59] in the Guile devroom [60] at FOSDEM. The talk was awesome — and recorded! Enjoy Natural script writing with Guile by "pretend Arne" ☺
presentation (pdf, 16 slides) [62] and its source (org) [63].
Have fun with wisp syntax [40]!
Update (2016-07-12): wisp v0.9.1 [64] released with a fix for multiline strings and many additional examples. For more info, see the NEWS file [65]. To test it, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.1.tar.gz;
tar xf wisp-0.9.1.tar.gz ; cd wisp-0.9.1/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2016-01-30): I presented Wisp [66] in the Guile devroom [67] at FOSDEM. The reception was unexpectedly positive — given some of the backlash the readable project [33] got I expected an exceptionally sceptical audience, but people rather asked about ways to put Wisp to good use, for example in templates, whether it works in the REPL (yes, it does) and whether it could help people start into Scheme.The atmosphere in the Guile devroom was very constructive and friendly during all talks, and I’m happy I could meet the Hackers there in person. I’m definitely taking good memories with me. Sadly the video did not make it, but the schedule-page [66] includes the presentation (pdf, 10 slides) [74] and its source (org) [75].Wisp is ”The power and simplicity of #lisp [68] with the familiar syntax of #python [69]” talk by @ArneBab [70] #fosdem [71] pic.twitter.com/TaGhIGruIU [72]
— Jan Nieuwenhuizen (@JANieuwenhuizen) January 30, 2016 [73]
Have fun with wisp syntax [40]!
Update (2016-01-04): Wisp is available in GNU Guix [76]! Thanks to the package [77] from Christopher Webber you can try Wisp easily on top of any distribution:
This already gives you Wisp at the REPL (take care to follow all instructions for installing Guix on top of another distro, especially the locales).guix package -i guile guile-wisp
guile --language=wisp
Have fun with wisp syntax [40]!
Update (2015-10-01): wisp v0.9.0 [78] released which no longer depends on Python for bootstrapping releases (but ./configure still asks for it — a fix for another day). And thanks to Christopher Webber there is now a patch [79] to install wisp within GNU Guix [76]. For more info, see the NEWS file [65]. To test it, install Guile 2.0.11 or later [19] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.0.tar.gz;
tar xf wisp-0.9.0.tar.gz ; cd wisp-0.9.0/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]!
Update (2015-09-12): wisp v0.8.6 [80] released with fixed macros in interpreted code, chunking by top-level forms,: .
parsed as nothing, ending chunks with a trailing period, updated example evolve [81] and added examples newbase60 [82], cli [83], cholesky decomposition [84], closure [85] and hoist in loop [86]. For more info, see the NEWS file [65].To test it, install Guile 2.0.x or 2.2.x [19] and Python 3 [87] and bootstrap wisp:
If it prints 23 (123 in NewBase60 [20]), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.6.tar.gz;
tar xf wisp-0.8.6.tar.gz ; cd wisp-0.8.6/;
./configure; make check;
examples/newbase60.w 123
That’s it - have fun with wisp syntax [40]! And a happy time together for the ones who merge their paths today ☺
Update (2015-04-10): wisp v0.8.3 [88] released with line information in backtraces. For more info, see the NEWS file [65].To test it, install Guile 2.0.x or 2.2.x [19] and Python 3 [87] and bootstrap wisp:
If it prints 120120 (two times 120, the factorial of 5), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.3.tar.gz;
tar xf wisp-0.8.3.tar.gz ; cd wisp-0.8.3/;
./configure; make check;
guile -L . --language=wisp tests/factorial.w; echo
That’s it - have fun with wisp syntax [40]!
Update (2015-03-18): wisp v0.8.2 [89] released with reader bugfixes, new examples [90] and an updated draft for SRFI 119 (wisp) [91]. For more info, see the NEWS file [65].To test it, install Guile 2.0.x or 2.2.x [19] and Python 3 [87] and bootstrap wisp:
If it prints 120120 (two times 120, the factorial of 5), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.2.tar.gz;
tar xf wisp-0.8.2.tar.gz ; cd wisp-0.8.2/;
./configure; make check;
guile -L . --language=wisp tests/factorial.w; echo
That’s it - have fun with wisp syntax [40]!
Update (2015-02-03): The wisp SRFI just got into draft state: SRFI-119 [91] — on its way to an official Scheme Request For Implementation!
Update (2014-11-19): wisp v0.8.1 [92] released with reader bugfixes. To test it, install Guile 2.0.x [19] and Python 3 [87] and bootstrap wisp:
If it prints 120120 (two times 120, the factorial of 5), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.1.tar.gz;
tar xf wisp-0.8.1.tar.gz ; cd wisp-0.8.1/;
./configure; make check;
guile -L . --language=wisp tests/factorial.w; echo
That’s it - have fun with wisp syntax [40]!
Update (2014-11-06): wisp v0.8.0 [93] released! The new parser now passes the testsuite and wisp files can be executed directly. For more details, see the NEWS [94] file. To test it, install Guile 2.0.x [19] and bootstrap wisp:
If it prints 120120 (two times 120, the factorial of 5), your wisp is fully operational.wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.0.tar.gz;
tar xf wisp-0.8.0.tar.gz ; cd wisp-0.8.0/;
./configure; make check;
guile -L . --language=wisp tests/factorial.w;
echo
That’s it - have fun with wisp syntax [40]!
On a personal note: It’s mindboggling that I could get this far! This is actually a fully bootstrapped indentation sensitive programming language with all the power of Scheme [95] underneath, and it’s a one-person when-my-wife-and-children-sleep sideproject. The extensibility of Guile [19] is awesome!
Update (2014-10-17): wisp v0.6.6 [96] has a new implementation of the parser which now uses the scheme read function. `wisp-scheme.w` parses directly to a scheme syntax-tree instead of a scheme file to be more suitable to an SRFI. For more details, see the NEWS [97] file. To test it, install Guile 2.0.x [19] and bootstrap wisp:
That’s it - have fun with wisp syntax [40] at the REPL!wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.6.6.tar.gz;
tar xf wisp-0.6.6.tar.gz; cd wisp-0.6.6;
./configure; make;
guile -L . --language=wisp
Caveat: It does not support the ' prefix yet (syntax point 4).
Update (2014-01-04): Resolved the name-clash together with Steve Purcell und Kris Jenkins: the javascript wisp-mode was renamed to wispjs-mode [98] and wisp.el is called wisp-mode 0.1.5 [99] again. It provides syntax highlighting for Emacs and minimal indentation support via tab. You can install it with `M-x package-install wisp-mode`
Update (2014-01-03): wisp-mode.el was renamed to wisp 0.1.4 [100] to avoid a name clash with wisp-mode for the javascript-based wisp.
Update (2013-09-13): Wisp now has a REPL! Thanks go to GNU Guile [19] and especially Mark Weaver, who guided me through the process (along with nalaginrut who answered my first clueless questions…).
To test the REPL, get the current code snapshot [101], unpack it, run./bootstrap.sh
, start guile with$ guile -L .
(requires guile 2.x) and enter,language wisp
.
Example usage:then hit enter thrice.display "Hello World!\n"
Voilà, you have wisp at the REPL!
Caveeat: the wisp-parser is still experimental and contains known bugs. Use it for testing, but please do not rely on it for important stuff, yet.
Update (2013-09-10): wisp-guile.w can now parse itself [102]! Bootstrapping: The magical feeling of seeing a language (dialect) grow up to live by itself:python3 wisp.py wisp-guile.w > 1 && guile 1 wisp-guile.w > 2 && guile 2 wisp-guile.w > 3 && diff 2 3
. Starting today, wisp is implemented in wisp.
Update (2013-08-08): Wisp 0.3.1 [103] released (Changelog [104]).
Wisp is a simple preprocessor which turns indentation sensitive syntax into Lisp syntax.
The basic goal is to create the simplest possible indentation based syntax which is able to express all possibilities of Lisp.
Basically it works by inferring the parentheses of lisp by reading the indentation of lines.
It is related to SRFI-49 [105] and the readable Lisp S-expressions Project [33] (and actually inspired by the latter), but it tries to Keep it Simple and Stupid: wisp is a simple preprocessor which can be called by any lisp implementation to add support for indentation sensitive syntax. To repeat the initial quote:
I love the syntax of Python, but crave the simplicity and power of Lisp.
With wisp I hope to make it possible to create lisp code which is easily readable for non-programmers (and me!) and at the same time keeps the simplicity and power of Lisp.
Its main technical improvement over SRFI-49 and Project Readable is using lines prefixed by a dot (". ") to mark the continuation of the parameters of a function after intermediate function calls.
The dot-syntax means, instead of marking every function call, it marks every line which does not begin with a function call - which is the much less common case in lisp-code.
See the Updates for information how to get the current version of wisp.
Enter
in Enter three witches [106].
display "Hello World!" ↦ (display "Hello World!")
display ↦ (display string-append "Hello " "World!" ↦ (string-append "Hello " "World!"))
display ↦ (display string-append "Hello " "World!" ↦ (string-append "Hello " "World!")) display "Hello Again!" ↦ (display "Hello Again!")
' "Hello World!" ↦ '("Hello World!")
string-append "Hello" ↦ (string-append "Hello" string-append " " "World" ↦ (string-append " " "World") . "!" ↦ "!")
let ↦ (let : ↦ ((msg "Hello World!")) msg "Hello World!" ↦ (display msg)) display msg ↦
define : hello who ↦ (define (hello who) display ↦ (display string-append "Hello " who "!" ↦ (string-append "Hello " who "!")))
define : hello who ↦ (define (hello who) _ display ↦ (display ___ string-append "Hello " who "!" ↦ (string-append "Hello " who "!")))
To make that easier to understand, let’s just look at the examples in more detail:
display "Hello World!" ↦ (display "Hello World!")
This one is easy: Just add a bracket before and after the content.
display "Hello World!" ↦ (display "Hello World!") display "Hello Again!" ↦ (display "Hello Again!")
Multiple lines with the same indentation are separate function calls (except if one of them starts with ". ", see Continue arguments, shown in a few lines).
display ↦ (display string-append "Hello " "World!" ↦ (string-append "Hello " "World!"))
If a line is more indented than a previous line, it is a sibling to the previous function: The brackets of the previous function gets closed after the (last) sibling line.
By using a . followed by a space as the first non-whitespace character on a line, you can mark it as continuation of the previous less-indented line. Then it is no function call but continues the list of parameters of the funtcion.
I use a very synthetic example here to avoid introducing additional unrelated concepts.
string-append "Hello" ↦ (string-append "Hello" string-append " " "World" ↦ (string-append " " "World") . "!" ↦ "!")
As you can see, the final "!" is not treated as a function call but as parameter to the first string-append.
This syntax extends the notion of the dot as identity function. In many lisp implementations1 we already have `(= a (. a))`.
= a ↦ (= a . a ↦ (. a))
With wisp, we extend that equality to `(= '(a b c) '((. a b c)))`.
. a b c ↦ a b c
If you use `let`, you often need double brackets. Since using pure indentation in empty lines would be really error-prone, we need a way to mark a line as indentation level.
To add multiple brackets, we use a colon to mark an intermediate line as additional indentation level.
let ↦ (let : ↦ ((msg "Hello World!")) msg "Hello World!" ↦ (display msg)) display msg ↦
Since we already use the colon as syntax element, we can make it possible to use it everywhere to open a bracket - even within a line containing other code. Since wide unicode characters would make it hard to find the indentation of that colon, such an inline-function call always ends at the end of the line. Practically that means, the opened bracket of an inline colon always gets closed at the end of the line.
define : hello who ↦ (define (hello who) display : string-append "Hello " who "!" ↦ (display (string-append "Hello " who "!")))
This also allows using inline-let:
let ↦ (let : msg "Hello World!" ↦ ((msg "Hello World!")) display msg ↦ (display msg))
and can be stacked for more compact code:
let : : msg "Hello World!" ↦ (let ((msg "Hello World!")) display msg ↦ (display msg))
To make the indentation visible in non-whitespace-preserving environments like badly written html, you can replace any number of consecutive initial spaces by underscores, as long as at least one whitespace is left between the underscores and any following character. You can escape initial underscores by prefixing the first one with \ ("\___ a" → "(___ a)"), if you have to use them as function names.
define : hello who ↦ (define (hello who) _ display ↦ (display ___ string-append "Hello " who "!" ↦ (string-append "Hello " who "!")))
I do not like adding any unnecessary syntax element to lisp. So I want to show explicitely why the syntax elements are required to meet the goal of wisp: indentation-based lisp with a simple preprocessor.
We have to be able to continue the arguments of a function after a call to a function, and we must be able to split the arguments over multiple lines. That’s what the leading dot allows. Also the dot at the beginning of the line as marker of the continuation of a variable list is a generalization of using the dot as identity function - which is an implementation detail in many lisps.
`(. a)` is just `a`.
So for the single variable case, this would not even need additional parsing: wisp could just parse ". a" to "(. a)" and produce the correct result in most lisps. But forcing programmers to always use separate lines for each parameter would be very inconvenient, so the definition of the dot at the beginning of the line is extended to mean “take every element in this line as parameter to the parent function”.
Essentially this dot-rule means that we mark variables at the beginning of lines instead of marking function calls, since in Lisp variables at the beginning of a line are much rarer than in other programming languages. In Lisp, assigning a value to a variable is a function call while it is a syntax element in many other languages. What would be a variable at the beginning of a line in other languages is a function call in Lisp.
(Optimize for the common case, not for the rare case)
For double brackets and for some other cases we must have a way to mark indentation levels without any code. I chose the colon, because it is the most common non-alpha-numeric character in normal prose which is not already reserved as syntax by lisp when it is surrounded by whitespace, and because it already gets used for marking keyword arguments to functions in Emacs Lisp, so it does not add completely alien characters.
The function call via inline " : " is a limited generalization of using the colon to mark an indentation level: If we add a syntax-element, we should use it as widely as possible to justify the added syntax overhead.
But if you need to use : as variable or function name, you can still do that by escaping it with a backslash (example: "\:"), so this does not forbid using the character.
In Python the whitespace hostile html already presents problems with sharing code - for example in email list archives and forums. But in Python the indentation can mostly be inferred by looking at the previous line: If that ends with a colon, the next line must be more indented (there is nothing to clearly mark reduced indentation, though). In wisp we do not have this help, so we need a way to survive in that hostile environment.
The underscore is commonly used to denote a space in URLs, where spaces are inconvenient, but it is rarely used in lisp (where the dash ("-") is mostly used instead), so it seems like a a natural choice.
You can still use underscores anywhere but at the beginning of the line. If you want to use it at the beginning of the line you can simply escape it by prefixing the first underscore with a backslash (example: "\___").
A few months ago I found the readable Lisp project [33] which aims at producing indentation based lisp, and I was thrilled. I had already done a small experiment with an indentation to lisp parser, but I was more than willing to throw out my crappy code for the well-integrated parser they had.
Fast forward half a year. It’s February 2013 and I started reading the readable list again after being out of touch for a few months because the birth of my daughter left little time for side-projects. And I was shocked to see that the readable folks had piled lots of additional syntax elements on their beautiful core model, which for me destroyed the simplicity and beauty of lisp. When language programmers add syntax using \\, $ and <>, you can be sure that it is no simple lisp anymore. To me readability does not just mean beautiful code, but rather easy to understand code with simple concepts which are used consistently. I prefer having some ugly corner cases to adding more syntax which makes the whole language more complex.
I told them about that [107] and proposed a simpler structure which achieved almost the same as their complex structure. To my horror they proposed adding my proposal to readable, making it even more bloated (in my opinion). We discussed a long time - the current syntax for inline-colons is a direct result of that discussion in the readable list - then Alan wrote me a nice mail [108], explaining that readable will keep its direction. He finished with «We hope you continue to work with or on indentation-based syntaxes for Lisp, whether sweet-expressions, your current proposal, or some other future notation you can develop.»
It took me about a month to answer him, but the thought never left my mind (@Alan: See what you did? You anchored the thought of indentation based lisp even deeper in my mind. As if I did not already have too many side-projects… :)).
Then I had finished the first version of a simple whitespace-to-lisp preprocessor.
And today I added support for reading indentation based lisp from standard input which allows actually using it as in-process preprocessor without needing temporary files, so I think it is time for a real release outside my Mercurial repository [109].
So: Have fun with wisp v0.2 (tarball) [110]!
PS: Wisp is linked in the comparisions of SRFI-110 [111].
Anhang | Größe |
---|---|
wisp-1.0.3.tar_.gz [13] | 756.71 KB |
Update: The recording is now online at ftp.fau.de/fosdem/2017/K.4.601/naturalscriptwritingguile.vp8.webm [112]
Here’s the stream to the Guile [19] devroom at #FOSDEM: https://live.fosdem.org/watch/k4601 [113]
Schedule (also on the FOSDEM page [60]):
Every one of these talks sounds awesome! Here’s where we get deep.
Update 2020: In Dryads Wake [114] I am starting a game using the way presented here to write dialogue-focused games with minimal ceremony. Demo: https://dryads-wake.1w6.org [115]
Update 2018: Bitbucket is dead to me. You can find the source at https://hg.sr.ht/~arnebab/ews [116]
Update 2017: A matured version of the work shown here was presented at FOSDEM 2017 as Natural script writing with Guile [59]. There is also a video of the presentation [112] (held by Chris Allan Webber; more info… [117]). Happy Hacking!
Programming languages allow expressing ideas in non-ambiguous ways. Let’s do a play.
say Yes, I do!
Yes, I do!
This is a sketch of applying Wisp [118] to a pet issue of mine: Writing the story of games with minimal syntax overhead, but still using a full-fledged programming language. My previous try was the TextRPG [119], using Python. It was fully usable. This experiment drafts a solution to show how much more is possible with Guile Scheme using Wisp syntax (also known an SRFI-119 [91]).
To follow the code here, you need Guile 2.0.11 [19] on a GNU Linux system. Then you can install Wisp and start a REPL with
wget https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.6.tar.gz
tar xf wi*z; cd wi*/; ./c*e; make check; guile -L . --language=wisp
For finding minimal syntax, the first thing to do is to look at how such a structure would be written for humans. Let’s take the obvious and use Shakespeare: Macbeth, Act 1, Scene 1 [120] (also it’s public domain, so we avoid all copyright issues). Note that in the original, the second and last non-empty line are shown as italic.
SCENE I. A desert place.
Thunder and lightning. Enter three Witches
First Witch
When shall we three meet again
In thunder, lightning, or in rain?
Second Witch
When the hurlyburly's done,
When the battle's lost and won.
Third Witch
That will be ere the set of sun.
First Witch
Where the place?
Second Witch
Upon the heath.
Third Witch
There to meet with Macbeth.
First Witch
I come, Graymalkin!
Second Witch
Paddock calls.
Third Witch
Anon.
ALL
Fair is foul, and foul is fair:
Hover through the fog and filthy air.
Exeunt
Let’s analyze this: A scene header, a scene description with a list of people, then the simple format
person
something said
and something more
For this draft, it should suffice to reproduce this format with a full fledged programming language.
This is how our code should look:
First Witch
When shall we three meet again
In thunder, lightning, or in rain?
As a first step, let’s see how code which simply prints this would look in plain Wisp. The simplest way would just use a multiline string:
display "First Witch When shall we three meet again In thunder, lightning, or in rain?\n"
That works, but it’s not really nice. For one thing, the program does not have any of the semantic information a human would have, so if we wanted to show the First Witch in a different color than the Second Witch, we’d already be lost. Also throwing everything in a string might work, but when we need highlighting of certain parts, it gets ugly: We actually have to do string parsing by hand.
But this is Scheme, so there’s a better way. We can go as far as writing the sentences plainly, if we add a macro which grabs the variable names for us. We can do a simple form of this in just six short lines:
define-syntax-rule : First_Witch a ... format #t "~A\n" string-join map : lambda (x) (string-join (map symbol->string x)) quote : a ... . "\n"
This already gives us the following syntax:
First_Witch When shall we three meet again In thunder, lightning, or in rain?
which prints
When shall we three meet again
In thunder, lightning, or in rain?
Note that :
, .
and , are only special when they are preceded by
whitespace or are the first elements on a line, so we can freely use
them here.
To polish the code, we could get rid of the underscore by treating
everything on the first line as part of the character (indented lines
are sublists of the main list, so a recursive syntax-case [121] macro can
distinguish them easily), and we could add highlighting with
comma-prefixed parens (via standard Scheme preprocessing these get
transformed into (unquote (...))
). Finally we could add a macro for
the scene, which creates these specialized parsers for all persons.
A completed parser could then read input files like the following:
SCENE I. A desert place. Thunder and lightning. Enter : First Witch Second Witch Third Witch First Witch When shall ,(emphasized we three) meet again In thunder, lightning, or in rain? Second Witch When the hurlyburly's done, When the battle's lost and won. ; ... ALL Fair is foul, and foul is fair: Hover through the fog and filthy air. action Exeunt
And with that the solution is sketched. I hope it was interesting for you to see how easy it is to create this!
Note also that this is not just a specialized text-parser. It provides access to all of Guile Scheme, so if you need interactivity or something like the branching story [122] from TextRPG, scene writers can easily add it without requiring help from the core system. That’s part of the Freedom for Developers from the language implementors which is at the core of GNU Guile [19].
Don’t use this as data interchange format for things downloaded from the web, though: It does give access to a full Turing complete language. That’s part of its power which allows you to realize a simple syntax without having to implementent all kinds of specialized features which are needed for only one or two scenes. If you want to exchange the stories, better create a restricted interchange-format which can be exported from scenes written in the general format. Use lossy serializiation to protect your users.
And that’s all I wanted to say ☺
Happy Hacking!
PS: For another use of Shakespeare in programming languages, see the Shakespeare programming language [123]. Where this article uses Wisp [124] as a very low ceremony language to represent very high level concepts, the Shapespeare programming language takes the opposite approach by providing an extremely high-ceremony language for very low-level concepts. Thanks to ZMeson [125] for reminding me ☺
Anhang | Größe |
---|---|
2015-09-12-Sa-Guile-scheme-wisp-for-low-ceremony-languages.org [126] | 6.35 KB |
enter-three-witches.w [127] | 1.23 KB |
Links:
[1] https://www.draketo.de/software/wisp
[2] http://www.draketo.de/proj/wisp/why-wisp.html
[3] https://hg.sr.ht/~arnebab/wisp
[4] https://gnu.org/s/guix
[5] https://aur.archlinux.org/packages/guile-wisp-hg
[6] https://hg.sr.ht/~arnebab/wisp/browse/examples
[7] https://hg.sr.ht/~arnebab/wisp/browse/tests
[8] https://twitter.com/dustyweb/status/646788662361849856
[9] https://identi.ca/cwebber/note/qG9yHAqJTseD_YLSDsUbjg
[10] http://dustycloud.org/blog/wisp-lisp-alternative/
[11] https://fosdem.org/2019/schedule/event/guixinfra/
[12] https://hg.sr.ht/~arnebab/wisp/browse/examples/with.w?rev=3447d48e85620c639131c0e1b47308503df5eb81#L15
[13] http://www.draketo.de/files/wisp-1.0.3.tar_.gz
[14] https://hg.sr.ht/~arnebab/wispserve
[15] https://hg.sr.ht/~arnebab/conf
[16] https://archive.fosdem.org/2017/schedule/event/naturalscriptwritingguile/
[17] https://hg.sr.ht/~arnebab/wisp/browse/examples/doctests.w?rev=ba80bf370de0#L7
[18] https://hg.sr.ht/~arnebab/wisp/browse/NEWS
[19] http://gnu.org/s/guile
[20] http://ttk.me/w/NewBase60
[21] https://webchat.freenode.net/?randomnick=1&channels=%23guile&uio=d4
[22] http://www.draketo.de/english/wisp#sec-2
[23] https://marmalade-repo.org/packages/wisp-mode
[24] http://sph.mn/computer/guides/c.html
[25] http://sph.mn/computer/guides/c/c-indent.html
[26] https://github.com/sph-mn/sph-sc
[27] https://www.draketo.de/proj/wisp/index.html
[28] https://bitbucket.org/ArneBab/site
[29] https://bitbucket.org/ArneBab/wisp/
[30] https://www.draketo.de/proj/hgsite/index.html
[31] https://bitbucket.org/ArneBab/wisp/downloads/wisp-1.0.tar.gz
[32] https://fosdem.org/2019/schedule/event/experiencewithwisp/
[33] http://readable.sourceforge.net/
[34] http://www.draketo.de/proj/with-guise-and-guile/wisp-project.html
[35] http://www.draketo.de/proj/with-guise-and-guile/wisp-tutorial.html
[36] https://bitbucket.org/ArneBab/wisp/src/v1.0/NEWS
[37] http://www.draketo.de/light/english/wisp-lisp-indentation-preprocessor#sec-2
[38] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.9.1.tar.gz
[39] https://bitbucket.org/ArneBab/wisp/src/v0.9.9.1/NEWS
[40] http://www.draketo.de/light/english/wisp-lisp-indentation-preprocessor#sec-3
[41] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.9.tar.gz
[42] https://bitbucket.org/ArneBab/wisp/src/v0.9.9/NEWS
[43] http://www.draketo.de/proj/with-guise-and-guile/index.html
[44] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.8.tar.gz
[45] https://bitbucket.org/ArneBab/wisp/src/v0.9.8/NEWS
[46] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.7.tar.gz
[47] https://bitbucket.org/ArneBab/wisp/src/v0.9.7/NEWS
[48] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.6.tar.gz
[49] http://www.draketo.de/proj/with-guise-and-guile/
[50] https://bitbucket.org/ArneBab/wisp/src/v0.9.6/NEWS
[51] https://www.archlinux.de/
[52] https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages
[53] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.2.tar.gz
[54] https://bitbucket.org/ArneBab/wisp/src/v0.9.2/examples/duel.w
[55] https://bitbucket.org/ArneBab/wisp/src/v0.9.2/examples/benchmark.w
[56] https://bitbucket.org/ArneBab/wisp/src/v0.9.2/NEWS
[57] https://www.gnu.org/software/guile/news/gnu-guile-220-released.html
[58] http://lists.gnu.org/archive/html/guile-devel/2017-03/msg00059.html
[59] https://fosdem.org/2017/schedule/event/naturalscriptwritingguile/
[60] https://fosdem.org/2017/schedule/track/gnu_guile/
[61] https://video.fosdem.org/2017/K.4.601/naturalscriptwritingguile.vp8.webm
[62] https://fosdem.org/2017/schedule/event/naturalscriptwritingguile/attachments/slides/1653/export/events/attachments/naturalscriptwritingguile/slides/1653/2017_01_30_Mo_natural_script_writing_guile.pdf
[63] https://fosdem.org/2017/schedule/event/naturalscriptwritingguile/attachments/slides/1654/export/events/attachments/naturalscriptwritingguile/slides/1654/2017_01_30_Mo_natural_script_writing_guile.org
[64] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.1.tar.gz
[65] https://bitbucket.org/ArneBab/wisp/src/v0.9.1/NEWS
[66] https://fosdem.org/2016/schedule/event/guilewisp/
[67] https://fosdem.org/2016/schedule/track/gnu_guile/
[68] https://twitter.com/hashtag/lisp?src=hash
[69] https://twitter.com/hashtag/python?src=hash
[70] https://twitter.com/ArneBab
[71] https://twitter.com/hashtag/fosdem?src=hash
[72] https://t.co/TaGhIGruIU
[73] https://twitter.com/JANieuwenhuizen/status/693376287730171904
[74] https://fosdem.org/2016/schedule/event/guilewisp/attachments/slides/911/export/events/attachments/guilewisp/slides/911/fosdem2016_arne_babenhauserheide_wisp.pdf
[75] https://fosdem.org/2016/schedule/event/guilewisp/attachments/other/912/export/events/attachments/guilewisp/other/912/fosdem2016_arne_babenhauserheide_wisp.org
[76] http://gnu.org/s/guix
[77] http://git.savannah.gnu.org/cgit/guix.git/tree/gnu/packages/guile.scm?id=c2e87caaa6b7efb9c18c46fd4f9d4370f68c8db7#n734
[78] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.9.0.tar.gz
[79] http://lists.gnu.org/archive/html/guix-devel/2015-10/msg00114.html
[80] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.6.tar.gz
[81] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/evolve.w
[82] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/newbase60.w
[83] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/cli.w
[84] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/cholesky.w
[85] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/closure.w
[86] https://bitbucket.org/ArneBab/wisp/src/v0.8.6/examples/hoist-in-loop.w
[87] http://python.org
[88] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.3.tar.gz
[89] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.2.tar.gz
[90] https://bitbucket.org/ArneBab/wisp/src/v0.8.2/examples
[91] http://srfi.schemers.org/srfi-119/srfi-119.html
[92] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.1.tar.gz
[93] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.8.0.tar.gz
[94] https://bitbucket.org/ArneBab/wisp/src/v0.8.0/NEWS
[95] http://schemers.org/
[96] https://bitbucket.org/ArneBab/wisp/downloads/wisp-0.6.6.tar.gz
[97] https://bitbucket.org/ArneBab/wisp/src/d62499d0d9b876e44c4717c0aed8c7ad98b7f8af/NEWS?at=default
[98] http://github.com/krisajenkins/wispjs-mode
[99] http://marmalade-repo.org/packages/wisp-mode/0.1.5
[100] http://marmalade-repo.org/packages/wisp/0.1.4
[101] https://bitbucket.org/ArneBab/wisp/get/v0.5.tar.bz2
[102] https://bitbucket.org/ArneBab/wisp/commits/0509e9e3418789ef307ae49b7dd862380c5c5bf8
[103] https://bitbucket.org/ArneBab/wisp/get/v0.3.1.tar.bz2
[104] http://www.draketo.de/proj/wisp/src/eef180617d24bad925b0e85769b8e851fb57f2dc/Changelog.html
[105] http://srfi.schemers.org/srfi-49/srfi-49.html
[106] https://bitbucket.org/ArneBab/wisp/src/5dfd8644882d181d61c479b0f82be0e644ca9fd6/examples/enter-three-witches.w#lines-210
[107] http://sourceforge.net/mailarchive/forum.php?thread_name=4161580.FVPfGjZCMV@fluss&forum_name=readable-discuss
[108] http://sourceforge.net/mailarchive/message.php?msg_id=30511834
[109] http://bitbucket.org/ArneBab/wisp
[110] https://bitbucket.org/ArneBab/wisp/get/v0.2.tar.bz2
[111] http://srfi.schemers.org/srfi-110/srfi-110.html#wisp
[112] http://ftp.fau.de/fosdem/2017/K.4.601/naturalscriptwritingguile.vp8.webm
[113] https://live.fosdem.org/watch/k4601
[114] https://hg.sr.ht/~arnebab/dryads-wake
[115] https://dryads-wake.1w6.org
[116] https://hg.sr.ht/~arnebab/ews
[117] http://www.draketo.de/english/wisp/fosdem2017-stream
[118] http://draketo.de/english/wisp
[119] https://pypi.python.org/pypi/TextRPG/
[120] http://shakespeare.mit.edu/macbeth/macbeth.1.1.html
[121] https://www.gnu.org/software/guile/manual/html_node/Syntax-Case.html
[122] https://bitbucket.org/ArneBab/textrpg/src/tip/branching_story.py
[123] https://en.wikipedia.org/wiki/Shakespeare_(programming_language)
[124] http://www.draketo.de/english/wisp
[125] https://www.reddit.com/r/programming/comments/3ku1uk/programming_languages_allow_expressing_ideas_in/cv14ph7
[126] http://www.draketo.de/files/2015-09-12-Sa-Guile-scheme-wisp-for-low-ceremony-languages_0.org
[127] http://www.draketo.de/files/enter-three-witches.w