wisp
 
(Arne Babenhauserheide)
2016-07-12: merge new examples, autotools work, slides and bugfixes. stable v0.9.1

merge new examples, autotools work, slides and bugfixes.

diff --git a/.bugs/bugs b/.bugs/bugs
--- a/.bugs/bugs
+++ b/.bugs/bugs
@@ -1,15 +1,16 @@
 testsuite: to pass, the tree-il has to match, not the emitted string. This allows for reader-only implementations. | owner:, open:False, id:00b74a730bbf076e73166e817ca7b0a273b376d4, time:1408224636.42
-wisp-scheme: backtraces should show the wisp source.         | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:0475df81a594a52d171a1b811752ca64e5a71df5, time:1426792099.58
+wisp-scheme: backtraces should show the wisp source.         | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:0475df81a594a52d171a1b811752ca64e5a71df5, time:1426792099.58
 wisp-scheme: unescape \_ and \:                              | owner:, open:False, id:086f61a06e16f1ef56e9917453bbd55b5879d15d, time:1415121255.99
 fails when I add stuff at the end of end of example.w        | owner:, open:False, id:08c68e1ce0c9798184c01806d2661a3220bff3cd, time:1363789693.79
 wisp-mode in quoted lists only the first item is colorized as data, but all words up to the last paren should be colorized. | owner:, open:True, id:1675ca3f894ed8470fa292149a476a2fa0d17140, time:1397196957.45
 add a testsuite for wisp parsers.                            | owner:, open:False, id:1c05d27ac916e1a823b8985a094947907c3c19af, time:1379064922.74
 wisp-mode: export wisp to html fails in org-mode: font-lock-fontify-keywords-region: Invalid regexp | owner:, open:False, id:1e46d8c05580c961c37a32d36c987a5dd1d34943, time:1389371020.39
+wisp-scheme: commans like ,expand do not work due to stray leading space | owner:, open:True, id:20df756e6c7f61885857c2a67a7111884294f9aa, time:1444645835.87
 an empty line with : should start with double parens         | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:2e188ddf44d36e4605030d3c58607ebfa97d189e, time:1390328674.43
 wisp-in-wisp: remove the leading . for continuation lines after inferring the brackets. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:2e42e5b64622f0cc383eb8acc3d510912e925bf0, time:1377476687.79
-interpret ` , : correctly.                                   | owner:, open:True, id:2feb5f048b55274c1bc7c8168c8cb358c0c8dd1d, time:1426777900.6
+interpret ` , : correctly.                                   | owner:, open:False, id:2feb5f048b55274c1bc7c8168c8cb358c0c8dd1d, time:1426777900.6
 '() gives REPR-QUOTE-...                                     | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:30c42de75c137f483245898e2a62af1e65cf19a6, time:1415060388.34
-multiple escaped initial underscores must be unescaped.      | owner:, open:True, id:314e45488da4c7c8298c4c64ece03359918d057b, time:1415959749.14
+multiple escaped initial underscores must be unescaped.      | owner:, open:False, id:314e45488da4c7c8298c4c64ece03359918d057b, time:1415959749.14
 wisp: handle general paren prefixes like quote, quasiquote, etc. | owner:, open:False, id:323ff94b5be635742619467e1cb44f4c0d96f63f, time:1379047798.47
 throw an exception when reducing indentation to an unknown indentation level. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:424186bd85f186b7279c5c59e2bd42f847284719, time:1376003568.91
 LANG=C breaks bootstrap: python encoding error: it uses ascii. | owner:, open:True, id:43c7461bfb6f35a90ff3f4497c8232e2457ce1c7, time:1427819877.7
