#+title: SRFI: wisp: simpler indentation-sensitive scheme #+options: toc:nil num:t ^:nil # wisp: indentation-based scheme project #+BEGIN_ABSTRACT This SRFI describes a simple syntax which allows making scheme easier to read for newcomers while keeping the simplicity, generality and elegance of s-expressions. Similar to SRFI-110, SRFI-49 and Python it uses indentation to group expressions. Like SRFI-110 wisp is general and homoiconic. Different from its precedessors, wisp only uses the absolute minimum of additional syntax-elements which are required for writing and exchanging arbitrary code-structures. As syntax elements it only uses a colon surrounded by whitespace, the period followed by whitespace as first code-character on the line and optional underscores followed by whitespace at the beginning of the line. It resolves a limitation of SRFI-110 and SRFI-49, both of which force the programmer to use a single argument per line if the arguments to a function need to be continued after a function-call. Wisp expressions can include any s-expressions and as such provide backwards compatibility. #+html: <table><tr><th>wisp</th><th>s-exp</th></tr><tr><td> #+BEGIN_SRC wisp define : hello who format #t "~A ~A!\n" . "Hello" who #+END_SRC #+html: </td><td> #+BEGIN_SRC scheme (define (hello who) (format #t "~A ~A!\n" "Hello" who)) #+END_SRC #+html: </td></tr></table> #+END_ABSTRACT # to make indentation-based code safe to share in non-whitespace preserving environments #+toc: headlines 2 * SRFI process :noexport: 1. Authors submit a proposal by using the http://srfi.schemers.org/ web page, or sending email to srfi minus editors at srfi dot schemers dot org. 2. Within 7 days, one of the editors will read and respond to the proposal. The response may be a request to clarify, justify, or withdraw the proposal. Such a request must not reflect the personal bias of an editor. Rather, it will be made strictly to maintain a high quality of submissions. The editors may not turn a proposal back more than twice. On the third submission, the editors will move the proposal to draft status if it conforms to the specification below. At the discretion of the editors, a proposal that does not completely conform may be moved to draft status (although it must conform before it will be moved to final status). 3. When the proposal has been vetted by the editors, it receives its SRFI number and becomes draft. The editors will create a mailing list for the discussion of the proposal. A proposal normally stays draft for 60 days. A short notice of the new draft SRFI, including the title and abstract, SRFI number, URL, and instructions to access the temporary mailing list, will be sent to srfi minus announce at srfi dot schemers dot org. As part of the initial editing process, the editors will ensure that related standards (R*RS, SRFIs, RFCs and others) are appropriately identified and that the proposal meets the structural requirements described below. If other related standards are identified during the comment process or after acceptance, the editors will keep the references up-to-date. 4. If the authors choose, they may submit revised versions of the proposal at any point during the comment period. Every such revision shall be announced to srfi minus announce at srfi dot schemers dot org, and all revisions will be retained in the permanent record of the SRFI. Re-submission may cause the comment period to be extended at the discretion of the editors. The total discussion period must not exceed 90 days. Active discussion or revision after 90 days normally suggests that a proposal has been revised at least 3 times and is not yet mature enough for standardization. 5. At the end of the 60-90 day comment period, the authors can choose to withdraw the proposal. If the editors determine that insufficient time for discussion has followed a significant revision of the proposal, the proposal will be withdrawn. Otherwise, the proposal will be made final if it meets the requirements below. The outcome will be announced to srfi minus announce at srfi dot schemers dot org. 6. If the SRFI is withdrawn at the end of the comment period, it will be moved to a withdrawn proposal archive. At the discretion of the editors, subsequent related proposals (by the same or different authors) may be encouraged to include/modify the withdrawn proposal and may be treated as a reactivation of the withdrawn proposal and move it back to draft. A withdrawn proposal may not normally be reactivated until 30 days after the withdrawal. 7. When the SRFI is accepted, it will be placed on the list of final SRFIs. This will include a link to the history of the proposal, including all earlier versions and the archive of the discussion from the comment period. Any identified SRFIs that are superseded or incompatible with the newly final SRFI will be updated to reflect this fact. * SRFI Structure :noexport: Every SRFI must meet the following requirements: 1. It must have a succinct title. 2. It must list the authors. 3. It must list related standards and SRFIs, including dependencies, conflicts, and replacements. 4. It must begin with an abstract. This will be fewer than 200 words long. It will outline the need for, and design of, the proposal. 5. It must contain a detailed rationale. This will typically be 200-500 words long and will explain why the proposal should be incorporated as a standard feature in Scheme implementations. If there are other standards which this proposal will replace or with which it will compete, the rationale should explain why the present proposal is a substantial improvement. 6. It must contain a detailed specification. This should be detailed enough that a conforming implementation could be completely created from this description. 7. It must contain a reference implementation. This requirement may be met (in order from the most to the least preferred) by: 1. A portable Scheme implementation (possibly using earlier SRFIs). This is the most desirable option, because then implementors can provide a (possibly slow) implementation with no effort. 2. A mostly-portable solution that uses some kind of hooks provided in some Scheme interpreter/compiler. In this case, a detailed specification of the hooks must be included so that the SRFI is self-contained. 3. An implementation-specific solution. Ideally, tricky issues that had to be dealt with in the implementation will be identified. 4. A separately available implementation, where a reference implementation is large or requires extensive modifications (rather than just additions) to an existing implementation. This implementation will eventually be archived along with the SRFI and the discussion related to it. 5. An outline of how it might be implemented. This should be considered a last resort, and in this case the rationale for the feature must be stronger. The reference implementation should normally conform to the specification in point 5. If there is any variance (such as the implementation being overly restrictive), the specification will be considered correct, the variance should be explained, and a timetable provided for the reference implementation to meet the specification. 8. A proposal must be submitted in HTML 3.2 format following the template located here. If the author(s) are not familiar with this, the editors will accept Plain ISO Latin 1 text and convert it to HTML, after which any revisions must remain in HTML. All proposals must be written in English, be properly formatted and be reasonably grammatical. 9. It must contain a copyright statement as follows (where AUTHOR should be replaced by the name(s) of the author(s) and YEAR will be the year in which the SRFI number is allocated): Copyright (C) AUTHOR (YEAR). 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. The editors may not reject a proposal because they disagree with the importance of the proposal, or because they think it is a wrong-headed approach to the problem. The editors may, however, reject a proposal because it does not meet the requirements listed here. In particular, lack of a reference implementation (as defined above) is grounds for rejection. This can only occur if the ``reference implementation'' requirement is being met by an outlined implementation (type 5), and there is consensus that the implementation outline is not adequate. Note that this is never a permanent rejection, because creation of an implementation of one of the other types is a complete refutation of this basis for rejection. The other likely basis for rejection is an inadequate design specification. In this case, the editors will attempt to help the author(s) conform to the requirements. Remember, even if a proposal becomes an final SRFI, the need for it must be compelling enough for implementors to decide to incorporate it into their systems, or it will have been a waste of time and effort for everyone involved. If the quality of any SRFI is not high, the likelihood of implementors adding this feature to their implementation is extremely low. * Authors - Arne Babenhauserheide ** Acknowledgments - Thanks for many constructive discussions goes to Alan Manuel K. Gloria and David A. Wheeler. - Also thanks to Mark Weaver for his help with the wisp parser and the guile integration - including a 20x speedup. * Related SRFIs - SRFI-49 (Indentation-sensitive syntax): superceded by this SRFI, - SRFI-110 (Sweet-expressions (t-expressions)): alternative to this SRFI, - SRFI-105 (neoteric expressions and curly infix): supported in this SRFI by treating curly braces like brackets and parentheses. Curly infix is required. - SRFI-30 (Nested Multi-line comments): complex interaction. Should be avoided at the beginning of lines, because it can make the indentation hard to distinguish for humans. SRFI-110 includes them, so there might be value in adding them. The wisp reference implementation does not treat them specially, though, which might create arbitrary complications. * Rationale A big strength of Scheme and other lisp-like languages is their minimalistic syntax. By using only the most common characters like the period, the comma, the quote and quasiquote, the hash, the semicolon and the parens for the syntax (=.,"'`#;()=), they are very close to natural language[fn:1]. Along with the minimal list-structure of the code, this gives these languages a timeless elegance. But as SRFI-110 explains very thoroughly (which we need not repeat here), the parentheses at the beginning of lines hurt readability and scare away newcomers. Additionally using indentation to mark the structure of the code follows naturally from the observation that most programmers use indentation, with many programmers letting their editor indent code automatically to fit the structure. Indentation is an important way how programmers understand code and using it directly to define the structure avoids errors due to mismatches between indentation and actual meaning. As a solution to this, SRFI-49 and SRFI-110 provide a way to write whitespace sensitive scheme, but both have their share of issues. As noted in SRFI-110, there are a number of implementation-problems in SRFI-49, as well as specification shortcomings like choosing the name “group” for the construct which is necessary to represent double parentheses. In addition to the problems named in SRFI-110, SRFI-49 is not able to continue the arguments to a function on one line, if a prior argument was a function call. The example code in the abstract would have to be written in SRFI-49 as follows: #+BEGIN_SRC scheme ,* 5 + 4 3 2 1 #+END_SRC SRFI-110 improves a lot over the implementation of SRFI-49. It resolves the group-naming and reduces the need to continue the argument-list by introducing 3 different grouping syntaxes (=$=, =\\= and =<* *>=). These additional syntax-elements however hurt readability for newcomers (obviously the authors of SRFI-110 disagree with this assertion. Their view is discussed in SRFI-110 in the section about wisp). The additional syntax elements lead to structures like the following (taken from examples from the readable project): #+BEGIN_SRC scheme myfunction x: \\ original-x y: \\ calculate-y original-y #+END_SRC #+BEGIN_SRC scheme a b $ c d e $ f g #+END_SRC #+BEGIN_SRC scheme let <* x getx() \\ y gety() *> ! {{x * x} + {y * y}} #+END_SRC This is not only hard to read, but also makes it harder to work with the code, because the programmer has to learn these additional syntax elements and keep them in mind before being able to understand the code. Like SRFI-49 SRFI-110 also cannot continue the argument-list without resorting to single-element lines, though it reduces this problem by the above grouping syntaxes and advertising the use of neoteric expressions from SRFI-105. ** Advantages of Wisp Wisp draws on the strength of SRFI-110 but avoids its complexities. It was conceived and improved in the discussions within the readable-project which preceded SRFI-110 and there is a comparison between readable in wisp in SRFI-110. Like SRFI-110, wisp is general and homoiconic and interacts nicely with SRFI-105 (neoteric expressions and curly infix). Like SRFI-110, the expressions are the same in the REPL and in code-files. Like SRFI-110, wisp has been used for implementing multiple smaller programs, though the biggest program in wisp is still its implementations (written in wisp and bootstrapped via a simpler wisp preprocessor). But unlike SRFI-110, wisp only uses the minimum of additional syntax-elements which are necessary to support arbitrary code-structures with indentation-sensitive code which is intended to be shared over the internet. To realize these syntax-elements, it generalizes existing syntax and draws on the most common non-letter non-math characters in prose. This allows keeping the actual representation of the code elegant and inviting to newcomers. ** Disadvantages of Wisp Using the colon as syntax element keeps the code very close to written prose, but it can interfere with type definitions as for example used in Typed Racket[fn:6]. This can be mitigated in let- and lambda-forms by using the parenthesized form. When doing so, wisp avoids the double-paren for type-declarations and as such makes them easier to catch by eye. For function definitions (the only =define= call where type declarations are needed in typed-racket[fn:7]), a =declare= macro directly before the =define= should work well. Using the period to continue the argument list is unusual compared to other languages and as such can lead to errors when trying to return a variable from a procedure and forgetting the period. * Specification The specification is separated into four parts: A general overview of the syntax, a more detailed description, justifications for each added syntax element and clarifications for technical details. ** Overview The basics of wisp syntax can be defined in 4 rules, each of which emerges directly from a requirement: *** Wisp syntax 1/4: function calls Indentation: #+BEGIN_SRC wisp display + 3 4 5 newline #+END_SRC becomes #+BEGIN_SRC scheme (display (+ 3 4 5)) (newline) #+END_SRC /requirement: call functions without parenthesis./ *** Wisp syntax 2/4: Continue Argument list The period: #+BEGIN_SRC wisp + 5 * 4 3 . 2 1 #+END_SRC becomes #+BEGIN_SRC scheme (+ 5 (* 4 3) 2 1) #+END_SRC This also works with just one argument after the period. To start a line without a function call, you have to prefix it with a period followed by whitespace.[fn:2] /requirement: continue the argument list of a function after an intermediate call to another function./ *** Wisp syntax 3/4: Double Parens The colon:[fn:3] #+BEGIN_SRC wisp let : x 1 y 2 z 3 body #+END_SRC becomes #+BEGIN_SRC scheme (let ((x 1) (y 2) (z 3)) (body)) #+END_SRC /requirement: represent code with two adjadent blocks in double-parentheses./ *** Wisp syntax 4/4: Resilient Indentation The underscore (optional): #+BEGIN_SRC wisp let _ : x 1 __ y 2 __ z 3 _ body #+END_SRC becomes #+BEGIN_SRC scheme (let ((x 1) (y 2) (z 3)) (body)) #+END_SRC /requirement: share code in environments which do not preserve whitespace./ *** Summary The syntax shown here is the minimal syntax required for the goal of wisp: indentation-based, general lisp with a simple preprocessor, and code which can be shared easily on the internet: - =.= to continue the argument list - =:= for double parens - =_= to survive HTML ** More detailed: Wisp syntax rules *** Unindented line *A line without indentation is a function call*, just as if it would start with a parenthesis. #+BEGIN_SRC wisp display "Hello World!" ; (display "Hello World!") #+END_SRC *** Sibling line *A line which is more indented than the previous line is a sibling to that line*: It opens a new parenthesis. #+BEGIN_SRC wisp display ; (display string-append "Hello " "World!" ; (string-append "Hello " "World!")) #+END_SRC *** Closing line *A line which is not more indented than previous line(s) closes the parentheses of all previous lines which have higher or equal indentation*. You should only reduce the indentation to indentation levels which were already used by parent lines, else the behaviour is undefined. #+BEGIN_SRC wisp display ; (display string-append "Hello " "World!" ; (string-append "Hello " "World!")) display "Hello Again!" ; (display "Hello Again!") #+END_SRC *** Prefixed line *To add any of ' , ` #' #, #` or #@, to the first parenthesis on a line, just prefix the line with that symbol* followed by at least one space. Implementations are free to add more prefix symbols. #+BEGIN_SRC wisp ' "Hello World!" ; '("Hello World!") #+END_SRC *** Continuing line *A line whose first non-whitespace characters is a dot followed by a space (". ") does not open a new parenthesis: it is treated as simple continuation of the first less indented previous line*. In the first line this means that this line does not start with a parenthesis and does not end with a parenthesis, just as if you had directly written it in lisp without the leading ". ". #+BEGIN_SRC wisp string-append "Hello" ; (string-append "Hello" string-append " " "World" ; (string-append " " "World") . "!" ; "!") #+END_SRC *** Empty indentation level *A line which contains only whitespace and a colon (":") defines an indentation level at the indentation of the colon*. It opens a parenthesis which gets closed by the next less-indented line. If you need to use a colon by itself. you can escape it as "\:". #+BEGIN_SRC wisp let ; (let : ; ( msg "Hello World!" ; (msg "Hello World!")) display msg ; (display msg)) #+END_SRC *** Inline Colon *A colon sourrounded by whitespace (" : ") starts a parenthesis which gets closed at the end of the line*. #+BEGIN_SRC wisp define : hello who ; (define (hello who) display ; (display string-append "Hello " who "!" ; (string-append "Hello " who "!"))) #+END_SRC If the colon starts a line, it starts a parenthesis which gets closed at the end of the line *and* defines an indentation level at the position of the colon. *** Initial Underscores *You can replace any number of consecutive initial spaces by underscores*, as long as at least one whitespace is left between the underscores and any following character. You can escape initial underscores by prefixing the first one with \ ("\___ a" → "(___ a)"), if you have to use them as function names. #+BEGIN_SRC wisp define : hello who ; (define (hello who) _ display ; (display ___ string-append "Hello " who "!" ; (string-append "Hello " who "!"))) #+END_SRC *** Parens and Strings *Linebreaks inside parentheses and strings are not considered linebreaks* for parsing indentation. To use parentheses at the beginning of a line without getting double parens, prefix the line with a period. #+BEGIN_SRC wisp define : stringy s string-append s "can be varied as follows: " string-capitalize s string-reverse s . (string-capitalize (string-reverse s)) . " " #+END_SRC ** Clarifications - Code-blocks end after 2 empty lines followed by a newline. Indented non-empty lines after 2 empty lines should be treated as error. A line is empty if it only contains whitespace. A line with a comment is never empty. - Inside parentheses, wisp parsing is disabled. Consequently linebreaks inside parentheses are not considered linebreaks for wisp-parsing. For the parser everything which happens inside parentheses is treated as a black box. - Square brackets and curly braces should be treated the same way as parentheses: They stop the indentation processing until they are closed. - Likewise linebreaks inside strings are not considered linebreaks for wisp-parsing. - A colon (:) at the beginning of a line adds an extra open parentheses that gets closed at end-of-line (rule 4.2.7) *and* defines an indentation level. - using a quote to escape a symbol separated from it by whitespace is forbidden. This would make the meaning of quoted lines ambigous. * Syntax justification /I do not like adding any unnecessary syntax element to lisp. So I want to show explicitely why the syntax elements are required./ #+html: <small> See also http://draketo.de/light/english/wisp-lisp-indentation-preprocessor#sec-4 #+html: </small> ** . (the dot) To represent general code trees, we have to be able to represent continuation of the arguments of a function with an intermediate call to another (or the same) function. The dot at the beginning of the line as marker of the continuation of a variable list is a generalization of using the dot as identity function - which is an implementation detail in many lisps. #+BEGIN_QUOTE =(. a)= is just =a= #+END_QUOTE So for the single variable case, this would not even need additional parsing: wisp could just parse =. a= to =(. a)= and produce the correct result in most lisps. But forcing programmers to always use separate lines for each parameter would be very inconvenient, so the definition of the dot at the beginning of the line is extended to mean “take every element in this line as parameter to the parent function”. #+BEGIN_QUOTE =(. a)= → =a= is generalized to =(. a b c)= → =a b c=. #+END_QUOTE At its core, this dot-rule means that we mark variables in the code instead of function calls. We do so, because variables at the beginning of a line are much rarer in Scheme than in other programming languages. ** : (the colon) For double parentheses and for some other cases we must have a way to mark indentation levels which do not contain code. Wisp uses the colon, because it is the most common non-alpha-numeric character in normal prose which is not already reserved as syntax by Scheme when it is surrounded by whitespace, and because it already gets used without sourrounding whitespace for marking keyword arguments to functions in Emacs Lisp and Common Lisp, so it does not add completely alien concepts. The inline function call via inline " : " is a limited generalization of using the colon to mark an indentation level: If we add a syntax-element, we should use it as widely as possible to justify adding syntax overhead. But if you need to use =:= as variable or function name, you can still do so by escaping it with a backslash (=\:=), so this does not forbid using the character. For simple cases, the colon could be replaced by clever whitespace parsing, but there are complex cases which make this impossible. The minimal example is a theoretical doublelet which does not require a body:[fn:4] #+BEGIN_SRC scheme (doublelet ((foo bar)) ((bla foo))) #+END_SRC The wisp version of this is #+BEGIN_SRC wisp doublelet : foo bar : ; <- this empty backstep is the real issue bla foo #+END_SRC or shorter with inline colon (which you can use only if you don’t need further indentation-syntax inside the assignment). #+BEGIN_SRC wisp doublelet : foo bar : bla foo #+END_SRC The need to be able to represent arbitrary syntax trees which can contain expressions like this is the real reason, why the colon exists. The inline and start-of-line use is only a generalization of that principle (we add a syntax-element, so we should see how far we can push it to reduce the effective cost of introducing the additional syntax). *** Clever whitespace-parsing which would not work There are two alternative ways to tackle this issue: deferred level-definition and fixed-width indentation. Defining intermediate indentation-levels by later elements (deferred definition) would be a problem, because it would create code which is really hard to understand. An example is the following: #+BEGIN_SRC wisp define (flubb) nubb hubb subb gam #+END_SRC would become #+BEGIN_SRC scheme (define (flubb) ((nubb)) ((hubb)) ((subb)) (gam)) #+END_SRC while #+BEGIN_SRC wisp define (flubb) nubb hubb subb #+END_SRC would become #+BEGIN_SRC scheme (define (flubb) (nubb) (hubb) (subb)) #+END_SRC Knowledge of later parts of the code would be necessary to understand the parts a programmer is working on at the moment. This would call for subtle errors which would be hard to track down, because the effect of a change in code would not be localized at the point where the change is done but could propagate backwards. Fixed indentation width (alternative option to inferring it from later lines) would make it really hard to write readable code. Stuff like this would not be possible: #+BEGIN_SRC wisp when equal? wrong isright? stuff fixstuff #+END_SRC ** _ (the underscore) In Python the whitespace hostile html already presents problems with sharing code - for example in email list archives and forums. But Python-programmers can mostly infer the indentation by looking at the previous line: If that ends with a colon, the next line must be more indented (there is nothing to clearly mark reduced indentation, though). In wisp we do not have this support, so we need a way to survive in the hostile environment of todays web. The underscore is commonly used to denote a space in URLs, where spaces are inconvenient, but it is rarely used in Scheme (where the dash ("-") is mostly used instead), so it seems like a a natural choice. You can still use underscores anywhere but at the beginning of the line, and even at the beginning of the line you simply need to escape it by prefixing the first underscore with a backslash ("\____"). * Implementation This reference implementation realizes a specialized parser for Scheme. It uses GNU Guile and can also be used at the REPL. The wisp code also contains a general wisp-preprocessor which can be used for any lisp-like language and can used as an external program which gets called on reading. It does not actually have to understand the code itself. This is not part of this SRFI, though. To allow for easy re-implementation, the chapter after the implementation itself contains a test-suite with commonly used wisp constructs and parenthesized counterparts. The wisp preprocessor implementation can be found at http://draketo.de/proj/wisp. Both implementations are explicitly licensed to allow inclusion in an SRFI. ** The generic wisp processor (code) TODO: Include the code from http://draketo.de/proj/wisp ** Test Suite The wisp test-suite consists of a large number of wisp-snippets and the corresponding scheme-code. 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. 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. This test-suite is also available in the [[http://draketo.de/proj/wisp][wisp repository]] along with a script-runner (runtests.sh) which tests the reference wisp-implementation with GNU Guile against this testsuite.[fn:5] *** tests/syntax-underscore.w #+begin_src wisp define : a b c _ d e ___ f ___ g h __ . i define : _ _ display "hello\n" \_ #+end_src *** tests/syntax-underscore.scm #+begin_src scheme (define (a b c) (d e (f) (g h) i)) (define (_) (display "hello\n")) (_) #+end_src *** tests/syntax-strings-parens.w #+begin_src wisp ; Test linebreaks in strings and brackets . "flubbub flabbab" hrug (nadda madda gadda "shoktom mee" " sep ka" hadda) gom flu sum [foo bar] barz {1 + [* 2 2]} mara { li + lo (mabba) } #+end_src *** tests/syntax-strings-parens.scm #+begin_src scheme ; Test linebreaks in strings and brackets "flubbub flabbab" (hrug (nadda madda gadda "shoktom mee" " sep ka" hadda) (gom)) (flu) (sum [foo bar] barz {1 + [* 2 2]}) (mara { li + lo (mabba) }) #+end_src *** tests/syntax-indent.w #+begin_src wisp define hello who format #t "Hello ~A\n" who define let : a 1 b 2 c 3 format #t "a: ~A, b: ~A, c: ~A" + a 2 . b c #+end_src *** tests/syntax-indent.scm #+begin_src scheme (define (hello who) (format #t "Hello ~A\n" who)) (define (let ( (a 1) (b 2) (c 3)) (format #t "a: ~A, b: ~A, c: ~A" (+ a 2) b c))) #+end_src *** tests/syntax-empty.w #+begin_src wisp #+end_src *** tests/syntax-empty.scm #+begin_src scheme #+end_src *** tests/syntax-dot.w #+begin_src wisp define : foo . "bar" define : bar ' 1 . . 2 ; pair display : foo newline display : bar newline #+end_src *** tests/syntax-dot.scm #+begin_src scheme (define (foo) "bar") (define (bar) '(1 . 2 )); pair (display (foo)) (newline) (display (bar)) (newline) #+end_src *** tests/syntax-colon.w #+begin_src wisp let : a 1 b 2 let : : . c 3 format #t "a: ~A, b: ~A, c: ~A" . a b c : a define : hello display "hello\n" let : a 1 b 2 format #t "a: ~A, b: ~A" . a b let : : a ' : let : ; foo a ' : a define : \: hello \: #+end_src *** tests/syntax-colon.scm #+begin_src scheme (let ( (a 1) (b 2)) (let ( ( c 3)) (format #t "a: ~A, b: ~A, c: ~A" a b c))) ((a)) (define (hello) (display "hello\n")) (let ((a 1) (b 2)) (format #t "a: ~A, b: ~A" a b)) (let ((a '()))) (let ( ; foo (a '()))) ( (a)) (define (:) (hello)) (:) #+end_src *** tests/sublist.w #+begin_src wisp ; sublists allow to start single line function calls with a colon ( : ). ; define : a b c let : : e . f . g #+end_src *** tests/sublist.scm #+begin_src scheme ; sublists allow to start single line function calls with a colon ( : ). (define (a b c) (let ((e . f)) g)) #+end_src *** tests/shebang.w #+begin_src wisp #!/usr/bin/wisp.py # !# ; This tests shebang lines #+end_src *** tests/shebang.scm #+begin_src scheme #!/usr/bin/wisp.py # !# ; This tests shebang lines #+end_src *** tests/readable-tests.w #+begin_src wisp define : fibfast n if : < n 2 . n fibup n 2 1 0 define : fibup maxnum count n-1 n-2 if : = maxnum count + n-1 n-2 fibup maxnum + count 1 + n-1 n-2 . n-1 define : factorial n if : <= n 1 . 1 * n factorial : - n 1 define (gcd x y) if (= y 0) . x gcd y rem x y define : add-if-all-numbers lst call/cc lambda : exit let loop : lst lst sum 0 if : null? lst . sum if : not : number? : car lst exit #f + : car lst loop : cdr lst #+end_src *** tests/readable-tests.scm #+begin_src scheme (define (fibfast n) (if (< n 2)) n (fibup n 2 1 0 )) (define (fibup maxnum count n-1 n-2) (if (= maxnum count) (+ n-1 n-2) (fibup maxnum (+ count 1 ) (+ n-1 n-2 ) n-1))) (define (factorial n) (if (<= n 1) 1 (* n (factorial (- n 1))))) (define (gcd x y) (if (= y 0)) x (gcd y (rem x y))) (define (add-if-all-numbers lst) (call/cc (lambda (exit) (let loop ( (lst lst ) (sum 0)) (if (null? lst) sum (if (not (number? (car lst))) (exit #f) (+ (car lst) (loop (cdr lst))))))))) #+end_src *** tests/range.w #+begin_src wisp import : rnrs define range case-lambda : n ; one-argument syntax range 0 n 1 : n0 n ; two-argument syntax range n0 n 1 : n0 n s ; three-argument syntax assert and for-all number? : list n0 n s not : zero? s let : : cmp : if (positive? s) >= <= let loop : i n0 acc '() if cmp i n reverse acc loop (+ i s) (cons i acc) display : apply string-append "" : map number->string : range 5 newline #+end_src *** tests/range.scm #+begin_src scheme (import (rnrs)) (define range (case-lambda ((n ); one-argument syntax (range 0 n 1)) ((n0 n ); two-argument syntax (range n0 n 1)) ((n0 n s ); three-argument syntax (assert (and (for-all number? (list n0 n s)) (not (zero? s)))) (let ((cmp (if (positive? s) >= <= ))) (let loop ((i n0 ) (acc '())) (if (cmp i n ) (reverse acc) (loop (+ i s) (cons i acc)))))))) (display (apply string-append "" (map number->string (range 5)))) (newline) #+end_src *** tests/quotecolon.w #+begin_src wisp #!/home/arne/wisp/wisp-multiline.sh ; !# define a 1 ; test whether ' : correctly gets turned into '( ; and whether brackets in commments are treated correctly. define a ' : 1 2 3 define a b c #+end_src *** tests/quotecolon.scm #+begin_src scheme #!/home/arne/wisp/wisp-multiline.sh ; !# (define a 1 ); test whether ' : correctly gets turned into '( ; and whether brackets in commments are treated correctly. (define a '(1 2 3)) (define (a b) (c)) #+end_src *** tests/namedlet.w #+begin_src wisp #!/home/arne/wisp/wisp-multiline.sh ; !# define : hello who display who let hello : who 0 if : = who 5 display who hello : + 1 who #+end_src *** tests/namedlet.scm #+begin_src scheme #!/home/arne/wisp/wisp-multiline.sh ; !# (define (hello who) (display who)) (let hello ((who 0)) (if (= who 5) (display who) (hello (+ 1 who)))) #+end_src *** tests/mtest.w #+begin_src wisp #!/home/arne/wisp/wisp-multiline.sh !# display 1 #+end_src *** tests/mtest.scm #+begin_src scheme #!/home/arne/wisp/wisp-multiline.sh !# (display 1) #+end_src *** tests/flexible-parameter-list.w #+begin_src wisp ; Test using a . as first parameter on a line by prefixing it with a second . define 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 "123" "345" "567" #+end_src *** tests/flexible-parameter-list.scm #+begin_src scheme ; Test using a . as first parameter on a line by prefixing it with a second . (define (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 "123" "345" "567") #+end_src *** tests/factorial.w #+begin_src wisp ;; short version ; note: once you use one inline colon, all the following forms on that ; line will get closed at the end of the line define : factorial n if : zero? n . 1 * n : factorial : - n 1 display : factorial 5 ;; more vertical space, less colons define : factorial n if : zero? n . 1 * n factorial - n 1 display : factorial 5 #+end_src *** tests/factorial.scm #+begin_src scheme ;; short version ; note: once you use one inline colon, all the following forms on that ; line will get closed at the end of the line (define (factorial n) (if (zero? n) 1 (* n (factorial (- n 1))))) (display (factorial 5 )) ;; more vertical space, less colons (define (factorial n) (if (zero? n) 1 (* n (factorial (- n 1))))) (display (factorial 5 )) #+end_src *** tests/example.w #+begin_src wisp define (a b c) let : d "i am a string do not break me!" : ; comment: 0 f ; comment : 1 ` g ; comment " : " 2 : h (I am in brackets: do not : change "me") . i , 'j k . l ; comment a c define : b :n o . "second defun : with a docstring!" message "I am here" . t define : c e f : g : h i j ' : k . l . : m define : _ \: __ __ . \: \_ b define : d let : a b c d a : : : c let : a b c let : : a b . a #+end_src *** tests/example.scm #+begin_src scheme (define (a b c) (let ( (d "i am a string do not break me!") ( ; comment: 0 (f) ; comment : 1 `(g )); comment " : " 2 ( (h (I am in brackets: do not : change "me")) i))) ,('j k) l ; comment (a c)) (define (b :n o) "second defun : with a docstring!" (message "I am here") t) (define (c e f) ((g)) ( (h (i)) (j)) '(()) (k) l (m)) (define (_ :) :) (_ b) (define (d) (let ((a b) (c d)))) (a (((c)))) (let ((a b) (c))) (let ((a b))) a #+end_src *** tests/continuation.w #+begin_src wisp a b c d e . f g h . i j k concat "I want " getwish from me . " - " username #+end_src *** tests/continuation.scm #+begin_src scheme (a b c d e f g h i j k) (concat "I want " (getwish from me) " - " username) #+end_src *** tests/btest.w #+begin_src wisp display "b" newline #+end_src *** tests/btest.scm #+begin_src scheme (display "b") (newline) #+end_src * Copyright Copyright (C) Arne Babenhauserheide (2013--2014). 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. * Footnotes [fn:1] The most common non-letter, non-math characters in prose are =.,":'_#?!;=, in the given order as derived from newspapers and other sources (for the ngram assembling scripts, see the [[http://bitbucket.org/ArneBab/evolve-keyboard-layout][evolve keyboard layout project]]). [fn:2] Conceptually, continuing the argument list with a period uses syntax to mark the rare case of not calling a function as opposed to marking the common case of calling a function. To back the claim, that calling a function is actually the common case in scheme-code, grepping the the modules in the Guile source code shows over 27000 code-lines which start with a paren and only slightly above 10000 code-lines which start with a non-paren, non-comment character. Since wisp-syntax mostly follows the regular scheme indentation guidelines (as realized for example by emacs), the whitespace in front of lines does not need to change. [fn:3] This special syntax for double parens cannot be replaced by clever whitespace parsing, because it is required for representing two consecutive forms which start with double parentheses. The only pure-whitespace alternative would be fixed-width indentation levels. [fn:4] I used a double let without action as example for the colon-syntax, even though that does nothing, because that makes it impossible to use later indentation to mark an intermediate indentation-level. Another reason why I would not use later indentation to define whether something earlier is a single or double indent is that this would call for subtle and really hard to find errors: [fn:5] 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: =PATH=~/guile-2.0.11/meta:${PATH} ./runtests.sh= [fn:6] Typed Racket uses calls of the form =(: x Number)= to declare types. These forms can still be used directly in parenthesized form, but in wisp-form the colon has to be replaced with =\:=. [fn:7] In most cases type-declarations are not needed in typed racket, since the type can be inferred. See [[http://docs.racket-lang.org/ts-guide/more.html?q=typed#%28part._when-annotations~3f%29][When do you need type annotations?]]