wisp.scm 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. #!/bin/bash
  2. (# -*- wisp -*-)
  3. (exec guile -L . --language=wisp -s "$0" "$@")
  4. ; !#
  5. ;; Scheme-only implementation of a wisp-preprocessor which output a
  6. ;; scheme code tree to feed to a scheme interpreter instead of a
  7. ;; preprocessed file.
  8. ;; Limitations:
  9. ;; - only unescapes up to 6 leading underscores at line start (\______)
  10. ;; - in some cases the source line information is missing in backtraces.
  11. ;; check for set-source-property!
  12. ;; Copyright (C) Arne Babenhauserheide (2014--2015). All Rights Reserved.
  13. ;; Permission is hereby granted, free of charge, to any person
  14. ;; obtaining a copy of this software and associated documentation
  15. ;; files (the "Software"), to deal in the Software without
  16. ;; restriction, including without limitation the rights to use, copy,
  17. ;; modify, merge, publish, distribute, sublicense, and/or sell copies
  18. ;; of the Software, and to permit persons to whom the Software is
  19. ;; furnished to do so, subject to the following conditions:
  20. ;;
  21. ;; The above copyright notice and this permission notice shall be
  22. ;; included in all copies or substantial portions of the Software.
  23. ;;
  24. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  28. ;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  29. ;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  30. ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  31. ;; SOFTWARE.
  32. (define-module (language wisp)
  33. #:export (wisp-scheme-read-chunk wisp-scheme-read-all
  34. wisp-scheme-read-file-chunk wisp-scheme-read-file
  35. wisp-scheme-read-string))
  36. ; use curly-infix by default
  37. (read-enable 'curly-infix)
  38. (use-modules
  39. (srfi srfi-1)
  40. (srfi srfi-11 ); for let-values
  41. (ice-9 rw ); for write-string/partial
  42. (ice-9 match))
  43. ;; Helper functions for the indent-and-symbols data structure: '((indent token token ...) ...)
  44. (define (line-indent line)
  45. (car line))
  46. (define (line-real-indent line)
  47. "Get the indentation without the comment-marker for unindented lines (-1 is treated as 0)."
  48. (let (( indent (line-indent line)))
  49. (if (= -1 indent)
  50. 0
  51. indent)))
  52. (define (line-code line)
  53. (let ((code (cdr line)))
  54. ; propagate source properties
  55. (when (not (null? code))
  56. (set-source-properties! code (source-properties line)))
  57. code))
  58. ; literal values I need
  59. (define readcolon
  60. (string->symbol ":"))
  61. (define wisp-uuid "e749c73d-c826-47e2-a798-c16c13cb89dd")
  62. ; define an intermediate dot replacement with UUID to avoid clashes.
  63. (define repr-dot ; .
  64. (string->symbol (string-append "REPR-DOT-" wisp-uuid)))
  65. ; allow using reader additions as the first element on a line to prefix the list
  66. (define repr-quote ; '
  67. (string->symbol (string-append "REPR-QUOTE-" wisp-uuid)))
  68. (define repr-unquote ; ,
  69. (string->symbol (string-append "REPR-UNQUOTE-" wisp-uuid)))
  70. (define repr-quasiquote ; `
  71. (string->symbol (string-append "REPR-QUASIQUOTE-" wisp-uuid)))
  72. (define repr-unquote-splicing ; ,@
  73. (string->symbol (string-append "REPR-UNQUOTESPLICING-" wisp-uuid)))
  74. (define repr-syntax ; #'
  75. (string->symbol (string-append "REPR-SYNTAX-" wisp-uuid)))
  76. (define repr-unsyntax ; #,
  77. (string->symbol (string-append "REPR-UNSYNTAX-" wisp-uuid)))
  78. (define repr-quasisyntax ; #`
  79. (string->symbol (string-append "REPR-QUASISYNTAX-" wisp-uuid)))
  80. (define repr-unsyntax-splicing ; #,@
  81. (string->symbol (string-append "REPR-UNSYNTAXSPLICING-" wisp-uuid)))
  82. ; TODO: wrap the reader to return the repr of the syntax reader
  83. ; additions
  84. (define (match-charlist-to-repr charlist)
  85. (let
  86. ((chlist (reverse charlist)))
  87. (cond
  88. ((equal? chlist (list #\.))
  89. repr-dot)
  90. ((equal? chlist (list #\'))
  91. repr-quote)
  92. ((equal? chlist (list #\,))
  93. repr-unquote)
  94. ((equal? chlist (list #\`))
  95. repr-quasiquote)
  96. ((equal? chlist (list #\, #\@ ))
  97. repr-unquote-splicing)
  98. ((equal? chlist (list #\# #\' ))
  99. repr-syntax)
  100. ((equal? chlist (list #\# #\, ))
  101. repr-unsyntax)
  102. ((equal? chlist (list #\# #\` ))
  103. repr-quasisyntax)
  104. ((equal? chlist (list #\# #\, #\@ ))
  105. repr-unsyntax-splicing)
  106. (else
  107. #f))))
  108. (define (wisp-read port)
  109. "wrap read to catch list prefixes."
  110. (let ((prefix-maxlen 4))
  111. (let longpeek
  112. ((peeked '())
  113. (repr-symbol #f))
  114. (cond
  115. ((or (< prefix-maxlen (length peeked)) (eof-object? (peek-char port)) (equal? #\space (peek-char port)) (equal? #\newline (peek-char port)) )
  116. (if repr-symbol ; found a special symbol, return it.
  117. ; TODO: Somehow store source-properties. The commented-out code below does not work.
  118. ; catch #t
  119. ; lambda ()
  120. ; write : source-properties symbol-or-symbols
  121. ; set-source-property! symbol-or-symbols 'filename : port-filename port
  122. ; set-source-property! symbol-or-symbols 'line : 1+ : port-line port
  123. ; set-source-property! symbol-or-symbols 'column : port-column port
  124. ; write : source-properties symbol-or-symbols
  125. ; lambda : key . arguments
  126. ; . #f
  127. repr-symbol
  128. (let unpeek
  129. ((remaining peeked))
  130. (cond
  131. ((equal? '() remaining )
  132. (read port )); let read to the work
  133. (else
  134. (unread-char (car remaining) port)
  135. (unpeek (cdr remaining)))))))
  136. (else
  137. (let*
  138. ((next-char (read-char port))
  139. (peeked (cons next-char peeked)))
  140. (longpeek
  141. peeked
  142. (match-charlist-to-repr peeked))))))))
  143. (define (line-continues? line)
  144. (equal? repr-dot (car (line-code line))))
  145. (define (line-only-colon? line)
  146. (and
  147. (equal? ":" (car (line-code line)))
  148. (null? (cdr (line-code line)))))
  149. (define (line-empty-code? line)
  150. (null? (line-code line)))
  151. (define (line-empty? line)
  152. (and
  153. ; if indent is -1, we stripped a comment, so the line was not really empty.
  154. (= 0 (line-indent line))
  155. (line-empty-code? line)))
  156. (define (line-strip-continuation line )
  157. (if (line-continues? line)
  158. (append
  159. (list
  160. (line-indent line))
  161. (cdr (line-code line)))
  162. line))
  163. (define (line-strip-indentation-marker line)
  164. "Strip the indentation markers from the beginning of the line"
  165. (cdr line))
  166. (define (indent-level-reduction indentation-levels level select-fun)
  167. "Reduce the INDENTATION-LEVELS to the given LEVEL and return the value selected by SELECT-FUN"
  168. (let loop
  169. ((newlevels indentation-levels)
  170. (diff 0))
  171. (cond
  172. ((= level (car newlevels))
  173. (select-fun (list diff indentation-levels)))
  174. ((< level (car newlevels))
  175. (loop
  176. (cdr newlevels)
  177. (1+ diff)))
  178. (else
  179. (throw 'wisp-syntax-error "Level ~A not found in the indentation-levels ~A.")))))
  180. (define (indent-level-difference indentation-levels level)
  181. "Find how many indentation levels need to be popped off to find the given level."
  182. (indent-level-reduction indentation-levels level
  183. (lambda (x ); get the count
  184. (car x))))
  185. (define (indent-reduce-to-level indentation-levels level)
  186. "Find how many indentation levels need to be popped off to find the given level."
  187. (indent-level-reduction indentation-levels level
  188. (lambda (x ); get the levels
  189. (car (cdr x)))))
  190. (define (chunk-ends-with-period currentsymbols next-char)
  191. "Check whether indent-and-symbols ends with a period, indicating the end of a chunk."
  192. (and (not (null? currentsymbols))
  193. (equal? #\newline next-char)
  194. (equal? repr-dot
  195. (list-ref currentsymbols (- (length currentsymbols) 1)))))
  196. (define (wisp-scheme-read-chunk-lines port)
  197. (let loop
  198. ((indent-and-symbols (list )); '((5 "(foobar)" "\"yobble\"")(3 "#t"))
  199. (inindent #t)
  200. (inunderscoreindent (equal? #\_ (peek-char port)))
  201. (incomment #f)
  202. (currentindent 0)
  203. (currentsymbols '())
  204. (emptylines 0))
  205. (cond
  206. ((>= emptylines 2 ); the chunk end has to be checked
  207. ; before we look for new chars in the
  208. ; port to make execution in the REPL
  209. ; after two empty lines work
  210. ; (otherwise it shows one more line).
  211. indent-and-symbols)
  212. (else
  213. (let ((next-char (peek-char port)))
  214. (cond
  215. ((eof-object? next-char)
  216. (append indent-and-symbols (list (append (list currentindent) currentsymbols))))
  217. ((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))))
  218. (append indent-and-symbols )); top-level form ends chunk
  219. ((chunk-ends-with-period currentsymbols next-char)
  220. ; the line ends with a period. This is forbidden in
  221. ; SRFI-119. Use it to end the line in the REPL without
  222. ; showing continuation dots (...).
  223. (append indent-and-symbols (list (append (list currentindent) (drop-right currentsymbols 1)))))
  224. ((and inindent (equal? #\space next-char))
  225. (read-char port ); remove char
  226. (loop
  227. indent-and-symbols
  228. #t ; inindent
  229. #f ; inunderscoreindent
  230. #f ; incomment
  231. (1+ currentindent)
  232. currentsymbols
  233. emptylines))
  234. ((and inunderscoreindent (equal? #\_ next-char))
  235. (read-char port ); remove char
  236. (loop
  237. indent-and-symbols
  238. #t ; inindent
  239. #t ; inunderscoreindent
  240. #f ; incomment
  241. (1+ currentindent)
  242. currentsymbols
  243. emptylines))
  244. ; any char but whitespace *after* underscoreindent is
  245. ; an error. This is stricter than the current wisp
  246. ; syntax definition. TODO: Fix the definition. Better
  247. ; start too strict. FIXME: breaks on lines with only
  248. ; underscores which should be empty lines.
  249. ((and inunderscoreindent (and (not (equal? #\space next-char)) (not (equal? #\newline next-char))))
  250. (throw 'wisp-syntax-error "initial underscores without following whitespace at beginning of the line after" (last indent-and-symbols)))
  251. ((equal? #\newline next-char)
  252. (read-char port ); remove the newline
  253. ; The following two lines would break the REPL by requiring one char too many.
  254. ; if : and (equal? #\newline next-char) : equal? #\return : peek-char port
  255. ; read-char port ; remove a full \n\r. Damn special cases...
  256. (let* ; distinguish pure whitespace lines and lines
  257. ; with comment by giving the former zero
  258. ; indent. Lines with a comment at zero indent
  259. ; get indent -1 for the same reason - meaning
  260. ; not actually empty.
  261. (
  262. (indent
  263. (cond
  264. (incomment
  265. (if (= 0 currentindent ); specialcase
  266. -1
  267. currentindent ))
  268. ((not (null? currentsymbols )); pure whitespace
  269. currentindent)
  270. (else
  271. 0)))
  272. (parsedline (append (list indent) currentsymbols))
  273. (emptylines
  274. (if (not (line-empty? parsedline))
  275. 0
  276. (1+ emptylines))))
  277. (when (not (= 0 (length parsedline)))
  278. ; set the source properties to parsedline so we can try to add them later.
  279. (set-source-property! parsedline 'filename (port-filename port))
  280. (set-source-property! parsedline 'line (port-line port)))
  281. ; TODO: If the line is empty. Either do it here and do not add it, just
  282. ; increment the empty line counter, or strip it later. Replace indent
  283. ; -1 by indent 0 afterwards.
  284. (loop
  285. (append indent-and-symbols (list parsedline))
  286. #t ; inindent
  287. (if (<= 2 emptylines)
  288. #f ; chunk ends here
  289. (equal? #\_ (peek-char port ))); are we in underscore indent?
  290. #f ; incomment
  291. 0
  292. '()
  293. emptylines)))
  294. ((equal? #t incomment)
  295. (read-char port ); remove one comment character
  296. (loop
  297. indent-and-symbols
  298. #f ; inindent
  299. #f ; inunderscoreindent
  300. #t ; incomment
  301. currentindent
  302. currentsymbols
  303. emptylines))
  304. ((or (equal? #\space next-char) (equal? #\tab next-char) (equal? #\return next-char) ); remove whitespace when not in indent
  305. (read-char port ); remove char
  306. (loop
  307. indent-and-symbols
  308. #f ; inindent
  309. #f ; inunderscoreindent
  310. #f ; incomment
  311. currentindent
  312. currentsymbols
  313. emptylines))
  314. ; | cludge to appease the former wisp parser
  315. ; | used for bootstrapping which has a
  316. ; v problem with the literal comment char
  317. ((equal? (string-ref ";" 0) next-char)
  318. (loop
  319. indent-and-symbols
  320. #f ; inindent
  321. #f ; inunderscoreindent
  322. #t ; incomment
  323. currentindent
  324. currentsymbols
  325. emptylines))
  326. (else ; use the reader
  327. (loop
  328. indent-and-symbols
  329. #f ; inindent
  330. #f ; inunderscoreindent
  331. #f ; incomment
  332. currentindent
  333. ; this also takes care of the hashbang and leading comments.
  334. (append currentsymbols (list (wisp-read port)))
  335. emptylines))))))))
  336. (define (line-code-replace-inline-colons line)
  337. "Replace inline colons by opening parens which close at the end of the line"
  338. ; format #t "replace inline colons for line ~A\n" line
  339. (let loop
  340. ((processed '())
  341. (unprocessed line))
  342. (cond
  343. ((null? unprocessed)
  344. ; format #t "inline-colons processed line: ~A\n" processed
  345. processed)
  346. ; replace : . with nothing
  347. ((and (<= 2 (length unprocessed)) (equal? readcolon (car unprocessed)) (equal? repr-dot (car (cdr unprocessed))))
  348. (loop
  349. (append processed
  350. (loop '() (cdr (cdr unprocessed))))
  351. '()))
  352. ((equal? readcolon (car unprocessed))
  353. (loop
  354. ; FIXME: This should turn unprocessed into a list.
  355. (append processed
  356. (list (loop '() (cdr unprocessed))))
  357. '()))
  358. (else
  359. (loop
  360. (append processed
  361. (list (car unprocessed)))
  362. (cdr unprocessed))))))
  363. (define (line-replace-inline-colons line)
  364. (cons
  365. (line-indent line)
  366. (line-code-replace-inline-colons (line-code line))))
  367. (define (line-strip-lone-colon line)
  368. "A line consisting only of a colon is just a marked indentation level. We need to kill the colon before replacing inline colons."
  369. (if
  370. (equal?
  371. (line-code line)
  372. (list readcolon))
  373. (list (line-indent line))
  374. line))
  375. (define (line-finalize line)
  376. "Process all wisp-specific information in a line and strip it"
  377. (let
  378. (
  379. (l
  380. (line-code-replace-inline-colons
  381. (line-strip-indentation-marker
  382. (line-strip-lone-colon
  383. (line-strip-continuation line))))))
  384. (when (not (null? (source-properties line)))
  385. (catch #t
  386. (lambda ()
  387. (set-source-properties! l (source-properties line)))
  388. (lambda (key . arguments)
  389. #f)))
  390. l))
  391. (define (wisp-add-source-properties-from source target)
  392. "Copy the source properties from source into the target and return the target."
  393. (catch #t
  394. (lambda ()
  395. (set-source-properties! target (source-properties source)))
  396. (lambda (key . arguments)
  397. #f))
  398. target)
  399. (define (wisp-propagate-source-properties code)
  400. "Propagate the source properties from the sourrounding list into every part of the code."
  401. (let loop
  402. ((processed '())
  403. (unprocessed code))
  404. (cond
  405. ((and (null? processed) (not (pair? unprocessed)) (not (list? unprocessed)))
  406. unprocessed)
  407. ((and (pair? unprocessed) (not (list? unprocessed)))
  408. (cons
  409. (wisp-propagate-source-properties (car unprocessed))
  410. (wisp-propagate-source-properties (cdr unprocessed))))
  411. ((null? unprocessed)
  412. processed)
  413. (else
  414. (let ((line (car unprocessed)))
  415. (if (null? (source-properties unprocessed))
  416. (wisp-add-source-properties-from line unprocessed)
  417. (wisp-add-source-properties-from unprocessed line))
  418. (loop
  419. (append processed (list (wisp-propagate-source-properties line)))
  420. (cdr unprocessed)))))))
  421. (define* (wisp-scheme-indentation-to-parens lines)
  422. "Add parentheses to lines and remove the indentation markers"
  423. (when
  424. (and
  425. (not (null? lines))
  426. (not (line-empty-code? (car lines)))
  427. (not (= 0 (line-real-indent (car lines ))))); -1 is a line with a comment
  428. (throw 'wisp-syntax-error
  429. (format #f "The first symbol in a chunk must start at zero indentation. Indentation and line: ~A"
  430. (car lines))))
  431. (let loop
  432. ((processed '())
  433. (unprocessed lines)
  434. (indentation-levels '(0)))
  435. (let*
  436. (
  437. (current-line
  438. (if (<= 1 (length unprocessed))
  439. (car unprocessed)
  440. (list 0 ))); empty code
  441. (next-line
  442. (if (<= 2 (length unprocessed))
  443. (car (cdr unprocessed))
  444. (list 0 ))); empty code
  445. (current-indentation
  446. (car indentation-levels))
  447. (current-line-indentation (line-real-indent current-line)))
  448. ; format #t "processed: ~A\ncurrent-line: ~A\nnext-line: ~A\nunprocessed: ~A\nindentation-levels: ~A\ncurrent-indentation: ~A\n\n"
  449. ; . processed current-line next-line unprocessed indentation-levels current-indentation
  450. (cond
  451. ; the real end: this is reported to the outside world.
  452. ((and (null? unprocessed) (not (null? indentation-levels)) (null? (cdr indentation-levels)))
  453. ; display "done\n"
  454. ; reverse the processed lines, because I use cons.
  455. processed)
  456. ; the recursion end-condition
  457. ((and (null? unprocessed))
  458. ; display "last step\n"
  459. ; this is the last step. Nothing more to do except
  460. ; for rolling up the indentation levels. return the
  461. ; new processed and unprocessed lists: this is a
  462. ; side-recursion
  463. (values processed unprocessed))
  464. ((null? indentation-levels)
  465. ; display "indentation-levels null\n"
  466. (throw 'wisp-programming-error "The indentation-levels are null but the current-line is null: Something killed the indentation-levels."))
  467. (else ; now we come to the line-comparisons and indentation-counting.
  468. (cond
  469. ((line-empty-code? current-line)
  470. ; display "current-line empty\n"
  471. ; We cannot process indentation without
  472. ; code. Just switch to the next line. This should
  473. ; only happen at the start of the recursion.
  474. ; TODO: Somehow preserve the line-numbers.
  475. (loop
  476. processed
  477. (cdr unprocessed)
  478. indentation-levels))
  479. ((and (line-empty-code? next-line) (<= 2 (length unprocessed )))
  480. ; display "next-line empty\n"
  481. ; TODO: Somehow preserve the line-numbers.
  482. ; take out the next-line from unprocessed.
  483. (loop
  484. processed
  485. (cons current-line
  486. (cdr (cdr unprocessed)))
  487. indentation-levels))
  488. ((> current-indentation current-line-indentation)
  489. ; display "current-indent > next-line\n"
  490. ; this just steps back one level via the side-recursion.
  491. (let ((previous-indentation (car (cdr indentation-levels))))
  492. (if (<= current-line-indentation previous-indentation)
  493. (values processed unprocessed)
  494. (begin ;; not yet used level! TODO: maybe throw an error here instead of a warning.
  495. (let ((linenumber (- (length lines) (length unprocessed))))
  496. (format (current-error-port) ";;; WARNING:~A: used lower but undefined indentation level (line ~A of the current chunk: ~S). This makes refactoring much more error-prone, therefore it might become an error in a later version of Wisp.\n" (source-property current-line 'line) linenumber (cdr current-line)))
  497. (loop
  498. processed
  499. unprocessed
  500. (cons ; recursion via the indentation-levels
  501. current-line-indentation
  502. (cdr indentation-levels)))))))
  503. ((= current-indentation current-line-indentation)
  504. ; display "current-indent = next-line\n"
  505. (let
  506. ((line (line-finalize current-line))
  507. (next-line-indentation (line-real-indent next-line)))
  508. (cond
  509. ((>= current-line-indentation next-line-indentation)
  510. ; simple recursiive step to the next line
  511. ; display "current-line-indent >= next-line-indent\n"
  512. (loop
  513. (append processed
  514. (if (line-continues? current-line)
  515. line
  516. (wisp-add-source-properties-from line (list line))))
  517. (cdr unprocessed ); recursion here
  518. indentation-levels))
  519. ((< current-line-indentation next-line-indentation)
  520. ; display "current-line-indent < next-line-indent\n"
  521. ; format #t "line: ~A\n" line
  522. ; side-recursion via a sublist
  523. (let-values
  524. (
  525. ((sub-processed sub-unprocessed)
  526. (loop
  527. line
  528. (cdr unprocessed ); recursion here
  529. indentation-levels)))
  530. ; format #t "side-recursion:\n sub-processed: ~A\n processed: ~A\n\n" sub-processed processed
  531. (loop
  532. (append processed (list sub-processed))
  533. sub-unprocessed ; simply use the recursion from the sub-recursion
  534. indentation-levels))))))
  535. ((< current-indentation current-line-indentation)
  536. ; display "current-indent < next-line\n"
  537. (loop
  538. processed
  539. unprocessed
  540. (cons ; recursion via the indentation-levels
  541. current-line-indentation
  542. indentation-levels)))
  543. (else
  544. (throw 'wisp-not-implemented
  545. (format #f "Need to implement further line comparison: current: ~A, next: ~A, processed: ~A."
  546. current-line next-line processed)))))))))
  547. (define (wisp-scheme-replace-inline-colons lines)
  548. "Replace inline colons by opening parens which close at the end of the line"
  549. (let loop
  550. ((processed '())
  551. (unprocessed lines))
  552. (if (null? unprocessed)
  553. processed
  554. (loop
  555. (append processed (list (line-replace-inline-colons (car unprocessed))))
  556. (cdr unprocessed)))))
  557. (define (wisp-scheme-strip-indentation-markers lines)
  558. "Strip the indentation markers from the beginning of the lines"
  559. (let loop
  560. ((processed '())
  561. (unprocessed lines))
  562. (if (null? unprocessed)
  563. processed
  564. (loop
  565. (append processed (cdr (car unprocessed)))
  566. (cdr unprocessed)))))
  567. (define (wisp-unescape-underscore-and-colon code)
  568. "replace \\_ and \\: by _ and :"
  569. (match code
  570. ((a ...)
  571. (map wisp-unescape-underscore-and-colon a))
  572. ('\_
  573. '_)
  574. ('\__
  575. '__)
  576. ('\___
  577. '___)
  578. ('\____
  579. '____)
  580. ('\_____
  581. '_____)
  582. ('\______
  583. '______)
  584. ('\_______
  585. '_______)
  586. ('\________
  587. '________)
  588. ('\_________
  589. '_________)
  590. ('\__________
  591. '__________)
  592. ('\___________
  593. '___________)
  594. ('\____________
  595. '____________)
  596. ('\:
  597. ':)
  598. (a
  599. a)))
  600. (define (wisp-replace-empty-eof code)
  601. "replace ((#<eof>)) by ()"
  602. ; FIXME: Actually this is a hack which fixes a bug when the
  603. ; parser hits files with only hashbang and comments.
  604. (if (and (not (null? code)) (pair? (car code)) (eof-object? (car (car code))) (null? (cdr code)) (null? (cdr (car code))))
  605. (list)
  606. code))
  607. (define (wisp-replace-paren-quotation-repr code)
  608. "Replace lists starting with a quotation symbol by
  609. quoted lists."
  610. (match code
  611. (('REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  612. (list 'quote (map wisp-replace-paren-quotation-repr a)))
  613. ((a ... 'REPR-QUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ); this is the quoted empty list
  614. (append
  615. (map wisp-replace-paren-quotation-repr a)
  616. (list (list 'quote (map wisp-replace-paren-quotation-repr b)))))
  617. (('REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd 'REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  618. (list 'quasiquote (list 'unquote (map wisp-replace-paren-quotation-repr a))))
  619. (('REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  620. (list 'unquote (map wisp-replace-paren-quotation-repr a)))
  621. ((a ... 'REPR-UNQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b )
  622. (append
  623. (map wisp-replace-paren-quotation-repr a)
  624. (list (list 'unquote (map wisp-replace-paren-quotation-repr b)))))
  625. (('REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  626. (list 'quasiquote (map wisp-replace-paren-quotation-repr a)))
  627. ((a ... 'REPR-QUASIQUOTE-e749c73d-c826-47e2-a798-c16c13cb89dd b ); this is the quoted empty list
  628. (append
  629. (map wisp-replace-paren-quotation-repr a)
  630. (list (list 'quasiquote (map wisp-replace-paren-quotation-repr b)))))
  631. (('REPR-UNQUOTESPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  632. (list 'unquote-splicing (map wisp-replace-paren-quotation-repr a)))
  633. (('REPR-SYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  634. (list 'syntax (map wisp-replace-paren-quotation-repr a)))
  635. (('REPR-UNSYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  636. (list 'unsyntax (map wisp-replace-paren-quotation-repr a)))
  637. (('REPR-QUASISYNTAX-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  638. (list 'quasisyntax (map wisp-replace-paren-quotation-repr a)))
  639. (('REPR-UNSYNTAXSPLICING-e749c73d-c826-47e2-a798-c16c13cb89dd a ...)
  640. (list 'unsyntax-splicing (map wisp-replace-paren-quotation-repr a)))
  641. ;; literal array as start of a line: # (a b) c -> (#(a b) c)
  642. ((#\# a ...)
  643. (with-input-from-string ;; hack to defer to read
  644. (string-append "#"
  645. (with-output-to-string
  646. (λ ()
  647. (write (map wisp-replace-paren-quotation-repr a)
  648. (current-output-port)))))
  649. read))
  650. ((a ...)
  651. (map wisp-replace-paren-quotation-repr a))
  652. (a
  653. a)))
  654. (define (wisp-make-improper code)
  655. "Turn (a #{.}# b) into the correct (a . b).
  656. read called on a single dot creates a variable named #{.}# (|.|
  657. in r7rs). Due to parsing the indentation before the list
  658. structure is known, the reader cannot create improper lists
  659. when it reads a dot. So we have to take another pass over the
  660. code to recreate the improper lists.
  661. Match is awesome!"
  662. (let
  663. (
  664. (improper
  665. (match code
  666. ((a ... b 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd c)
  667. (append (map wisp-make-improper a)
  668. (cons (wisp-make-improper b) (wisp-make-improper c))))
  669. ((a ...)
  670. (map wisp-make-improper a))
  671. (a
  672. a))))
  673. (define (syntax-error li msg)
  674. (throw 'wisp-syntax-error (format #f "incorrect dot-syntax #{.}# in code: ~A: ~A" msg li)))
  675. (if #t
  676. improper
  677. (let check
  678. ((tocheck improper))
  679. (match tocheck
  680. ; lists with only one member
  681. (('REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd)
  682. (syntax-error tocheck "list with the period as only member"))
  683. ; list with remaining dot.
  684. ((a ...)
  685. (if (and (member repr-dot a))
  686. (syntax-error tocheck "leftover period in list")
  687. (map check a)))
  688. ; simple pair - this and the next do not work when parsed from wisp-scheme itself. Why?
  689. (('REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd . c)
  690. (syntax-error tocheck "dot as first element in already improper pair"))
  691. ; simple pair, other way round
  692. ((a . 'REPR-DOT-e749c73d-c826-47e2-a798-c16c13cb89dd)
  693. (syntax-error tocheck "dot as last element in already improper pair"))
  694. ; more complex pairs
  695. ((? pair? a)
  696. (let
  697. ((head (drop-right a 1))
  698. (tail (last-pair a)))
  699. (cond
  700. ((equal? repr-dot (car tail))
  701. (syntax-error tocheck "equal? repr-dot : car tail"))
  702. ((equal? repr-dot (cdr tail))
  703. (syntax-error tocheck "equal? repr-dot : cdr tail"))
  704. ((member repr-dot head)
  705. (syntax-error tocheck "member repr-dot head"))
  706. (else
  707. a))))
  708. (a
  709. a))))))
  710. (define (wisp-scheme-read-chunk port)
  711. "Read and parse one chunk of wisp-code"
  712. (let (( lines (wisp-scheme-read-chunk-lines port)))
  713. (wisp-make-improper
  714. (wisp-replace-empty-eof
  715. (wisp-unescape-underscore-and-colon
  716. (wisp-replace-paren-quotation-repr
  717. (wisp-propagate-source-properties
  718. (wisp-scheme-indentation-to-parens lines))))))))
  719. (define (wisp-scheme-read-all port)
  720. "Read all chunks from the given port"
  721. (let loop
  722. ((tokens '()))
  723. (cond
  724. ((eof-object? (peek-char port))
  725. tokens)
  726. (else
  727. (loop
  728. (append tokens (wisp-scheme-read-chunk port)))))))
  729. (define (wisp-scheme-read-file path)
  730. (call-with-input-file path wisp-scheme-read-all))
  731. (define (wisp-scheme-read-file-chunk path)
  732. (call-with-input-file path wisp-scheme-read-chunk))
  733. (define (wisp-scheme-read-string str)
  734. (call-with-input-string str wisp-scheme-read-all))
  735. (define (wisp-scheme-read-string-chunk str)
  736. (call-with-input-string str wisp-scheme-read-chunk))
  737. ;;;; Test special syntax
  738. ; ;; quote the list
  739. ; write
  740. ; wisp-scheme-read-string "moo
  741. ; foo
  742. ; ' bar
  743. ; baz waz"
  744. ; newline
  745. ; ;; quote the symbol - in wisp, whitespace after quote is not allowed!
  746. ; write
  747. ; wisp-scheme-read-string "moo
  748. ; foo
  749. ; 'bar
  750. ; baz waz"
  751. ; newline
  752. ; ; ;; quote the list with colon
  753. ; write
  754. ; wisp-scheme-read-string "moo : ' foo
  755. ; foo
  756. ; ' bar bah
  757. ; baz waz"
  758. ; newline
  759. ; ; ;; syntax the list
  760. ; write
  761. ; wisp-scheme-read-string "moo : #' foo
  762. ; foo
  763. ; #' bar bah
  764. ; baz waz"
  765. ; newline
  766. ;
  767. ;;;; Test improper lists
  768. ;;;; Good cases
  769. ; write
  770. ; wisp-scheme-read-string "foo . bar"
  771. ; newline
  772. ; write
  773. ; wisp-scheme-read-string "foo .
  774. ; . bar"
  775. ; newline
  776. ; write
  777. ; wisp-scheme-read-string "foo
  778. ; . . bar"
  779. ; newline
  780. ; write
  781. ; wisp-scheme-read-string "moo
  782. ; foo
  783. ; . . bar
  784. ; baz waz"
  785. ; newline
  786. ;;;; Syntax Error cases
  787. ; write
  788. ; wisp-scheme-read-string "foo
  789. ; . . ."
  790. ; newline
  791. ; write
  792. ; wisp-scheme-read-string "moo : . . bar"
  793. ; write
  794. ; wisp-scheme-read-string "foo .
  795. ; . . bar"
  796. ; newline
  797. ; write
  798. ; wisp-scheme-read-string "moo
  799. ; foo
  800. ; . . bar baz
  801. ; baz waz"
  802. ; newline
  803. ;;;; stranger stuff
  804. ; write
  805. ; wisp-scheme-read-string "foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo \"\n\" \n___ . goo . hoo"
  806. ; newline
  807. ; display
  808. ; wisp-scheme-read-string " foo ; bar\n ; nop \n\n; nup\n; nup \n \n\n\nfoo : moo"
  809. ; newline
  810. ; write : wisp-scheme-read-file-chunk "wisp-scheme.w"
  811. ; newline
  812. ; call-with-output-file "wisp-guile.scm"
  813. ; lambda : port
  814. ; map
  815. ; lambda : chunk
  816. ; write chunk port
  817. ; wisp-scheme-read-file "wisp-guile.w"
  818. ; run all chunks in wisp-guile.w as parsed by wisp-scheme.w. Give wisp-guile.w to parse as argument.
  819. ; map primitive-eval : wisp-scheme-read-file "wisp-guile.w" ; actually runs wisp-guile.w with the arguments supplied to this script.
  820. ; uncomment the previous line, then run the next line in the shell. If 1 and 2 are equal, this parser works!
  821. ; 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