@@ -23,6 +24,7 @@ wisp-guile.w does not yet remove the lea
 inline ' : should be '(                                      | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:72d534a8b23b4cb168017f1bb7d8816f0ea170c4, time:1366497335.26
 failing test tests/shebang.w                                 | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:74a851f83af8996465a7b24d8453161beb0f0fd5, time:1379106761.57
 non-nested multiline comments with #! !#. Requires restructuring. | owner:, open:False, id:7a57614fa920b2ddad002d044b144d0bb7c34f84, time:1389364108.01
+wisp-guile: multiline-stringsn break. See tests/multiline-string.w | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:7e457dfe042341ea24d0b57c4f0f48fa3d9affc3, time:1462055749.4
 wisp-scheme: interpret , : as ,() similar to : ,             | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:85e150dcb10c49d8f51db525e07d24e83bdba0f1, time:1416432201.21
 wisp-guile: support nested multi-line comments with #| ... |#: multiline comments (srfi-30). Requires restructuring. | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:8cf6202873d4454f57813dd17cf60432059f7c62, time:1389569421.6
 wisp-scheme: Does not recognize the . #!curly-infix request for curly-infix or other reader syntax. | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:91f27adb7d4e378e034b3408b6e4616f707f9587, time:1418162368.88
@@ -37,6 +39,7 @@ make this work: let : : origfile ( open-
 wisp.py breaks on \ - quote, escaped backslash, quote. Ignored, because wisp.py is only needed for bootstrapping. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:d75a93ca58ade5b3e3e51f1e7ee9782e743ac131, time:1377424552.02
 comments containing a closing parenthesis can break the parser. | owner:, open:False, id:d9147504868960e5fbc2648474d48ce5c9bd1a02, time:1374838747.22
 breaks on empty files                                        | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:e40fa7a93eb2c497dca1af7eed22ad5ed5cfbe7f, time:1390325470.91
+quoting and unquoting sometimes breaks                       | owner:, open:True, id:e445b9ed2403cd366e556f384129c08970f0b77a, time:1444647166.82
 wisp-scheme: breaks on lines with only underscores. These should be treated as empty lines. | owner:, open:False, id:e464b5ce49deb14a80f67d50c6d70043ca9bde25, time:1415124488.16
 quote as only char in a line gets parenthized instead of becoming a paren-prefix. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:eb7468387e90bb5d13f5a5d81c6f4a704f2ca0fb, time:1390326369.6
 in the REPL output can be delayed by one line: Appears only when submitting the next command. | owner:, open:False, id:f1e42bbd4c17a2dec886c26d9c14e770bcff66d2, time:1415972414.48
diff --git a/.bugs/details/e445b9ed2403cd366e556f384129c08970f0b77a.txt b/.bugs/details/e445b9ed2403cd366e556f384129c08970f0b77a.txt
new file mode 100644
--- /dev/null
+++ b/.bugs/details/e445b9ed2403cd366e556f384129c08970f0b77a.txt
@@ -0,0 +1,46 @@
+# Lines starting with '#' and sections without content
+# are not displayed by a call to 'details'
+#
+[paths]
+# Paths related to this bug.
+# suggested format: REPO_PATH:LINENUMBERS
+
+
+[details]
+# Additional details
+
+This is a collection of problems connected to quoting.
+
+
+` ,(+ 1 2) ,(+ 2 3) , : + 4 5
+
+should be equivalent to
+
+`(,(+ 1 2) ,(+ 2 3) ,(+ 4 5))
+
+but gives 
+
+(REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd (unquote (+ 1 2)) (unquote (+ 2 3)) (unquote (+ 4 5))
+
+this works:
+
+` ,(+ 1 2) ,(+ 2 3) : , + 4 5
+
+display ` , : + 1
+should give (display `(,(+ 1)))
+but gives (display `(,((+ 1))))
+
+[expected]
+# The expected result
+
+
+[actual]
+# What happened instead
+
+
+[reproduce]
+# Reproduction steps
+
+
+[comments]
+# Comments and updates - leave your name
diff --git a/Makefile.am b/Makefile.am
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,37 +1,55 @@
-wisp = wisp-scheme.scm language/wisp/spec.scm
-wisp_SOURCES = wisp-guile.w wisp-reader.w wisp-scheme.w
-EXTRA_DIST = $(wisp_SOURCES) $(wisp_DATA) bootstrap.sh bootstrap-reader.sh examples tests wisp-repl-guile.sh testrunner.w wisp.py wisp.scm
-CLEANFILES = ${wisp} ChangeLog
-DISTCLEANFILES = ${CLEANFILES} @abs_top_builddir@/1 @abs_top_builddir@/2 @abs_top_builddir@/syntaxtests.sh @abs_top_builddir@/syntaxtestsreader.sh
+SUFFICES = .w .scm .sh
+
+sitedir = $(GUILE_SITE)
+site_DATA = wisp-scheme.scm
+
+langdir = ${datarootdir}/guile/$(GUILE_EFFECTIVE_VERSION)/language/wisp
+lang_DATA = language/wisp/spec.scm
+
+WISP = wisp-guile.w wisp-reader.w wisp-scheme.w
+
+EXTRA_DIST = $(WISP) bootstrap.sh bootstrap-reader.sh examples tests wisp-repl-guile.sh testrunner.w wisp.py wisp.scm
+CLEANFILES = 1 2
+DISTCLEANFILES = $(lang_DATA) $(site_DATA)
 # don't spout out lots of stuff at each distcheck. Disable for debugging.
 AM_DISTCHECK_CONFIGURE_FLAGS="--quiet"
 
-
-all : ${wisp}
-
 ChangeLog :
 	hg log --style changelog > ChangeLog
 
-# emacs org-mode beamer build instructions
-${wisp} : input.in.intermediate
-
-.INTERMEDIATE: input.in.intermediate
-input.in.intermediate: ${wisp_SOURCES} wisp.scm
-	@abs_top_srcdir@/bootstrap-reader.sh @abs_top_srcdir@ @abs_top_builddir@ @guile@ wisp.scm 2>&1 | sed "s/^;;;.*//" 2>&1 | grep . 1>&2 ; test ! $$? -eq 0 # it worked if grep does not find anything
+.INTERMEDIATE: .mydatastuff
+$(site_DATA) $(lang_DATA): .wispbootstrap
+.wispbootstrap : ${WISP} wisp.scm
+	@abs_top_srcdir@/bootstrap-reader.sh @abs_top_srcdir@ @abs_top_builddir@ @GUILE@ wisp.scm 2>&1 | sed "s/^;;;.*//" 2>&1 | grep . 1>&2 ; test ! $$? -eq 0 # it worked if grep does not find anything
 
 wisp.scm: wisp-guile.w wisp.py
-	@abs_top_srcdir@/bootstrap.sh @abs_top_srcdir@ @guile@ @python3@ 2>&1 | sed "s/^;;;.*//" 2>&1 | grep . 1>&2 ; test ! $$? -eq 0 # it worked if grep does not find anything
+	@abs_top_srcdir@/bootstrap.sh @abs_top_srcdir@ @GUILE@ @python3@ 2>&1 | sed "s/^;;;.*//" 2>&1 | grep . 1>&2 ; test ! $$? -eq 0 # it worked if grep does not find anything
 
-.PHONY: syntaxtests.sh
+.INTERMEDIATE: syntaxtests.sh
 syntaxtests.sh : wisp.scm tests/runtests-scheme-preprocessor.sh
-	echo '#!/bin/bash' > @abs_top_builddir@/$@
+	echo '#!/usr/bin/env bash' > @abs_top_builddir@/$@
 	echo @abs_top_srcdir@/tests/runtests-scheme-preprocessor.sh @abs_top_srcdir@ @abs_top_builddir@ >> @abs_top_builddir@/$@
 	chmod +x @abs_top_builddir@/$@
 
-.PHONY: syntaxtestsreader.sh
-syntaxtestsreader.sh : ${wisp} wisp.scm tests/runtests-scheme-reader.sh
-	echo '#!/bin/bash' > @abs_top_builddir@/$@
+.INTERMEDIATE: syntaxtestsreader.sh
+syntaxtestsreader.sh : ${WISP} wisp.scm tests/runtests-scheme-reader.sh
+	echo '#!/usr/bin/env bash' > @abs_top_builddir@/$@
 	echo @abs_top_srcdir@/tests/runtests-scheme-reader.sh @abs_top_srcdir@ @abs_top_builddir@ >> @abs_top_builddir@/$@
 	chmod +x @abs_top_builddir@/$@
 
 TESTS=syntaxtests.sh syntaxtestsreader.sh
+
+# The installchecks won't work in a "make distcheck", because
+# they won't be installed in the final location used by the tools.
+# FIXME: automake should honor DESTDIR set by make distcheck
+if IS_MAKE_DISTCHECK
+install-siteDATA:
+	echo "Running 'make distcheck'; local installchecks disabled."
+install-langDATA:
+	echo "Running 'make distcheck'; local installchecks disabled."
+uninstall-siteDATA:
+	echo "Running 'make distcheck'; local installchecks disabled."
+uninstall-langDATA:
+	echo "Running 'make distcheck'; local installchecks disabled."
+else !IS_MAKE_DISTCHECK
+endif !IS_MAKE_DISTCHECK
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,38 @@
+wisp ():
+- 
+
+wisp 0.9.1:
+- add enter three witches: minimum overhead for a theater script
+- examples/with.w: syntax for a with keyword
+  which works like with in Python
+- wisp-mode: add indentation cycling patch
+- Use GUILE_PKG instead of PKG_CHECK_MODULES
+  thanks to Jan Wedekind
+- "make install" adds the wisp language to Guile
+  thanks to Jan Wedekind
+- add examples/securepassword.w
+- add slides for FOSDEM 2016 talk
+- examples/d20world.w: implement basic plotting on a map.
+- more of the examples use the guile reader by default
+  thanks to Kevin W. van Rooijen
+- examples/d6.w: add critical rolls
+- use GUILE_PROGS to check for guile instead of using homegrown stuff
+- fix multiline string bug when the string includes \"
+  tracked down by Michael Lamb
+- new example: say.w (companion to
+  http://draketo.de/english/wisp/shakespeare )
+- add running mean and async
+- add ensemble estimation
+- always use /usr/bin/env bash as hashbang for shell scripts.
+- fix factorial code
+- improve handling of "` , :" (incomplete, see bug e44)
+- unescape up to 6 leading underscores (fix bug 31)
+
 wisp 0.9.0 (2015-09-24):
 - actually distribute wisp.scm
 - wisp now starts from wisp.scm. To bootstrap a release from Python, 
   remove wisp.scm and run `make wisp.scm`
+  starting with 0.9.0, installing wisp no longer depends on Python.
 
 wisp 0.8.7 (2015-09-24):
 - new example say.w, companion to 
diff --git a/bootstrap-reader.sh b/bootstrap-reader.sh
--- a/bootstrap-reader.sh
+++ b/bootstrap-reader.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 if [[ x"$1" == x"" ]]; then 
     srcdir=.
diff --git a/bootstrap.sh b/bootstrap.sh
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 # Bootstrap wisp-guile with wisp.py
 if [[ x"$1" == x"" ]]; then 
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -1,18 +1,38 @@
 dnl run `autoreconf -i` to generate a configure script. 
 dnl Then run ./configure to generate a Makefile.
 dnl Finally run make to generate the project.
-AC_INIT([wisp], [0.9.0],
+AC_INIT([wisp], [0.9.1],
         [arne_bab@web.de])
 # Check for programs I need for my build
 AC_CANONICAL_TARGET
-AC_ARG_VAR([guile], [How to call GNU Guile 2.0.x.])
-AC_CHECK_TARGET_TOOL([guile], [guile-2.0], [no])
-AS_IF([test "x$guile" = "xno"],
-      [AC_MSG_ERROR([cannot find GNU Guile 2.0 or later.])])
+
+# search for Guile using the guile m4 files.
+# see https://www.gnu.org/software/guile/manual/html_node/Autoconf-Macros.html
+# This provides @GUILE@ to Makefile.am
+GUILE_PKG([2.2 2.0 1.8])
+GUILE_PROGS
+GUILE_SITE_DIR
+
+dnl set installation prefix for language file to Guile location
+AC_PREFIX_PROGRAM([guile])
+
 AC_ARG_VAR([python3], [How to call Python 3.])
 AC_CHECK_TARGET_TOOL([python3], [python3], [no])
 AS_IF([test "x$python3" = "xno"],
-      [AC_MSG_ERROR([cannot find Python 3 which is required for bootstrapping.])])
+      [AC_MSG_WARN([cannot find Python 3 which is required for development bootstrapping.])])
+
+# Is this a normal install, or a "make distcheck"?  We need to disable
+# the tests in a "make distcheck" that won't work.
+# FIXME: automake should honor DESTDIR set by make distcheck
+is_make_distcheck=no
+AS_CASE([$prefix],
+  [*/_inst],
+    [AC_MSG_NOTICE([[Prefix ends in /_inst; this is a 'make distcheck'.]])
+     is_make_distcheck=yes])
+AM_CONDITIONAL([IS_MAKE_DISTCHECK], [test "x$is_make_distcheck" = x"yes"])
+AC_MSG_CHECKING([final decision IS_MAKE_DISTCHECK (running "make distcheck"?)])
+AM_COND_IF([IS_MAKE_DISTCHECK], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])]) 
+
 # Run automake
 # Use GNU style. Note that ChangeLog is created on every commit 
 # by a commit hook in .hg/hgrc
@@ -21,4 +41,5 @@ AS_IF([test "x$python3" = "xno"],
 AM_INIT_AUTOMAKE([gnu])
 AM_MAINTAINER_MODE([enable])
 AC_CONFIG_FILES([Makefile])
+
 AC_OUTPUT
diff --git a/docs/fosdem2016.org b/docs/fosdem2016.org
new file mode 100644
--- /dev/null
+++ b/docs/fosdem2016.org
@@ -0,0 +1,454 @@
+#+title: 
+# ^ no title page, but title on the slides
+#+LATEX: \title{wisp}
+
+#+LaTeX_CLASS: beamer
+#+LaTeX_CLASS_OPTIONS: [presentation]
+#+BEAMER_THEME: Boadilla
+#+options: toc:nil
+
+#+latex: \renewcommand{\inserttotalframenumber}{10}
+
+
+* Wisp - SRFI-119
+
+#+latex: \vspace{1.3cm}
+
+**                                                                :B_columns:
+    :PROPERTIES:
+    :BEAMER_env: columns
+    :END:
+***                                                                   :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.42
+   :END:
+
+#+BEGIN_SRC wisp 
+define : factorial n
+    if : zero? n
+       . 1
+       * n : factorial {n - 1}
+#+END_SRC
+
+**                                                                  :B_quote:
+   :PROPERTIES:
+   :BEAMER_env: quote
+   :END:
+
+#+latex: \vspace{1.3cm}
+
+\begin{center}
+I love the syntax of Python, \\
+but crave the simplicity and power of Lisp.
+\end{center}
+
+
+* Why Wisp?
+
+
+
+** 
+   :PROPERTIES:
+   :BEAMER_act: <2-2>
+   :END:
+
+\centering
+\Large
+
+\textyen Hello World!\pounds
+
+** 
+   :PROPERTIES:
+   :BEAMER_act: <3-4>
+   :END:
+
+\centering
+\Large
+
+Hello World!
+
+** 
+   :PROPERTIES:
+   :BEAMER_act: <1-1>
+   :END:
+
+\centering
+\Large
+
+(Hello World!)
+
+** Notes                                                            :B_quote:
+   :PROPERTIES:
+   :BEAMER_act: <4-4>
+   :BEAMER_env: quote
+   :END:
+
+- The first and last letter are important for word recognition.¹
+
+- Over 70% of the codelines in the Guile scheme source start with a paren \Rightarrow ceremony.
+
+- Many people avoid Lisp-like languages because of the parens.² 
+
+\footnotesize
+
+¹: Though not all-important. See \\ [[http://www.mrc-cbu.cam.ac.uk/people/matt.davis/cmabridge/][www.mrc-cbu.cam.ac.uk/people/matt.davis/cmabridge/]]
+
+²: Also see [[http://srfi.schemers.org/srfi-110/srfi-110.html#cant-improve][srfi.schemers.org/srfi-110/srfi-110.html#cant-improve]]
+
+* The most common letters: Lisp and Scheme are awesome
+
+** 
+
+\centering
+\Huge
+=.,":'_#?!;=
+
+**                                                          :B_ignoreheading:
+   :PROPERTIES:
+   :BEAMER_env: ignoreheading
+   :END:
+
+\centering
+/The most common non-letter, non-math characters in prose¹/
+
+\vspace{0.3cm}
+
+** 
+
+\centering
+\Huge
+=()=
+
+**                                                          :B_ignoreheading:
+   :PROPERTIES:
+   :BEAMER_env: ignoreheading
+   :END:
+
+\centering
+/The most common paired characters¹/
+
+**                                                          :B_ignoreheading:
+   :PROPERTIES:
+   :BEAMER_env: ignoreheading
+   :END:
+
+
+\vspace{0.5cm}
+
+\raggedright
+\footnotesize
+¹: From letter distributions in newspapers, see: \\ [[https://bitbucket.org/ArneBab/evolve-keyboard-layout/src/tip/1-gramme.arne.txt][bitbucket.org/ArneBab/evolve-keyboard-layout/src/tip/1-gramme.arne.txt]]
+
+* Wisp in a nutshell
+**                                                                :B_columns:
+   :PROPERTIES:
+   :BEAMER_env: columns
+   :END:
+
+
+
+***                                                                   :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.48
+   :END:
+\vspace{0.5cm}
+
+#+BEGIN_SRC wisp 
+define : factorial n
+    if : zero? n
+       . 1
+       * n : factorial {n - 1}
+#+END_SRC
+
+
+***                                                                   :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.48
+   :END:
+\vspace{0.5cm}
+
+#+BEGIN_SRC wisp
+(define (factorial n)
+    (if (zero? n)
+      1
+      (* n (factorial {n - 1}))))
+#+END_SRC
+
+**                                                          :B_ignoreheading:
+   :PROPERTIES:
+   :BEAMER_env: ignoreheading
+   :END:
+
+\vspace{1cm}
+
+\footnotesize
+
+- indent as with parens, dot-prefix, inline-:, and use SRFI-105.
+
+- Wisp uses the minimal syntax required to represent arbitrary structure: \\ Syntax justification: [[http://draketo.de/english/wisp#sec-4][draketo.de/english/wisp#sec-4]]
+
+- Many more examples in “From Python to Guile Scheme”: \\ info: [[http://draketo.de/py2guile][draketo.de/py2guile]] \\ download: [[http://draketo.de/proj/py2guile/py2guile.pdf][draketo.de/proj/py2guile/py2guile.pdf]]
+
+
+* Implementation
+
+** REPL and Reader (language wisp spec)                       :B_block:BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.57
+   :BEAMER_env: block
+   :END:
+
+#+BEGIN_SRC wisp
+  define-language wisp
+    . #:title "Wisp Scheme Syntax.."
+    . #:reader read-one-wisp-sexp
+    . #:compilers `(
+        (tree-il . ,compile-tree-il))
+    . #:decompilers `(
+        (tree-il . ,decompile-tree-il))
+    . #:evaluator (lambda (x module) 
+                     primitive-eval x)
+    . #:printer write
+    . #:make-default-environment
+    lambda :
+      let : : m : make-fresh-user-module
+        module-define! m 'current-reader 
+                          make-fluid
+        module-set! m 'format simple-format
+        . m
+#+END_SRC
+
+** Preprocessor (wisp.scm)                                    :B_block:BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.39
+   :BEAMER_env: block
+   :END:
+
+#+BEGIN_SRC sh
+guile wisp.scm tests/hello.w
+#+END_SRC
+
+#+BEGIN_SRC scheme
+(define (hello who)
+  ;; include the newline
+  (format #t "~A ~A!\n"
+          "Hello" who))
+(hello "Wisp")
+#+END_SRC
+
+\footnotesize
+(Plan B: You can always go back)
+
+
+* Applications?
+**                                                                    :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.50
+   :END:
+
+*** Example: User Scripts
+
+#+BEGIN_SRC wisp
+Enter : First_Witch
+        Second_Witch
+        Third_Witch
+
+First_Witch
+  When shall we three meet again
+  In thunder, lightning, or in rain?
+#+END_SRC
+
+***                                                         :B_ignoreheading:
+    :PROPERTIES:
+    :BEAMER_env: ignoreheading
+    :END:
+
+This displays
+
+***                                                                 :B_block:
+   :PROPERTIES:
+   :BEAMER_env: block
+   :END:
+\footnotesize
+#+BEGIN_EXAMPLE
+First Witch
+  When shall we three meet again
+  In thunder, lightning, or in rain?
+#+END_EXAMPLE
+
+***                                                         :B_ignoreheading:
+    :PROPERTIES:
+    :BEAMER_env: ignoreheading
+    :END:
+
+\footnotesize
+- [[http://draketo.de/english/wisp/shakespeare][draketo.de/english/wisp/shakespeare]]
+- Templates, executable pseudocode, REPL-interaction, configuration, ...
+
+* Solutions
+
+** Run examples/newbase60.w as script
+
+#+BEGIN_SRC wisp
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp \
+           -e '(@@ (examples newbase60) main)' \
+           -s "$0" "$@"
+; !#
+define-module : examples newbase60
+
+define : main args
+  ...
+#+END_SRC
+
+** Use Wisp code from parenthesized Scheme
+
+- precompile: =guile --language=wisp module=
+- then just import as usual: =(use-modules (...))=
+
+* Experience
+
+**                                                                  :B_quote:
+   :PROPERTIES:
+   :BEAMER_env: quote
+   :END:
+
+\vspace{1cm}
+
+»ArneBab's alternate sexp syntax is best I've seen; pythonesque, hides parens but keeps power« — Christopher Webber \\ \rightarrow [[http://dustycloud.org/blog/wisp-lisp-alternative/][dustycloud.org/blog/wisp-lisp-alternative/]]
+
+\vspace{1cm}
+
+**                                                                  :B_block:
+   :PROPERTIES:
+   :BEAMER_env: block
+   :END:
+
+- Wisp is implemented in Wisp (850 lines, two implementations).
+- Examples: 4 lines (factorial) to 330 lines (advection on icosaheder).
+
+* Try Wisp
+
+** Install
+
+#+BEGIN_SRC sh
+guix package -i guile guile-wisp
+guile --language=wisp
+#+END_SRC
+
+#+BEGIN_SRC sh
+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
+#+END_SRC
+
+- [[http://draketo.de/english/wisp][http://draketo.de/english/wisp]]
+
+** Emacs mode for syntax highlighting
+
+- M-x package-install [RET] *wisp-mode* [RET]
+- https://marmalade-repo.org/packages/wisp-mode
+
+
+* Thank you!
+
+***                                               :B_alertblock:BMCOL:
+    :PROPERTIES:
+    :BEAMER_col: 0.032
+    :BEAMER_env: alertblock
+    :END:
+
+$\ddot \smile$
+
+* Appendix                                                       :B_appendix:
+  :PROPERTIES:
+  :BEAMER_env: appendix
+  :END:
+
+* Why not SRFI-110 or SRFI-49?
+
+** SRFI-49                                                            :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.45
+   :END:
+
+*** SRFI-49                                                         :B_block:
+    :PROPERTIES:
+    :BEAMER_env: block
+    :END:
+
+#+BEGIN_SRC wisp
+  + 5
+    * 4 3
+    2
+    1
+    0
+#+END_SRC
+
+- Cannot continue the argument list
+
+*** Wisp                                                            :B_block:
+    :PROPERTIES:
+    :BEAMER_env: block
+    :END:
+
+#+BEGIN_SRC wisp
+  + 5
+    * 4 3
+    . 2 1 0
+
+#+END_SRC
+
+** SRFI-110                                                   :B_block:BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.45
+   :BEAMER_env: block
+   :END:
+
+#+BEGIN_SRC wisp
+myfunction 
+  x: \\ original-x
+  y: \\ calculate-y original-y
+#+END_SRC
+
+#+BEGIN_SRC wisp
+  a b $ c d e $ f g
+#+END_SRC
+
+#+BEGIN_SRC wisp
+  let <* x getx() \\ y gety() *>
+  ! {{x * x} + {y * y}}
+#+END_SRC
+
+- most common letters?
+
+* Keep parens where they help readability
+
+
+**                                                                    :BMCOL:
+   :PROPERTIES:
+   :BEAMER_col: 0.45
+   :END:
+
+
+#+BEGIN_SRC wisp
+cond 
+  : and (null? l) (zero? a)
+    . '()
+  else
+    cons a l
+#+END_SRC
+
+
+#+BEGIN_SRC wisp
+map 
+  lambda (x) (+ x 1)
+  list 1 2 3
+#+END_SRC
+
+
+# Local Variables:
+# org-latex-minted-options: (("linenos" "false") ("frame" "lines") ("framesep" "6pt") ("fontsize" "\\footnotesize"))
+# End:
diff --git a/docs/interactive-run.txt b/docs/interactive-run.txt
new file mode 100755
--- /dev/null
+++ b/docs/interactive-run.txt
@@ -0,0 +1,31 @@
+#!/bin/sh
+guile -L . --language=wisp << EOF
+
+display "Hello World!\n" .
+
+define : hello
+  display "Hello World!"
+  newline .
+
+hello .
+
+define-syntax-rule : hello who
+  format #t "Hello ~A!\n" (symbol->string 'who)
+
+
+hello World .
+
+define-syntax-rule : welcome-to what
+  format #t "Welcome to ~A!\n" : symbol->string 'what .
+
+welcome-to Wisp .
+
+define-syntax-rule : welcome-to . what
+  format #t "Welcome to ~A!\n"
+    string-join : map symbol->string 'what .
+
+welcome-to syntax like Python with the simplicity and power of Lisp .
+
+display 'http://draketo.de/english/wisp
+newline
+EOF
diff --git a/examples/cholesky.w b/examples/cholesky.w
old mode 100644
new mode 100755
--- a/examples/cholesky.w
+++ b/examples/cholesky.w
@@ -6,7 +6,7 @@ exec guile -L $(dirname $(dirname $(real
 ;; Cholesky decomposition, following https://de.wikipedia.org/wiki/Cholesky-Zerlegung#Pseudocode
 
 define-module : examples cholesky
-              . #:exports : cholesky!
+              . #:export : cholesky!
 
 use-modules : guildhall ext foof-loop
 
diff --git a/examples/d20world.w b/examples/d20world.w
--- a/examples/d20world.w
+++ b/examples/d20world.w
@@ -1,4 +1,6 @@
-#!/home/arne/wisp/wisp-multiline.sh 
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples d20world) main)' -s "$0" "$@"
 ; !#
 
 ; A world projected on a d20 (20-sided die, ikosaeder)
@@ -17,6 +19,7 @@ define-module : examples d20world
               . #:export : world neighbors d20-as-text d20-diffuse
 
 use-modules : ice-9 format
+use-modules : srfi srfi-1
 use-modules 
   : ice-9 popen
         . #:select : open-output-pipe close-pipe
@@ -188,150 +191,266 @@ define : d20-advect world advection-dire
                  loop : cdr neighbors-to-advect
 
 
-define φ : * (/ 1 2) : 1+ : sqrt 5
+define d20numbers '(1 14 10 6
+                    19 18 4 8 9 16
+                    2 3 17 13 12 5
+                    11 15 7 20)
+
+define : cellidx->dienumber idx
+       list-ref d20numbers idx
+
+define : dienumber->cellidx number
+       list-index (λ(x)(= x number)) d20numbers
+
+
+define : latlonsixthslabidx latfromtop lonfrac
+       . "calculate the index in a sixth longitude slab of the icosaeder"
+       ; TODO: use shortest surface distance from center of triangle as faster metric.
+       let*
+          : triangleheight : / (sqrt 3) 2
+            length-top-to-bottom-at-lon0 : + 1 (* 2 triangleheight)
+            height-deg : * 180 : / triangleheight length-top-to-bottom-at-lon0
+            side-deg : * 180 : / 1 length-top-to-bottom-at-lon0
+          ; in one sixth of the icosaeder, there are 6 reachable
+          ; fields. I am indexing them from top to bottom.
+          ; format #t "latfromtop: ~a, lonfrac: ~a, height-deg/3: ~a, side-deg: ~a\n" latfromtop lonfrac (/ height-deg 3) side-deg
+          cond
+            : < latfromtop : / height-deg 3
+              . 0
+            : < latfromtop : - (* 2 (/ height-deg 3)) (* lonfrac (/ height-deg 3))
+              . 0
+            : < latfromtop : * 2 : / height-deg 3
+              . 1
+            : < latfromtop : + (* 2 (/ height-deg 3)) (* lonfrac (* 2 (/ height-deg 3)))
+              . 1
+            : < latfromtop : * 4 : / height-deg 3
+              . 2
+            : < latfromtop : - (+ side-deg (* 2 (/ height-deg 3))) (* lonfrac (- (+ side-deg (* 2 (/ height-deg 3))) (* 4 (/ height-deg 3))))
+              . 2
+            : < latfromtop : + side-deg : * 2 : / height-deg 3
+              . 3
+            : < latfromtop : + (+ side-deg (* 2 (/ height-deg 3))) (* lonfrac (- (+ side-deg (* 4 (/ height-deg 3))) (+ side-deg (* 2 (/ height-deg 3)))))
+              . 3
+            : < latfromtop : - (+ side-deg (* 5 (/ height-deg 3))) (* lonfrac (- (+ side-deg (* 5 (/ height-deg 3))) (+ side-deg (* 4 (/ height-deg 3)))))
+              . 4
+            else
+              . 5
+         
 
 define : latlon2cellidx lat lon 
-        . "Convert a position given as latitude and longitude into the correct cell index."
-        ; cell 1 (index 0) is on top, cell 20 at the bottom. The right
-        ; border of cell 2 is situated at longitude 0. With that, the
-        ; left corner of cell 19 is at longitude 180. Top and bottom
-        ; are point-symmetric. We can cleanly divide the upper part of
-        ; the icosaeder into 3 regions by longitude. Let's do that.
-        let*
-            : upper : > lat 0
-              ; we start by switching to a symmetric longitude
-              slon : if upper lon : + lon 180
-              ; the sector number is defined by the uppermost triangle
-              ; in it.
-              sector : if (< slon 120) 4 (if (< slon 270) 3 2)
+        . "Convert a position given as latitude (-90 .. 90) and
+longitude (0 .. 360) into the correct cell index.
+
+This uses heavy linear approximation."
+        ; FIXME: there is still a bug, as shown in the map plot.
+        ; cell 1 (index 0) is on top, cell 20 at the bottom. The left
+        ; border of cell 2 is situated at longitude 0. We can cleanly
+        ; divide the upper part of the icosaeder into 3 regions by
+        ; longitude. Let's do that.
+        let* ; the sector number is defined by the uppermost triangle
+            : sector : if (< lon 120) 2 (if (< lon 240) 4 3)
               ; we start by calculating the fraction inside the sector
-              lonsectorfraction : modulo slon 120
+              lonsectorfraction : modulo lon 120
               ; we can further subdivide the sector by longitude into two subsectors
-              subseclon : if (< lon 60) lon (-120 lon)
-              ; TODO find some more symmetry or start nontrivial geometry.
-            . #t
+              subsector : if (< lonsectorfraction 60) 0 1
+              subseclon : if (= subsector 0) lonsectorfraction (- 120 lonsectorfraction)
+              lonfrac : / subseclon 60
+              latfromtop : - 90 lat
+              sixthslab : latlonsixthslabidx latfromtop lonfrac
+              ; for each sector and subsector, set the dienumber
+              slabsec->index '((2 . ((1 14 19 13 15 20) (1 14 16 17 11 20)))
+                               (4 . ((1 6 9 3 11 20) (1 6 8 2 7 20)))
+                               (3 . ((1 10 4 5 7 20) (1 10 18 12 15 20))))
+            dienumber->cellidx
+              list-ref
+                list-ref
+                  assoc-ref slabsec->index sector
+                  . subsector
+                . sixthslab
 
 
-display : d20-as-text world
-newline
+define : main args
+       . "Test the code"
+       if : > 2 (length args)
+         set! args : append args '("88") ; lat
+       if : > 3 (length args)
+         set! args : append args '("45") ; lon
+       display : latlon2cellidx (string->number (first (take-right args 2))) (string->number (last args))
+       newline
+       display : d20-as-text world
+       newline
+       
+       ; format #t "Diffuse ~A\n" 0.01
+       ; d20-diffuse world neighbors 0.01
+       ; display : d20-as-text world
+       ; newline
+       ; format #t "Advect ~A\n" 0.1
+       ; d20-advect world advection-directions 0.1
+       ; display : d20-as-text world
+       ; newline
+       ; format #t "Diffuse ~A\n" 0.1
+       ; d20-diffuse world neighbors 0.1
+       ; display : d20-as-text world
+       ; newline
+       format #t "Diffuse: ~A*(~A)\n" 100 0.1
+       let loop : : steps 100
+           cond
+             : = 0 steps
+               . world
+             else
+               d20-diffuse world neighbors 0.1
+               loop : 1- steps
+       display : d20-as-text world
+       newline
+       let 
+         : number 20
+           val 1
+         format #t "disturb: ~A to ~A\n" number val
+         vector-set! world (1- number) val
+         display : d20-as-text world
+         newline
+       ; format #t "Diffuse ~A\n" 0.1
+       ; d20-diffuse world neighbors 0.1
+       ; display : d20-as-text world
+       ; newline
+       ; 
+       ; format #t "Advect: ~A*(~A)\n" 1000 0.001
+       ; let loop : : steps 1000
+       ;     cond
+       ;       : = 0 steps
+       ;         . world
+       ;       else
+       ;         d20-advect world advection-directions 0.001
+       ;         display : d20-as-text world
+       ;         d20-cursor-up-text world
+       ;         loop : 1- steps
+       ; display : d20-as-text world
+       ; newline
+       ; format #t "Diffuse: ~A*(~A)\n" 1000 0.004
+       ; let loop : : steps 1000
+       ;     cond
+       ;       : = 0 steps
+       ;         . world
+       ;       else
+       ;         d20-diffuse world neighbors 0.004
+       ;         display : d20-as-text world
+       ;         d20-cursor-up-text world
+       ;         loop : 1- steps
+       display : d20-as-text world
+       newline
+       format #t "Diffuse+Advect: ~A*(~A+~A)\n" 500 0.002 0.003
+       let loop : : steps 500
+           cond
+             : = 0 steps
+               . world
+             else
+               d20-diffuse world neighbors 0.002
+               d20-advect world advection-directions 0.003
+               display : d20-as-text world
+               d20-cursor-up-text world
+               loop : 1- steps
+       display : d20-as-text world
+       newline
+       
+       let
+         :
+           v
+              let loop 
+                : lon 359
+                  lat 89
+                  map '()
+                  zone '()
+                cond
+                  : and (= lat -90) (= lon 0)
+                    cons : cons (vector-ref world (latlon2cellidx lat lon)) zone
+                      . map
+                  : = lon 0
+                    loop
+                      . 359
+                      - lat 1
+                      cons : cons (vector-ref world (latlon2cellidx lat lon)) zone
+                        . map
+                      . '()
+                  else
+                    loop
+                      - lon 1
+                      . lat
+                      . map
+                      cons : vector-ref world (latlon2cellidx lat lon)
+                        . zone
+           port : open-output-pipe "python"
+         display "a = \"" port
+         write v port 
+         display "\"" port
+         newline port
+         display "a = eval(a.replace('(', '[').replace(')', ']').replace(' ',', '))" port
+         newline port
+         display "import numpy as np
+import pylab as pl
+import mpl_toolkits.basemap as bm
+arr = np.array(a)
 
-format #t "Diffuse ~A\n" 0.01
-d20-diffuse world neighbors 0.01
-display : d20-as-text world
-newline
-format #t "Advect ~A\n" 0.1
-d20-advect world advection-directions 0.1
-display : d20-as-text world
-newline
-format #t "Diffuse ~A\n" 0.1
-d20-diffuse world neighbors 0.1
-display : d20-as-text world
-newline
-format #t "Diffuse: ~A*(~A)\n" 100 0.1
-let loop : : steps 100
-    cond
-      : = 0 steps
-        . world
-      else
-        d20-diffuse world neighbors 0.1
-        loop : 1- steps
-display : d20-as-text world
-newline
-let 
-  : number 20
-    val 1
-  format #t "disturb: ~A to ~A\n" number val
-  vector-set! world (1- number) val
-  display : d20-as-text world
-  newline
-format #t "Diffuse ~A\n" 0.1
-d20-diffuse world neighbors 0.1
-display : d20-as-text world
-newline
+m = bm.Basemap(projection='cea', resolution='l', lat_ts=37.5)
+m.drawcoastlines(color='k',linewidth=0.3)
+m.drawmeridians(np.arange(-120.0, 180.0, 60), labels=[0,0,0,1], linewidth=0.15) # , yoffset=6) # labels = [left,right,top,bottom]
+m.drawparallels(np.arange(-60.0, 90.0, 30), labels=[1,0,0,0], linewidth=0.15)
+ny, nx = arr.shape
+lons, lats = pl.meshgrid(range(-nx/2, nx/2 + nx%2),
+                         range(-ny/2, ny/2 + ny%2))
+x, y = m(lons, lats)
 
-format #t "Advect: ~A*(~A)\n" 1000 0.001
-let loop : : steps 1000
-    cond
-      : = 0 steps
-        . world
-      else
-        d20-advect world advection-directions 0.001
-        display : d20-as-text world
-        d20-cursor-up-text world
-        loop : 1- steps
-display : d20-as-text world
-newline
-format #t "Diffuse: ~A*(~A)\n" 1000 0.004
-let loop : : steps 1000
-    cond
-      : = 0 steps
-        . world
-      else
-        d20-diffuse world neighbors 0.004
-        display : d20-as-text world
-        d20-cursor-up-text world
-        loop : 1- steps
-display : d20-as-text world
-newline
-format #t "Diffuse+Advect: ~A*(~A+~A)\n" 1000 0.002 0.001
-let loop : : steps 1000
-    cond
-      : = 0 steps
-        . world
-      else
-        d20-diffuse world neighbors 0.002
-        d20-advect world advection-directions 0.001
-        display : d20-as-text world
-        d20-cursor-up-text world
-        loop : 1- steps
-display : d20-as-text world
-newline
-
-; now plot the result
-let : : port : open-output-pipe "python"
-  format port "from mpl_toolkits.mplot3d import Axes3D, art3d
-import numpy as np
-import scipy as sp
-from matplotlib import cm
-import matplotlib.pyplot as plt
-from scipy.spatial import Delaunay
-
-def Icosahedron():
-    h = 0.5*(1+np.sqrt(5))
-    p1 = np.array([[0,1,h],[0,1,-h],[0,-1,h],[0,-1,-h]])
-    p2 = p1[:,[1,2,0]]
-    p3 = p1[:,[2,0,1]]
-    return np.vstack((p1,p2,p3))
-
-Ico = Icosahedron()
-tri = Delaunay(Ico)
-CH = tri.convex_hull
-points = tri.points
-
-fig = plt.figure(figsize=(4.0,4.0))
-ax = fig.add_subplot(111, projection='3d')
-
-print points
-for i in range(points.shape[0]):
-    neighbors = tri.neighbors[i,:]
-    for n in range(points.shape[0]):
-        pts = []
-        for u in range(points.shape[0]):
-            pt = np.zeros((3,3))
-            pt[0,:] = points[(i),:]
-            pt[1,:] = points[(n),:]
-            pt[2,:] = points[(u),:]
-            # print pt
-            pt *= 0.5
-            pt += 0.5
-            pts.append(pt)
-        tr = art3d.Poly3DCollection(pts)
-        tr.set_color([(0.9*i)/points.shape[0]] + [(0.9*n)/points.shape[0]]*3)
-        ax.add_collection3d(tr)
-# ax.plot_surface(x, y, z, color='g')
-
-plt.show()
-
-exit()\n"
-  close-pipe port
-
+m.pcolormesh(x, y, arr, cmap=pl.get_cmap('Paired'))
+pl.colorbar()
+pl.show()
+" port
+         close-pipe port
+       newline
+         
+       
+;        ; now plot the result
+;        let : : port : open-output-pipe "python"
+;          format port "from mpl_toolkits.mplot3d import Axes3D, art3d
+; import numpy as np
+; import scipy as sp
+; from matplotlib import cm
+; import matplotlib.pyplot as plt
+; from scipy.spatial import Delaunay
+; 
+; def Icosahedron():
+;     h = 0.5*(1+np.sqrt(5))
+;     p1 = np.array([[0,1,h],[0,1,-h],[0,-1,h],[0,-1,-h]])
+;     p2 = p1[:,[1,2,0]]
+;     p3 = p1[:,[2,0,1]]
+;     return np.vstack((p1,p2,p3))
+; 
+; Ico = Icosahedron()
+; tri = Delaunay(Ico)
+; CH = tri.convex_hull
+; points = tri.points
+; 
+; fig = plt.figure(figsize=(4.0,4.0))
+; ax = fig.add_subplot(111, projection='3d')
+; 
+; print points
+; for i in range(points.shape[0]):
+;     neighbors = tri.neighbors[i,:]
+;     for n in range(points.shape[0]):
+;         pts = []
+;         for u in range(points.shape[0]):
+;             pt = np.zeros((3,3))
+;             pt[0,:] = points[(i),:]
+;             pt[1,:] = points[(n),:]
+;             pt[2,:] = points[(u),:]
+;             # print pt
+;             pt *= 0.5
+;             pt += 0.5
+;             pts.append(pt)
+;         tr = art3d.Poly3DCollection(pts)
+;         tr.set_color([(0.9*i)/points.shape[0]] + [(0.9*n)/points.shape[0]]*3)
+;         ax.add_collection3d(tr)
+; # ax.plot_surface(x, y, z, color='g')
+; 
+; plt.show()
+; 
+; exit()\n"
+;          close-pipe port
diff --git a/examples/d6.w b/examples/d6.w
--- a/examples/d6.w
+++ b/examples/d6.w
@@ -1,27 +1,42 @@
-#!/home/arne/wisp/wisp-multiline.sh 
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples d6) main)' -s "$0" "$@"
 ; !#
 
 define-module : examples d6
    . #:export : roll check
 
+use-modules : srfi srfi-1
+
 ; basic d6 rules, implemented in guile
 
 define : roll
     . "Roll one ± d6"
-    let : : die '(-5 -3 -1 2 4 6)
-        list-ref die : random 6 : random-state-from-platform
-
+    let* 
+        : eyes '(-5 -3 -1 2 4 6)
+          d6 : lambda () : list-ref eyes : random 6 : random-state-from-platform
+        let rolling : : rolled : cons (d6) '()
+            cond
+              : = 1 (length rolled)
+                if : not : member (first rolled) '(-5, 6)
+                     first rolled
+                     rolling : cons (d6) rolled
+              : not : equal? (first rolled) (second rolled)
+                apply + : cdr rolled
+              else
+                rolling : cons (d6) rolled
+                
+                
+          
 define : check skill target effect-threshold
     . "Check whether a given skill-roll succeeds and provide a margin of success."
     let : : result : + skill : roll
         if : > result target
-            if : < effect-threshold : - result target
-                . 1
-                . #t
+            floor/ {result - target} effect-threshold
             . #f
 
-display : check 12 9 3
-newline
-display : roll
-
-
+define : main args
+         display : check 12 9 3
+         newline
+         newline
+         display : roll
diff --git a/examples/ensemble-estimation.w b/examples/ensemble-estimation.w
--- a/examples/ensemble-estimation.w
+++ b/examples/ensemble-estimation.w
@@ -78,19 +78,31 @@ define* : write-multiple . x
 
 ;; Start with the simple case: One variable and independent observations (R diagonal)
 ;; First define a truth
-define x^seed '(0.5 0.6 7 0.1 0.7 0.9 0.8 0.4)
+define x^seed '(0.5 0.6 2 0.1) ; 0.7 0.9 0.8 0.4)
+;; The size is the length of the seed, squared, each multiplied by each
 define x^true : append-ec (: i (length x^seed)) : list-ec (: j x^seed) : * j : list-ref x^seed i
 ;; And add an initial guess of the parameters
-define x^b : append-ec (: i (length x^seed))  '(1 1 1 1 1 1 1 1) ; initial guess
-define P : make-covariance-matrix-from-standard-deviations : append-ec (: i (length x^seed)) '(0.5 0.1 0.3 0.1 0.2 0.2 0.2 0.2)
+define x^b : append-ec (: i (length x^seed))  '(1 1 1 1) ; 1 1 1 1) ; initial guess
+define P : make-covariance-matrix-from-standard-deviations : append-ec (: i (length x^seed)) '(0.5 0.1 0.3 0.1) ; 0.2 0.2 0.2 0.2)
 
 ;; Then generate observations
-define y⁰-num 3000
+define y⁰-num 1000
 define y⁰-pos-max 100
 ;; At the positions where they are measured. Drawn randomly to avoid
 ;; giving an undue weight to later values.
 define y⁰-pos : list-ec (: i y⁰-num) : * (random:uniform) y⁰-pos-max
 
+define : H-single-parameter xi xi-pos pos
+       . "Observation function for a single parameter."
+       let* 
+         : xi-posdist : abs : / {pos - xi-pos} {y⁰-pos-max / 20}
+         cond
+           : < 5 xi-posdist 
+             . 0
+           else
+             * xi pos 
+               exp : - : expt xi-posdist 2
+
 ;; We need an observation operator to generate observations from true values
 define : H x pos
        . "Observation operator. It generates modelled observations from the input.
@@ -103,20 +115,16 @@ x are parameters to be optimized, pos is
              x-pos : list-ec (: i len) : * ystretch {{i + 0.5} / {len + 1}}
            apply +
                  list-ec (: i len)
-                      * : list-ref x i
-                          . pos
-                          exp 
-                            - 
-                              expt 
-                                / {pos - (list-ref x-pos i)} {ystretch / 20}
-                                . 2
-
+                      H-single-parameter 
+                        list-ref x i
+                        list-ref x-pos i
+                        . pos
 
 ;; We start with true observations which we will disturb later to get
 ;; the equivalent of measured observations
 define y^true : list-ec (: i y⁰-pos) : H x^true i
 ;; now we disturb the observations with a fixed standard deviation. This assumes uncorrelated observations.
-define y⁰-std 50
+define y⁰-std 10
 define y⁰ : list-ec (: i y^true) : + i : * y⁰-std : random:normal
 ;; and define the covariance matrix. This assumes uncorrelated observations.
 define R : make-covariance-matrix-from-standard-deviations : list-ec (: i y⁰-num) y⁰-std
@@ -201,21 +209,40 @@ Limitations: y is a single value. R and 
 
 define : main args
     let*
-      : optimized : EnSRT H x^b P y⁰ R y⁰-pos 30
+      : optimized : EnSRT H x^b P y⁰ R y⁰-pos 40
         x-opt : list-ref optimized 0
         x-deviations : list-ref optimized 1
         ; std : sqrt : * {1 / {(length x-deviations) - 1}} : sum-ec (: i x-deviations) : expt i 2
-      format #t "x⁰: ~A ± ~A\nx:  ~A ± ~A\nx^t:~A\ny:  ~A ± \ny⁰: ~A ± ~A\nnoise: ~A\n" 
+      format #t "x⁰:   ~A ± ~A\nx:    ~A ± ~A\nx^t:  ~A\nx-t/σ:~A\ny̅:    ~A ± ~A\ny̅⁰:   ~A ± ~A\ny̅^t:  ~A\nnoise:~A\n" 
                  . x^b
                  list-ec (: i (length x^b)) : list-ref (list-ref P i) i
                  . x-opt 
                  list-ec (: i (length x-opt))
                     apply standard-deviation-from-deviations : list-ec (: j x-deviations) : list-ref j i
                  . x^true
+                 list-ec (: i (length x-opt)) 
+                   / : - (list-ref x-opt i) (list-ref x^true i)
+                       apply standard-deviation-from-deviations : list-ec (: j x-deviations) : list-ref j i
                  * {1 / (length y⁰)} : apply + : map (lambda (x) (H x-opt x)) y⁰-pos
+                 apply standard-deviation-from-deviations
+                   append-ec (: i (length x-deviations))
+                     let*
+                       : 
+                         x-opt+dev
+                           list-ec (: j (length x-opt))
+                             + : list-ref x-opt j
+                                 list-ref
+                                   list-ref x-deviations i
+                                   . j
+                         y-opt+dev : map (lambda (x) (H x-opt+dev x)) y⁰-pos
+                         y-opt : map (lambda (x) (H x-opt x)) y⁰-pos
+                       map (lambda (x y) (- x y)) y-opt+dev y-opt
+                     ; list-ec (: i (length y-opt))
+                     ;   - (list-ref y-opt+dev i) (list-ref y-opt i)
                  ; apply standard-deviation-from-deviations : map H x-deviations ; FIXME: This only works for trivial H.
                  mean y⁰
                  standard-deviation y⁰
+                 * {1 / (length y⁰)} : apply + : map (lambda (x) (H x^true x)) y⁰-pos
                  . y⁰-std
       ; now plot the result
       let : : port : open-output-pipe "python"
diff --git a/examples/enter-three-witches.w b/examples/enter-three-witches.w
new file mode 100755
--- /dev/null
+++ b/examples/enter-three-witches.w
@@ -0,0 +1,49 @@
+#!/usr/bin/env sh
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples enter-three-witches) main)' -s "$0" "$@"
+; !#
+
+define-module : examples enter-three-witches
+
+use-modules : ice-9 optargs
+
+define-syntax Enter
+ syntax-rules ()
+  : _ (name) b ...
+    begin
+      define-syntax name
+        syntax-rules ::: ()
+          : _ (c :::) d :::
+            format #t "~A\n  ~A\n\n" 
+               string-join 
+                 string-split (symbol->string 'name) #\_
+               string-join 
+                 map : lambda (x) (string-join (map symbol->string x))
+                       quote : (c :::) d :::
+                 . "\n  "
+          : _ c d :::
+            ;; allow for modifier keywords after the name
+            begin
+              format #t "~A:\n" : symbol->string 'c
+              name d :::
+          : _ c :::
+            begin #t c :::
+      Enter b ...
+  : _ b ...
+    begin 
+
+
+define : main args
+  Enter : First_Witch
+          Second_Witch
+          Third_Witch
+  
+  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.
diff --git a/examples/factorial.w b/examples/factorial.w
--- a/examples/factorial.w
+++ b/examples/factorial.w
@@ -7,7 +7,7 @@ define-module : examples factorial
 
 define : factorial n            ;   (define (factorial n)
     if : zero? n                ;       (if (zero? n)
-       . n                      ; =>        n
+       . 1                      ; =>        1
        * n : factorial {n - 1}  ;           (* n (factorial {n - 1}))))
 
 define : main args
diff --git a/examples/fib.w b/examples/running_mean_std.w
copy from examples/fib.w
copy to examples/running_mean_std.w
--- a/examples/fib.w
+++ b/examples/running_mean_std.w
@@ -1,81 +1,34 @@
 #!/home/arne/wisp/wisp-multiline.sh 
 ; !#
 
-;; Fibonacci Functions
+use-modules : srfi srfi-11
 
-define : fibonacci n
-    . "Get Fibonacci Element N in Linear Time"
-    let rek : (i 0) (u 1) (v 1)
-        if : >= i : - n 2
-            . v
-            rek (+ i 1) v (+ u v) ; else
+define : running-stat-fun
+     let
+       : n 0
+         sum 0
+         sum² 0
+       define : mean-std x
+                set! n : + n 1
+                set! sum : + sum x
+                set! sum² : + sum² : expt x 2
+                let*
+                  : mean : / sum n
+                    σ 
+                      - : / sum² n
+                        . mean
+                  values mean σ
+       . mean-std
 
-; display : fib 5
+define statfun : running-stat-fun
 
-;; Try it with curly infix
-
-;; First activate curly infix
-. #!curly-infix 
-
-;; Now define fibonacci with curly infix.
-define : fibonacci n
-    . "Get Fibonacci Element N in Linear Time"
-    let rek : (i 0) (u 1) (v 1)
-        if {i >= {n - 2}}
-            . v
-            rek {i + 1} v {u + v}
-
-display 
-  . {1 + 1}
+write : statfun 5
 newline
-
-;; Due to the compatibility with curly-infix, the following is no longer possible.
-
-;; Try an infix notation with curly brackets - curly infix from readable as simple macro
-;; define-syntax {
-;;     syntax-rules : { }
-;;         : { left infix right } 
-;;           infix left right
-;; 
-;; ; display : { 1 + 2 }
-;; 
-;; ;; Now do the fibonacci again
-;; define : fibcurl n
-;;     . "Get Fibonacci Elements in Linear Time"
-;;     let rek : (i 0) (u 1) (v 1)
-;;         if : { i >= ({ n - 2 }) }
-;;             . v
-;;             rek ({ i + 1 }) v ({ u + v }) ; else
-;; 
-;; ; display : fibcurl 5
-;; 
-;; ;; Do a more complete syntax-rule
-;; 
-;; ;; Try an infix notation with curly brackets - curly infix from readable as simple macro
-;; define-syntax {
-;;     syntax-rules : { }
-;;         : { l in r } 
-;;           in l r
-;;         : { { ll lin lr } in r } 
-;;           in (lin ll lr) r
-;;         : { l in { rl rin rr } } 
-;;           in l (rin rl rr)
-;;         : { { ll lin lr } in { rl rin rr } } 
-;;           in (lin ll lr) (rin rl rr)
-;; 
-;; ;; And a complete infix-fibonacci
-;; define : fibcurl2 n
-;;     . "Get Fibonacci Elements in Linear Time"
-;;     let rek : (i 0) (u 1) (v 1)
-;;         if : { i >= { n - 2 } }
-;;             . v
-;;             rek 
-;;                { i + 1 } 
-;;                . v 
-;;                { u + v }
-;; 
-;; ;; But to be frank: Prefix looks better.
-;; 
-;; display : { { 1 + 2 } * { 2 * 3 } }
-;; ; display : fibcurl2 5
-;; ; TODO: Make the macro recursive, so it can actually cover arbitrary depths of curly braces.
+write : statfun 4
+newline
+let-values 
+    : (mean σ) : statfun 5
+    display mean 
+    display '±
+    display σ
+    newline
diff --git a/examples/securepassword.w b/examples/securepassword.w
new file mode 100755
--- /dev/null
+++ b/examples/securepassword.w
@@ -0,0 +1,313 @@
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples securepassword) main)' -s "$0" "$@"
+; !#
+
+;; Create secure passwords, usable on US and German keyboards without problems
+
+;; As of 2011, a single device can do 2,800,000,000 guesses per
+;; second.  Today this should be 10 billion guesses per second.
+;; According to a recovery company which sells crackers at 1.5k$, as
+;; of 2016 a zip-file can be attacked with 100,000 guesses per
+;; second. Ars Technica reports 8 billion attacks on md5 on a single
+;; device in 2013[1].
+
+;; Codinghorror quotes[2] codohale[3] on the cost of buying 5 billion
+;; cracked md5 hashes per second in 2010 for just 3$ per hour. This
+;; should be around 20 billion guesses per second today.
+
+;; I will from now on call 20 billion guesses per second for 3$ per
+;; hour the "strong attack" and 100,000 guesses per second the "weak
+;; attack".
+
+;; [1]: http://arstechnica.com/security/2013/05/how-crackers-make-minced-meat-out-of-your-passwords/
+;; [2]: https://blog.codinghorror.com/speed-hashing/
+;; [3]: http://codahale.com/how-to-safely-store-a-password/ 
+
+;; A password with 8 letters and 1 delimiter (entropy 49) would on
+;; average withstand the strong attack with a single device for 4
+;; hours, so you could buy a cracked md5-secured 8 letter + 1
+;; delimiter password for 12$ (assuming that it was salted, otherwise
+;; you can buy all these md5’ed passwords for around 24$).
+
+;; The 8 letter and 1 delimiter password would withstand the weak
+;; attack until 2031 (when it would be cracked in one year, with a
+;; cost of 26k$), assuming doubling of processing power every two
+;; years. Cracking it in one day would be possible in 2048, paying
+;; just 72$.
+
+;; (yearstillcrackable 49)
+;; => ((in-one-second 64.78071905112638)
+;;     (in-one-day 31.983231667249996)
+;;     (in-one-year 14.957750741642995))
+
+;; A password with 12 letters and 2 delimiters (length 12, entropy 75)
+;; should withstand the strong attack until 2047 (then it would be
+;; cracked in one year), assuming doubling of processing power every
+;; two years, the weak until 2083.
+
+;; For every factor of 1000 (i.e. 1024 computers), the time to get a
+;; solution is reduced by 20 years.  Using every existing cell phone,
+;; the 12 letter key would be cracked by the method with 100,000
+;; guesses per second in 2021 (within one year). Facebook could do
+;; that with Javascript, so you might want to use a longer password if
+;; your data has to be secure against the whole planet for longer than
+;; 5 years.
+
+;; (yearstillcrackable 75 #:guesses/second 1.e5 #:number-of-devices 2.e9)
+;; => ((in-one-second 54.986013343153864)
+;;     (in-one-day 22.188525959277467)
+;;     (in-one-year 5.163045033670471))
+
+;; Using Landauer’s principle[4], we can estimate the minimum energy
+;; needed to to check a password solution with a computer at room
+;; temperature, assuming that reversible entropy computing isn’t
+;; realized and quantum computers have to stick to Landauer’s limit: A
+;; single bit-flip requires approximately 3 Zeptojoule[5] at room
+;; temperature, so we can flip 333 e18 bits per second with one Watt
+;; of Energy. Processing any information requires at least one
+;; bit-flip. Reducing the temperature to 1.e-7K (reachable with
+;; evaporative cooling) would theoretically allow increasing the bit
+;; flips per Joule to 1e30. That gives a plausible maximum of password
+;; checks per expended energy. Assuming that someone would dedicate a
+;; large nuclear powerplant with 1 Gigawatt of output to cracking your
+;; password, a 160 bit password would withstand the attack for about
+;; 23 years.
+
+;; [4]: https://en.wikipedia.org/wiki/Landauer's_principle
+;; [5]: http://advances.sciencemag.org/content/2/3/e1501492 "DOI: 10.1126/sciadv.1501492"
+
+;; With the password scheme described here, a password with 28 letters
+;; and 6 delimiters (178 bits of entropy) should be secure for almost
+;; 6 million years in the Landauer limit at 1.e-7K, with the energy of
+;; a large nuclear power plant devoted to cracking it.
+
+;; (years-to-crack-landau-limit-evaporative-cooling-nuclear-powerplant 178)
+;; => 6070231.659195759
+
+;; With 24 letters and 5 delimiters it would only last about one
+;; month, though. Mind exponentials and the linear limit of the human
+;; lifespan :)
+
+;; However using the total energy output of the sun (about 0.5e21 W),
+;; a 28 letter, 6 delimiter password would survive for just about 6
+;; minutes. To reach 50 years of password survival against an attacker
+;; harnessing the energy of the sun (a type II civilization on the
+;; Kardashev scale[6] devoting its whole civilization to cracking your
+;; password), you’d need 200 bits of entropy: 32 letters and 7
+;; delimiters. A 36 letter, 8 delimiter password (230 bits of entropy)
+;; would last about 54 billion years. With that it would very likely
+;; outlast that civilization (especially if the civilization devotes
+;; all its power to crack your password) and maybe even its star. They
+;; could in theory just get lucky, though.
+
+;; [6]: https://en.wikipedia.org/wiki/Kardashev_scale
+
+;; An example of a 28 letter, 6 delimiter password would be:
+;; 7XAG,isCF+soGX.f8i6,Vf7P+pG3J!4Xhf
+;; Don’t use this one, though :)
+
+;; If you ever wanted to anger a type II civilization, encrypt their
+;; vital information with a 36 letter, 8 delimiter password like this:
+;; HArw-CUCG+AxRg-WAVN-5KRC*1bRq.v9Tc+SAgG,QfUc
+;; Keep in mind, though, that they might have other means to get it
+;; than brute force. And when they come for you, they will all be
+;; *really angry* :)
+;; Or they might just have developed reversible computing, then all
+;; these computations are just a fun game to stretch the mind :)
+
+
+define-module : examples securepassword
+              . #:export : password yearstillcrackable
+
+import
+    only (srfi srfi-27) random-source-make-integers
+      . make-random-source random-source-randomize!
+    only (srfi srfi-1) second third iota
+    srfi srfi-42
+    ice-9 optargs
+
+
+define : years-to-crack-landau-limit-evaporative-cooling-nuclear-powerplant entropy
+       . "Estimate of the years needed to crack the password in the landauer limit"
+       let*
+        : seconds/day : * 60 60 24
+          days/year 365.25
+          tempK 1e-7
+          room-temp-fraction {tempK / 300}
+          guesses/Joule {1 / {3e-21 * room-temp-fraction}}
+          Gigawatt 1e9
+          guesses/second : * Gigawatt guesses/Joule
+          seconds/day : * 60 60 24
+          days/year 365.25
+          guesses/year : * guesses/second seconds/day days/year
+        / (expt 2 entropy) guesses/year 2
+
+
+define : years-to-crack-landau-limit-evaporative-cooling-draining-a-star entropy
+       . "Estimate of the years needed to crack the password in
+the landauer limit using the whole power output of a
+sun-like star"
+       let*
+        : watt-powerplant 1e9
+          watt-star 0.5e21 
+        * : years-to-crack-landau-limit-evaporative-cooling-nuclear-powerplant entropy
+          / watt-powerplant watt-star 
+
+
+define* : secondstocrack entropy #:key (guesses/second 100000) (number-of-devices 1)
+       . "Estimate of the seconds it will take to crack the password with the given computing power"
+       / (expt 2 entropy) guesses/second number-of-devices 2
+
+
+define* : hourstocrack entropy #:key . args
+       . "Estimate of the hours it will take to crack the password with the given computing power"
+       let*
+        : seconds/hour : * 60 60
+        / : apply secondstocrack : cons entropy args
+          . seconds/hour
+
+
+define* : daystocrack entropy . args
+       . "Estimate of the days it will take to crack the password with the given computing power"
+       let*
+        : seconds/day : * 60 60 24
+        / : apply secondstocrack : cons entropy args
+          . seconds/day
+
+
+define* : yearstocrack entropy . args
+       . "Estimate of the years it will take to crack the password with the given computing power"
+       let*
+        : days/year 365.25
+          seconds/day : * 60 60 24
+        / : apply secondstocrack : cons entropy args
+          * days/year seconds/day
+
+
+define* : yearstillcrackable entropy #:key (guesses/second 100000) (number-of-devices 1)
+       . "Estimate of the years it will take until the password
+is crackable, assuming a doubling of computing power every
+two years" 
+       let
+        : seconds/day : * 60 60 24
+          days/year 365.25
+        ` 
+             in-one-second
+               , * 2
+                  / 
+                    log : / (expt 2 entropy) (* guesses/second number-of-devices)
+                    log 2
+             in-one-day
+               , * 2
+                  / 
+                    log : / (expt 2 entropy) (* seconds/day guesses/second number-of-devices)
+                    log 2
+             in-one-year 
+               , * 2
+                  / 
+                    log : / (expt 2 entropy) (* days/year seconds/day guesses/second number-of-devices)
+                    log 2
+
+
+define : entropy-per-letter lettercount
+       . "calculate the entropy of adding a randomly chosen
+letter from a number of letters equal to LETTERCOUNT"
+       / : log lettercount
+         log 2
+
+
+;; newbase60 without yz_: 55 letters, 5.78 bits of entropy per letter.
+define qwertysafeletters "0123456789ABCDEFGHJKLMNPQRSTUVWXabcdefghijkmnopqrstuvwx"
+;; delimiters: 2.8 bits of entropy per delimiter, in the same place on main keys or the num-pad.
+define delimiters ",.+-*/!"
+
+define random-source : make-random-source
+random-source-randomize! random-source
+
+
+define random-integer 
+       random-source-make-integers random-source
+
+
+define : randomletter letters
+      string-ref letters
+        random-integer
+          string-length letters
+
+
+define : flatten e
+    cond 
+       : pair? e
+         ` 
+           ,@ flatten : car e 
+           ,@ flatten : cdr e
+       : null? e
+         list
+       else 
+         list e
+
+
+define : password/srfi-42 length
+       . "Generate a password with the given length in letters 
+(not counting delimiters)."
+       list->string
+         append-ec (: i (iota length 1))
+           cons : randomletter qwertysafeletters
+             if : and (not (= i length)) : zero? : modulo i 4
+                cons : randomletter delimiters
+                  list
+                list
+
+
+define : password/map length
+       . "Generate a password with the given length in letters 
+(not counting delimiters)."
+       list->string
+         flatten
+           map
+             lambda : i
+               let
+                 : letter : randomletter qwertysafeletters
+                 if : and (not (= i length)) : zero? : modulo i 4
+                    list letter 
+                      randomletter delimiters
+                    list letter
+             iota length 1
+
+
+define : password length
+       . "Generate a password with the given length in letters 
+(not counting delimiters)."
+       let fill
+         : letters '()
+           remaining length
+         if : zero? remaining
+            reverse-list->string letters
+            fill
+              cons : randomletter qwertysafeletters
+                if : and (not (= length remaining)) : zero? : modulo remaining 4
+                   cons : randomletter delimiters
+                        . letters
+                   . letters
+              - remaining 1
+
+
+define : main args
+      let
+       :
+         len
+           if : <= 2 : length args
+              string->number : second args
+              . 8
+       let 
+         : idx (if (> 3 (length args)) 1 (string->number (third args)))
+         cond
+           : = idx 1
+             display : password len
+           : = idx 2
+             display : password/map len
+           : = idx 3
+             display : password/srfi-42 len
+         newline
+
diff --git a/examples/sh.w b/examples/sh.w
new file mode 100755
--- /dev/null
+++ b/examples/sh.w
@@ -0,0 +1,25 @@
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples sh) main)' -s "$0" "$@"
+; !#
+
+;; simplest way to run shell commands
+
+define-module : examples sh
+              . #:export : sh
+
+use-modules : srfi srfi-1
+
+define : ->string thing
+  if : symbol? thing
+    symbol->string thing
+    format #f "\"~A\"" thing
+
+define : run-me . args
+  system : string-join : map ->string args
+
+define-syntax-rule : sh args ...
+  apply run-me : quote : args ...
+
+define : main args
+  sh echo foo | sed s/o/u/
diff --git a/examples/d6.w b/examples/with.w
copy from examples/d6.w
copy to examples/with.w
--- a/examples/d6.w
+++ b/examples/with.w
@@ -1,27 +1,39 @@
-#!/home/arne/wisp/wisp-multiline.sh 
+#!/usr/bin/env sh
+# -*- wisp -*-
+exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples with) main)' -s "$0" "$@"
 ; !#
 
-define-module : examples d6
-   . #:export : roll check
+;; A cleaner way to implement this might be using dynamic-wind.
 
-; basic d6 rules, implemented in guile
+;; FIXME: This might not be continuation-safe and might break if the
+;; code in the with block uses dynamic-wind. Check whether it’s safe
+;; and fix it if not.
 
-define : roll
-    . "Roll one ± d6"
-    let : : die '(-5 -3 -1 2 4 6)
-        list-ref die : random 6 : random-state-from-platform
+define-module : examples with
 
-define : check skill target effect-threshold
-    . "Check whether a given skill-roll succeeds and provide a margin of success."
-    let : : result : + skill : roll
-        if : > result target
-            if : < effect-threshold : - result target
-                . 1
-                . #t
-            . #f
+import : oop goops
 
-display : check 12 9 3
-newline
-display : roll
+define : enter thing
+       . thing
+define-generic enter
 
+define : exit thing
+       . thing
+define-generic exit
 
+define-syntax with
+    syntax-rules : as
+      : _ thing as name thunk ...
+        let*
+           : name : enter thing
+             res : begin thunk ...
+           exit thing
+           . res
+
+define-method : exit (thing <port>)
+              . "Ensure that a port is always closed at the end of the with-block."
+              close-port thing
+
+define : main args
+         with (open-file "with.w" "r") as port
+              format #t "~a\n" : read port
diff --git a/tests/fast-sum.scm b/tests/fast-sum.scm
new file mode 100644
--- /dev/null
+++ b/tests/fast-sum.scm
@@ -0,0 +1,16 @@
+(use-modules (srfi srfi-1))
+
+; only for the nice test
+#!curly-infix
+
+(define-syntax fast-sum
+  (syntax-rules (iota)
+    ((fast-sum (iota count start))
+     (+ 1
+        (apply - 
+          (map (lambda (x) (/ {x * {x + 1} } 2))
+                (list {count + {start - 1}} start)))))
+    ((fast-sum e)
+     (apply + e))))
+
+
diff --git a/tests/fast-sum.w b/tests/fast-sum.w
new file mode 100644
--- /dev/null
+++ b/tests/fast-sum.w
@@ -0,0 +1,14 @@
+use-modules : srfi srfi-1
+
+; only for the nice test
+. #!curly-infix
+
+define-syntax fast-sum
+  syntax-rules : iota
+    : fast-sum : iota count start
+      + 1
+        apply - 
+          map : lambda (x) : / {x * {x + 1} } 2
+                list {count + {start - 1}} start
+    : fast-sum e
+      apply + e
diff --git a/tests/hello.scm b/tests/hello.scm
--- a/tests/hello.scm
+++ b/tests/hello.scm
@@ -1,4 +1,5 @@
 (define (hello who)
+  ;; include the newline
   (format #t "~A ~A!\n"
           "Hello" who))
 (hello "Wisp")
diff --git a/tests/hello.w b/tests/hello.w
--- a/tests/hello.w
+++ b/tests/hello.w
@@ -1,4 +1,5 @@
 define : hello who
+  ;; include the newline
   format #t "~A ~A!\n"
           . "Hello" who
 hello "Wisp"
diff --git a/tests/multiline-string.scm b/tests/multiline-string.scm
new file mode 100644
--- /dev/null
+++ b/tests/multiline-string.scm
@@ -0,0 +1,6 @@
+(display "
+  This is a
+  \"multi-line\"
+  string.
+  ")
+
diff --git a/tests/multiline-string.w b/tests/multiline-string.w
new file mode 100644
--- /dev/null
+++ b/tests/multiline-string.w
@@ -0,0 +1,5 @@
+display "
+  This is a
+  \"multi-line\"
+  string.
+  "
diff --git a/wisp-guile.w b/wisp-guile.w
--- a/wisp-guile.w
+++ b/wisp-guile.w
@@ -134,8 +134,8 @@ Ends with three consecutive linebreaks o
                                 not : char=? lastchar #\\ ; if the last char is not a backslash (escaped quote)
                                 ; or the last char is a backslash preceded by an uneven number of backslashes (so the backslash is actually an escaped backslash)
                                 and : char=? lastchar #\\
-                                      ; not : equal? #f : string-match "\\([^\\]\\)+\\(\\\\\\\\\\)*[\\]$" text ; matches [^\](\\)*\$ - non-backslash + arbitrary number of pairs of backslashes + final backslash which undoes the escaping from the lastchar (by actually escaping the lastchar)
-                                      endsinunevenbackslashes text
+                                      ; if all backslashes before the lastchar are paired, the final quote is escaped.
+                                      not : endsinunevenbackslashes text
                         char=? lastchar #\space ; when the last char was a space, I can get into a string
                         char=? lastchar #\newline ; same for newline chars
                         char=? lastchar #\return 
diff --git a/wisp-mode.el b/wisp-mode.el
--- a/wisp-mode.el
+++ b/wisp-mode.el
@@ -1,9 +1,11 @@
 ;;; wisp-mode.el --- Tools for wisp: the Whitespace-to-Lisp preprocessor
 
-;; Copyright (C) 2013  Arne Babenhauserheide <arne_bab@web.de>
+;; Copyright (C) 2013--2016  Arne Babenhauserheide <arne_bab@web.de>
+;; Copyright (C) 2015--2016  Kevin W. van Rooijen — indentation and tools
+;;               from https://github.com/kwrooijen/indy/blob/master/indy.el
 
 ;; Author: Arne Babenhauserheide <arne_bab@web.de>
-;; Version: 0.2.1
+;; Version: 0.2.3
 ;; Keywords: languages, lisp
 
 ;; This program is free software; you can redistribute it and/or
@@ -28,8 +30,9 @@
 ;; 
 ;; For details on wisp, see 
 ;; http://draketo.de/light/english/wisp-lisp-indentation-preprocessor
-;; 
-;; If you came here looking for wisp the lisp-to-javascript compiler[1], have a look at wispjs-mode[2].
+;;
+;; If you came here looking for wisp the lisp-to-javascript
+;; compiler[1], have a look at wispjs-mode[2].
 ;; 
 ;; [1]: http://jeditoolkit.com/try-wisp
 ;; 
@@ -70,7 +73,7 @@
 ; note: for easy testing: emacs -Q wisp-mode.el -e eval-buffer wisp-guile.w -e delete-other-windows
 
 
-(defvar wisp-builtin '("define" "define-syntax" "syntax-rules" "defun" "let*" "let" "setq" "set!" "set" "if" "when" "while" "set!" "and" "or" "not" "char=?"))
+(defvar wisp-builtin '("define" "define-syntax" "syntax-rules" "syntax-case" "define-syntax-rule" "defun" "let*" "let" "setq" "set!" "set" "if" "when" "while" "set!" "and" "or" "not" "char=?"))
 
 ; TODO: Add special treatment for defun foo : bar baz ⇒ foo = function, bar and baz not.
 ; TODO: Add highlighting for `, , and other macro-identifiers.
@@ -107,6 +110,93 @@
      (" : \\| \\. " . font-lock-keyword-face) ; leading : or .
      ))
   "Default highlighting expressions for wisp mode.")
+(defun wisp--prev-indent ()
+  "Get the amount of indentation spaces if the previous line."
+  (save-excursion
+    (previous-line 1)
+    (while (wisp--line-empty?)
+      (previous-line 1))
+    (back-to-indentation)
+    (current-column)))
+
+(defun wisp--line-empty? ()
+  "Check if the current line is empty."
+  (string-match "^\s*$" (wisp--get-current-line)))
+
+(defun wisp--get-current-line ()
+  "Get the current line as a string."
+  (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
+
+(defun wisp--current-indent ()
+  "Get the amount of indentation spaces if the current line."
+  (save-excursion
+    (back-to-indentation)
+    (current-column)))
+
+(defun indy--fix-num (num)
+  "Make sure NUM is a valid number for calculating indentation."
+  (cond
+   ((not num) 0)
+   ((< num 0) 0)
+   (t num)))
+
+(defun wisp--indent (num)
+  "Indent the current line by the amount of provided in NUM."
+  (unless (equal (wisp--current-indent) num)
+    (let* ((num (max num 0))
+           (ccn (+ (current-column) (- num (wisp--current-indent)))))
+      (indent-line-to num)
+      (move-to-column (indy--fix-num ccn)))))
+
+;;;###autoload
+(defun wisp--tab ()
+  "Cycle through indentations depending on the previous line."
+  (interactive)
+  (let* ((curr (wisp--current-indent))
+         (prev (wisp--prev-indent))
+         (width (cond
+             ((< curr (- prev tab-width)) (- prev tab-width))
+             ((< curr prev) prev)
+             ((equal curr prev) (+ prev tab-width))
+             (t  0))))
+    (wisp--indent width)))
+
+
+(defun wisp-indent-current-line (&optional unindented-ok)
+  "Sets the indentation of the current line. Derived from
+indent-relative."
+  (interactive "P")
+  (let ((start-column (current-column))
+        indent)
+    (save-excursion
+      (beginning-of-line)
+      (if (re-search-backward "^[^\n]" nil t)
+          (let ((end (save-excursion (forward-line 1) (point))))
+  (setq tab-width 4)
+            (move-to-column start-column)
+            ; TODO: If the previous line is less indented by exactly 4
+            ; characters, de-dent to previous-line minus 4. If the
+            ; previous line is more indented, indent to the
+            ; indentation of the previous line. If both lines are
+            ; equally indented, indent to either the previous line
+            ; plus 4, or to the first occurence of a colon, if that’s
+            ; less.
+            (cond
+             ((= (current-column) (- start-column 4))
+              (setq indent (- (current-column) 4))))
+             
+            (or (looking-at "[ \t]")
+                unindented-ok
+                (skip-chars-forward "^ \t" end))
+            (skip-chars-forward " \t" end)
+            (or (= (point) end) (setq indent (current-column))))))
+    (if indent
+        (let ((opoint (point-marker)))
+          (indent-to indent 0)
+          (if (> opoint (point))
+              (goto-char opoint))
+          (move-marker opoint nil))
+      (tab-to-tab-stop))))
 
 ; use this mode automatically
 ;;;###autoload
@@ -123,7 +213,7 @@
   (set (make-local-variable 'parse-sexp-ignore-comments) t)
   (set (make-local-variable 'font-lock-defaults) wisp-font-lock-keywords)
   (set (make-local-variable 'mode-require-final-newline) t)
-  (local-set-key (kbd "<tab>") 'indent-relative))
+  (local-set-key (kbd "<tab>") 'wisp--tab))
 
                         
 
diff --git a/wisp-multiline.sh b/wisp-multiline.sh
--- a/wisp-multiline.sh
+++ b/wisp-multiline.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 # wisp-multiline.sh --- run multiline wisp code
 
 # Copyright (C) 2013 Arne Babenhauserheide <arne_bab@web.de>
diff --git a/wisp-reader.w b/wisp-reader.w
--- a/wisp-reader.w
+++ b/wisp-reader.w
@@ -53,7 +53,7 @@ define : read-one-wisp-sexp port env
 define-language wisp
   . #:title "Wisp Scheme Syntax. See SRFI-119 for details. THIS IS EXPERIMENTAL, USE AT YOUR OWN RISK"
   ; . #:reader read-one-wisp-sexp
-  . #:reader : lambda (port env) : let ((x (read-one-wisp-sexp port env))) x
+  . #:reader read-one-wisp-sexp ; : lambda (port env) : let ((x (read-one-wisp-sexp port env))) (display x)(newline) x ;
   . #:compilers `((tree-il . ,compile-tree-il))
   . #:decompilers `((tree-il . ,decompile-tree-il))
   . #:evaluator : lambda (x module) : primitive-eval x
diff --git a/wisp-repl-guile.sh b/wisp-repl-guile.sh
--- a/wisp-repl-guile.sh
+++ b/wisp-repl-guile.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env bash
 
 # if the spec file does not exist yet, run the build chain
 if test ! -f language/wisp/spec.scm; then
diff --git a/wisp-scheme.w b/wisp-scheme.w
--- a/wisp-scheme.w
+++ b/wisp-scheme.w
@@ -7,15 +7,10 @@ exec guile -L . --language=wisp -s "$0" 
 ;; scheme code tree to feed to a scheme interpreter instead of a
 ;; preprocessed file.
 
-;; Plan:
-;; read reads the first expression from a string. It ignores comments,
-;; so we have to treat these specially. Our wisp-reader only needs to
-;; worry about whitespace.
-;; 
-;; So we can skip all the string and bracket linebreak escaping and
-;; directly create a list of codelines with indentation. For this we
-;; then simply reuse the appropriate function from the generic wisp
-;; preprocessor.
+;; Limitations:
+;; - only unescapes up to 6 leading underscores at line start (\______)
+;; - in some cases the source line information is missing in backtraces.
+;;   check for set-source-property!
 
 ;; Copyright (C) Arne Babenhauserheide (2014--2015). All Rights Reserved.
 
@@ -613,6 +608,16 @@ define : wisp-unescape-underscore-and-co
                map wisp-unescape-underscore-and-colon a
              '\_
                . '_
+             '\__
+               . '__
+             '\___
+               . '___
+             '\____
+               . '____
+             '\_____
+               . '_____
+             '\______
+               . '______
              '\:
                . ':
              a
@@ -638,6 +643,8 @@ define : wisp-replace-paren-quotation-re
                 append
                         map wisp-replace-paren-quotation-repr a
                         list : list 'quote : map wisp-replace-paren-quotation-repr b
+             : 'REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd 'REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...
+                list 'quasiquote : list 'unquote : map wisp-replace-paren-quotation-repr a
              : 'REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...
                 list 'unquote : map wisp-replace-paren-quotation-repr a
              : a ... 'REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b