(Arne Babenhauserheide)
2015-09-24: merge ending chunk with trailing period, included wisp.scm and more stable merge ending chunk with trailing period, included wisp.scm and more resilient spec.
diff --git a/.hgsigs b/.hgsigs --- a/.hgsigs +++ b/.hgsigs @@ -21,3 +21,5 @@ 36b8c0daff2cd8cadb73b0dcc19c16a60f5b58eb a8ea98c78d90ae3e1b4daaf4bab3e2a7a847a06f 0 iJwEAAEIAAYFAlUoSqEACgkQ3M8NswvBBUihJQP/XB8kHNEcsTj5pgBtMepmIX/3CmVaz6ZNgzhSzJSS1oz2DMbXPJh32QaDwRd5wCoNksD00ch7e9UWhTSZztI/yDY9KwZrTV/OIDFzIdfSsdDO4J0WNxHmgymHSfitCjHcgSvT/C9/mubhNECPrQQUx08FMnNiNvcmGpwVOwakNNw= 41a3bcb0e07cff2119e6df4574afe7af01231724 0 iQEcBAABAgAGBQJViY75AAoJEFyD82SnDaCexVAH/1TKjisW+ka4V+1Hk6L+rd+35Migij9RzfMYwfaHzi9GtpuqkDrjrRxlh8R6P/b+bHTTXwfcZ1mk6Otstsutg/469qBfF5Mb09Zoi0y4g6xt6O75Jcac8vT3Sx22hUoh0GwqRMd7DZcNjO8tTOwV0Ssraqs2VURXwIF11D9g8weconamgQqs7uWgQ4Ku6qUXFfbzv0Sk3GHCyRfzwu3IZkvXymi+sVBNmq9Wtzn14tTsgOeFSmDL8EREK/yENyckzZbxbLO8TrNmL2VeZ6SidBDOaf9nDoyh2dWYdi10dxt7lbjcaPlYXEdSxm3V7fSp8d/kF5DMuefcGThZfKsE5aM= 22bf6277df51f10ca9192bde088e6c062f9190a8 0 iQEcBAABAgAGBQJVt5NXAAoJEFyD82SnDaCevQIH/RIYkKQLBklUHMYvHO655PXNAHpU0OSMAjnggTiuNrY6IZqQASj/AWdAopPXMh5KrJ25Sg5ktlnxEw5paA68CFZCsc9HHuBwffkgN1kjOqafyI9FA46obCCnly1Mpo4814R+LbdUtcPerTDy4cJq6FlQal2l130Hi0ASVvfqqT73NbAl3EtBnYF2ecTiklHiUCZLlgMUXSBf3UxF3xzoY1g1tbtktVTVHbh+MDYPjpaM3y2CiYCaicS2kMyFRaNGEmh+4oHSAwEu9i6pZQtuhJItehbT+qNDJMc+Zs5DmYbH60Osv1mVn9MjQTeKoIWuBRHyPwuZXOstaYlNkSNqsvI= +6ef7889cef134b20afbd9a0268b5e49c88de73c5 0 iJwEAAEIAAYFAlXzWfkACgkQ3M8NswvBBUin3QP7B9iVEOI9DRVgzXT9Na59J7comA1SQWvfmVL2sU62BI7xApRr5Oz2dTI9Do1ZN2SgIf1pn7ozW9zXgrpOD7lgXMQJ61olxbzCQYF+O42Ro2GDBLX1X/ws6aADp46geMlzomH8PFjloYZJ+bF0eKhntdIpd6rebyhr8RPYhhMCccM= +e6a76861c5e62e5ed221fbed9edbb20033269512 0 iQEcBAABCAAGBQJWA/HXAAoJEFyD82SnDaCecvYIAKYst6/ChpZe6JWR76/kh1ybutctS/rPeuY0567TqdXdxTRbQjm88o2//R7nzBNjoQFvsFN9yQjgpUpShn1PkA27uhha5JeowD2ErPDCD5kFaGRoA/vRLPLa7BAOz87Oh9EJHkFBbWj/exHmLqauNR2H5VFyhHGafFdUcL9SqDKN6zdtDZaYdD78g+LoypvYQxUxcv4J9Ssp1or4izb/mNhCGJj063q2V7imMLAAeM8xsXycAM1re8NRLDXwo44PKvcu6B/qrNtnRr5K2QtqIrNpEWNHC0roPjwf2IzBzL0VL6LU4oK7VVghDvcqC1lid3aVrRwFRSa7UXKkb1re0V8= diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -30,3 +30,5 @@ 41c48043ca33bf47311a93d0545b13a0578c3cf0 a4bca2a0f2f6659d97b1db471ae9803119b80529 v0.8.4 f0096bf5f3baee5017be94f49c70515fe2a535b3 wisp-mode-0.2.1 fb551bbe7084d22ef0c8e35df3864eb2aef46005 v0.8.5 +625eec6805f907b9d6338d09c8199e9ed3e79ab1 v0.8.6 +e6977cfff0c8e0d2b1b33f724e0e4607ea15f703 v0.8.7 diff --git a/Makefile.am b/Makefile.am --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,7 @@ ChangeLog : .INTERMEDIATE: input.in.intermediate input.in.intermediate: ${wisp_SOURCES} - @abs_top_srcdir@/bootstrap.sh @abs_top_srcdir@ @guile@ @python3@ 2>&1 | sed "s/^;;;.*//" 2>&1 | grep . 1>&2 ; test ! $$? -eq 0 # grep did 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 syntaxtests.sh : wisp.scm tests/runtests-scheme-preprocessor.sh diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -1,3 +1,21 @@ +wisp 0.8.7 (2015-09-24): +- new example say.w, companion to +- bootstrap now explicitly requires bash +- include wisp.scm in the tarball to allow for pure-guile bootstrapping. +- spec: handle locale errors more gracefully +- improve readme + +wisp 0.8.6 (2015-09-12): +- wisp-scheme.w allows ending a chunk with a trailing period. This + syntax is reserved for future use in SRFI-119, so you should not + rely on this in production code. It is mainly intended as REPL + feature to avoid visual clutter while running single lines, like + quick calculations. +- new examples: cholesky decomposition, symmetric matrix, closure, + hoist-in-loop (cps transformation transcoded to wisp). +- updated examples: evolve.w +- clearer bootstrap output + wisp 0.8.5 (2015-07-28): - wisp-scheme.w now parses : . to nothing. The colon adds parens, the dot removes them. This provides compatibility to the older parser. diff --git a/README b/README --- a/README +++ b/README @@ -19,7 +19,7 @@ For a short presentation, see [Why Wisp? Note that this is full-fledged scheme, with all its capabilities like hygienic macros (programmable syntax!) and full tail recursion. -[wisp-website]: http://draketo.de/light/english/wisp-lisp-indentation-preprocessor "wisp: Whitespace to Lisp: An indentation to parentheses preprocessor to get more readable Lisp" +[wisp-website]: http://draketo.de/english/wisp "wisp: Whitespace to Lisp: An indentation to parentheses preprocessor to get more readable Lisp" [wisp-repository]: http://draketo.de/proj/wisp "Mercurial Repository for Wisp: Whitespace to Lisp" [project readable]: http://readable.sourceforge.net/ "Readable Lisp S-expressions Project" @@ -55,13 +55,13 @@ If you want to use a curly-infix express Notes ----- -Standardization: A [SRFI](srfi.html)[^srfi][^ess] is in the works. +Standardization: Wisp is standardized as [SRFI 119](http://srfi.schemers.org/srfi-119/)[^srfi][^ess]. [^srfi]: SRFI is the abbreviation of Scheme Request for Implementation. It is the official schemisch way of suggesting new features. SRFIs are maintained at [srfi.schemers.org/](http://srfi.schemers.org/). [^ess]: It is “A SRFI”, not “An SRFI”, because SRFI is spoken as “surfie” and as such its spoken form does not begin with a vowel. -Copyright: 2013--2014 Arne Babenhauserheide +Copyright: 2013--2015 Arne Babenhauserheide License: GPLv3 or later diff --git a/bootstrap.sh b/bootstrap.sh --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/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,7 +1,7 @@ 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.8.5], +AC_INIT([wisp], [0.8.7], [arne_bab@web.de]) # Check for programs I need for my build AC_CANONICAL_TARGET diff --git a/docs/srfi-119/README b/docs/srfi-119/README new file mode 100644 --- /dev/null +++ b/docs/srfi-119/README @@ -0,0 +1,22 @@ +SRFI-119 supporting files +========================= + + +Bootstrap wisp using Python 3 +----------------------------- + + sh bootstrap.sh + +This creates wisp-preprocessor.scm (the version in the tarball is called wisp-guile.scm to avoid overwriting it) +and wisp-parser.scm (the version in the tarball is called wisp-scheme.scm) + + +Description +----------- + +wisp-preprocessor.scm reads files in wisp-syntax and outputs generic scheme. + + usage: guile wisp-preprocessor.scm <wisp-file> > <scheme-file> + +wisp-parser.scm provides procedures for reading s-expressions from wisp-files. + diff --git a/docs/srfi-119/bootstrap.sh b/docs/srfi-119/bootstrap.sh new file mode 100755 --- /dev/null +++ b/docs/srfi-119/bootstrap.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Bootstrap wisp using Python 3 + +# This creates wisp-preprocessor.scm (the version in the tarball is called wisp-guile.scm to avoid overwriting it) +# and wisp-parser.scm (the version in the tarball is called wisp-scheme.scm) + +# usage: guile wisp-preprocessor.scm <wisp-file> > <scheme-file> + +# wisp-parser.scm provides procedures for reading s-expressions from wisp-files. + +python3 wisp.py wisp-guile.w > 1 \ + && guile 1 wisp-guile.w > 2 \ + && guile 2 wisp-guile.w > wisp-preprocessor.scm \ + && diff 2 wisp-preprocessor.scm \ + && guile wisp-preprocessor.scm wisp-scheme.w > wisp-parser.scm \ + && rm 1 2 + diff --git a/docs/srfi-119/srfi-testsuite.html b/docs/srfi-119/srfi-testsuite.html new file mode 100644 --- /dev/null +++ b/docs/srfi-119/srfi-testsuite.html @@ -0,0 +1,957 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<head> +<title>Test Suite</title> +<!-- 2014-12-23 Di 22:50 --> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> +<meta name="generator" content="Org-mode" /> +<meta name="author" content="Arne Babenhauserheide" /> +<style type="text/css"> + <!--/*--><![CDATA[/*><!--*/ + .title { text-align: center; } + .todo { font-family: monospace; color: red; } + .done { color: green; } + .tag { background-color: #eee; font-family: monospace; + padding: 2px; font-size: 80%; font-weight: normal; } + .timestamp { color: #bebebe; } + .timestamp-kwd { color: #5f9ea0; } + .right { margin-left: auto; margin-right: 0px; text-align: right; } + .left { margin-left: 0px; margin-right: auto; text-align: left; } + .center { margin-left: auto; margin-right: auto; text-align: center; } + .underline { text-decoration: underline; } + #postamble p, #preamble p { font-size: 90%; margin: .2em; } + p.verse { margin-left: 3%; } + pre { + border: 1px solid #ccc; + box-shadow: 3px 3px 3px #eee; + padding: 8pt; + font-family: monospace; + overflow: auto; + margin: 1.2em; + } + pre.src { + position: relative; + overflow: visible; + padding-top: 1.2em; + } + pre.src:before { + display: none; + position: absolute; + background-color: white; + top: -10px; + right: 10px; + padding: 3px; + border: 1px solid black; + } + pre.src:hover:before { display: inline;} + pre.src-sh:before { content: 'sh'; } + pre.src-bash:before { content: 'sh'; } + pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } + pre.src-R:before { content: 'R'; } + pre.src-perl:before { content: 'Perl'; } + pre.src-java:before { content: 'Java'; } + pre.src-sql:before { content: 'SQL'; } + + table { border-collapse:collapse; } + caption.t-above { caption-side: top; } + caption.t-bottom { caption-side: bottom; } + td, th { vertical-align:top; } + th.right { text-align: center; } + th.left { text-align: center; } + th.center { text-align: center; } + td.right { text-align: right; } + td.left { text-align: left; } + td.center { text-align: center; } + dt { font-weight: bold; } + .footpara:nth-child(2) { display: inline; } + .footpara { display: block; } + .footdef { margin-bottom: 1em; } + .figure { padding: 1em; } + .figure p { text-align: center; } + .inlinetask { + padding: 10px; + border: 2px solid gray; + margin: 10px; + background: #ffffcc; + } + #org-div-home-and-up + { text-align: right; font-size: 70%; white-space: nowrap; } + textarea { overflow-x: auto; } + .linenr { font-size: smaller } + .code-highlighted { background-color: #ffff00; } + .org-info-js_info-navigation { border-style: none; } + #org-info-js_console-label + { font-size: 10px; font-weight: bold; white-space: nowrap; } + .org-info-js_search-highlight + { background-color: #ffff00; color: #000000; font-weight: bold; } + /*]]>*/--> +</style> +<script type="text/javascript"> +/* +@licstart The following is the entire license notice for the +JavaScript code in this tag. + +Copyright (C) 2012-2013 Free Software Foundation, Inc. + +The JavaScript code in this tag is free software: you can +redistribute it and/or modify it under the terms of the GNU +General Public License (GNU GPL) as published by the Free Software +Foundation, either version 3 of the License, or (at your option) +any later version. The code is distributed WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + +As additional permission under GNU GPL version 3 section 7, you +may distribute non-source (e.g., minimized or compacted) forms of +that code without the copy of the GNU GPL normally required by +section 4, provided you include this license notice and a URL +through which recipients can access the Corresponding Source. + + +@licend The above is the entire license notice +for the JavaScript code in this tag. +*/ +<!--/*--><![CDATA[/*><!--*/ + function CodeHighlightOn(elem, id) + { + var target = document.getElementById(id); + if(null != target) { + elem.cacheClassElem = elem.className; + elem.cacheClassTarget = target.className; + target.className = "code-highlighted"; + elem.className = "code-highlighted"; + } + } + function CodeHighlightOff(elem, id) + { + var target = document.getElementById(id); + if(elem.cacheClassElem) + elem.className = elem.cacheClassElem; + if(elem.cacheClassTarget) + target.className = elem.cacheClassTarget; + } +/*]]>*///--> +</script> +</head> +<body> +<div id="content"> +<h1 class="title">Test Suite</h1> +<p> +The wisp test-suite consists of a large number of wisp-snippets and the corresponding scheme-code. +</p> + +<p> +A wisp-implementation may call itself compliant with the wisp test-suite if the code tree parsed from the wisp file is the same as a code tree parsed from the equivalent Scheme file. +</p> + +<p> +A wisp-implementation may call itself a compliant wisp pre-processor if it successfully converts each wisp-snippet into the corresponging scheme-snippet. Blank lines at the end of the file and non-functional white-space in the produced scheme-file do not matter for this purpose. +</p> + +<p> +This test-suite is also available in the <a href="http://draketo.de/proj/wisp">wisp repository</a> along with a script-runner (runtests.sh) which tests the reference wisp-implementation with GNU Guile against this testsuite.<sup><a id="fnr.1" name="fnr.1" class="footref" href="#fn.1">1</a></sup> +</p> + +<div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1"><span class="section-number-2">1</span> tests/syntax-underscore.w</h2> +<div class="outline-text-2" id="text-1"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">a</span> b c +<span style="color: #0000ff;">_ d</span> e +<span style="color: #0000ff;">___ f</span> +<span style="color: #0000ff;">___</span> g h +<span style="color: #0000ff;">__</span><span style="color: #a020f0;"> . </span>i + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">_</span> +<span style="color: #0000ff;">_ display</span> <span style="color: #8b2252;">"hello\n"</span> + +<span style="color: #0000ff;">\_</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2"><span class="section-number-2">2</span> tests/syntax-underscore.scm</h2> +<div class="outline-text-2" id="text-2"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">a</span> b c) + (d e + (f) + (g h) + i)) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">_</span>) + (display <span style="color: #8b2252;">"hello\n"</span>)) + +(_) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3"><span class="section-number-2">3</span> tests/syntax-strings-parens.w</h2> +<div class="outline-text-2" id="text-3"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">; </span><span style="color: #b22222;">Test linebreaks in strings and brackets</span> + +<span style="color: #a020f0;">. </span><span style="color: #8b2252;">"flubbub</span> + +<span style="color: #8b2252;">flabbab"</span> + +hrug (<span style="color: #0000ff;">nadda</span> +<span style="color: #0000ff;">madda</span> gadda <span style="color: #8b2252;">"shoktom</span> +<span style="color: #8b2252;"> mee"</span> <span style="color: #8b2252;">" sep </span> +<span style="color: #8b2252;">ka"</span> +<span style="color: #0000ff;"> hadda)</span> +<span style="color: #0000ff;"> gom</span> + +<span style="color: #0000ff;">flu</span> + +<span style="color: #0000ff;">sum</span> [foo +<span style="color: #0000ff;">bar]</span> barz {1 + [* <span style="color: #008b8b;">2</span> <span style="color: #008b8b;">2</span>]} + +<span style="color: #0000ff;">mara</span> { +<span style="color: #0000ff;">li</span> +<span style="color: #0000ff;">+</span> +<span style="color: #0000ff;">lo</span> - (<span style="color: #0000ff;">mabba</span>) +<span style="color: #0000ff;">}</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-4" class="outline-2"> +<h2 id="sec-4"><span class="section-number-2">4</span> tests/syntax-strings-parens.scm</h2> +<div class="outline-text-2" id="text-4"> +<div class="org-src-container"> + +<pre class="src src-scheme"><span style="color: #b22222;">; </span><span style="color: #b22222;">Test linebreaks in strings and brackets</span> + +<span style="color: #8b2252;">"flubbub</span> + +<span style="color: #8b2252;">flabbab"</span> + +(hrug (nadda +madda gadda <span style="color: #8b2252;">"shoktom</span> +<span style="color: #8b2252;"> mee"</span> <span style="color: #8b2252;">" sep </span> +<span style="color: #8b2252;">ka"</span> + hadda) + (gom)) + +(flu) + +(sum [foo +bar] barz {1 + [* 2 2]}) + +(mara { +li ++ +lo - (mabba) +}) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-5" class="outline-2"> +<h2 id="sec-5"><span class="section-number-2">5</span> tests/syntax-indent.w</h2> +<div class="outline-text-2" id="text-5"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">define</span> +<span style="color: #0000ff;"> hello</span> who +<span style="color: #0000ff;"> format</span> <span style="color: #008b8b;">#t</span> <span style="color: #8b2252;">"Hello ~A\n"</span> who + +<span style="color: #483d8b;">define</span> + <span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> :</span> +<span style="color: #0000ff;"> a</span> <span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> b</span> <span style="color: #008b8b;">2</span> +<span style="color: #0000ff;"> c</span> <span style="color: #008b8b;">3</span> +<span style="color: #0000ff;"> format</span> <span style="color: #008b8b;">#t</span> <span style="color: #8b2252;">"a: ~A, b: ~A, c: ~A"</span> +<span style="color: #0000ff;"> +</span> a <span style="color: #008b8b;">2</span> +<span style="color: #a020f0;"> . </span> b c +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-6" class="outline-2"> +<h2 id="sec-6"><span class="section-number-2">6</span> tests/syntax-indent.scm</h2> +<div class="outline-text-2" id="text-6"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">define</span> + (hello who) + (format #t <span style="color: #8b2252;">"Hello ~A\n"</span> who)) + +(<span style="color: #a020f0;">define</span> + (<span style="color: #a020f0;">let</span> + ( + (a 1) + (b 2) + (c 3)) + (format #t <span style="color: #8b2252;">"a: ~A, b: ~A, c: ~A"</span> + (+ a 2) + b c))) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-7" class="outline-2"> +<h2 id="sec-7"><span class="section-number-2">7</span> tests/syntax-empty.w</h2> +<div class="outline-text-2" id="text-7"> +<div class="org-src-container"> + +<pre class="src src-wisp"></pre> +</div> +</div> +</div> +<div id="outline-container-sec-8" class="outline-2"> +<h2 id="sec-8"><span class="section-number-2">8</span> tests/syntax-empty.scm</h2> +<div class="outline-text-2" id="text-8"> +<div class="org-src-container"> + +<pre class="src src-scheme"></pre> +</div> +</div> +</div> +<div id="outline-container-sec-9" class="outline-2"> +<h2 id="sec-9"><span class="section-number-2">9</span> tests/syntax-dot.w</h2> +<div class="outline-text-2" id="text-9"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">foo</span> +<span style="color: #a020f0;"> . </span><span style="color: #8b2252;">"bar"</span> + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">bar</span> +<span style="color: #0000ff;"> '</span> <span style="color: #008b8b;">1</span> +<span style="color: #a020f0;"> . . </span><span style="color: #008b8b;">2</span> <span style="color: #b22222;">; </span><span style="color: #b22222;">pair</span> + +<span style="color: #0000ff;">display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">foo</span> +<span style="color: #0000ff;">newline</span> +<span style="color: #0000ff;">display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">bar</span> +<span style="color: #0000ff;">newline</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-10" class="outline-2"> +<h2 id="sec-10"><span class="section-number-2">10</span> tests/syntax-dot.scm</h2> +<div class="outline-text-2" id="text-10"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">foo</span>) + <span style="color: #8b2252;">"bar"</span>) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">bar</span>) + '(1 + . 2 ))<span style="color: #b22222;">; </span><span style="color: #b22222;">pair</span> + +(display (foo)) +(newline) +(display (bar)) +(newline) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-11" class="outline-2"> +<h2 id="sec-11"><span class="section-number-2">11</span> tests/syntax-colon.w</h2> +<div class="outline-text-2" id="text-11"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> :</span> +<span style="color: #0000ff;"> a</span> <span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> b</span> <span style="color: #008b8b;">2</span> + <span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> :</span> +<span style="color: #a020f0;"> :</span> +<span style="color: #a020f0;"> . </span>c <span style="color: #008b8b;">3</span> +<span style="color: #0000ff;"> format</span> <span style="color: #008b8b;">#t</span> <span style="color: #8b2252;">"a: ~A, b: ~A, c: ~A"</span> +<span style="color: #a020f0;"> . </span> a b c + +<span style="color: #a020f0;">: </span>a + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">hello</span> +<span style="color: #0000ff;"> display</span> <span style="color: #8b2252;">"hello\n"</span> + +<span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">a</span> <span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> b</span> <span style="color: #008b8b;">2</span> +<span style="color: #0000ff;"> format</span> <span style="color: #008b8b;">#t</span> <span style="color: #8b2252;">"a: ~A, b: ~A"</span> +<span style="color: #a020f0;"> . </span> a b + +<span style="color: #483d8b;">let</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">:</span> a ' : + +<span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> : </span> <span style="color: #b22222;">; </span><span style="color: #b22222;">foo</span> +<span style="color: #0000ff;"> a</span> + ' + +<span style="color: #a020f0;">:</span> + a + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">\:</span> +<span style="color: #0000ff;"> hello</span> + +<span style="color: #0000ff;">\:</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-12" class="outline-2"> +<h2 id="sec-12"><span class="section-number-2">12</span> tests/syntax-colon.scm</h2> +<div class="outline-text-2" id="text-12"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">let</span> + ( + (a 1) + (b 2)) + (<span style="color: #a020f0;">let</span> + ( + ( + c 3)) + (format #t <span style="color: #8b2252;">"a: ~A, b: ~A, c: ~A"</span> + a b c))) + +((a)) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">hello</span>) + (display <span style="color: #8b2252;">"hello\n"</span>)) + +(<span style="color: #a020f0;">let</span> + ((a 1) + (b 2)) + (format #t <span style="color: #8b2252;">"a: ~A, b: ~A"</span> + a b)) + +(<span style="color: #a020f0;">let</span> ((a '()))) + +(<span style="color: #a020f0;">let</span> + ( <span style="color: #b22222;">; </span><span style="color: #b22222;">foo</span> + (a + '()))) + +( + (a)) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">:</span>) + (hello)) + +(:) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-13" class="outline-2"> +<h2 id="sec-13"><span class="section-number-2">13</span> tests/sublist.w</h2> +<div class="outline-text-2" id="text-13"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">; </span><span style="color: #b22222;">sublists allow to start single line function calls with a colon ( : ).</span> + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">a</span> b c + <span style="color: #483d8b;">let</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">:</span> e<span style="color: #a020f0;"> . </span>f +<span style="color: #a020f0;"> . </span>g +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-14" class="outline-2"> +<h2 id="sec-14"><span class="section-number-2">14</span> tests/sublist.scm</h2> +<div class="outline-text-2" id="text-14"> +<div class="org-src-container"> + +<pre class="src src-scheme"><span style="color: #b22222;">; </span><span style="color: #b22222;">sublists allow to start single line function calls with a colon ( : ).</span> + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">a</span> b c) + (<span style="color: #a020f0;">let</span> ((e . f)) + g)) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-15" class="outline-2"> +<h2 id="sec-15"><span class="section-number-2">15</span> tests/hashbang.w</h2> +<div class="outline-text-2" id="text-15"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">#!/usr/bin/wisp.py # !#</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">This tests hashbang lines</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-16" class="outline-2"> +<h2 id="sec-16"><span class="section-number-2">16</span> tests/hashbang.scm</h2> +<div class="outline-text-2" id="text-16"> +<div class="org-src-container"> + +<pre class="src src-scheme">#!/usr/bin/wisp.py # !# +<span style="color: #b22222;">; </span><span style="color: #b22222;">This tests hashbang lines</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-17" class="outline-2"> +<h2 id="sec-17"><span class="section-number-2">17</span> tests/readable-tests.w</h2> +<div class="outline-text-2" id="text-17"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">fibfast</span> n + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;"><</span> n <span style="color: #008b8b;">2</span> +<span style="color: #a020f0;"> . </span>n +<span style="color: #0000ff;"> fibup</span> n <span style="color: #008b8b;">2</span> <span style="color: #008b8b;">1</span> <span style="color: #008b8b;">0</span> + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">fibup</span> maxnum count n-1 n-2 + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">=</span> maxnum count +<span style="color: #0000ff;"> +</span> n-1 n-2 +<span style="color: #0000ff;"> fibup</span> maxnum +<span style="color: #0000ff;"> +</span> count <span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> +</span> n-1 n-2 +<span style="color: #a020f0;"> . </span>n-1 + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span> n + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;"><=</span> n <span style="color: #008b8b;">1</span> +<span style="color: #a020f0;"> . </span><span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> *</span> n +<span style="color: #0000ff;"> factorial</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">-</span> n <span style="color: #008b8b;">1</span> + +<span style="color: #483d8b;">define</span> (<span style="color: #0000ff;">gcd</span> x y) + <span style="color: #483d8b;">if</span> (<span style="color: #0000ff;">=</span> y <span style="color: #008b8b;">0</span>) +<span style="color: #a020f0;"> . </span>x +<span style="color: #0000ff;"> gcd</span> y +<span style="color: #0000ff;"> rem</span> x y + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">add-if-all-numbers</span> lst +<span style="color: #0000ff;"> call/cc</span> +<span style="color: #0000ff;"> lambda</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">exit</span> + <span style="color: #483d8b;">let</span> loop +<span style="color: #a020f0;"> : </span> +<span style="color: #0000ff;"> lst</span> lst +<span style="color: #0000ff;"> sum</span> <span style="color: #008b8b;">0</span> + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">null?</span> lst +<span style="color: #a020f0;"> . </span>sum + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #483d8b;">not</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">number?</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">car</span> lst +<span style="color: #0000ff;"> exit</span> <span style="color: #008b8b;">#f</span> +<span style="color: #0000ff;"> +</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">car</span> lst +<span style="color: #0000ff;"> loop</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">cdr</span> lst +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-18" class="outline-2"> +<h2 id="sec-18"><span class="section-number-2">18</span> tests/readable-tests.scm</h2> +<div class="outline-text-2" id="text-18"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">fibfast</span> n) + (<span style="color: #a020f0;">if</span> (< n 2)) + n + (fibup n 2 1 0 )) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">fibup</span> maxnum count n-1 n-2) + (<span style="color: #a020f0;">if</span> (= maxnum count) + (+ n-1 n-2) + (fibup maxnum + (+ count 1 ) + (+ n-1 n-2 ) + n-1))) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">factorial</span> n) + (<span style="color: #a020f0;">if</span> (<= n 1) + 1 + (* n + (factorial (- n 1))))) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">gcd</span> x y) + (<span style="color: #a020f0;">if</span> (= y 0)) + x + (gcd y + (rem x y))) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">add-if-all-numbers</span> lst) + (<span style="color: #a020f0;">call/cc</span> + (<span style="color: #a020f0;">lambda</span> (exit) + (<span style="color: #a020f0;">let</span> <span style="color: #0000ff;">loop</span> + ( + (lst lst ) + (sum 0)) + (<span style="color: #a020f0;">if</span> (null? lst) + sum + (<span style="color: #a020f0;">if</span> (not (number? (car lst))) + (exit #f) + (+ (car lst) + (loop (cdr lst))))))))) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-19" class="outline-2"> +<h2 id="sec-19"><span class="section-number-2">19</span> tests/quotecolon.w</h2> +<div class="outline-text-2" id="text-19"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">#!/home/arne/wisp/wisp-multiline.sh </span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">!#</span> +<span style="color: #483d8b;">define</span> a <span style="color: #008b8b;">1</span> <span style="color: #b22222;">; </span><span style="color: #b22222;">test whether ' : correctly gets turned into '(</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">and whether brackets in commments are treated correctly.</span> + +<span style="color: #483d8b;">define</span> a '<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">1</span> <span style="color: #008b8b;">2</span> <span style="color: #008b8b;">3</span> + +<span style="color: #483d8b;">define</span> +<span style="color: #0000ff;"> a</span> b +<span style="color: #0000ff;"> c</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-20" class="outline-2"> +<h2 id="sec-20"><span class="section-number-2">20</span> tests/quotecolon.scm</h2> +<div class="outline-text-2" id="text-20"> +<div class="org-src-container"> + +<pre class="src src-scheme">#!/home/arne/wisp/wisp-multiline.sh +<span style="color: #b22222;">; </span><span style="color: #b22222;">!#</span> +(<span style="color: #a020f0;">define</span> <span style="color: #0000ff;">a</span> 1 )<span style="color: #b22222;">; </span><span style="color: #b22222;">test whether ' : correctly gets turned into '(</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">and whether brackets in commments are treated correctly.</span> + +(<span style="color: #a020f0;">define</span> <span style="color: #0000ff;">a</span> '(1 2 3)) + +(<span style="color: #a020f0;">define</span> + (a b) + (c)) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-21" class="outline-2"> +<h2 id="sec-21"><span class="section-number-2">21</span> tests/namedlet.w</h2> +<div class="outline-text-2" id="text-21"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">#!/home/arne/wisp/wisp-multiline.sh </span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">!#</span> +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">hello</span> who +<span style="color: #0000ff;"> display</span> who + +<span style="color: #483d8b;">let</span> hello +<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">who</span> <span style="color: #008b8b;">0</span> + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">=</span> who <span style="color: #008b8b;">5</span> +<span style="color: #0000ff;"> display</span> who +<span style="color: #0000ff;"> hello</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">+</span> <span style="color: #008b8b;">1</span> who +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-22" class="outline-2"> +<h2 id="sec-22"><span class="section-number-2">22</span> tests/namedlet.scm</h2> +<div class="outline-text-2" id="text-22"> +<div class="org-src-container"> + +<pre class="src src-scheme">#!/home/arne/wisp/wisp-multiline.sh +<span style="color: #b22222;">; </span><span style="color: #b22222;">!#</span> +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">hello</span> who) + (display who)) + +(<span style="color: #a020f0;">let</span> <span style="color: #0000ff;">hello</span> + ((who 0)) + (<span style="color: #a020f0;">if</span> (= who 5) + (display who) + (hello (+ 1 who)))) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-23" class="outline-2"> +<h2 id="sec-23"><span class="section-number-2">23</span> tests/flexible-parameter-list.w</h2> +<div class="outline-text-2" id="text-23"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">; </span><span style="color: #b22222;">Test using a . as first parameter on a line by prefixing it with a second .</span> +<span style="color: #483d8b;">define</span> +<span style="color: #0000ff;"> a</span> i +<span style="color: #a020f0;"> . . </span>b +<span style="color: #0000ff;"> unless</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">>=</span> i<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">length</span> b +<span style="color: #0000ff;"> display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">number->string</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">length</span> b +<span style="color: #0000ff;"> display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">list-ref</span> b i +<span style="color: #0000ff;"> newline</span> +<span style="color: #0000ff;"> apply</span> a ( <span style="color: #0000ff;">+</span> i <span style="color: #008b8b;">1</span> ) b + + +<span style="color: #0000ff;">a</span> <span style="color: #008b8b;">0</span> <span style="color: #8b2252;">"123"</span> <span style="color: #8b2252;">"345"</span> <span style="color: #8b2252;">"567"</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-24" class="outline-2"> +<h2 id="sec-24"><span class="section-number-2">24</span> tests/flexible-parameter-list.scm</h2> +<div class="outline-text-2" id="text-24"> +<div class="org-src-container"> + +<pre class="src src-scheme"><span style="color: #b22222;">; </span><span style="color: #b22222;">Test using a . as first parameter on a line by prefixing it with a second .</span> +(<span style="color: #a020f0;">define</span> + (a i + . b) + (unless (>= i (length b)) + (display (number->string (length b ))) + (display (list-ref b i)) + (newline) + (apply a ( + i 1 ) b))) + + +(a 0 <span style="color: #8b2252;">"123"</span> <span style="color: #8b2252;">"345"</span> <span style="color: #8b2252;">"567"</span>) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-25" class="outline-2"> +<h2 id="sec-25"><span class="section-number-2">25</span> tests/factorial.w</h2> +<div class="outline-text-2" id="text-25"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #b22222;">;; </span><span style="color: #b22222;">short version</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">note: once you use one inline colon, all the following forms on that</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">line will get closed at the end of the line</span> + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span> n + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">zero?</span> n +<span style="color: #a020f0;"> . </span><span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> *</span> n<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">-</span> n <span style="color: #008b8b;">1</span> + +<span style="color: #0000ff;">display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span> <span style="color: #008b8b;">5</span> + + +<span style="color: #b22222;">;; </span><span style="color: #b22222;">more vertical space, less colons</span> +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span> n + <span style="color: #483d8b;">if</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">zero?</span> n +<span style="color: #a020f0;"> . </span><span style="color: #008b8b;">1</span> +<span style="color: #0000ff;"> *</span> n +<span style="color: #0000ff;"> factorial</span> +<span style="color: #0000ff;"> -</span> n <span style="color: #008b8b;">1</span> + +<span style="color: #0000ff;">display</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">factorial</span> <span style="color: #008b8b;">5</span> +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-26" class="outline-2"> +<h2 id="sec-26"><span class="section-number-2">26</span> tests/factorial.scm</h2> +<div class="outline-text-2" id="text-26"> +<div class="org-src-container"> + +<pre class="src src-scheme"><span style="color: #b22222;">;; </span><span style="color: #b22222;">short version</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">note: once you use one inline colon, all the following forms on that</span> +<span style="color: #b22222;">; </span><span style="color: #b22222;">line will get closed at the end of the line</span> + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">factorial</span> n) + (<span style="color: #a020f0;">if</span> (zero? n) + 1 + (* n (factorial (- n 1))))) + +(display (factorial 5 )) + + +<span style="color: #b22222;">;; </span><span style="color: #b22222;">more vertical space, less colons</span> +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">factorial</span> n) + (<span style="color: #a020f0;">if</span> (zero? n) + 1 + (* n + (factorial + (- n 1))))) + +(display (factorial 5 )) +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-27" class="outline-2"> +<h2 id="sec-27"><span class="section-number-2">27</span> tests/example.w</h2> +<div class="outline-text-2" id="text-27"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #483d8b;">define</span> (<span style="color: #0000ff;">a</span> b c) + <span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> : </span> +<span style="color: #0000ff;"> d</span> <span style="color: #8b2252;">"i am a string</span> +<span style="color: #8b2252;">do not break me!"</span> +<span style="color: #a020f0;"> : </span> + <span style="color: #b22222;">; </span><span style="color: #b22222;">comment: 0</span> + f +<span style="color: #b22222;">; </span><span style="color: #b22222;">comment : 1</span> +<span style="color: #0000ff;"> `</span> g <span style="color: #b22222;">; </span><span style="color: #b22222;">comment " : " 2</span> +<span style="color: #a020f0;"> : </span> +<span style="color: #0000ff;"> h</span> (<span style="color: #0000ff;">I</span> am in brackets: +<span style="color: #0000ff;"> do</span> <span style="color: #483d8b;">not</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">change</span> <span style="color: #8b2252;">"me"</span>) +<span style="color: #a020f0;"> . </span>i +<span style="color: #0000ff;"> ,</span><span style="color: #008b8b;"> 'j</span> k + +<span style="color: #a020f0;"> . </span>l + +<span style="color: #b22222;">; </span><span style="color: #b22222;">comment</span> + +<span style="color: #0000ff;"> a</span> c + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">b</span> :n o +<span style="color: #a020f0;"> . </span><span style="color: #8b2252;">"second defun : with a docstring!"</span> +<span style="color: #0000ff;"> message</span> <span style="color: #8b2252;">"I am here"</span> +<span style="color: #a020f0;"> . </span>t + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">c</span> e f +<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">g</span> +<span style="color: #a020f0;"> :</span> +<span style="color: #0000ff;"> h</span> +<span style="color: #0000ff;"> i</span> +<span style="color: #0000ff;"> j</span> +<span style="color: #0000ff;"> '</span> : +<span style="color: #0000ff;"> k</span> +<span style="color: #a020f0;"> . </span>l +<span style="color: #a020f0;"> . </span>: <span style="color: #0000ff;">m</span> + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">_</span> \: +<span style="color: #0000ff;">__</span> +<span style="color: #0000ff;">__</span><span style="color: #a020f0;"> . </span>\: + +<span style="color: #0000ff;">\_</span> b + +<span style="color: #483d8b;">define</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">d</span> + <span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">a</span> b +<span style="color: #0000ff;"> c</span> d + +<span style="color: #0000ff;">a</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">:</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">c</span> + +<span style="color: #483d8b;">let</span> +<span style="color: #a020f0;"> : </span><span style="color: #0000ff;">a</span> b + c + +<span style="color: #483d8b;">let</span><span style="color: #a020f0;"> : </span><span style="color: #0000ff;">:</span> a b + +<span style="color: #a020f0;">. </span>a +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-28" class="outline-2"> +<h2 id="sec-28"><span class="section-number-2">28</span> tests/example.scm</h2> +<div class="outline-text-2" id="text-28"> +<div class="org-src-container"> + +<pre class="src src-scheme">(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">a</span> b c) + (<span style="color: #a020f0;">let</span> + ( + (d <span style="color: #8b2252;">"i am a string</span> +<span style="color: #8b2252;">do not break me!"</span>) + ( + <span style="color: #b22222;">; </span><span style="color: #b22222;">comment: 0</span> + (f) +<span style="color: #b22222;">; </span><span style="color: #b22222;">comment : 1</span> + `(g ))<span style="color: #b22222;">; </span><span style="color: #b22222;">comment " : " 2</span> + ( + (h (I am in brackets: + do not : change <span style="color: #8b2252;">"me"</span>)) + i))) + ,('j k) + + l + +<span style="color: #b22222;">; </span><span style="color: #b22222;">comment</span> + + (a c)) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">b</span> <span style="color: #483d8b;">:n</span> o) + <span style="color: #8b2252;">"second defun : with a docstring!"</span> + (message <span style="color: #8b2252;">"I am here"</span>) + t) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">c</span> e f) + ((g)) + ( + (h + (i)) + (j)) + '(()) + (k) + l + (m)) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">_</span> :) + + :) + +(_ b) + +(<span style="color: #a020f0;">define</span> (<span style="color: #0000ff;">d</span>) + (<span style="color: #a020f0;">let</span> + ((a b) + (c d)))) + +(a (((c)))) + +(<span style="color: #a020f0;">let</span> + ((a b) + (c))) + +(<span style="color: #a020f0;">let</span> ((a b))) + +a +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-29" class="outline-2"> +<h2 id="sec-29"><span class="section-number-2">29</span> tests/continuation.w</h2> +<div class="outline-text-2" id="text-29"> +<div class="org-src-container"> + +<pre class="src src-wisp"><span style="color: #0000ff;">a</span> b c d e +<span style="color: #a020f0;"> . </span>f g h +<span style="color: #a020f0;"> . </span>i j k + +<span style="color: #0000ff;">concat</span> <span style="color: #8b2252;">"I want "</span> +<span style="color: #0000ff;"> getwish</span> from me +<span style="color: #a020f0;"> . </span><span style="color: #8b2252;">" - "</span> username +</pre> +</div> +</div> +</div> +<div id="outline-container-sec-30" class="outline-2"> +<h2 id="sec-30"><span class="section-number-2">30</span> tests/continuation.scm</h2> +<div class="outline-text-2" id="text-30"> +<div class="org-src-container"> + +<pre class="src src-scheme">(a b c d e + f g h + i j k) + +(concat <span style="color: #8b2252;">"I want "</span> + (getwish from me) + <span style="color: #8b2252;">" - "</span> username) +</pre> +</div> +</div> +</div> +<div id="footnotes"> +<h2 class="footnotes">Footnotes: </h2> +<div id="text-footnotes"> + +<div class="footdef"><sup><a id="fn.1" name="fn.1" class="footnum" href="#fnr.1">1</a></sup> <p> +To run the tests in the wisp testsuite with a separately built GNU Guile, you can use any given guile interpreter by adjusting the following command: <code>PATH=~/guile-2.0.11/meta:${PATH} ./runtests.sh</code> +</p></div> + + +</div> +</div></div> +<div id="postamble" class="status"> +<p class="author">Author: Arne Babenhauserheide</p> +<p class="date">Created: 2014-12-23 Di 22:50</p> +<p class="creator"><a href="http://www.gnu.org/software/emacs/">Emacs</a> 24.3.1 (<a href="http://orgmode.org">Org</a> mode 8.2.6)</p> +<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p> +</div> +</body> +</html> diff --git a/docs/srfi-119/wisp-bootstrap.py b/docs/srfi-119/wisp-bootstrap.py new file mode 100755 --- /dev/null +++ b/docs/srfi-119/wisp-bootstrap.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 +# wisp.py --- Whitespace-to-Lisp preprocessor. + +# Copyright (C) 2013 Arne Babenhauserheide <arne_bab@web.de> + +# Author: Arne Babenhauserheide <arne_bab@web.de> + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +"""whitespace to lisp converter. + +Essentially it just adds brackets for indentation to allow writing +lisp with indentation senstitive syntax. + +Currently it is written in Python, because I like Python as language, +but crave the power of lisp. +""" + +def replaceinwisp(code, string, replacement): + """Replace the given string with the replacement, but only in + indentation sensitive parts of the code. + + Essentially replace everywhere except in brackets or strings. + + :param code: Arbitrary wisp code to process. + :param string: A string to replace. + :param replacement: The replacement string. + + :return: (code, count): The new code and a count of replacements. + """ + count = 0 + instring = False + incomment = False + inbrackets = 0 + strlen = len(string) + for n in range(len(code) - strlen): + i = code[n] + # comments start with a ; - but only in regular wisp code or in brackets. + if not incomment and not instring and i == ";" and not code[n-2:n] == "#\\": + incomment = not incomment + # a linebreak ends the comment + if incomment: + if i == "\n": + incomment = not incomment + # all processing stops in comments + continue + # FIXME: This still breaks at "\\" + if i == '"' and (not code[n-1:n] == "\\" or (code[n-2:n] == "\\\\" and not code[n-3:n] == "\\\\\\")): + instring = not instring + # all processing stops in strings + if instring: + continue + if i == "(" and not code[n-2:n] == "#\\": + inbrackets += 1 + elif i == ")" and not code[n-2:n] == "#\\": + inbrackets -= 1 + # all processing stops in brackets + if inbrackets: + continue + # here we do the actual replacing + if code[n:n+strlen] == string: + count += 1 + code = code[:n] + replacement + code[n+strlen:] + return code, count + + +class UndefinedIndentationLevel(IndentationError): + """Unindent does not match any outer indentation level.""" + + +class Line: + def __init__(self, line): + """Parse one line in which linebreaks within strings and + brackets already got replaced by a temporary placeholder.""" + # Visible indentation: If the line starts with any number of + # _, followed by a space, treat those _ as spaces. + if line.startswith("_"): + for i,letter in enumerate(line): + if letter != "_": + # rewind the index to the last underscore + i -= 1 + break + # increment the index to the first + # non-underscore. Required to treat end of string and end + # of underscores the same + i += 1 + # here line[i-1] is _. Check if line[i+1] is a space or if + # the line ends after the last underscore + if line[i:i+1] == " " or not line[i:]: + line = (i)*" " + line[i:] + # \_ escapes the underscore at the beginning of a line, so you + # can use identifiers which only consist of underscores. + elif line.startswith("\_"): + line = "_" + line[2:] + + #: prefix to go around the outer bracket: '(, ,( or `( + self.prefix = "" + # check if this is a continuation of the parent line + self.continues = line.lstrip().startswith(". ") + if self.continues: + self.content = line.lstrip()[2:].lstrip() + else: + self.content = line.lstrip() + # check if the line is prefixed with any combination of ' ` and , + if not self.continues: + while (self.content.startswith("' ") or + self.content.startswith(", ") or + self.content.startswith("` ") or + self.content.startswith("#, ") or # scheme macros + self.content.startswith("#` ") or + self.content.startswith("#' ") or + self.content.startswith("#,@, ")): + self.prefix += self.content.split(" ")[0] + self.content = self.content[2:] + + # care for lines starting with ": " (a colon followed by a space and more chars) + self.indent = len(line) - len(line.lstrip()) + if self.content.startswith(": ") and self.content[2:].lstrip(): + # just add a space in front of the " : ". Then it will be + # captured by as inline : later. With this, the following are almost equal: + # ": a b" and + # ": + # a b" + + # The only difference between both is that ": a b" cannot + # have siblings in subsequent lines: The function call + # ends on this line. + self.content = " " + self.content + + if self.content.strip() == ":" or self.content.strip() == "": + self.content = "" + + # split a possible comment + self.comment = "" + instring = False + for n, i in enumerate(self.content): + if i == '"' and not self.content[n-1:n] == "\\": + instring = not instring + if not instring and i == ";" and not self.content[n-2:n] == "#\\": + self.comment = self.content[n+1:] + self.content = self.content[:n] + break + + # treat inline " : " as opening a bracket which gets closed at + # the end of the line if the : is at the end of the line, add + # () to avoid being dependent on whitespace at the end of the + # line. + bracketstoclose = 0 + instring = False + inbrackets = 0 + # go backwards through the content to be able to leave out the + # space after a colon without breaking later colons. + for n, i in reversed(list(enumerate(self.content))): + if i == '"' and not self.content[n-1:n] == "\\": + instring = not instring + if not instring and i == ")" and not self.content[n-2:n] == "#\\": + inbrackets += 1 + elif not instring and i == "(" and not self.content[n-2:n] == "#\\": + inbrackets -= 1 + if (not instring and + not inbrackets and + i == ":" and # optimization to be able to avoid string + # slicing when there can be no hit. + n # avoid content[-1:2] (which is an unnecessary + # slicing, since it is always "" + ): + if self.content[n-1:n+2] == " : " or self.content[n-1:] == " :": + bracketstoclose += 1 + # treat ' : as '( + if self.content[n-3:n+1] == " ' :": + self.content = self.content[:n-2] + "'(" + self.content[n+2:] + else: + # we have to keep the space after the colon (" : " + # → " ( "), otherwise we cannot use two + # consecutive colons (" : : ") which would be surprising. + self.content = self.content[:n] + "(" + self.content[n+2:] + + # after the full line processing, replace " \\: " "\n\\: " and + # " \\:\n" (inside line, start of a line, end of a line) by " + # : ", "\n: " and " :\n" respectively to allow escaping : as + # expression. + self.content, count = replaceinwisp(self.content, " \\: ", " : ") + if self.content.startswith("\\: "): + self.content = ": " + self.content[3:] + elif self.content.endswith(" \\:"): + self.content = self.content[:-3] + " :" + elif self.content == "\\:": # empty function or variable call + self.content = ":" + + # add closing brackets + self.content += ")" * bracketstoclose + + #: Is the line effectively empty? + self.empty = False + onlycomment = (line.split(";")[1:] and # there is content after the comment sign + not line.split(";")[0].count('"') % 2 and # but the first comment sign is not in a string + not line.split(";")[0].strip()) # there is no content before the comment sign + if line.strip() == "" or onlycomment: + self.empty = True + + +def nostringbreaks(code): + """remove linebreaks inside strings (will be readded at the end)""" + instring = False + nostringbreaks = [] + for n, char in enumerate(code): + if char == '"' and not code[n-1:n] == "\\": + instring = not instring + if instring and char == "\n": + nostringbreaks.append("\\LINEBREAK") + else: + nostringbreaks.append(char) + return "".join(nostringbreaks) + + +def nobracketbreaks(code): + """remove linebreaks inside brackets (will be readded at the end).""" + instring = False + incomment = False + inbracket = 0 + nostringbreaks = [] + for n, char in enumerate(code): + # comments start with a ; - but only in regular wisp code or in brackets. + if not incomment and not instring and char == ";" and not code[n-2:n] == "#\\": + incomment = not incomment + # a linebreak ends the comment + if incomment: + if char == "\n": + incomment = not incomment + # all processing stops in comments + nostringbreaks.append(char) + continue + if char == '"' and not code[n-1:n] == "\\": + instring = not instring + if char == '(' and not instring and not code[n-2:n] == "#\\": + inbracket += 1 + elif char == ')' and not instring and not code[n-2:n] == "#\\": + inbracket -= 1 + if inbracket and char == "\n": + nostringbreaks.append("\\LINEBREAK") + else: + nostringbreaks.append(char) + return "".join(nostringbreaks) + + +def processlines(lines, prev, codestartindex, levels, lisplines, emptylines): + """Process all lines after the first.""" + # process further lines: adjust the content of the current line, but only append + for n, line in enumerate(lines[codestartindex+1:]): + n += codestartindex + 2 + # ignore empty lines and comment-only lines + if line.empty: + # simply keep empty lines and ignore their indentation + # readd a possible comment + if line.comment: + line.content += ";" + line.comment + # keep the line, do not track it in any way + emptylines.append(line.indent * " " + line.content) + continue + + # care for leading brackets + # continuing lines do not get a leading bracket. + if not line.continues: + line.content = line.prefix + "(" + line.content + + # care for closing brackets + # rising indent: sibling function or variable + if line.indent > prev.indent: + levels.append(line.indent) + lisplines.append(prev.indent * " " + prev.content) + # same indent: neighbour function of variable: close the previour lines bracket + if line.indent == prev.indent: + if not prev.continues: + lisplines.append(prev.indent * " " + prev.content + ")") + else: + lisplines.append(prev.indent * " " + prev.content) + # lower indent: parent funtion or variable. Find the number of brackets to close + if prev.indent > line.indent: + bracketstoclose = len([level for level in levels if level >= line.indent]) + if not line.indent in levels[-bracketstoclose:]: + raise UndefinedIndentationLevel("Unindent of line " + str(n) + " does not match any outer indentation level.\n" + line.indent*" " + "|\n" + line.indent*" " + "v\n" + line.indent*" " + line.content) + levels = levels[:-bracketstoclose + 1] + if prev.continues: + bracketstoclose -= 1 + lisplines.append(prev.indent * " " + prev.content + ")" * bracketstoclose) + + # add a possible comment + if prev.comment: + lisplines[-1] += ";" + prev.comment + + prev = line + lisplines.extend(emptylines) + emptylines = [] + + # postprocessing the loop. + if prev and prev.continues: + levels.pop() + if prev: + lisplines.append(prev.indent * " " + prev.content + ")" * (len(levels))) + lisplines.extend(emptylines) + return prev, lisplines, emptylines, levels + + +def wisp2lisp(code): + """Turn wisp code to lisp code.""" + # TODO: extract the shebang before preprocessing the code. + + # if the code is empty, just return an empty string + if not code: + return code + + # first get rid of linebreaks in strings + code = nostringbreaks(code) + # and of linebreaks inside brackets + code = nobracketbreaks(code) + + # now read the indentation + lines = [] + for line in code.splitlines(): + lines.append(Line(line)) + + # finally emit matching lisp code + # write into the lisp lines with a delay of 1 line + lisplines = [] + # effectively empty lines to be appended + emptylines = [] + levels = [0] + prev = lines[0] + #: The index of the first code line + codestartindex = 0 + + # process the first lines in the file. + + # Shebang lines must be used verbatim + if not prev.indent and prev.content.startswith("#!"): + codestartindex += 1 + if prev.comment: + prev.content += ";" + prev.comment + lisplines.append(prev.content) + if codestartindex < len(lines): + prev = lines[codestartindex] + else: + prev = None + + # initial comment lines need special treatment to avoid starting + # them with () (implementation detail) + while prev and prev.empty: + codestartindex += 1 + if prev.comment: + prev.content += ";" + prev.comment + lisplines.append(prev.indent * " " + prev.content) + if codestartindex < len(lines): + prev = lines[codestartindex] + else: + prev = None + if prev and not prev.continues: + prev.content = prev.prefix + "(" + prev.content + + # run the linereader loop. This does the main work - aside from + # the preprocessing in the Line class. + if prev: + prev, lisplines, emptylines, levels = processlines(lines, prev, codestartindex, + levels, lisplines, emptylines) + + # postprocessing the resulting lisplines: the loop is not perfect… + # get rid of brackets around empty lines + for n,i in enumerate(lisplines): + if i.lstrip() == "()": + lisplines[n] = "" + + return "\n".join(lisplines).replace("\\LINEBREAK", "\n") + + +if __name__ == "__main__": + import sys + import optparse + parser = optparse.OptionParser("[-o outfile] [file | -]") + parser.add_option("-o", "--output", default="") + opts, args = parser.parse_args() + if args: + sourcefile = args[0] + else: + sourcefile = "example.w" + # accept stdin as input + if sourcefile == "-": + wisp = sys.stdin.read() + else: + with open(sourcefile) as f: + wisp = f.read() + if opts.output: + with open(opts.output, "w") as f: + f.write(wisp2lisp(wisp) + "\n") + else: + print(wisp2lisp(wisp)) diff --git a/docs/srfi-119/wisp-guile.scm b/docs/srfi-119/wisp-guile.scm new file mode 100644 --- /dev/null +++ b/docs/srfi-119/wisp-guile.scm @@ -0,0 +1,835 @@ +#!/home/arne/wisp/wisp-multiline.sh +; !# + +;; This file might need to be licensed permissively for inclusion in +;; an SRFI. Only change it, if you agree to this possible relicensing +;; of your contribution to this file. I will not accept changes here +;; which do not allow that. + +; we need to be able to replace end-of-line characters in brackets and strings + +;; TODO: Check whether I can offload the string processing to the +;; read-function. That’s a source of endless complications. Required: +;; A kind of unrolling step which appends the string-representation of +;; the read strings back into the code. I would have to process a list +;; of strings instead of one big string. Or rather, each line would be +;; a list of strings. + +;; bootstrap via python3 wisp.py wisp-guile.w > 1 && guile 1 wisp-guile.w > 2 && guile 2 wisp-guile.w > 3 && diff 2 3 +;; +;; -Author: Arne Babenhauserheide + +;; Copyright (C) Arne Babenhauserheide (2013--2015). All Rights Reserved. + +;; Permission is hereby granted, free of charge, to any person +;; obtaining a copy of this software and associated documentation +;; files (the "Software"), to deal in the Software without +;; restriction, including without limitation the rights to use, copy, +;; modify, merge, publish, distribute, sublicense, and/or sell copies +;; of the Software, and to permit persons to whom the Software is +;; furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be +;; included in all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. + + +(define-module (wisp) + #:export (wisp2lisp wisp-chunkreader)) + +(use-modules + ((srfi srfi-1)) + ((ice-9 regex))) + +(define (endsinunevenbackslashes text ); comment + (if (= 0 (string-length text)) + #f + (let counter + ((last (string-take-right text 1)) + (rest (string-append " " (string-drop-right text 1))) + (count 0)) + (cond + ((= 0 (string-length rest )); end clause: read all + (odd? count)) + ; end clause: no \ + ((not (equal? last (string #\\))) + (odd? count)) + (else + (counter (string-take-right rest 1) (string-drop-right rest 1) (+ 1 count))))))) + + +(define (nostringandbracketbreaks inport) + "Replace all linebreaks inside strings and brackets with placeholders." + (let ((expressions (list (nostringandbracketbreaksreader inport)))) + (while (not (eof-object? (peek-char inport))) + (set! expressions (append expressions (list (nostringandbracketbreaksreader inport))))) + (string-join expressions "\n"))) + + +(define (nostringandbracketbreaksreader inport) + "Read one wisp-expression from the inport. +Ends with three consecutive linebreaks or eof." + ; Replace end of line characters in brackets and strings + ; FIXME: Breaks if the string is shorter than 2 chars + ; FIXME: Breaks if the text begins with a comment. + (let* + ((lastchar (read-char inport)) + (nextchar (read-char inport)) + (text (if (eof-object? lastchar) "" (string lastchar))) + (incomment #f) + (incommentfirstchar #f ); first char of a comment + (instring #f) + (inbrackets 0) + (incharform 0 )); #\<something> + (while + (not + (or (eof-object? nextchar) + (and + (or (char=? nextchar #\newline ) (char=? nextchar #\return ) ) + (or (char=? lastchar #\newline ) (char=? lastchar #\return ) ) + (string-suffix? "\n\n" text )))); text includes lastchar + ; incommentfirstchar is only valid for exactly one char + (when incommentfirstchar (set! incommentfirstchar #f )) + ; but add incommentfirstchar if we just started the text + (when (equal? text ";" ); initial comment + (set! incommentfirstchar #f) + (set! incomment #t) + (set! text (string-append text "\\REALCOMMENTHERE"))) + ; already started char forms win over everything, so process them first. + ; already started means: after the #\ + ; FIXME: Fails to capture #t and #f which can kill line splitting if it happens inside brackets + (when (= incharform 1) + (when (not (and (char=? lastchar #\# ) (or (char=? #\f nextchar) (char=? #\t nextchar)))) + ; format #t "1: set incharform 0: lastchar ~a nextchar ~a instring ~a incomment ~a incharform ~a" lastchar nextchar instring incomment incharform + ; newline + (set! incharform 0))) + + (when (>= incharform 2) + (if (or (char=? nextchar #\space) (char=? + nextchar #\newline ) (char=? nextchar #\return ) ) + (begin + ; format #t "2: set incharform 0: lastchar ~a nextchar ~a instring ~a incomment ~a incharform ~a" lastchar nextchar instring incomment incharform + ; newline + (set! incharform 0)) + ; else + (set! incharform (+ incharform 1)))) + ; check if we switch to a string: last char is space, linebreak or in a string, not in a charform, not in a comment + (when + (and + (char=? nextchar #\") + (not incomment) + (< incharform 1) + (or + (and + instring ; when I’m in a string, I can get out + (or + (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)))) + (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 ) + (and (not instring ); outside of strings, brackets are pseudo-whitespace, too + (or + (char=? lastchar #\( ) + (char=? lastchar #\)) + (char=? lastchar #\[ ) + (char=? lastchar #\]) + ; TODO: Only match for braces {} if curly infix is enabled + (char=? lastchar #\{ ) + (char=? lastchar #\}))))) + (set! instring (not instring))) + ; check if we switch to a comment + (when + (and + ; FIXME: this should be + ; char=? nextchar #\; + (equal? ";" (string nextchar)) + (not incomment) + (not instring) + (< incharform 2)) + (set! incomment #t) + (set! incommentfirstchar #t) + ; this also closes any potential charform + (set! incharform 0)) + (when + (and incomment + (or + (char=? nextchar #\return) + (char=? nextchar #\newline))) + (set! incomment #f)) + + ; check for the beginning of a charform + (when + (and + (not instring) + (not incomment) + (char=? lastchar #\space) + (char=? nextchar #\#)) + (set! incharform 1)) + ; check whether a charform is continued + (when + (and + (= incharform 1) + (char=? lastchar #\#) + (char=? nextchar #\\)) + (set! incharform 2)) + + ; check for brackets + ; FIXME: This only fixes a single linebreak inside parens, but if a second occurs on the same line it breaks. I do not know why. Maybe something with having lastchar as linebreak. + (when (not (or instring incomment)) + (when + (and + (not (string-suffix? text "#")) + (not (char=? #\\ lastchar)) + (not (endsinunevenbackslashes (string-drop-right text (min 1 (string-length text)))))) + ; TODO: Only match for braces {} if curly infix is enabled + ; FIXME: Catch wrong ordering of parens/brackets/braces like ({)} + (when (or (equal? "[" (string nextchar)) (equal? "(" (string nextchar)) (equal? "{" (string nextchar))) + (set! inbrackets (+ inbrackets 1))) + (when (or (equal? "}" (string nextchar)) (equal? ")" (string nextchar)) (equal? "]" (string nextchar))) + (set! inbrackets (- inbrackets 1))))) + (if (or instring (> inbrackets 0)) + (if (char=? nextchar #\newline) + ; we have to actually construct the escape + ; sequence here to be able to parse ourselves. + (set! text (string-append text (string-append "\\LINE_" "BREAK_N"))) + (if (char=? nextchar #\return) + (set! text (string-append text (string-append "\\LINE_" "BREAK_R"))) + ; else + (set! text (string-append text (string nextchar))))) + ; mark the start of a comment, so we do not have to + ; repeat the string matching in later code. We include + ; the comment character! + ; not (instring or inbrackets) = neither instring nor inbrackets + (if incommentfirstchar + (set! text (string-append text ( string nextchar ) "\\REALCOMMENTHERE")) + ; when not in brackets or string or starting a + ; comment: just append the char + (set! text (string-append text (string nextchar))))) + + (set! lastchar nextchar) + (set! nextchar (read-char inport))) + ; return the text + text)) + + +; As next part we have split a text into a list of lines which we can process one by one. +(define (splitlines inport ) + (let + ((lines '()) + (nextchar (read-char inport)) + (nextline "")) + (while (not (eof-object? nextchar)) + (if (not (or (char=? nextchar #\return ) (char=? nextchar #\newline ))) + (set! nextline (string-append nextline (string nextchar))) + (begin + (set! lines (append lines (list nextline))) + (set! nextline ""))) + (set! nextchar (read-char inport))) + (append lines (list nextline)))) + +(define (line-indent line) + (list-ref line 0)) + +(define (line-content line) + (list-ref line 1)) + +(define (line-comment line) + (list-ref line 2)) + +(define (line-continues? line) + "Check whether the line is a continuation of a previous line (should not start with a bracket)." + (if (equal? #f (line-content line)) + #f ; this is the EOF line. It does not continue (to ensure that the last brackets get closed) + (string-prefix? ". " (line-content line)))) + +(define (line-empty-code? line) + "Check whether the code-part of the line is empty: contains only whitespace and/or comment." + (equal? "" (line-content line))) + +(define (line-only-colon? line) + "Check whether the line content consists only of a colon and whitespace." + (equal? ":" (string-trim-right (line-content line)))) + +(define (line-only-prefix? line prefix) + "Check whether the line content consists only of a given prefix and whitespace." + (equal? prefix (string-trim-right (line-content line)))) + +(define (line-merge-comment line) + "Merge comment and content into the content. Return the new line." + (let + ((indent (line-indent line)) + (content (line-content line)) + (comment (line-comment line))) + (if (equal? "" comment) + line ; no change needed + (list indent (string-append content ";" comment) + "")))) + + +; skip the leading indentation +(define (skipindent inport) + (let skipper + ((inunderbars #t) + (indent 0) + (nextchar (read-char inport))) + ; when the file ends, do not do anything else + (if (not (eof-object? nextchar )) + ; skip underbars + (if inunderbars + (if (char=? nextchar #\_ ); still in underbars? + (skipper + #t ; still in underbars? + (+ indent 1) + (read-char inport)) + ; else, reevaluate without inunderbars + (skipper #f indent nextchar)) + ; else: skip remaining spaces + (if (char=? nextchar #\space) + (skipper + #f + (+ indent 1) + (read-char inport)) + (begin + (unread-char nextchar inport) + indent))) + indent))) + + +; Now we have to split a single line into indentation, content and comment. +(define (splitindent inport) + (let + ((indent (skipindent inport))) + (let + ((nextchar (read-char inport)) + (inindent #t ); it always begins in indent + (incomment #f ); but not in a comment + (commentstart #f) + (commentstartidentifier "\\REALCOMMENTHERE") + (commentstartidentifierlength 16) + (commentidentifierindex 0) + (content "") + (comment "")) + (while (not (eof-object? nextchar)) + ; check whether we leave the content + ; FIXME: (wisp.py) the reader cuts the ; here, when I write it as this: + ; when : and ( not incomment ) : char=? nextchar #\; + ; FIXME: THIS mistreats #\; as comment! (shown 4 lines after this comment…) + (when + (and + (not incomment) + ; FIXME: this should be but would break + ; char=? nextchar #\; + (equal? ";" (string nextchar)) + (not (string-suffix? ( string #\# #\\ ) content))) + (set! commentstart #t) + (set! comment (string-append comment (string nextchar))) + (set! nextchar (read-char inport)) + (continue)) + ; check whether we stay in the commentcheck + (when (and commentstart (char=? nextchar (string-ref commentstartidentifier commentidentifierindex))) + + (set! commentidentifierindex (+ commentidentifierindex 1)) + (set! comment (string-append comment (string nextchar))) + (when (= commentidentifierindex commentstartidentifierlength) + (set! commentstart #f) + (set! incomment #t) + ; reset used variables + (set! commentidentifierindex 0) + (set! comment "")) + (set! nextchar (read-char inport)) + (continue)) + ; if we cannot complete the commentcheck, we did not start a real comment. Append it to the content + (when (and commentstart (not (char=? nextchar (string-ref commentstartidentifier commentidentifierindex)))) + (set! commentstart #f) + (set! content (string-append content comment (string nextchar))) + (set! comment "") + (set! commentidentifierindex 0) + (set! nextchar (read-char inport)) + (continue)) + ; if we are in the comment, just append to the comment + (when incomment + (set! comment (string-append comment (string nextchar))) + (set! nextchar (read-char inport)) + (continue)) + ; if nothing else is true, we are in the content + (set! content (string-append content (string nextchar))) + (set! nextchar (read-char inport))) + (when commentstart + (set! content (string-append content comment)) + (set! comment "")) + ; return the indentation, the content and the comment + (list indent content comment)))) + + +; Now use the function to split a list of lines +(define (linestoindented lines) + (let splitter + ((unprocessed lines) + (processed '())) + (if (equal? unprocessed '()) + processed + ; else: let-recursion + (splitter + (list-tail unprocessed 1) + (append processed + (list + (call-with-input-string + (list-ref unprocessed 0) + splitindent))))))) + + +(define (read-whole-file filename) + (let ((origfile (open-file filename "r"))) + (let reader + ((text "") + (nextchar (read-char origfile))) + (if (eof-object? nextchar) + text + (reader + (string-append text (string nextchar)) + (read-char origfile)))))) + + + +(define (wisp2lisp-add-inline-colon-brackets line) + "Add inline colon brackets to a wisp-line (indent,content,comment). + +A line with only a colon and whitespace gets no additional parens! + +Also unescape \\: to :. +" + ; if the line only consists of a colon and whitespace, do not change it. + (if (line-only-colon? line) + line + (let ((content (line-content line))) + ; replace final " :" by a function call. There we are by definition of the line-splitting not in a string. + (when (string-suffix? " :" content) + (set! content (string-append (string-drop-right content 1) "()"))) + ; process the content in reverse direction, so we can detect ' : and turn it into '( + ; let linebracketizer ( ( instring #f ) ( inbrackets 0 ) ( bracketstoadd 0 ) ( unprocessed content ) ( processed "" ) ) + (let linebracketizer (( instring #f ) ( inbrackets 0 ) ( bracketstoadd 0 ) ( unprocessed content ) ( processed "" ) ) + (if (< (string-length unprocessed) 2) + ; if unprocessed is < 2 chars, it cannot contain ": ". We are done. + (list + (line-indent line) + (string-append unprocessed processed (xsubstring ")" 0 bracketstoadd)) + (line-comment line)) + ; else + (let + ((lastletter (string-take-right unprocessed 1)) + (lastupto3 (string-take-right unprocessed (min 3 (string-length unprocessed)))) + (lastupto4 (string-take-right unprocessed (min 4 (string-length unprocessed)))) + (lastupto6 (string-take-right unprocessed (min 6 (string-length unprocessed))))) + ; check if we’re in a string + (when + (or + (and + (not instring) + (equal? "\"" lastletter) + (not (equal? "#\\\"" lastupto3))) + (and + instring + (equal? "\"" lastletter) + (not (endsinunevenbackslashes (string-drop-right unprocessed 1))))) + (set! instring (not instring))) + (when (not instring) + (when + (or + ; TODO: Only match for braces {} if curly infix is enabled + ; FIXME: Catch wrong ordering of parens/brackets/braces like ({)} + (and (equal? "{" lastletter) (not (equal? "#\\{" lastupto3))) + (and (equal? "[" lastletter) (not (equal? "#\\[" lastupto3))) + (and (equal? "(" lastletter) (not (equal? "#\\(" lastupto3)))) + (set! inbrackets (- inbrackets 1))) + (when + (or + (and (equal? ")" lastletter) (not (equal? "#\\)" lastupto3))) + (and (equal? "]" lastletter) (not (equal? "#\\]" lastupto3))) + (and (equal? "}" lastletter) (not (equal? "#\\}" lastupto3)))) + (set! inbrackets (+ 1 inbrackets )))); remember that we're going backwards! + ; error handling: inbrackets must never be smaller than 0 - due to the line splitting. + (when (< inbrackets 0) + (throw 'more-inline-brackets-closed-than-opened inbrackets line)) + ; when we’re in a string or in brackets , just skip to the next char + (cond + ((or instring (> inbrackets 0)) + (linebracketizer instring inbrackets bracketstoadd + (string-drop-right unprocessed 1) + (string-append lastletter processed))) + ; else check for " : ": That adds a new inline bracket + ; support : at the beginning of a line, too. + ((or (equal? " : " lastupto3) (equal? ": " lastupto3)) + ; replace the last 2 chars with "(" and note + ; that we need an additional closing bracket + ; at the end. + (linebracketizer instring inbrackets (+ 1 bracketstoadd ) + (string-append (string-drop-right unprocessed 2) ) + (string-append "(" processed))) + ; turn " ' (" into " '(", do not modify unprocessed, except to shorten it! + ; same for ` , #' #` #, #,@, + ((and (string-prefix? "(" processed) (equal? " ' " lastupto3)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 2) "'") + processed)) + ((and (string-prefix? "(" processed) (equal? " , " lastupto3)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 2) ",") + processed)) + ((and (string-prefix? "(" processed) (equal? " ` " lastupto3)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 2) "`") + processed)) + ((and (string-prefix? "(" processed) (equal? " #` " lastupto4)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 3) "#`") + processed)) + ((and (string-prefix? "(" processed) (equal? " #' " lastupto4)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 3) "#'") + processed)) + ((and (string-prefix? "(" processed) (equal? " #, " lastupto4)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 3) "#,") + processed)) + ((and (string-prefix? "(" processed) (equal? " #,@, " lastupto6)) + ; leave out the second space + (linebracketizer instring inbrackets bracketstoadd + (string-append (string-drop-right unprocessed 5) "#,@,") + processed)) + (else ; just go on + (linebracketizer instring inbrackets bracketstoadd + (string-drop-right unprocessed 1) + (string-append lastletter processed)))))))))) + + +(define (last-indent levels) + "Retrieve the indentation of the last line: Simply the highest level." + (list-ref levels 0)) + +(define (line-add-starting-bracket line) + "Add a starting bracket to the line, if it is no continuation line (it is more indented than the previous). + +If line starts with one of ' , ` #` #' #, #,@, then turn it into '(... instead of ('... + +If line is indented and only contains : and optional whitespace, remove the :. + +The line *must* have a whitespace after the prefix, except if the prefix is the only non-whitespace on the line." + ; if the line only contains a colon, we just replace its content with an opening paren. + (if (line-only-colon? line ); FIXME: Check for this somewhere else. + (list + (line-indent line) + (string-append "(" (string-drop (line-content line) 1 )); keep whitespace + (line-comment line)) + (let loop ((paren-prefixes (list "' " ", " "` " "#` " "#' " "#, " "#,@, "))) + ; first check whether we are done checking + (if (null-list? paren-prefixes) + ; construct the line structure: '(indentation-depth content comment) + (list + (line-indent line) + (string-append + "(" + (line-content line)) + (line-comment line)) + ; otherwise check all possible prefixes + (let* + ((prefix (car paren-prefixes)) + (prefix-no-space (string-drop-right prefix 1))) + (cond + ((string-prefix? prefix (line-content line)) + (list + (line-indent line) + (string-append + prefix-no-space "(" + (string-drop (line-content line) (string-length prefix))) + (line-comment line))) + ((line-only-prefix? line prefix-no-space) + (list + (line-indent line) + (string-append + (string-drop-right prefix 1) "(" + (string-drop (line-content line) (string-length prefix-no-space))) + (line-comment line))) + (else + (loop (cdr paren-prefixes))))))))) + +(define (line-add-closing-brackets line number) + "Add a closing bracket to the line." + (list + (line-indent line) + (string-append + (line-content line) + (xsubstring ")" 0 number)) + (line-comment line))) + +(define (line-indent-brackets-to-close line-indent levels line-continues prev-continues) + "Find the number of brackets to close to reduce the levels to the line-indent." + ; adjust the levels until the highest indentation level is equal + ; to the indentation of the next line. Then check for + ; continuation. + (let closer ((bracketstoclose 0) (rest levels)) + (let ((highest-level (list-ref rest 0))) + ; finish-condition + (if (= line-indent highest-level) + (if prev-continues + bracketstoclose + (+ 1 bracketstoclose)) + (if (> line-indent highest-level) + (closer (- bracketstoclose 1) (append (list line-indent) rest )) + (closer (+ bracketstoclose 1) (list-tail rest 1))))))) + + +(define (line-indent-brackets-to-open line-indent levels line-continues prev-continues) + "Find the number of brackets to open to fit the line-indent and continuation marker." + (if line-continues + 0 + 1)) + +(define (line-indent-levels-adjust levels next-indent) + "Add or remove levels so the highest remaining level matches next-indent." + (let adjuster ((lev levels)) + (let ((highest-level (list-ref lev 0))) + (if (= next-indent highest-level) + lev + (if (> next-indent highest-level) + (append (list next-indent) lev) + (adjuster (list-tail lev 1))))))) + +(define (line-drop-continuation-dot line) + (let ((content (line-content line))) + (list + (line-indent line) + (if (line-continues? line) + (string-drop content 2) + content) + (line-comment line )))) + +(define (wisp2lisp-parse lisp prev lines) + "Parse the body of the wisp-code." + (set! prev (wisp2lisp-add-inline-colon-brackets prev )); prev already is a code-line. + (if (not (or (line-continues? prev) (line-empty-code? prev))) + (set! prev (line-add-starting-bracket prev))) + (set! lines (map-in-order wisp2lisp-add-inline-colon-brackets lines)) + (let bracketizer ((levels '(0)) (pre prev) (unprocessed lines) (processed lisp) (whitespace '())) + ; levels is the list of levels, with the lowest to the right. i.e: '(12 8 4 0) + ; once we processed everything, we pass the bracketizer pre as f one last time + (if (equal? #f (line-content pre)) + processed + (let ((next (if (equal? unprocessed '()) (list 0 #f #f) (list-ref unprocessed 0 )))); this is the break condition for the next loop! + (if (line-empty-code? next ); empty lines get silently added, but otherwise ignored + (bracketizer levels pre + (list-tail unprocessed 1) + processed + (append whitespace (list next))) + ; firstoff add the next indent to the levels, so we only work on the levels, prev-continues, next-continues and next-indent + ; if pre was a continuation, the real levels are 1 lower than the counted levels + (let* + ((next-indent (line-indent next)) + (pre-indent (line-indent pre)) + (pre-continues (line-continues? pre)) + (next-continues (line-continues? next)) + (final-line (equal? #f (line-content next))) + (bracketstocloseprev (if (line-empty-code? pre) 0 (line-indent-brackets-to-close next-indent levels next-continues pre-continues))) + (bracketstoopennext (line-indent-brackets-to-open next-indent levels next-continues pre-continues)) + (newnext (if final-line next (if (> bracketstoopennext 0) (line-add-starting-bracket next) next))) + (newpre (line-drop-continuation-dot (line-add-closing-brackets pre bracketstocloseprev))) + (newlevels (line-indent-levels-adjust levels next-indent))) + (bracketizer newlevels newnext + (if final-line unprocessed (list-tail unprocessed 1)) + (append processed (list newpre) whitespace) + (list)))))))) + + +(define (wisp2lisp-initial-comments lisp prev lines) + "Keep all starting comments: do not start them with a bracket." + (let skip-initial-comments ((lisp lisp) (prev prev) (lines lines)) + (if (= 0 (length lines )); file only contained comments, maybe including the hashbang + (list lisp prev lines) + (if (line-empty-code? prev) + (skip-initial-comments (append lisp (list prev)) + (list-ref lines 0) (list-tail lines 1)) + (list lisp prev lines))))) + +(define (wisp2lisp-hashbang lisp prev unprocessed) + "Parse a potential initial hashbang line." + (if + (and + (equal? lisp '() ); really the first line + (equal? 0 (line-indent prev)) + (string-prefix? "#!" (line-content prev))) + (wisp2lisp-hashbang (append lisp (list (line-merge-comment prev))) + (list-ref unprocessed 0) (list-tail unprocessed 1)) + (list lisp prev unprocessed))) + +(define (wisp2lisp-lines lines) + "Parse indentation in the lines to add the correct brackets." + (if (equal? lines '()) + '() + (let + ((lisp '() ); the processed lines + (prev (list-ref lines 0 )); the last line + (unprocessed (list-tail lines 1 ))); obvious :) + (let* + ((hashbanged (wisp2lisp-hashbang lisp prev unprocessed)) + (deinitialized (apply wisp2lisp-initial-comments hashbanged)) + (parsed (apply wisp2lisp-parse deinitialized))) + parsed)))) + +(define (line-unescape-underscore-and-colon line) + "Unescape underscores at the beginning of the line and colon." + (let loop + ((processed "") + (unprocessed (line-content line))) + (if (equal? "" unprocessed) + (list + (line-indent line) + processed + (line-comment line)) + (let + ((next (string (string-ref unprocessed 0)))) + (if (equal? "" processed ) + (cond + ; get rid of \_ + ((string-prefix? "(\\_" unprocessed) + (loop processed (string-append "(" (string-drop unprocessed 2)))) + ; get rid of \: + ((string-prefix? "(\\:" unprocessed) + (loop processed (string-append "(" (string-drop unprocessed 2)))) + ; get rid of . \: + ((string-prefix? "\\:" unprocessed) + (loop processed (string-drop unprocessed 1))) + (else + (loop + (string-append processed next) + (string-drop unprocessed 1)))) + (cond + ((string-prefix? " \\:" unprocessed) + (loop + (string-append processed " :" ) + (string-drop unprocessed 3))) + ((string-prefix? "(\\:" unprocessed) + (loop + (string-append processed "(:" ) + (string-drop unprocessed 3))) + (else + (loop + (string-append processed next) + (string-drop unprocessed 1))))))))) + +(define (unescape-underscore-and-colon lines) + "Unescape underscores at the beginning of each line and colon." + (let loop + ((processed '()) + (unprocessed lines)) + (if (equal? unprocessed '()) + processed + (let ((current (car unprocessed))) + (loop + (append processed (list (line-unescape-underscore-and-colon current))) + (cdr unprocessed)))))) + + + +(define* (string-replace-substring s substr replacement #:optional (start 0) (end (string-length s))) + "Replace every instance of substring in s by replacement." + (let ((substr-length (string-length substr))) + (if (zero? substr-length) + (error "string-replace-substring: empty substr") + (let loop + ((start start) + (pieces (list (substring s 0 start)))) + (let ((idx (string-contains s substr start end))) + (if idx + (loop (+ idx substr-length) + (cons* replacement + (substring s start idx) + pieces)) + (string-concatenate-reverse + (cons (substring s start) + pieces)))))))) + + +(define (unescape-linebreaks text) + "unescape linebreaks" + (string-replace-substring + ; we have to construct the placeholders here to avoid unescaping them when we parse ourselves… + (string-replace-substring text (string-append "\\LINE_" "BREAK_N") (string #\newline)) + (string-append "\\LINE_" "BREAK_R") + (string #\return ))) + + +(define (unescape-comments text) + "unescape comments" + (string-replace-substring text + ; we have to construct the placeholders here to avoid unescaping them when we parse ourselves… + (string-append ";" "\\REALCOMMENTHERE") + ";")) + + +(define (wisp-chunkreader inport) + "Read one wisp-expression from inport, without escaping of fake newlines but with correct detection of real new lines. + Realized by reading with newline and comment escaping and unescaping both again after reading." + (unescape-comments + (unescape-linebreaks + (nostringandbracketbreaksreader inport)))) + + +(define (join-lisp-lines lisp-lines) + (let join ((joined "") (unprocessed lisp-lines)) + (if (not (equal? unprocessed '())) + (let* + ((next (list-ref unprocessed 0)) + (nextstring + (string-append + (xsubstring " " 0 (line-indent next)) + ; here we re-add all necessary linebreakswe get rid + (unescape-linebreaks (line-content next)) + (if (equal? "" (line-comment next )) + "" + (string-append ";" (line-comment next))) + "\n"))) + (join (string-append joined nextstring) (list-tail unprocessed 1))) + joined))) + +(define (wisp2lisp text ) + (let* + ((nobreaks (call-with-input-string text nostringandbracketbreaks)) + (textlines (call-with-input-string nobreaks splitlines)) + (lines (linestoindented textlines)) + (lisp-lines (wisp2lisp-lines lines)) + (clean-lines (unescape-underscore-and-colon lisp-lines))) + (join-lisp-lines clean-lines))) + + ; first step: Be able to mirror a file to stdout +(if (< 1 (length (command-line))) + (let* + ((filename (list-ref ( command-line ) 1)) + (text (read-whole-file filename)) + ; Lines consist of lines with indent, content and comment. See + ; line-indent, line-content, line-comment and the other + ; line-functions for details. + ; textlines : split-wisp-lines text + ; lines : linestoindented textlines + (lisp (wisp2lisp text))) + (display lisp) + (newline)) + #f) + + diff --git a/docs/srfi-119/wisp-guile.w b/docs/srfi-119/wisp-guile.w new file mode 100755 --- /dev/null +++ b/docs/srfi-119/wisp-guile.w @@ -0,0 +1,833 @@ +#!/home/arne/wisp/wisp-multiline.sh +; !# + +;; This file might need to be licensed permissively for inclusion in +;; an SRFI. Only change it, if you agree to this possible relicensing +;; of your contribution to this file. I will not accept changes here +;; which do not allow that. + +; we need to be able to replace end-of-line characters in brackets and strings + +;; TODO: Check whether I can offload the string processing to the +;; read-function. That’s a source of endless complications. Required: +;; A kind of unrolling step which appends the string-representation of +;; the read strings back into the code. I would have to process a list +;; of strings instead of one big string. Or rather, each line would be +;; a list of strings. + +;; bootstrap via python3 wisp.py wisp-guile.w > 1 && guile 1 wisp-guile.w > 2 && guile 2 wisp-guile.w > 3 && diff 2 3 +;; +;; -Author: Arne Babenhauserheide + +;; Copyright (C) Arne Babenhauserheide (2013--2015). All Rights Reserved. + +;; Permission is hereby granted, free of charge, to any person +;; obtaining a copy of this software and associated documentation +;; files (the "Software"), to deal in the Software without +;; restriction, including without limitation the rights to use, copy, +;; modify, merge, publish, distribute, sublicense, and/or sell copies +;; of the Software, and to permit persons to whom the Software is +;; furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be +;; included in all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. + + +define-module : wisp + . #:export : wisp2lisp wisp-chunkreader + +use-modules + : srfi srfi-1 + : ice-9 regex + +define : endsinunevenbackslashes text ; comment + if : = 0 : string-length text + . #f + let counter + : last : string-take-right text 1 + rest : string-append " " : string-drop-right text 1 + count 0 + cond + : = 0 : string-length rest ; end clause: read all + odd? count + ; end clause: no \ + : not : equal? last : string #\\ + odd? count + else + counter (string-take-right rest 1) (string-drop-right rest 1) (+ 1 count) + + +define : nostringandbracketbreaks inport + . "Replace all linebreaks inside strings and brackets with placeholders." + let : : expressions : list : nostringandbracketbreaksreader inport + while : not : eof-object? : peek-char inport + set! expressions : append expressions : list : nostringandbracketbreaksreader inport + string-join expressions "\n" + + +define : nostringandbracketbreaksreader inport + . "Read one wisp-expression from the inport. +Ends with three consecutive linebreaks or eof." + ; Replace end of line characters in brackets and strings + ; FIXME: Breaks if the string is shorter than 2 chars + ; FIXME: Breaks if the text begins with a comment. + let* +__ : lastchar : read-char inport +____ nextchar : read-char inport +____ text : if (eof-object? lastchar) "" : string lastchar + incomment #f + incommentfirstchar #f ; first char of a comment + instring #f + inbrackets 0 + incharform 0 ; #\<something> + while + not + or : eof-object? nextchar + and + or (char=? nextchar #\newline ) (char=? nextchar #\return ) + or (char=? lastchar #\newline ) (char=? lastchar #\return ) + string-suffix? "\n\n" text ; text includes lastchar + ; incommentfirstchar is only valid for exactly one char + when incommentfirstchar : set! incommentfirstchar #f + ; but add incommentfirstchar if we just started the text + when : equal? text ";" ; initial comment + set! incommentfirstchar #f + set! incomment #t + set! text : string-append text "\\REALCOMMENTHERE" + ; already started char forms win over everything, so process them first. + ; already started means: after the #\ + ; FIXME: Fails to capture #t and #f which can kill line splitting if it happens inside brackets + when : = incharform 1 + when : not : and (char=? lastchar #\# ) : or (char=? #\f nextchar) (char=? #\t nextchar) + ; format #t "1: set incharform 0: lastchar ~a nextchar ~a instring ~a incomment ~a incharform ~a" lastchar nextchar instring incomment incharform + ; newline + set! incharform 0 + + when : >= incharform 2 + if : or (char=? nextchar #\space) (char=? + nextchar #\newline ) (char=? nextchar #\return ) + begin + ; format #t "2: set incharform 0: lastchar ~a nextchar ~a instring ~a incomment ~a incharform ~a" lastchar nextchar instring incomment incharform + ; newline + set! incharform 0 + ; else + set! incharform : + incharform 1 + ; check if we switch to a string: last char is space, linebreak or in a string, not in a charform, not in a comment + when + and + char=? nextchar #\" + not incomment + < incharform 1 + or + and + . instring ; when I’m in a string, I can get out + or + 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 + 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 + and : not instring ; outside of strings, brackets are pseudo-whitespace, too + or + char=? lastchar #\( + char=? lastchar #\) + char=? lastchar #\[ + char=? lastchar #\] + ; TODO: Only match for braces {} if curly infix is enabled + char=? lastchar #\{ + char=? lastchar #\} + set! instring : not instring + ; check if we switch to a comment + when + and + ; FIXME: this should be + ; char=? nextchar #\; + equal? ";" : string nextchar + not incomment + not instring + < incharform 2 + set! incomment #t + set! incommentfirstchar #t + ; this also closes any potential charform + set! incharform 0 + when + and incomment + or + char=? nextchar #\return + char=? nextchar #\newline + set! incomment #f + + ; check for the beginning of a charform + when + and + not instring + not incomment + char=? lastchar #\space + char=? nextchar #\# + set! incharform 1 + ; check whether a charform is continued + when + and + = incharform 1 + char=? lastchar #\# + char=? nextchar #\\ + set! incharform 2 + + ; check for brackets + ; FIXME: This only fixes a single linebreak inside parens, but if a second occurs on the same line it breaks. I do not know why. Maybe something with having lastchar as linebreak. + when : not : or instring incomment + when + and + not : string-suffix? text "#" + not : char=? #\\ lastchar + not : endsinunevenbackslashes : string-drop-right text : min 1 : string-length text + ; TODO: Only match for braces {} if curly infix is enabled + ; FIXME: Catch wrong ordering of parens/brackets/braces like ({)} + when : or (equal? "[" (string nextchar)) (equal? "(" (string nextchar)) (equal? "{" (string nextchar)) + set! inbrackets : + inbrackets 1 + when : or (equal? "}" (string nextchar)) (equal? ")" (string nextchar)) (equal? "]" (string nextchar)) + set! inbrackets : - inbrackets 1 + if : or instring : > inbrackets 0 + if : char=? nextchar #\newline + ; we have to actually construct the escape + ; sequence here to be able to parse ourselves. + set! text : string-append text : string-append "\\LINE_" "BREAK_N" + if : char=? nextchar #\return + set! text : string-append text : string-append "\\LINE_" "BREAK_R" + ; else + set! text : string-append text : string nextchar + ; mark the start of a comment, so we do not have to + ; repeat the string matching in later code. We include + ; the comment character! + ; not (instring or inbrackets) = neither instring nor inbrackets + if incommentfirstchar + set! text : string-append text ( string nextchar ) "\\REALCOMMENTHERE" + ; when not in brackets or string or starting a + ; comment: just append the char + set! text : string-append text : string nextchar + + set! lastchar nextchar + set! nextchar : read-char inport + ; return the text + . text + + +; As next part we have split a text into a list of lines which we can process one by one. +define : splitlines inport + let + : lines '() + nextchar : read-char inport + nextline "" + while : not : eof-object? nextchar + if : not : or (char=? nextchar #\return ) (char=? nextchar #\newline ) + set! nextline : string-append nextline : string nextchar + begin + set! lines : append lines (list nextline) + set! nextline "" + set! nextchar : read-char inport + append lines : list nextline + +define : line-indent line + list-ref line 0 + +define : line-content line + list-ref line 1 + +define : line-comment line + list-ref line 2 + +define : line-continues? line + . "Check whether the line is a continuation of a previous line (should not start with a bracket)." + if : equal? #f : line-content line + . #f ; this is the EOF line. It does not continue (to ensure that the last brackets get closed) + string-prefix? ". " : line-content line + +define : line-empty-code? line + . "Check whether the code-part of the line is empty: contains only whitespace and/or comment." + equal? "" : line-content line + +define : line-only-colon? line + . "Check whether the line content consists only of a colon and whitespace." + equal? ":" : string-trim-right : line-content line + +define : line-only-prefix? line prefix + . "Check whether the line content consists only of a given prefix and whitespace." + equal? prefix : string-trim-right : line-content line + +define : line-merge-comment line + . "Merge comment and content into the content. Return the new line." + let + : indent : line-indent line + content : line-content line + comment : line-comment line + if : equal? "" comment + . line ; no change needed + list indent : string-append content ";" comment + . "" + + +; skip the leading indentation +define : skipindent inport + let skipper + : inunderbars #t + indent 0 + nextchar : read-char inport + ; when the file ends, do not do anything else + if : not : eof-object? nextchar + ; skip underbars + if inunderbars + if : char=? nextchar #\_ ; still in underbars? + skipper + . #t ; still in underbars? + + indent 1 + read-char inport + ; else, reevaluate without inunderbars + skipper #f indent nextchar + ; else: skip remaining spaces + if : char=? nextchar #\space + skipper + . #f + + indent 1 + read-char inport + begin + unread-char nextchar inport + . indent + . indent + + +; Now we have to split a single line into indentation, content and comment. +define : splitindent inport + let + : indent : skipindent inport + let + : nextchar : read-char inport + inindent #t ; it always begins in indent + incomment #f ; but not in a comment + commentstart #f + commentstartidentifier "\\REALCOMMENTHERE" + commentstartidentifierlength 16 + commentidentifierindex 0 + content "" + comment "" + while : not : eof-object? nextchar + ; check whether we leave the content + ; FIXME: (wisp.py) the reader cuts the ; here, when I write it as this: + ; when : and ( not incomment ) : char=? nextchar #\; + ; FIXME: THIS mistreats #\; as comment! (shown 4 lines after this comment…) + when + and + not incomment + ; FIXME: this should be but would break + ; char=? nextchar #\; + equal? ";" : string nextchar + not : string-suffix? ( string #\# #\\ ) content + set! commentstart #t + set! comment : string-append comment : string nextchar + set! nextchar : read-char inport + continue + ; check whether we stay in the commentcheck + when : and commentstart : char=? nextchar : string-ref commentstartidentifier commentidentifierindex + + set! commentidentifierindex : + commentidentifierindex 1 + set! comment : string-append comment : string nextchar + when : = commentidentifierindex commentstartidentifierlength + set! commentstart #f + set! incomment #t + ; reset used variables + set! commentidentifierindex 0 + set! comment "" + set! nextchar : read-char inport + continue + ; if we cannot complete the commentcheck, we did not start a real comment. Append it to the content + when : and commentstart : not : char=? nextchar : string-ref commentstartidentifier commentidentifierindex + set! commentstart #f + set! content : string-append content comment : string nextchar + set! comment "" + set! commentidentifierindex 0 + set! nextchar : read-char inport + continue + ; if we are in the comment, just append to the comment + when incomment + set! comment : string-append comment : string nextchar + set! nextchar : read-char inport + continue + ; if nothing else is true, we are in the content + set! content : string-append content : string nextchar + set! nextchar : read-char inport + when commentstart + set! content : string-append content comment + set! comment "" + ; return the indentation, the content and the comment + list indent content comment + + +; Now use the function to split a list of lines +define : linestoindented lines + let splitter + : unprocessed lines + processed '() + if : equal? unprocessed '() + . processed + ; else: let-recursion + splitter + list-tail unprocessed 1 + append processed + list + call-with-input-string + list-ref unprocessed 0 + . splitindent + + +define : read-whole-file filename + let : : origfile : open-file filename "r" + let reader + : text "" + nextchar : read-char origfile + if : eof-object? nextchar + . text + reader + string-append text : string nextchar + read-char origfile + + + +define : wisp2lisp-add-inline-colon-brackets line + . "Add inline colon brackets to a wisp-line (indent,content,comment). + +A line with only a colon and whitespace gets no additional parens! + +Also unescape \\: to :. +" + ; if the line only consists of a colon and whitespace, do not change it. + if : line-only-colon? line + . line + let : : content : line-content line + ; replace final " :" by a function call. There we are by definition of the line-splitting not in a string. + when : string-suffix? " :" content + set! content : string-append (string-drop-right content 1) "()" + ; process the content in reverse direction, so we can detect ' : and turn it into '( + ; let linebracketizer ( ( instring #f ) ( inbrackets 0 ) ( bracketstoadd 0 ) ( unprocessed content ) ( processed "" ) ) + let linebracketizer : ( instring #f ) ( inbrackets 0 ) ( bracketstoadd 0 ) ( unprocessed content ) ( processed "" ) + if : < (string-length unprocessed) 2 + ; if unprocessed is < 2 chars, it cannot contain ": ". We are done. + list + line-indent line + string-append unprocessed processed : xsubstring ")" 0 bracketstoadd + line-comment line + ; else + let + : lastletter : string-take-right unprocessed 1 + lastupto3 : string-take-right unprocessed : min 3 : string-length unprocessed + lastupto4 : string-take-right unprocessed : min 4 : string-length unprocessed + lastupto6 : string-take-right unprocessed : min 6 : string-length unprocessed + ; check if we’re in a string + when + or + and + not instring + equal? "\"" lastletter + not : equal? "#\\\"" lastupto3 + and + . instring + equal? "\"" lastletter + not : endsinunevenbackslashes : string-drop-right unprocessed 1 + set! instring : not instring + when : not instring + when + or + ; TODO: Only match for braces {} if curly infix is enabled + ; FIXME: Catch wrong ordering of parens/brackets/braces like ({)} + and (equal? "{" lastletter) : not : equal? "#\\{" lastupto3 + and (equal? "[" lastletter) : not : equal? "#\\[" lastupto3 + and (equal? "(" lastletter) : not : equal? "#\\(" lastupto3 + set! inbrackets : - inbrackets 1 + when + or + and (equal? ")" lastletter) : not : equal? "#\\)" lastupto3 + and (equal? "]" lastletter) : not : equal? "#\\]" lastupto3 + and (equal? "}" lastletter) : not : equal? "#\\}" lastupto3 + set! inbrackets : + 1 inbrackets ; remember that we're going backwards! + ; error handling: inbrackets must never be smaller than 0 - due to the line splitting. + when : < inbrackets 0 + throw 'more-inline-brackets-closed-than-opened inbrackets line + ; when we’re in a string or in brackets , just skip to the next char + cond + : or instring : > inbrackets 0 + linebracketizer instring inbrackets bracketstoadd + . : string-drop-right unprocessed 1 + . : string-append lastletter processed + ; else check for " : ": That adds a new inline bracket + ; support : at the beginning of a line, too. + : or (equal? " : " lastupto3) (equal? ": " lastupto3) + ; replace the last 2 chars with "(" and note + ; that we need an additional closing bracket + ; at the end. + linebracketizer instring inbrackets : + 1 bracketstoadd + string-append (string-drop-right unprocessed 2) + string-append "(" processed + ; turn " ' (" into " '(", do not modify unprocessed, except to shorten it! + ; same for ` , #' #` #, #,@, + : and (string-prefix? "(" processed) : equal? " ' " lastupto3 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 2) "'") + . processed + : and (string-prefix? "(" processed) : equal? " , " lastupto3 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 2) ",") + . processed + : and (string-prefix? "(" processed) : equal? " ` " lastupto3 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 2) "`") + . processed + : and (string-prefix? "(" processed) : equal? " #` " lastupto4 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 3) "#`") + . processed + : and (string-prefix? "(" processed) : equal? " #' " lastupto4 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 3) "#'") + . processed + : and (string-prefix? "(" processed) : equal? " #, " lastupto4 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 3) "#,") + . processed + : and (string-prefix? "(" processed) : equal? " #,@, " lastupto6 + ; leave out the second space + linebracketizer instring inbrackets bracketstoadd + . (string-append (string-drop-right unprocessed 5) "#,@,") + . processed + else ; just go on + linebracketizer instring inbrackets bracketstoadd + . (string-drop-right unprocessed 1) + . (string-append lastletter processed) + + +define : last-indent levels + . "Retrieve the indentation of the last line: Simply the highest level." + list-ref levels 0 + +define : line-add-starting-bracket line + . "Add a starting bracket to the line, if it is no continuation line (it is more indented than the previous). + +If line starts with one of ' , ` #` #' #, #,@, then turn it into '(... instead of ('... + +If line is indented and only contains : and optional whitespace, remove the :. + +The line *must* have a whitespace after the prefix, except if the prefix is the only non-whitespace on the line." + ; if the line only contains a colon, we just replace its content with an opening paren. + if : line-only-colon? line ; FIXME: Check for this somewhere else. + list + line-indent line + string-append "(" : string-drop (line-content line) 1 ; keep whitespace + line-comment line + let loop : : paren-prefixes : list "' " ", " "` " "#` " "#' " "#, " "#,@, " + ; first check whether we are done checking + if : null-list? paren-prefixes + ; construct the line structure: '(indentation-depth content comment) + list + line-indent line + string-append + . "(" + line-content line + line-comment line + ; otherwise check all possible prefixes + let* + : prefix : car paren-prefixes + prefix-no-space : string-drop-right prefix 1 + cond + : string-prefix? prefix : line-content line + list + line-indent line + string-append + . prefix-no-space "(" + string-drop (line-content line) : string-length prefix + line-comment line + : line-only-prefix? line prefix-no-space + list + line-indent line + string-append + . (string-drop-right prefix 1) "(" + string-drop (line-content line) : string-length prefix-no-space + line-comment line + else + loop : cdr paren-prefixes + +define : line-add-closing-brackets line number + . "Add a closing bracket to the line." + list + line-indent line + string-append + line-content line + xsubstring ")" 0 number + line-comment line + +define : line-indent-brackets-to-close line-indent levels line-continues prev-continues + . "Find the number of brackets to close to reduce the levels to the line-indent." + ; adjust the levels until the highest indentation level is equal + ; to the indentation of the next line. Then check for + ; continuation. + let closer : (bracketstoclose 0) (rest levels) + let : : highest-level : list-ref rest 0 + ; finish-condition + if : = line-indent highest-level + if prev-continues + . bracketstoclose + + 1 bracketstoclose + if : > line-indent highest-level + closer (- bracketstoclose 1) : append (list line-indent) rest + closer (+ bracketstoclose 1) : list-tail rest 1 + + +define : line-indent-brackets-to-open line-indent levels line-continues prev-continues + . "Find the number of brackets to open to fit the line-indent and continuation marker." + if line-continues + . 0 + . 1 + +define : line-indent-levels-adjust levels next-indent + . "Add or remove levels so the highest remaining level matches next-indent." + let adjuster : (lev levels) + let : : highest-level : list-ref lev 0 + if : = next-indent highest-level + . lev + if : > next-indent highest-level + append (list next-indent) lev + adjuster : list-tail lev 1 + +define : line-drop-continuation-dot line + let : : content : line-content line + list + line-indent line + if : line-continues? line + string-drop content 2 + . content + line-comment line + +define : wisp2lisp-parse lisp prev lines + . "Parse the body of the wisp-code." + set! prev : wisp2lisp-add-inline-colon-brackets prev ; prev already is a code-line. + if : not : or (line-continues? prev) (line-empty-code? prev) + set! prev : line-add-starting-bracket prev + set! lines : map-in-order wisp2lisp-add-inline-colon-brackets lines + let bracketizer : (levels '(0)) (pre prev) (unprocessed lines) (processed lisp) (whitespace '()) + ; levels is the list of levels, with the lowest to the right. i.e: '(12 8 4 0) + ; once we processed everything, we pass the bracketizer pre as f one last time + if : equal? #f : line-content pre + . processed + let : : next : if (equal? unprocessed '()) (list 0 #f #f) : list-ref unprocessed 0 ; this is the break condition for the next loop! + if : line-empty-code? next ; empty lines get silently added, but otherwise ignored + bracketizer levels pre + list-tail unprocessed 1 + . processed + append whitespace : list next + ; firstoff add the next indent to the levels, so we only work on the levels, prev-continues, next-continues and next-indent + ; if pre was a continuation, the real levels are 1 lower than the counted levels + let* + : next-indent : line-indent next + pre-indent : line-indent pre + pre-continues : line-continues? pre + next-continues : line-continues? next + final-line : equal? #f : line-content next + bracketstocloseprev : if (line-empty-code? pre) 0 : line-indent-brackets-to-close next-indent levels next-continues pre-continues + bracketstoopennext : line-indent-brackets-to-open next-indent levels next-continues pre-continues + newnext : if final-line next : if (> bracketstoopennext 0) (line-add-starting-bracket next) next + newpre : line-drop-continuation-dot : line-add-closing-brackets pre bracketstocloseprev + newlevels : line-indent-levels-adjust levels next-indent + bracketizer newlevels newnext + if final-line unprocessed : list-tail unprocessed 1 + append processed (list newpre) whitespace + list + + +define : wisp2lisp-initial-comments lisp prev lines + . "Keep all starting comments: do not start them with a bracket." + let skip-initial-comments : (lisp lisp) (prev prev) (lines lines) + if : = 0 : length lines ; file only contained comments, maybe including the hashbang + list lisp prev lines + if : line-empty-code? prev + skip-initial-comments : append lisp : list prev + . (list-ref lines 0) (list-tail lines 1) + list lisp prev lines + +define : wisp2lisp-hashbang lisp prev unprocessed + . "Parse a potential initial hashbang line." + if + and + equal? lisp '() ; really the first line + equal? 0 : line-indent prev + string-prefix? "#!" : line-content prev + wisp2lisp-hashbang : append lisp : list : line-merge-comment prev + . (list-ref unprocessed 0) (list-tail unprocessed 1) + list lisp prev unprocessed + +define : wisp2lisp-lines lines + . "Parse indentation in the lines to add the correct brackets." + if : equal? lines '() + . '() + let + : lisp '() ; the processed lines + prev : list-ref lines 0 ; the last line + unprocessed : list-tail lines 1 ; obvious :) + let* + : hashbanged : wisp2lisp-hashbang lisp prev unprocessed + deinitialized : apply wisp2lisp-initial-comments hashbanged + parsed : apply wisp2lisp-parse deinitialized + . parsed + +define : line-unescape-underscore-and-colon line + . "Unescape underscores at the beginning of the line and colon." + let loop + : processed "" + unprocessed : line-content line + if : equal? "" unprocessed + list + line-indent line + . processed + line-comment line + let + : next : string : string-ref unprocessed 0 + if : equal? "" processed + cond + ; get rid of \_ + : string-prefix? "(\\_" unprocessed + loop processed : string-append "(" : string-drop unprocessed 2 + ; get rid of \: + : string-prefix? "(\\:" unprocessed + loop processed : string-append "(" : string-drop unprocessed 2 + ; get rid of . \: + : string-prefix? "\\:" unprocessed + loop processed : string-drop unprocessed 1 + else + loop + string-append processed next + string-drop unprocessed 1 + cond + : string-prefix? " \\:" unprocessed + loop + string-append processed " :" + string-drop unprocessed 3 + : string-prefix? "(\\:" unprocessed + loop + string-append processed "(:" + string-drop unprocessed 3 + else + loop + string-append processed next + string-drop unprocessed 1 + +define : unescape-underscore-and-colon lines + . "Unescape underscores at the beginning of each line and colon." + let loop + : processed '() + unprocessed lines + if : equal? unprocessed '() + . processed + let : : current : car unprocessed + loop + append processed : list : line-unescape-underscore-and-colon current + cdr unprocessed + + + +define* : string-replace-substring s substr replacement #:optional (start 0) (end (string-length s)) + . "Replace every instance of substring in s by replacement." + let : : substr-length : string-length substr + if : zero? substr-length + error "string-replace-substring: empty substr" + let loop + : start start + pieces : list : substring s 0 start + let : : idx : string-contains s substr start end + if idx + loop : + idx substr-length + cons* replacement + substring s start idx + . pieces + string-concatenate-reverse + cons : substring s start + . pieces + + +define : unescape-linebreaks text + . "unescape linebreaks" + string-replace-substring + ; we have to construct the placeholders here to avoid unescaping them when we parse ourselves… + string-replace-substring text (string-append "\\LINE_" "BREAK_N") : string #\newline + string-append "\\LINE_" "BREAK_R" + string #\return + + +define : unescape-comments text + . "unescape comments" + string-replace-substring text + ; we have to construct the placeholders here to avoid unescaping them when we parse ourselves… + string-append ";" "\\REALCOMMENTHERE" + . ";" + + +define : wisp-chunkreader inport + . "Read one wisp-expression from inport, without escaping of fake newlines but with correct detection of real new lines. + Realized by reading with newline and comment escaping and unescaping both again after reading." + unescape-comments + unescape-linebreaks + nostringandbracketbreaksreader inport + + +define : join-lisp-lines lisp-lines + let join : (joined "") (unprocessed lisp-lines) + if : not : equal? unprocessed '() + let* + : next : list-ref unprocessed 0 + nextstring + string-append + xsubstring " " 0 : line-indent next + ; here we re-add all necessary linebreakswe get rid + unescape-linebreaks : line-content next + if : equal? "" : line-comment next + . "" + string-append ";" : line-comment next + . "\n" + join (string-append joined nextstring) (list-tail unprocessed 1) + . joined + +define : wisp2lisp text + let* + : nobreaks : call-with-input-string text nostringandbracketbreaks + textlines : call-with-input-string nobreaks splitlines + lines : linestoindented textlines + lisp-lines : wisp2lisp-lines lines + clean-lines : unescape-underscore-and-colon lisp-lines + join-lisp-lines clean-lines + + ; first step: Be able to mirror a file to stdout +if : < 1 : length : command-line + let* + : filename : list-ref ( command-line ) 1 + text : read-whole-file filename + ; Lines consist of lines with indent, content and comment. See + ; line-indent, line-content, line-comment and the other + ; line-functions for details. + ; textlines : split-wisp-lines text + ; lines : linestoindented textlines + lisp : wisp2lisp text + display lisp + newline + . #f diff --git a/docs/srfi-119/wisp-scheme.scm b/docs/srfi-119/wisp-scheme.scm new file mode 100644 --- /dev/null +++ b/docs/srfi-119/wisp-scheme.scm @@ -0,0 +1,831 @@ +#!/bin/bash +(# -*- wisp -*-) +(exec guile -L . --language=wisp -s "$0" "$@") +; !# + +;; Scheme-only implementation of a wisp-preprocessor which output a +;; 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. + +;; Copyright (C) Arne Babenhauserheide (2014--2015). All Rights Reserved. + +;; Permission is hereby granted, free of charge, to any person +;; obtaining a copy of this software and associated documentation +;; files (the "Software"), to deal in the Software without +;; restriction, including without limitation the rights to use, copy, +;; modify, merge, publish, distribute, sublicense, and/or sell copies +;; of the Software, and to permit persons to whom the Software is +;; furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be +;; included in all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. + + +(define-module (wisp-scheme) + #:export (wisp-scheme-read-chunk wisp-scheme-read-all + wisp-scheme-read-file-chunk wisp-scheme-read-file + wisp-scheme-read-string)) + +; use curly-infix by default +(read-enable 'curly-infix) + +(use-modules + (srfi srfi-1) + (srfi srfi-11 ); for let-values + (ice-9 rw ); for write-string/partial + (ice-9 match)) + +;; Helper functions for the indent-and-symbols data structure: '((indent token token ...) ...) +(define (line-indent line) + (car line)) + +(define (line-real-indent line) + "Get the indentation without the comment-marker for unindented lines (-1 is treated as 0)." + (let (( indent (line-indent line))) + (if (= -1 indent) + 0 + indent))) + +(define (line-code line) + (let ((code (cdr line))) + ; propagate source properties + (when (not (null? code)) + (set-source-properties! code (source-properties line))) + code)) + +; literal values I need +(define readcolon + (string->symbol ":")) + +(define wisp-uuid "e749c73d-c826-47e2-a798-c16c13cb89dd") +; define an intermediate dot replacement with UUID to avoid clashes. +(define repr-dot ; . + (string->symbol (string-append "REPR-DOT-" wisp-uuid))) + +; allow using reader additions as the first element on a line to prefix the list +(define repr-quote ; ' + (string->symbol (string-append "REPR-QUOTE-" wisp-uuid))) +(define repr-unquote ; , + (string->symbol (string-append "REPR-UNQUOTE-" wisp-uuid))) +(define repr-quasiquote ; ` + (string->symbol (string-append "REPR-QUASIQUOTE-" wisp-uuid))) +(define repr-unquote-splicing ; ,@ + (string->symbol (string-append "REPR-UNQUOTESPLICING-" wisp-uuid))) + +(define repr-syntax ; #' + (string->symbol (string-append "REPR-SYNTAX-" wisp-uuid))) +(define repr-unsyntax ; #, + (string->symbol (string-append "REPR-UNSYNTAX-" wisp-uuid))) +(define repr-quasisyntax ; #` + (string->symbol (string-append "REPR-QUASISYNTAX-" wisp-uuid))) +(define repr-unsyntax-splicing ; #,@ + (string->symbol (string-append "REPR-UNSYNTAXSPLICING-" wisp-uuid))) + +; TODO: wrap the reader to return the repr of the syntax reader +; additions + +(define (match-charlist-to-repr charlist) + (let + ((chlist (reverse charlist))) + (cond + ((equal? chlist (list #\.)) + repr-dot) + ((equal? chlist (list #\')) + repr-quote) + ((equal? chlist (list #\,)) + repr-unquote) + ((equal? chlist (list #\`)) + repr-quasiquote) + ((equal? chlist (list #\, #\@ )) + repr-unquote-splicing) + ((equal? chlist (list #\# #\' )) + repr-syntax) + ((equal? chlist (list #\# #\, )) + repr-unsyntax) + ((equal? chlist (list #\# #\` )) + repr-quasisyntax) + ((equal? chlist (list #\# #\, #\@ )) + repr-unsyntax-splicing) + (else + #f)))) + +(define (wisp-read port) + "wrap read to catch list prefixes." + (let ((prefix-maxlen 4)) + (let longpeek + ((peeked '()) + (repr-symbol #f)) + (cond + ((or (< prefix-maxlen (length peeked)) (eof-object? (peek-char port)) (equal? #\space (peek-char port)) (equal? #\newline (peek-char port)) ) + (if repr-symbol ; found a special symbol, return it. + ; TODO: Somehow store source-properties. The commented-out code below does not work. + ; catch #t + ; lambda () + ; write : source-properties symbol-or-symbols + ; set-source-property! symbol-or-symbols 'filename : port-filename port + ; set-source-property! symbol-or-symbols 'line : 1+ : port-line port + ; set-source-property! symbol-or-symbols 'column : port-column port + ; write : source-properties symbol-or-symbols + ; lambda : key . arguments + ; . #f + repr-symbol + (let unpeek + ((remaining peeked)) + (cond + ((equal? '() remaining ) + (read port )); let read to the work + (else + (unread-char (car remaining) port) + (unpeek (cdr remaining))))))) + (else + (let* + ((next-char (read-char port)) + (peeked (cons next-char peeked))) + (longpeek + peeked + (match-charlist-to-repr peeked)))))))) + + + +(define (line-continues? line) + (equal? repr-dot (car (line-code line)))) + +(define (line-only-colon? line) + (and + (equal? ":" (car (line-code line))) + (null? (cdr (line-code line))))) + +(define (line-empty-code? line) + (null? (line-code line))) + +(define (line-empty? line) + (and + ; if indent is -1, we stripped a comment, so the line was not really empty. + (= 0 (line-indent line)) + (line-empty-code? line))) + +(define (line-strip-continuation line ) + (if (line-continues? line) + (append + (list + (line-indent line)) + (cdr (line-code line))) + line)) + +(define (line-strip-indentation-marker line) + "Strip the indentation markers from the beginning of the line" + (cdr line)) + +(define (indent-level-reduction indentation-levels level select-fun) + "Reduce the INDENTATION-LEVELS to the given LEVEL and return the value selected by SELECT-FUN" + (let loop + ((newlevels indentation-levels) + (diff 0)) + (cond + ((= level (car newlevels)) + (select-fun (list diff indentation-levels))) + ((< level (car newlevels)) + (loop + (cdr newlevels) + (1+ diff))) + (else + (throw 'wisp-syntax-error "Level ~A not found in the indentation-levels ~A."))))) + +(define (indent-level-difference indentation-levels level) + "Find how many indentation levels need to be popped off to find the given level." + (indent-level-reduction indentation-levels level + (lambda (x ); get the count + (car x)))) + +(define (indent-reduce-to-level indentation-levels level) + "Find how many indentation levels need to be popped off to find the given level." + (indent-level-reduction indentation-levels level + (lambda (x ); get the levels + (car (cdr x))))) + + +(define (wisp-scheme-read-chunk-lines port) + (let loop + ((indent-and-symbols (list )); '((5 "(foobar)" "\"yobble\"")(3 "#t")) + (inindent #t) + (inunderscoreindent (equal? #\_ (peek-char port))) + (incomment #f) + (currentindent 0) + (currentsymbols '()) + (emptylines 0)) + (cond + ((>= emptylines 2 ); the chunk end has to be checked + ; before we look for new chars in the + ; port to make execution in the REPL + ; after two empty lines work + ; (otherwise it shows one more line). + indent-and-symbols) + (else + (let ((next-char (peek-char port))) + (cond + ((eof-object? next-char) + (append indent-and-symbols (list (append (list currentindent) currentsymbols)))) + ((and inindent (zero? currentindent) (not incomment) (not (null? indent-and-symbols)) (not inunderscoreindent) (not (or (equal? #\space next-char) (equal? #\newline next-char) (equal? (string-ref ";" 0) next-char)))) + (append indent-and-symbols )); top-level form ends chunk + ((and inindent (equal? #\space next-char)) + (read-char port ); remove char + (loop + indent-and-symbols + #t ; inindent + #f ; inunderscoreindent + #f ; incomment + (1+ currentindent) + currentsymbols + emptylines)) + ((and inunderscoreindent (equal? #\_ next-char)) + (read-char port ); remove char + (loop + indent-and-symbols + #t ; inindent + #t ; inunderscoreindent + #f ; incomment + (1+ currentindent) + currentsymbols + emptylines)) + ; any char but whitespace *after* underscoreindent is + ; an error. This is stricter than the current wisp + ; syntax definition. TODO: Fix the definition. Better + ; start too strict. FIXME: breaks on lines with only + ; underscores which should empty lines. + ((and inunderscoreindent (and (not (equal? #\space next-char)) (not (equal? #\newline next-char)))) + (throw 'wisp-syntax-error "initial underscores without following whitespace at beginning of the line after" (last indent-and-symbols))) + ((equal? #\newline next-char) + (read-char port ); remove the newline + ; The following two lines would break the REPL by requiring one char too many. + ; if : and (equal? #\newline next-char) : equal? #\return : peek-char port + ; read-char port ; remove a full \n\r. Damn special cases... + (let* ; distinguish pure whitespace lines and lines + ; with comment by giving the former zero + ; indent. Lines with a comment at zero indent + ; get indent -1 for the same reason - meaning + ; not actually empty. + ( + (indent + (cond + (incomment + (if (= 0 currentindent ); specialcase + -1 + currentindent )) + ((not (null? currentsymbols )); pure whitespace + currentindent) + (else + 0))) + (parsedline (append (list indent) currentsymbols)) + (emptylines + (if (not (line-empty? parsedline)) + 0 + (1+ emptylines)))) + (when (not (= 0 (length parsedline))) + ; set the source properties to parsedline so we can try to add them later. + (set-source-property! parsedline 'filename (port-filename port)) + (set-source-property! parsedline 'line (port-line port))) + ; TODO: If the line is empty. Either do it here and do not add it, just + ; increment the empty line counter, or strip it later. Replace indent + ; -1 by indent 0 afterwards. + (loop + (append indent-and-symbols (list parsedline)) + #t ; inindent + (if (<= 2 emptylines) + #f ; chunk ends here + (equal? #\_ (peek-char port ))); are we in underscore indent? + #f ; incomment + 0 + '() + emptylines))) + ((equal? #t incomment) + (read-char port ); remove one comment character + (loop + indent-and-symbols + #f ; inindent + #f ; inunderscoreindent + #t ; incomment + currentindent + currentsymbols + emptylines)) + ((or (equal? #\space next-char) (equal? #\tab next-char) (equal? #\return next-char) ); remove whitespace when not in indent + (read-char port ); remove char + (loop + indent-and-symbols + #f ; inindent + #f ; inunderscoreindent + #f ; incomment + currentindent + currentsymbols + emptylines)) + ; | cludge to appease the former wisp parser + ; | which had a problem with the literal comment + ; v char. + ((equal? (string-ref ";" 0) next-char) + (loop + indent-and-symbols + #f ; inindent + #f ; inunderscoreindent + #t ; incomment + currentindent + currentsymbols + emptylines)) + (else ; use the reader + (loop + indent-and-symbols + #f ; inindent + #f ; inunderscoreindent + #f ; incomment + currentindent + ; this also takes care of the hashbang and leading comments. + ; TODO: If used from Guile, activate curly infix via read-options. + (append currentsymbols (list (wisp-read port))) + emptylines)))))))) + + +(define (line-code-replace-inline-colons line) + "Replace inline colons by opening parens which close at the end of the line" + ; format #t "replace inline colons for line ~A\n" line + (let loop + ((processed '()) + (unprocessed line)) + (cond + ((null? unprocessed) + ; format #t "inline-colons processed line: ~A\n" processed + processed) + ((equal? readcolon (car unprocessed)) + (loop + ; FIXME: This should turn unprocessed into a list. + (append processed + (list (loop '() (cdr unprocessed)))) + '())) + (else + (loop + (append processed + (list (car unprocessed))) + (cdr unprocessed)))))) + +(define (line-replace-inline-colons line) + (cons + (line-indent line) + (line-code-replace-inline-colons (line-code line)))) + +(define (line-strip-lone-colon line) + "A line consisting only of a colon is just a marked indentation level. We need to kill the colon before replacing inline colons." + (if + (equal? + (line-code line) + (list readcolon)) + (list (line-indent line)) + line)) + +(define (line-finalize line) + "Process all wisp-specific information in a line and strip it" + (let + ( + (l + (line-code-replace-inline-colons + (line-strip-indentation-marker + (line-strip-lone-colon + (line-strip-continuation line)))))) + (when (not (null? (source-properties line))) + (catch #t + (lambda () + (set-source-properties! l (source-properties line))) + (lambda (key . arguments) + #f))) + l)) + +(define (wisp-add-source-properties-from source target) + "Copy the source properties from source into the target and return the target." + (catch #t + (lambda () + (set-source-properties! target (source-properties source))) + (lambda (key . arguments) + #f)) + target) + +(define (wisp-propagate-source-properties code) + "Propagate the source properties from the sourrounding list into every part of the code." + (let loop + ((processed '()) + (unprocessed code)) + (cond + ((and (null? processed) (not (pair? unprocessed)) (not (list? unprocessed))) + unprocessed) + ((and (pair? unprocessed) (not (list? unprocessed))) + (cons + (wisp-propagate-source-properties (car unprocessed)) + (wisp-propagate-source-properties (cdr unprocessed)))) + ((null? unprocessed) + processed) + (else + (let ((line (car unprocessed))) + (if (null? (source-properties unprocessed)) + (wisp-add-source-properties-from line unprocessed) + (wisp-add-source-properties-from unprocessed line)) + (loop + (append processed (list (wisp-propagate-source-properties line))) + (cdr unprocessed))))))) + +(define (wisp-scheme-indentation-to-parens lines) + "Add parentheses to lines and remove the indentation markers" + ; FIXME: Find new algorithm which mostly uses current-line + ; and the indentation-levels for tracking. The try I have in + ; here right now is wrong. + (when + (and + (not (null? lines)) + (not (line-empty-code? (car lines))) + (not (= 0 (line-real-indent (car lines ))))); -1 is a line with a comment + (throw 'wisp-syntax-error + (format #f "The first symbol in a chunk must start at zero indentation. Indentation and line: ~A" + (car lines)))) + (let loop + ((processed '()) + (unprocessed lines) + (indentation-levels '(0))) + (let* + ( + (current-line + (if (<= 1 (length unprocessed)) + (car unprocessed) + (list 0 ))); empty code + (next-line + (if (<= 2 (length unprocessed)) + (car (cdr unprocessed)) + (list 0 ))); empty code + (current-indentation + (car indentation-levels)) + (current-line-indentation (line-real-indent current-line))) + ; format #t "processed: ~A\ncurrent-line: ~A\nnext-line: ~A\nunprocessed: ~A\nindentation-levels: ~A\ncurrent-indentation: ~A\n\n" + ; . processed current-line next-line unprocessed indentation-levels current-indentation + (cond + ; the real end: this is reported to the outside world. + ((and (null? unprocessed) (not (null? indentation-levels)) (null? (cdr indentation-levels))) + ; display "done\n" + ; reverse the processed lines, because I use cons. + processed) + ; the recursion end-condition + ((and (null? unprocessed)) + ; display "last step\n" + ; this is the last step. Nothing more to do except + ; for rolling up the indentation levels. return the + ; new processed and unprocessed lists: this is a + ; side-recursion + (values processed unprocessed)) + ((null? indentation-levels) + ; display "indentation-levels null\n" + (throw 'wisp-programming-error "The indentation-levels are null but the current-line is null: Something killed the indentation-levels.")) + (else ; now we come to the line-comparisons and indentation-counting. + (cond + ((line-empty-code? current-line) + ; display "current-line empty\n" + ; We cannot process indentation without + ; code. Just switch to the next line. This should + ; only happen at the start of the recursion. + ; TODO: Somehow preserve the line-numbers. + (loop + processed + (cdr unprocessed) + indentation-levels)) + ((and (line-empty-code? next-line) (<= 2 (length unprocessed ))) + ; display "next-line empty\n" + ; TODO: Somehow preserve the line-numbers. + ; take out the next-line from unprocessed. + (loop + processed + (cons current-line + (cdr (cdr unprocessed))) + indentation-levels)) + ((> current-indentation current-line-indentation) + ; display "current-indent > next-line\n" + ; this just steps back one level via the side-recursion. + (values processed unprocessed)) + ((= current-indentation current-line-indentation) + ; display "current-indent = next-line\n" + (let + ((line (line-finalize current-line)) + (next-line-indentation (line-real-indent next-line))) + (cond + ((>= current-line-indentation next-line-indentation) + ; simple recursiive step to the next line + ; display "current-line-indent >= next-line-indent\n" + (loop + (append processed + (if (line-continues? current-line) + line + (wisp-add-source-properties-from line (list line)))) + (cdr unprocessed ); recursion here + indentation-levels)) + ((< current-line-indentation next-line-indentation) + ; display "current-line-indent < next-line-indent\n" + ; format #t "line: ~A\n" line + ; side-recursion via a sublist + (let-values + ( + ((sub-processed sub-unprocessed) + (loop + line + (cdr unprocessed ); recursion here + indentation-levels))) + ; format #t "side-recursion:\n sub-processed: ~A\n processed: ~A\n\n" sub-processed processed + (loop + (append processed (list sub-processed)) + sub-unprocessed ; simply use the recursion from the sub-recursion + indentation-levels)))))) + ((< current-indentation current-line-indentation) + ; display "current-indent < next-line\n" + (loop + processed + unprocessed + (cons ; recursion via the indentation-levels + current-line-indentation + indentation-levels))) + (else + (throw 'wisp-not-implemented + (format #f "Need to implement further line comparison: current: ~A, next: ~A, processed: ~A." + current-line next-line processed))))))))) + + +(define (wisp-scheme-replace-inline-colons lines) + "Replace inline colons by opening parens which close at the end of the line" + (let loop + ((processed '()) + (unprocessed lines)) + (if (null? unprocessed) + processed + (loop + (append processed (list (line-replace-inline-colons (car unprocessed)))) + (cdr unprocessed))))) + + +(define (wisp-scheme-strip-indentation-markers lines) + "Strip the indentation markers from the beginning of the lines" + (let loop + ((processed '()) + (unprocessed lines)) + (if (null? unprocessed) + processed + (loop + (append processed (cdr (car unprocessed))) + (cdr unprocessed))))) + +(define (wisp-unescape-underscore-and-colon code) + "replace \\_ and \\: by _ and :" + (match code + ((a ...) + (map wisp-unescape-underscore-and-colon a)) + ('\_ + '_) + ('\: + ':) + (a + a))) + + +(define (wisp-replace-empty-eof code) + "replace ((#<eof>)) by ()" + ; FIXME: Actually this is a hack which fixes a bug when the + ; parser hits files with only hashbang and comments. + (if (and (not (null? code)) (pair? (car code)) (eof-object? (car (car code))) (null? (cdr code)) (null? (cdr (car code)))) + (list) + code)) + + +(define (wisp-replace-paren-quotation-repr code) + "Replace lists starting with a quotation symbol by + quoted lists." + (match code + (('REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'quote (map wisp-replace-paren-quotation-repr a))) + ((a ... 'REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ); this is the quoted empty list + (append + (map wisp-replace-paren-quotation-repr a) + (list (list 'quote (map wisp-replace-paren-quotation-repr b))))) + (('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 ) + (append + (map wisp-replace-paren-quotation-repr a) + (list (list 'unquote (map wisp-replace-paren-quotation-repr b))))) + (('REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'quasiquote (map wisp-replace-paren-quotation-repr a))) + ((a ... 'REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ); this is the quoted empty list + (append + (map wisp-replace-paren-quotation-repr a) + (list (list 'quasiquote (map wisp-replace-paren-quotation-repr b))))) + (('REPR-UNQUOTESPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'unquote-splicing (map wisp-replace-paren-quotation-repr a))) + (('REPR-SYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'syntax (map wisp-replace-paren-quotation-repr a))) + (('REPR-UNSYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'unsyntax (map wisp-replace-paren-quotation-repr a))) + (('REPR-QUASISYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'quasisyntax (map wisp-replace-paren-quotation-repr a))) + (('REPR-UNSYNTAXSPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ...) + (list 'unsyntax-splicing (map wisp-replace-paren-quotation-repr a))) + ((a ...) + (map wisp-replace-paren-quotation-repr a)) + (a + a))) + +(define (wisp-make-improper code) + "Turn (a #{.}# b) into the correct (a . b). + +read called on a single dot creates a variable named #{.}# (|.| +in r7rs). Due to parsing the indentation before the list +structure is known, the reader cannot create improper lists +when it reads a dot. So we have to take another pass over the +code to recreate the improper lists. + +Match is awesome!" + (let + ( + (improper + (match code + ((a ... b 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd c) + (append (map wisp-make-improper a) + (cons (wisp-make-improper b) (wisp-make-improper c)))) + ((a ...) + (map wisp-make-improper a)) + (a + a)))) + (define (syntax-error li msg) + (throw 'wisp-syntax-error (format #f "incorrect dot-syntax #{.}# in code: ~A: ~A" msg li))) + (if #t + improper + (let check + ((tocheck improper)) + (match tocheck + ; lists with only one member + (('REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd) + (syntax-error tocheck "list with the period as only member")) + ; list with remaining dot. + ((a ...) + (if (and (member repr-dot a)) + (syntax-error tocheck "leftover period in list") + (map check a))) + ; simple pair - this and the next do not work when parsed from wisp-scheme itself. Why? + (('REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd . c) + (syntax-error tocheck "dot as first element in already improper pair")) + ; simple pair, other way round + ((a . 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd) + (syntax-error tocheck "dot as last element in already improper pair")) + ; more complex pairs + ((? pair? a) + (let + ((head (drop-right a 1)) + (tail (last-pair a))) + (cond + ((equal? repr-dot (car tail)) + (syntax-error tocheck "equal? repr-dot : car tail")) + ((equal? repr-dot (cdr tail)) + (syntax-error tocheck "equal? repr-dot : cdr tail")) + ((member repr-dot head) + (syntax-error tocheck "member repr-dot head")) + (else + a)))) + (a + a)))))) + +(define (wisp-scheme-read-chunk port) + "Read and parse one chunk of wisp-code" + (let (( lines (wisp-scheme-read-chunk-lines port))) + (wisp-make-improper + (wisp-replace-empty-eof + (wisp-unescape-underscore-and-colon + (wisp-replace-paren-quotation-repr + (wisp-propagate-source-properties + (wisp-scheme-indentation-to-parens lines)))))))) + +(define (wisp-scheme-read-all port) + "Read all chunks from the given port" + (let loop + ((tokens '())) + (cond + ((eof-object? (peek-char port)) + tokens) + (else + (loop + (append tokens (wisp-scheme-read-chunk port))))))) + +(define (wisp-scheme-read-file path) + (call-with-input-file path wisp-scheme-read-all)) + +(define (wisp-scheme-read-file-chunk path) + (call-with-input-file path wisp-scheme-read-chunk)) + +(define (wisp-scheme-read-string str) + (call-with-input-string str wisp-scheme-read-all)) + +(define (wisp-scheme-read-string-chunk str) + (call-with-input-string str wisp-scheme-read-chunk)) + + +;;;; Test special syntax +; ;; quote the list +; write +; wisp-scheme-read-string "moo +; foo +; ' bar +; baz waz" +; newline +; ;; quote the symbol - in wisp, whitespace after quote is not allowed! +; write +; wisp-scheme-read-string "moo +; foo +; 'bar +; baz waz" +; newline +; ; ;; quote the list with colon +; write +; wisp-scheme-read-string "moo : ' foo +; foo +; ' bar bah +; baz waz" +; newline +; ; ;; syntax the list +; write +; wisp-scheme-read-string "moo : #' foo +; foo +; #' bar bah +; baz waz" +; newline +; +;;;; Test improper lists +;;;; Good cases +; write +; wisp-scheme-read-string "foo . bar" +; newline +; write +; wisp-scheme-read-string "foo . +; . bar" +; newline +; write +; wisp-scheme-read-string "foo +; . . bar" +; newline +; write +; wisp-scheme-read-string "moo +; foo +; . . bar +; baz waz" +; newline +;;;; Syntax Error cases +; write +; wisp-scheme-read-string "foo +; . . ." +; newline +; write +; wisp-scheme-read-string "moo : . . bar" +; write +; wisp-scheme-read-string "foo . +; . . bar" +; newline +; write +; wisp-scheme-read-string "moo +; foo +; . . bar baz +; baz waz" +; newline +;;;; stranger stuff +; write +; wisp-scheme-read-string "foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo \"\n\" \n___ . goo . hoo" +; newline +; display +; wisp-scheme-read-string " foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo" +; newline +; write : wisp-scheme-read-file-chunk "wisp-scheme.w" +; newline +; call-with-output-file "wisp-guile.scm" +; lambda : port +; map +; lambda : chunk +; write chunk port +; wisp-scheme-read-file "wisp-guile.w" +; run all chunks in wisp-guile.w as parsed by wisp-scheme.w. Give wisp-guile.w to parse as argument. +; map primitive-eval : wisp-scheme-read-file "wisp-guile.w" ; actually runs wisp-guile.w with the arguments supplied to this script. +; uncomment the previous line, then run the next line in the shell. If 1 and 2 are equal, this parser works! +; guile wisp.scm wisp-scheme.w > wisp-scheme.scm; guile wisp-scheme.scm wisp-guile.w > 1; guile wisp.scm wisp-guile.w > 2; diff 1 2 + + + diff --git a/docs/srfi-119/wisp-scheme.w b/docs/srfi-119/wisp-scheme.w new file mode 100755 --- /dev/null +++ b/docs/srfi-119/wisp-scheme.w @@ -0,0 +1,829 @@ +#!/bin/bash +# -*- wisp -*- +exec guile -L . --language=wisp -s "$0" "$@" +; !# + +;; Scheme-only implementation of a wisp-preprocessor which output a +;; 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. + +;; Copyright (C) Arne Babenhauserheide (2014--2015). All Rights Reserved. + +;; Permission is hereby granted, free of charge, to any person +;; obtaining a copy of this software and associated documentation +;; files (the "Software"), to deal in the Software without +;; restriction, including without limitation the rights to use, copy, +;; modify, merge, publish, distribute, sublicense, and/or sell copies +;; of the Software, and to permit persons to whom the Software is +;; furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be +;; included in all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. + + +define-module : wisp-scheme + . #:export (wisp-scheme-read-chunk wisp-scheme-read-all + wisp-scheme-read-file-chunk wisp-scheme-read-file + wisp-scheme-read-string) + +; use curly-infix by default +read-enable 'curly-infix + +use-modules + srfi srfi-1 + srfi srfi-11 ; for let-values + ice-9 rw ; for write-string/partial + ice-9 match + +;; Helper functions for the indent-and-symbols data structure: '((indent token token ...) ...) +define : line-indent line + car line + +define : line-real-indent line + . "Get the indentation without the comment-marker for unindented lines (-1 is treated as 0)." + let : : indent : line-indent line + if : = -1 indent + . 0 + . indent + +define : line-code line + let : : code : cdr line + ; propagate source properties + when : not : null? code + set-source-properties! code : source-properties line + . code + +; literal values I need +define readcolon + string->symbol ":" + +define wisp-uuid "e749c73d-c826-47e2-a798-c16c13cb89dd" +; define an intermediate dot replacement with UUID to avoid clashes. +define repr-dot ; . + string->symbol : string-append "REPR-DOT-" wisp-uuid + +; allow using reader additions as the first element on a line to prefix the list +define repr-quote ; ' + string->symbol : string-append "REPR-QUOTE-" wisp-uuid +define repr-unquote ; , + string->symbol : string-append "REPR-UNQUOTE-" wisp-uuid +define repr-quasiquote ; ` + string->symbol : string-append "REPR-QUASIQUOTE-" wisp-uuid +define repr-unquote-splicing ; ,@ + string->symbol : string-append "REPR-UNQUOTESPLICING-" wisp-uuid + +define repr-syntax ; #' + string->symbol : string-append "REPR-SYNTAX-" wisp-uuid +define repr-unsyntax ; #, + string->symbol : string-append "REPR-UNSYNTAX-" wisp-uuid +define repr-quasisyntax ; #` + string->symbol : string-append "REPR-QUASISYNTAX-" wisp-uuid +define repr-unsyntax-splicing ; #,@ + string->symbol : string-append "REPR-UNSYNTAXSPLICING-" wisp-uuid + +; TODO: wrap the reader to return the repr of the syntax reader +; additions + +define : match-charlist-to-repr charlist + let + : chlist : reverse charlist + cond + : equal? chlist : list #\. + . repr-dot + : equal? chlist : list #\' + . repr-quote + : equal? chlist : list #\, + . repr-unquote + : equal? chlist : list #\` + . repr-quasiquote + : equal? chlist : list #\, #\@ + . repr-unquote-splicing + : equal? chlist : list #\# #\' + . repr-syntax + : equal? chlist : list #\# #\, + . repr-unsyntax + : equal? chlist : list #\# #\` + . repr-quasisyntax + : equal? chlist : list #\# #\, #\@ + . repr-unsyntax-splicing + else + . #f + +define : wisp-read port + . "wrap read to catch list prefixes." + let : : prefix-maxlen 4 + let longpeek + : peeked '() + repr-symbol #f + cond + : or (< prefix-maxlen (length peeked)) (eof-object? (peek-char port)) (equal? #\space (peek-char port)) (equal? #\newline (peek-char port)) + if repr-symbol ; found a special symbol, return it. + ; TODO: Somehow store source-properties. The commented-out code below does not work. + ; catch #t + ; lambda () + ; write : source-properties symbol-or-symbols + ; set-source-property! symbol-or-symbols 'filename : port-filename port + ; set-source-property! symbol-or-symbols 'line : 1+ : port-line port + ; set-source-property! symbol-or-symbols 'column : port-column port + ; write : source-properties symbol-or-symbols + ; lambda : key . arguments + ; . #f + . repr-symbol + let unpeek + : remaining peeked + cond + : equal? '() remaining + read port ; let read to the work + else + unread-char (car remaining) port + unpeek : cdr remaining + else + let* + : next-char : read-char port + peeked : cons next-char peeked + longpeek + . peeked + match-charlist-to-repr peeked + + + +define : line-continues? line + equal? repr-dot : car : line-code line + +define : line-only-colon? line + and + equal? ":" : car : line-code line + null? : cdr : line-code line + +define : line-empty-code? line + null? : line-code line + +define : line-empty? line + and + ; if indent is -1, we stripped a comment, so the line was not really empty. + = 0 : line-indent line + line-empty-code? line + +define : line-strip-continuation line + if : line-continues? line + append + list + line-indent line + cdr : line-code line + . line + +define : line-strip-indentation-marker line + . "Strip the indentation markers from the beginning of the line" + cdr line + +define : indent-level-reduction indentation-levels level select-fun + . "Reduce the INDENTATION-LEVELS to the given LEVEL and return the value selected by SELECT-FUN" + let loop + : newlevels indentation-levels + diff 0 + cond + : = level : car newlevels + select-fun : list diff indentation-levels + : < level : car newlevels + loop + cdr newlevels + 1+ diff + else + throw 'wisp-syntax-error "Level ~A not found in the indentation-levels ~A." + +define : indent-level-difference indentation-levels level + . "Find how many indentation levels need to be popped off to find the given level." + indent-level-reduction indentation-levels level + lambda : x ; get the count + car x + +define : indent-reduce-to-level indentation-levels level + . "Find how many indentation levels need to be popped off to find the given level." + indent-level-reduction indentation-levels level + lambda : x ; get the levels + car : cdr x + + +define : wisp-scheme-read-chunk-lines port + let loop + : indent-and-symbols : list ; '((5 "(foobar)" "\"yobble\"")(3 "#t")) + inindent #t + inunderscoreindent : equal? #\_ : peek-char port + incomment #f + currentindent 0 + currentsymbols '() + emptylines 0 + cond + : >= emptylines 2 ; the chunk end has to be checked + ; before we look for new chars in the + ; port to make execution in the REPL + ; after two empty lines work + ; (otherwise it shows one more line). + . indent-and-symbols + else + let : : next-char : peek-char port + cond + : eof-object? next-char + append indent-and-symbols : list : append (list currentindent) currentsymbols + : and inindent (zero? currentindent) (not incomment) (not (null? indent-and-symbols)) (not inunderscoreindent) (not (or (equal? #\space next-char) (equal? #\newline next-char) (equal? (string-ref ";" 0) next-char))) + append indent-and-symbols ; top-level form ends chunk + : and inindent : equal? #\space next-char + read-char port ; remove char + loop + . indent-and-symbols + . #t ; inindent + . #f ; inunderscoreindent + . #f ; incomment + 1+ currentindent + . currentsymbols + . emptylines + : and inunderscoreindent : equal? #\_ next-char + read-char port ; remove char + loop + . indent-and-symbols + . #t ; inindent + . #t ; inunderscoreindent + . #f ; incomment + 1+ currentindent + . currentsymbols + . emptylines + ; any char but whitespace *after* underscoreindent is + ; an error. This is stricter than the current wisp + ; syntax definition. TODO: Fix the definition. Better + ; start too strict. FIXME: breaks on lines with only + ; underscores which should empty lines. + : and inunderscoreindent : and (not (equal? #\space next-char)) (not (equal? #\newline next-char)) + throw 'wisp-syntax-error "initial underscores without following whitespace at beginning of the line after" : last indent-and-symbols + : equal? #\newline next-char + read-char port ; remove the newline + ; The following two lines would break the REPL by requiring one char too many. + ; if : and (equal? #\newline next-char) : equal? #\return : peek-char port + ; read-char port ; remove a full \n\r. Damn special cases... + let* ; distinguish pure whitespace lines and lines + ; with comment by giving the former zero + ; indent. Lines with a comment at zero indent + ; get indent -1 for the same reason - meaning + ; not actually empty. + : + indent + cond + incomment + if : = 0 currentindent ; specialcase + . -1 + . currentindent + : not : null? currentsymbols ; pure whitespace + . currentindent + else + . 0 + parsedline : append (list indent) currentsymbols + emptylines + if : not : line-empty? parsedline + . 0 + 1+ emptylines + when : not : = 0 : length parsedline + ; set the source properties to parsedline so we can try to add them later. + set-source-property! parsedline 'filename : port-filename port + set-source-property! parsedline 'line : port-line port + ; TODO: If the line is empty. Either do it here and do not add it, just + ; increment the empty line counter, or strip it later. Replace indent + ; -1 by indent 0 afterwards. + loop + append indent-and-symbols : list parsedline + . #t ; inindent + if : <= 2 emptylines + . #f ; chunk ends here + equal? #\_ : peek-char port ; are we in underscore indent? + . #f ; incomment + . 0 + . '() + . emptylines + : equal? #t incomment + read-char port ; remove one comment character + loop + . indent-and-symbols + . #f ; inindent + . #f ; inunderscoreindent + . #t ; incomment + . currentindent + . currentsymbols + . emptylines + : or (equal? #\space next-char) (equal? #\tab next-char) (equal? #\return next-char) ; remove whitespace when not in indent + read-char port ; remove char + loop + . indent-and-symbols + . #f ; inindent + . #f ; inunderscoreindent + . #f ; incomment + . currentindent + . currentsymbols + . emptylines + ; | cludge to appease the former wisp parser + ; | which had a problem with the literal comment + ; v char. + : equal? (string-ref ";" 0) next-char + loop + . indent-and-symbols + . #f ; inindent + . #f ; inunderscoreindent + . #t ; incomment + . currentindent + . currentsymbols + . emptylines + else ; use the reader + loop + . indent-and-symbols + . #f ; inindent + . #f ; inunderscoreindent + . #f ; incomment + . currentindent + ; this also takes care of the hashbang and leading comments. + ; TODO: If used from Guile, activate curly infix via read-options. + append currentsymbols : list : wisp-read port + . emptylines + + +define : line-code-replace-inline-colons line + . "Replace inline colons by opening parens which close at the end of the line" + ; format #t "replace inline colons for line ~A\n" line + let loop + : processed '() + unprocessed line + cond + : null? unprocessed + ; format #t "inline-colons processed line: ~A\n" processed + . processed + : equal? readcolon : car unprocessed + loop + ; FIXME: This should turn unprocessed into a list. + append processed + list : loop '() (cdr unprocessed) + . '() + else + loop + append processed + list : car unprocessed + cdr unprocessed + +define : line-replace-inline-colons line + cons + line-indent line + line-code-replace-inline-colons : line-code line + +define : line-strip-lone-colon line + . "A line consisting only of a colon is just a marked indentation level. We need to kill the colon before replacing inline colons." + if + equal? + line-code line + list readcolon + list : line-indent line + . line + +define : line-finalize line + . "Process all wisp-specific information in a line and strip it" + let + : + l + line-code-replace-inline-colons + line-strip-indentation-marker + line-strip-lone-colon + line-strip-continuation line + when : not : null? : source-properties line + catch #t + lambda () + set-source-properties! l : source-properties line + lambda : key . arguments + . #f + . l + +define : wisp-add-source-properties-from source target + . "Copy the source properties from source into the target and return the target." + catch #t + lambda () + set-source-properties! target : source-properties source + lambda : key . arguments + . #f + . target + +define : wisp-propagate-source-properties code + . "Propagate the source properties from the sourrounding list into every part of the code." + let loop + : processed '() + unprocessed code + cond + : and (null? processed) (not (pair? unprocessed)) (not (list? unprocessed)) + . unprocessed + : and (pair? unprocessed) (not (list? unprocessed)) + cons + wisp-propagate-source-properties (car unprocessed) + wisp-propagate-source-properties (cdr unprocessed) + : null? unprocessed + . processed + else + let : : line : car unprocessed + if : null? : source-properties unprocessed + wisp-add-source-properties-from line unprocessed + wisp-add-source-properties-from unprocessed line + loop + append processed : list : wisp-propagate-source-properties line + cdr unprocessed + +define : wisp-scheme-indentation-to-parens lines + . "Add parentheses to lines and remove the indentation markers" + ; FIXME: Find new algorithm which mostly uses current-line + ; and the indentation-levels for tracking. The try I have in + ; here right now is wrong. + when + and + not : null? lines + not : line-empty-code? : car lines + not : = 0 : line-real-indent : car lines ; -1 is a line with a comment + throw 'wisp-syntax-error + format #f "The first symbol in a chunk must start at zero indentation. Indentation and line: ~A" + car lines + let loop + : processed '() + unprocessed lines + indentation-levels '(0) + let* + : + current-line + if : <= 1 : length unprocessed + car unprocessed + list 0 ; empty code + next-line + if : <= 2 : length unprocessed + car : cdr unprocessed + list 0 ; empty code + current-indentation + car indentation-levels + current-line-indentation : line-real-indent current-line + ; format #t "processed: ~A\ncurrent-line: ~A\nnext-line: ~A\nunprocessed: ~A\nindentation-levels: ~A\ncurrent-indentation: ~A\n\n" + ; . processed current-line next-line unprocessed indentation-levels current-indentation + cond + ; the real end: this is reported to the outside world. + : and (null? unprocessed) (not (null? indentation-levels)) (null? (cdr indentation-levels)) + ; display "done\n" + ; reverse the processed lines, because I use cons. + . processed + ; the recursion end-condition + : and (null? unprocessed) + ; display "last step\n" + ; this is the last step. Nothing more to do except + ; for rolling up the indentation levels. return the + ; new processed and unprocessed lists: this is a + ; side-recursion + values processed unprocessed + : null? indentation-levels + ; display "indentation-levels null\n" + throw 'wisp-programming-error "The indentation-levels are null but the current-line is null: Something killed the indentation-levels." + else ; now we come to the line-comparisons and indentation-counting. + cond + : line-empty-code? current-line + ; display "current-line empty\n" + ; We cannot process indentation without + ; code. Just switch to the next line. This should + ; only happen at the start of the recursion. + ; TODO: Somehow preserve the line-numbers. + loop + . processed + cdr unprocessed + . indentation-levels + : and (line-empty-code? next-line) : <= 2 : length unprocessed + ; display "next-line empty\n" + ; TODO: Somehow preserve the line-numbers. + ; take out the next-line from unprocessed. + loop + . processed + cons current-line + cdr : cdr unprocessed + . indentation-levels + : > current-indentation current-line-indentation + ; display "current-indent > next-line\n" + ; this just steps back one level via the side-recursion. + values processed unprocessed + : = current-indentation current-line-indentation + ; display "current-indent = next-line\n" + let + : line : line-finalize current-line + next-line-indentation : line-real-indent next-line + cond + : >= current-line-indentation next-line-indentation + ; simple recursiive step to the next line + ; display "current-line-indent >= next-line-indent\n" + loop + append processed + if : line-continues? current-line + . line + wisp-add-source-properties-from line : list line + cdr unprocessed ; recursion here + . indentation-levels + : < current-line-indentation next-line-indentation + ; display "current-line-indent < next-line-indent\n" + ; format #t "line: ~A\n" line + ; side-recursion via a sublist + let-values + : + : sub-processed sub-unprocessed + loop + . line + cdr unprocessed ; recursion here + . indentation-levels + ; format #t "side-recursion:\n sub-processed: ~A\n processed: ~A\n\n" sub-processed processed + loop + append processed : list sub-processed + . sub-unprocessed ; simply use the recursion from the sub-recursion + . indentation-levels + : < current-indentation current-line-indentation + ; display "current-indent < next-line\n" + loop + . processed + . unprocessed + cons ; recursion via the indentation-levels + . current-line-indentation + . indentation-levels + else + throw 'wisp-not-implemented + format #f "Need to implement further line comparison: current: ~A, next: ~A, processed: ~A." + . current-line next-line processed + + +define : wisp-scheme-replace-inline-colons lines + . "Replace inline colons by opening parens which close at the end of the line" + let loop + : processed '() + unprocessed lines + if : null? unprocessed + . processed + loop + append processed : list : line-replace-inline-colons : car unprocessed + cdr unprocessed + + +define : wisp-scheme-strip-indentation-markers lines + . "Strip the indentation markers from the beginning of the lines" + let loop + : processed '() + unprocessed lines + if : null? unprocessed + . processed + loop + append processed : cdr : car unprocessed + cdr unprocessed + +define : wisp-unescape-underscore-and-colon code + . "replace \\_ and \\: by _ and :" + match code + : a ... + map wisp-unescape-underscore-and-colon a + '\_ + . '_ + '\: + . ': + a + . a + + +define : wisp-replace-empty-eof code + . "replace ((#<eof>)) by ()" + ; FIXME: Actually this is a hack which fixes a bug when the + ; parser hits files with only hashbang and comments. + if : and (not (null? code)) (pair? (car code)) (eof-object? (car (car code))) (null? (cdr code)) (null? (cdr (car code))) + list + . code + + +define : wisp-replace-paren-quotation-repr code + . "Replace lists starting with a quotation symbol by + quoted lists." + match code + : 'REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'quote : map wisp-replace-paren-quotation-repr a + : a ... 'REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ; this is the quoted empty list + append + map wisp-replace-paren-quotation-repr a + list : list 'quote : map wisp-replace-paren-quotation-repr b + : '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 + append + map wisp-replace-paren-quotation-repr a + list : list 'unquote : map wisp-replace-paren-quotation-repr b + : 'REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'quasiquote : map wisp-replace-paren-quotation-repr a + : a ... 'REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ; this is the quoted empty list + append + map wisp-replace-paren-quotation-repr a + list : list 'quasiquote : map wisp-replace-paren-quotation-repr b + : 'REPR-UNQUOTESPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'unquote-splicing : map wisp-replace-paren-quotation-repr a + : 'REPR-SYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'syntax : map wisp-replace-paren-quotation-repr a + : 'REPR-UNSYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'unsyntax : map wisp-replace-paren-quotation-repr a + : 'REPR-QUASISYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'quasisyntax : map wisp-replace-paren-quotation-repr a + : 'REPR-UNSYNTAXSPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ... + list 'unsyntax-splicing : map wisp-replace-paren-quotation-repr a + : a ... + map wisp-replace-paren-quotation-repr a + a + . a + +define : wisp-make-improper code + . "Turn (a #{.}# b) into the correct (a . b). + +read called on a single dot creates a variable named #{.}# (|.| +in r7rs). Due to parsing the indentation before the list +structure is known, the reader cannot create improper lists +when it reads a dot. So we have to take another pass over the +code to recreate the improper lists. + +Match is awesome!" + let + : + improper + match code + : a ... b 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd c + append (map wisp-make-improper a) + cons (wisp-make-improper b) (wisp-make-improper c) + : a ... + map wisp-make-improper a + a + . a + define : syntax-error li msg + throw 'wisp-syntax-error (format #f "incorrect dot-syntax #{.}# in code: ~A: ~A" msg li) + if #t + . improper + let check + : tocheck improper + match tocheck + ; lists with only one member + : 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd + syntax-error tocheck "list with the period as only member" + ; list with remaining dot. + : a ... + if : and (member repr-dot a) + syntax-error tocheck "leftover period in list" + map check a + ; simple pair - this and the next do not work when parsed from wisp-scheme itself. Why? + : 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd . c + syntax-error tocheck "dot as first element in already improper pair" + ; simple pair, other way round + : a . 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd + syntax-error tocheck "dot as last element in already improper pair" + ; more complex pairs + : ? pair? a + let + : head : drop-right a 1 + tail : last-pair a + cond + : equal? repr-dot : car tail + syntax-error tocheck "equal? repr-dot : car tail" + : equal? repr-dot : cdr tail + syntax-error tocheck "equal? repr-dot : cdr tail" + : member repr-dot head + syntax-error tocheck "member repr-dot head" + else + . a + a + . a + +define : wisp-scheme-read-chunk port + . "Read and parse one chunk of wisp-code" + let : : lines : wisp-scheme-read-chunk-lines port + wisp-make-improper + wisp-replace-empty-eof + wisp-unescape-underscore-and-colon + wisp-replace-paren-quotation-repr + wisp-propagate-source-properties + wisp-scheme-indentation-to-parens lines + +define : wisp-scheme-read-all port + . "Read all chunks from the given port" + let loop + : tokens '() + cond + : eof-object? : peek-char port + . tokens + else + loop + append tokens : wisp-scheme-read-chunk port + +define : wisp-scheme-read-file path + call-with-input-file path wisp-scheme-read-all + +define : wisp-scheme-read-file-chunk path + call-with-input-file path wisp-scheme-read-chunk + +define : wisp-scheme-read-string str + call-with-input-string str wisp-scheme-read-all + +define : wisp-scheme-read-string-chunk str + call-with-input-string str wisp-scheme-read-chunk + + +;;;; Test special syntax +; ;; quote the list +; write +; wisp-scheme-read-string "moo +; foo +; ' bar +; baz waz" +; newline +; ;; quote the symbol - in wisp, whitespace after quote is not allowed! +; write +; wisp-scheme-read-string "moo +; foo +; 'bar +; baz waz" +; newline +; ; ;; quote the list with colon +; write +; wisp-scheme-read-string "moo : ' foo +; foo +; ' bar bah +; baz waz" +; newline +; ; ;; syntax the list +; write +; wisp-scheme-read-string "moo : #' foo +; foo +; #' bar bah +; baz waz" +; newline +; +;;;; Test improper lists +;;;; Good cases +; write +; wisp-scheme-read-string "foo . bar" +; newline +; write +; wisp-scheme-read-string "foo . +; . bar" +; newline +; write +; wisp-scheme-read-string "foo +; . . bar" +; newline +; write +; wisp-scheme-read-string "moo +; foo +; . . bar +; baz waz" +; newline +;;;; Syntax Error cases +; write +; wisp-scheme-read-string "foo +; . . ." +; newline +; write +; wisp-scheme-read-string "moo : . . bar" +; write +; wisp-scheme-read-string "foo . +; . . bar" +; newline +; write +; wisp-scheme-read-string "moo +; foo +; . . bar baz +; baz waz" +; newline +;;;; stranger stuff +; write +; wisp-scheme-read-string "foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo \"\n\" \n___ . goo . hoo" +; newline +; display +; wisp-scheme-read-string " foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo" +; newline +; write : wisp-scheme-read-file-chunk "wisp-scheme.w" +; newline +; call-with-output-file "wisp-guile.scm" +; lambda : port +; map +; lambda : chunk +; write chunk port +; wisp-scheme-read-file "wisp-guile.w" +; run all chunks in wisp-guile.w as parsed by wisp-scheme.w. Give wisp-guile.w to parse as argument. +; map primitive-eval : wisp-scheme-read-file "wisp-guile.w" ; actually runs wisp-guile.w with the arguments supplied to this script. +; uncomment the previous line, then run the next line in the shell. If 1 and 2 are equal, this parser works! +; guile wisp.scm wisp-scheme.w > wisp-scheme.scm; guile wisp-scheme.scm wisp-guile.w > 1; guile wisp.scm wisp-guile.w > 2; diff 1 2 + diff --git a/examples/cholesky.w b/examples/cholesky.w new file mode 100644 --- /dev/null +++ b/examples/cholesky.w @@ -0,0 +1,46 @@ +#!/usr/bin/env sh +# -*- wisp -*- +exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples cholesky) main)' -s "$0" "$@" +; !# + +;; Cholesky decomposition, following https://de.wikipedia.org/wiki/Cholesky-Zerlegung#Pseudocode + +define-module : examples cholesky + . #:exports : cholesky! + +use-modules : guildhall ext foof-loop + +define : matrrix-ref X u v + list-ref (list-ref X u) v + +define : matrrix-set! X u v val + list-set! (list-ref X u) v val + + +define : cholesky! a + . "Modifies the square matirx a to contain its cholesky decomposition. + +sets a to g with a = ggT, + +a is represented as list of lists." + let : : n : length a + loop : : for i : up-from 1 : to n + loop : : for j : up-from 1 : to i + let : : sum : matrix-ref a i j + when : >= j 1 + loop : : for k : up-from 1 : to {j - 1} + set! sum : - sum : * (matrix-ref a i k) (matrix-ref a j k) + cond + : > i j ; lower triangle + matrix-set! a i j + / sum : matrix-ref a j j + . a + : > sum 0 ; diagonal element + matrix-set! a i i : sqrt sum + . a + else + throw 'matrix-numerically-not-symmetric-positive-definite + + +define : main args + display : cholesky! '((1 2)(2 4)) diff --git a/examples/closure.w b/examples/closure.w new file mode 100644 --- /dev/null +++ b/examples/closure.w @@ -0,0 +1,26 @@ +#!/usr/bin/env sh +# -*- wisp -*- +exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples closure) main)' -s "$0" "$@" +; !# + +;; A simple example for a closure + + +define counting-closure ; simple variable + let : : counter 0 ; provide counter to hold local data + lambda () ; the variable is bound to a function -> callable + set! counter : 1+ counter ; adjust the counter shared by all function calls + . counter + + +; counter is created outside the function definition (lambda), so the +; change survives over function calls. It is function-local data. + + +define : main args + display : counting-closure + newline ; 1 + display : counting-closure + newline ; 2 + display : counting-closure + newline ; 3 diff --git a/examples/evolve.w b/examples/evolve.w old mode 100644 new mode 100755 --- a/examples/evolve.w +++ b/examples/evolve.w @@ -1,20 +1,38 @@ -#!/home/arne/wisp/wisp-multiline.sh +#!/usr/bin/env sh +# -*- wisp -*- +exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples evolve) main)' -s "$0" "$@" ; !# -; A small experiment on a complete evolutionary algorithm. +; One thousand monkeys: A small experiment on a complete evolutionary algorithm. + +; TODO: genetic with mutate+combinate, population which keeps the best and directed which only keeps improvements ; NOTE: This only works after preprocessing to scheme. +define-module : examples evolve + . #:export : main + ; Get the eval string which allows for selecting the language. use-modules : ice-9 eval-string +define evalsyntax "0123456789+-*/: ()" -define evalsyntax "0123456789+-*/ ()" + +define : paired-char? char + or (equal? #\) char) (equal? #\( char) + define : mutate-replace evalstring - let + let* : eval-index : random : string-length evalstring replace-index : random : string-length evalsyntax + remove-char : string-ref evalstring eval-index + insert-char : string-ref evalsyntax replace-index + ; double step, if mutating a paired character + evalstring + if : not : or (paired-char? insert-char) (paired-char? remove-char) + . evalstring + mutate-replace evalstring string-replace evalstring evalsyntax eval-index : + eval-index 1 . replace-index : + replace-index 1 @@ -28,29 +46,44 @@ define : mutate-permutate evalstring define : mutate-insert evalstring - let + let* : eval-index : random : string-length evalstring insert-index : random : string-length evalsyntax + insert-char : string-ref evalsyntax insert-index + ; double step, if mutating a paired character + evalstring + if : not : paired-char? insert-char + . evalstring + mutate-insert evalstring string-append substring evalstring 0 eval-index - string : string-ref evalsyntax insert-index + string insert-char substring evalstring eval-index +define : mutate-remove-by-index evalstring index + string-append + substring evalstring 0 index + substring evalstring : + index 1 + + define : mutate-remove evalstring if : <= 1 : string-length evalstring ; cannot remove from a 0 string . evalstring - let + let* : eval-index : random : - (string-length evalstring) 1 - string-append - substring evalstring 0 eval-index - substring evalstring : + eval-index 1 - + eval-char : string-ref evalstring eval-index + ; double step, if mutating a paired character + evalstring + if : not : paired-char? eval-char + . evalstring + mutate-remove evalstring + mutate-remove-by-index evalstring eval-index define : mutate-eval evalstring eval-string : string-append "(" evalstring ")" - . #:lang 'scheme + . #:lang 'scheme ; TODO: use wisp define : better mutated original @@ -85,23 +118,45 @@ define : evolve-remove evalstring evolve-step evalstring mutate-remove +define : evolution-step string + let : : action : random 4 + cond + : = action 0 + evolve-replace string + : = action 1 + evolve-permutate string + : = action 2 + evolve-insert string + : = action 3 + evolve-remove string + + +define : evolution-population initialstring steps population-size + . "a population with 50% survivors." + . initialstring + define : evolution initialstring steps + ; TODO: use let loop : (step 0) (string initialstring) - let : : action : random 4 - if : >= step steps - . string - cond - : = action 0 - loop (+ step 1) (evolve-replace string) - : = action 1 - loop (+ step 1) (evolve-permutate string) - : = action 2 - loop (+ step 1) (evolve-insert string) - : = action 3 - loop (+ step 1) (evolve-remove string) + if : >= step steps + . string + loop + 1+ step + evolution-step string + -define : run +define : main args ; firstoff, seed the random number generator! set! *random-state* : random-state-from-platform - display : evolution "+ 1 (- 2 1)" 1000 - newline + let + : opt : evolution "+ 123 (- 2 1)" 1000 + write opt + newline + write : mutate-eval opt + newline + + +define : main + display "foo" + newline + run diff --git a/examples/hoist-in-loop.w b/examples/hoist-in-loop.w new file mode 100644 --- /dev/null +++ b/examples/hoist-in-loop.w @@ -0,0 +1,105 @@ +;; This is partial example code taken from the loop optimization Guile code at +;; http://git.savannah.gnu.org/gitweb/?p=guile.git;a=blob;f=module/language/cps/licm.scm;h=3b343a66bd8ed4a591a9e97edbf1179a4d3a78a8;hb=HEAD + +; I chose this example because this code felt very dense when I first +; read it, so I wanted to check whether this improves with wisp +; syntax. + +; but first the copyright information from the header of the file: + +;;; Continuation-passing style (CPS) intermediate language (IL) + +;; Copyright (C) 2013, 2014, 2015 Free Software Foundation, Inc. + +;;;; This library is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU Lesser General Public +;;;; License as published by the Free Software Foundation; either +;;;; version 3 of the License, or (at your option) any later version. +;;;; +;;;; This library is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;;; Lesser General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU Lesser General Public +;;;; License along with this library; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +define : hoist-in-loop cps entry body-labels succs preds effects + let* + : + interior-succs + intmap-map + lambda : label succs + intset-intersect succs body-labels + . succs + sorted-labels : compute-reverse-post-order interior-succs entry + header-label : fresh-label + header-cont : intmap-ref cps entry + loop-vars + match header-cont + : $ $kargs names vars + list->intset vars + loop-effects + persistent-intmap + intset-fold + lambda : label loop-effects + let + : + label* + if : eqv? label entry + . header-label + . label + fx : intmap-ref effects label + intmap-add! loop-effects label* fx + body-labels empty-intmap + pre-header-label entry + pre-header-cont + match header-cont + : $ $kargs names vars term + let : : vars* : map (lambda (_) (fresh-var)) vars + build-cont + $kargs names vars* + $continue header-label #f + $values vars* + cps : intmap-add! cps header-label header-cont + cps : intmap-replace! cps pre-header-label pre-header-cont + to-visit + match sorted-labels + : head . tail + unless : eqv? head entry + error "what?" + cons header-label tail + define : rename-back-edges cont + define : rename label + if : eqv? label entry + . header-label + . label + rewrite-cont cont + : $ $kargs names vars : $ $continue kf src : $ $branch kt exp + $kargs names vars + $continue (rename kf) src : $branch (rename kt) ,exp + : $ $kargs names vars : $ $continue k src exp + $kargs names vars + $continue (rename k) src ,exp + : $ $kreceive ($ $arity req () rest) k + $kreceive req rest (rename k) + let lp + : cps cps + to-visit to-visit + loop-vars loop-vars + loop-effects loop-effects + pre-header-label pre-header-label + always-reached? #t + match to-visit + () cps + : label . to-visit + call-with-values + lambda : + hoist-one cps label (intmap-ref cps label) preds + . loop-vars loop-effects + . pre-header-label always-reached? + lambda : cps cont loop-vars loop-effects pre-header-label always-reached? + lp : intmap-replace! cps label : rename-back-edges cont + . to-visit + . loop-vars loop-effects pre-header-label always-reached? diff --git a/examples/say.w b/examples/say.w new file mode 100755 --- /dev/null +++ b/examples/say.w @@ -0,0 +1,25 @@ +#!/usr/bin/env sh +# -*- wisp -*- +exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples say) main)' -s "$0" "$@" +; !# + +; Simple specialized syntax for writing natural text with scheme. + +define-module : examples say + . #:export : main + +; TODO: rewrite for syntax-case with recursion into sub-lists. +; Goal: say Yes, this works ,(red 1 2) . + +; TODO: longterm goal: simply syntax for writing plays. The header +; with active persons defines macros which are like say, but +; personalized. The code should read like the output of +; classical JRPGs. + +define-syntax-rule : say a ... + format #t "~A\n" + string-join + map symbol->string : quote : a ... + +define : main argv + say Yes, this works! diff --git a/wisp-reader.w b/wisp-reader.w --- a/wisp-reader.w +++ b/wisp-reader.w @@ -19,7 +19,18 @@ define-module : language wisp spec . #:export : wisp ; Set locale to something which supports unicode. Required to avoid using fluids. -setlocale LC_ALL "" +catch #t + lambda : + setlocale LC_ALL "" + lambda : key . parameters + let : : locale-fallback "en_US.UTF-8" + format (current-error-port) + string-join + list "Warning: setlocale LC_ALL \"\" failed with ~A: ~A" + . "using explicit ~A locale. Please setup your locale.\n" + . "\n " + . key parameters locale-fallback + setlocale LC_ALL locale-fallback ;;; ;;; Language definition diff --git a/wisp-scheme.w b/wisp-scheme.w --- a/wisp-scheme.w +++ b/wisp-scheme.w @@ -222,6 +222,12 @@ define : indent-reduce-to-level indentat lambda : x ; get the levels car : cdr x +define : chunk-ends-with-period currentsymbols next-char + . "Check whether indent-and-symbols ends with a period, indicating the end of a chunk." + and : not : null? currentsymbols + equal? #\newline next-char + equal? repr-dot + list-ref currentsymbols (- (length currentsymbols) 1) define : wisp-scheme-read-chunk-lines port let loop @@ -246,6 +252,11 @@ define : wisp-scheme-read-chunk-lines po append indent-and-symbols : list : append (list currentindent) currentsymbols : and inindent (zero? currentindent) (not incomment) (not (null? indent-and-symbols)) (not inunderscoreindent) (not (or (equal? #\space next-char) (equal? #\newline next-char) (equal? (string-ref ";" 0) next-char))) append indent-and-symbols ; top-level form ends chunk + : chunk-ends-with-period currentsymbols next-char + ; the line ends with a period. This is forbidden in + ; SRFI-119. Use it to end the line in the REPL without + ; showing continuation dots (...). + append indent-and-symbols : list : append (list currentindent) (drop-right currentsymbols 1) : and inindent : equal? #\space next-char read-char port ; remove char loop