2018-09-12 21:44:02 -04:00
|
|
|
|
;;;; Prebirth Lisp implemented in Prebirth Lisp (self-hosting)
|
|
|
|
|
;;;;
|
|
|
|
|
;;;; Copyright (C) 2017, 2018 Mike Gerwitz
|
|
|
|
|
;;;;
|
|
|
|
|
;;;; This file is part of Ulambda Scheme.
|
|
|
|
|
;;;;
|
|
|
|
|
;;;; Ulambda Scheme is free software: you can redistribute it and/or modify
|
|
|
|
|
;;;; it under the terms of the GNU Affero 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 Affero General Public License
|
|
|
|
|
;;;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
;;;;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2018-09-11 20:20:48 -04:00
|
|
|
|
;;; THIS IS TEMPORARY CODE that will be REWRITTEN IN REBIRTH LISP ITSELF
|
2018-02-08 23:40:00 -05:00
|
|
|
|
;;; after a very basic bootstrap is complete. It is retained as an
|
|
|
|
|
;;; important artifact for those who wish to build Ulambda Scheme from
|
|
|
|
|
;;; scratch without using another version of Ulambda itself. This is called
|
|
|
|
|
;;; "self-hosting".
|
2017-08-31 01:01:06 -04:00
|
|
|
|
;;;
|
|
|
|
|
;;; This is the Prebirth Lisp implementation of the JavaScript Prebirth
|
|
|
|
|
;;; compiler, found in `prebirth.js'---that compiler can be used to compile
|
|
|
|
|
;;; this compiler, which can then be used to compile itself, completing the
|
2017-09-02 01:30:13 -04:00
|
|
|
|
;;; bootstrapping process. This process is termed "Birth", and the process
|
|
|
|
|
;;; is successful if the output of Birth compiling itself is byte-for-byte
|
|
|
|
|
;;; identical to the output of compiling Birth with Prebirth.
|
2017-08-31 01:01:06 -04:00
|
|
|
|
;;;
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;;; This is largely a 1:1 translation of `prebirth.js'. See that file for
|
|
|
|
|
;;; terminology.
|
2017-09-02 01:30:13 -04:00
|
|
|
|
;;;
|
|
|
|
|
;;; Note that we're dealing with a small subset of Scheme here, so certain
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;;; things might be done differently given a proper implementation.
|
|
|
|
|
;;;
|
|
|
|
|
;;; The next step after this is ``Rebirth'': both Prebirth and Birth require
|
|
|
|
|
;;; the manually written `libprebirth.js' to function. Rebirth will remove
|
|
|
|
|
;;; that completely, which bootstraps the runtime in its entirety. At that
|
|
|
|
|
;;; point, all development will be exclusively in Scheme and we can get on
|
2018-02-08 23:40:00 -05:00
|
|
|
|
;;; with Ulambda Scheme.
|
2018-09-12 21:44:02 -04:00
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
|
2017-08-31 01:01:06 -04:00
|
|
|
|
|
|
|
|
|
;; pair selection
|
|
|
|
|
(define (cadr xs)
|
|
|
|
|
(car (cdr xs)))
|
|
|
|
|
(define (caadr xs)
|
|
|
|
|
(car (car (cdr xs))))
|
|
|
|
|
(define (caddr xs)
|
|
|
|
|
(car (cdr (cdr xs))))
|
2017-09-02 01:30:13 -04:00
|
|
|
|
(define (cadddr xs)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(car (cdr (cdr (cdr xs)))))
|
|
|
|
|
(define (caddddr xs)
|
|
|
|
|
(car (cdr (cdr (cdr (cdr xs))))))
|
|
|
|
|
(define (cddr xs)
|
|
|
|
|
(cdr (cdr xs)))
|
|
|
|
|
|
|
|
|
|
(define (not x)
|
|
|
|
|
(if x #f #t))
|
2017-08-31 01:01:06 -04:00
|
|
|
|
|
2017-09-02 01:30:13 -04:00
|
|
|
|
;; for convenience
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(define (es:match-regexp re s)
|
|
|
|
|
(es:match (es:regexp re) s))
|
2017-08-31 01:01:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Convert source input into a string of tokens.
|
|
|
|
|
;;
|
|
|
|
|
;; This is the lexer. Whitespace is ignored. The grammar consists of
|
|
|
|
|
;; simple s-expressions.
|
|
|
|
|
;;
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
;; Tokens are produced with `make-token'. The source SRC will be
|
|
|
|
|
;; left-truncated as input is processed. POS exists for producing metadata
|
|
|
|
|
;; for error reporting---it has no impact on parsing.
|
|
|
|
|
;;
|
|
|
|
|
;; This implementation was originally recursive and immutable, but the stack
|
|
|
|
|
;; was being exhausted, so it was refactored into an inferior
|
2017-12-12 01:03:37 -05:00
|
|
|
|
;; implementation. Note the use of `es:while' and `es:break'---these are
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
;; quick fixes to the problem of stack exhaustion in browsers (where we have
|
|
|
|
|
;; no control over the stack limit); proper tail call support will come
|
|
|
|
|
;; later when we have a decent architecture in place.
|
2017-08-31 01:01:06 -04:00
|
|
|
|
;;
|
|
|
|
|
;; The result is a list of tokens. See `token' for the format.
|
|
|
|
|
(define (lex src pos)
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(let ((toks (list)))
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(es:while #t ; browser stack workaround
|
|
|
|
|
(let* ((ws (or (es:match-regexp "^\\s+"
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
src)
|
|
|
|
|
(list "")))
|
|
|
|
|
(ws-len (string-length (car ws)))
|
|
|
|
|
(trim (substring src ws-len)) ; ignore whitespace, if any
|
|
|
|
|
(newpos (+ pos ws-len))) ; adj pos to account for removed ws
|
|
|
|
|
|
|
|
|
|
(if (string=? "" trim)
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(es:break) ; EOF and we're done
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
|
|
|
|
|
;; normally we'd use `string-ref' here, but then we'd have to
|
|
|
|
|
;; implement Scheme characters, so let's keep this simple and keep
|
|
|
|
|
;; with strings
|
|
|
|
|
(let* ((ch (substring trim 0 1))
|
|
|
|
|
(t (case ch
|
|
|
|
|
;; comments extend until the end of the line
|
2017-12-12 01:03:37 -05:00
|
|
|
|
((";") (let ((eol (es:match-regexp "^(.*?)(\\n|$)" trim)))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(make-token "comment" (cadr eol) trim newpos)))
|
|
|
|
|
|
|
|
|
|
;; left and right parenthesis are handled in the same
|
|
|
|
|
;; manner: they produce distinct tokens with
|
|
|
|
|
;; single-character lexemes
|
|
|
|
|
(("(") (make-token "open" ch trim newpos))
|
|
|
|
|
((")") (make-token "close" ch trim newpos))
|
|
|
|
|
|
|
|
|
|
;; strings are delimited by opening and closing ASCII
|
|
|
|
|
;; double quotes, which can be escaped with a
|
|
|
|
|
;; backslash
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(("\"") (let ((str (es:match-regexp
|
2017-12-05 00:47:26 -05:00
|
|
|
|
"^\"(|(?:.|\\\n)*?[^\\\\])\""
|
|
|
|
|
trim)))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(or str (parse-error
|
|
|
|
|
src pos
|
|
|
|
|
"missing closing string delimiter"))
|
|
|
|
|
;; a string token consists of the entire
|
|
|
|
|
;; string including quotes as its lexeme,
|
|
|
|
|
;; but its value will be the value of the
|
|
|
|
|
;; string without quotes due to the `str'
|
|
|
|
|
;; match group (see `token')
|
|
|
|
|
(make-token "string" str trim newpos)))
|
|
|
|
|
|
|
|
|
|
(else
|
|
|
|
|
;; anything else is considered a symbol up until
|
|
|
|
|
;; whitespace or any of the aforementioned
|
|
|
|
|
;; delimiters
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(let ((symbol (es:match-regexp "^[^\\s()\"]+"
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
trim)))
|
|
|
|
|
(make-token "symbol" symbol trim newpos))))))
|
|
|
|
|
|
|
|
|
|
;; yikes---see notes in docblock with regards to why
|
|
|
|
|
;; we're using mutators here
|
|
|
|
|
(set! toks (append toks (list (car t))))
|
|
|
|
|
(set! src (cadr t))
|
|
|
|
|
(set! pos (caddr t))))))
|
|
|
|
|
toks))
|
2017-08-31 01:01:06 -04:00
|
|
|
|
|
|
|
|
|
|
2017-09-02 01:30:13 -04:00
|
|
|
|
|
2017-08-31 12:44:41 -04:00
|
|
|
|
;; Throw an error with a window of surrounding source code.
|
|
|
|
|
;;
|
|
|
|
|
;; The "window" is simply ten characters to the left and right of the
|
|
|
|
|
;; first character of the source input SRC that resulted in the error.
|
|
|
|
|
;; It's a little more than useless.
|
|
|
|
|
(define (parse-error src pos msg)
|
|
|
|
|
(let ((window (substring src (- pos 10) (+ pos 10))))
|
|
|
|
|
(error (string-append msg " (pos " pos "): " window)
|
|
|
|
|
src)))
|
|
|
|
|
|
|
|
|
|
|
2017-09-02 01:30:13 -04:00
|
|
|
|
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
;; Produce a token, left-truncate src, and update pos.
|
2017-08-31 01:01:06 -04:00
|
|
|
|
;;
|
|
|
|
|
;; Unlike the JS Prebirth implementation which uses a key/value object,
|
|
|
|
|
;; we're just using a simple list.
|
|
|
|
|
;;
|
|
|
|
|
;; The expected arguments are: the token type TYPE, the match group or
|
|
|
|
|
;; string MATCH, left-truncated source code SRC, and the position POS
|
|
|
|
|
;; relative to the original source code.
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(define (make-token type match src pos)
|
2017-08-31 01:01:06 -04:00
|
|
|
|
(let* ((parts (if (list? match) match (list match match)))
|
|
|
|
|
(lexeme (car parts))
|
|
|
|
|
;; the value is the first group of the match (indicating what we
|
|
|
|
|
;; are actually interested in), and the lexeme is the full match,
|
|
|
|
|
;; which might include, for example, string delimiters
|
|
|
|
|
(value (or (and (pair? (cdr parts))
|
|
|
|
|
(cadr parts))
|
|
|
|
|
lexeme))
|
|
|
|
|
(len (string-length lexeme)))
|
|
|
|
|
|
|
|
|
|
;; produce token and recurse on `lex', left-truncating the source
|
|
|
|
|
;; string to discard what we have already processed
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(list (list (quote token) type lexeme value pos)
|
|
|
|
|
(substring src len)
|
|
|
|
|
(+ pos len))))
|
2017-08-31 01:01:06 -04:00
|
|
|
|
|
|
|
|
|
|
2017-09-02 01:30:13 -04:00
|
|
|
|
;; various accessor procedures for token lists (we're Prebirth Lisp here,
|
|
|
|
|
;; so no record support or anything fancy!)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(define (token? t) (and (pair? t) (symbol=? (quote token) (car t))))
|
|
|
|
|
(define (token-type t) (cadr t))
|
|
|
|
|
(define (token-lexeme t) (caddr t))
|
|
|
|
|
(define (token-value t) (cadddr t))
|
|
|
|
|
(define (token-pos t) (caddddr t))
|
2017-09-02 01:30:13 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Produce an AST from the given string SRC of sexps
|
|
|
|
|
;;
|
|
|
|
|
;; This is essentially the CST with whitespace removed. It first invokes
|
|
|
|
|
;; the lexer to produce a token string from the input sexps SRC. From this,
|
|
|
|
|
;; it verifies only proper nesting (that SRC does not close sexps too early
|
|
|
|
|
;; and that EOF isn't reached before all sexps are closed) and produces an
|
|
|
|
|
;; AST that is an isomorphism of the original sexps.
|
|
|
|
|
(define (parse-lisp src)
|
|
|
|
|
;; accessor methods to make you and me less consfused
|
|
|
|
|
(define (ast-depth ast) (car ast))
|
|
|
|
|
(define (ast-tree ast) (cadr ast))
|
|
|
|
|
(define (ast-stack ast) (caddr ast))
|
|
|
|
|
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; perform a leftmost reduction on the token string
|
2017-09-02 01:30:13 -04:00
|
|
|
|
(define (toks->ast toks)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(fold
|
2017-09-02 01:30:13 -04:00
|
|
|
|
(lambda (token result)
|
|
|
|
|
(let ((depth (ast-depth result))
|
|
|
|
|
(xs (ast-tree result))
|
|
|
|
|
(stack (ast-stack result))
|
|
|
|
|
(type (token-type token))
|
|
|
|
|
(pos (token-pos token)))
|
|
|
|
|
|
|
|
|
|
;; there are very few token types to deal with (again, this is a
|
|
|
|
|
;; very simple bootstrap lisp)
|
|
|
|
|
(case type
|
|
|
|
|
;; ignore comments
|
|
|
|
|
(("comment") result)
|
|
|
|
|
|
|
|
|
|
;; when beginning a new expression, place the expression
|
|
|
|
|
;; currently being processed onto a stack, allocate a new list,
|
|
|
|
|
;; and we'll continue processing into that new list
|
|
|
|
|
(("open") (list (+ depth 1)
|
|
|
|
|
(list)
|
|
|
|
|
(cons xs stack)))
|
|
|
|
|
|
|
|
|
|
;; once we reach the end of the expression, pop the parent off of
|
|
|
|
|
;; the stack and append the new list to it
|
|
|
|
|
(("close") (if (zero? depth)
|
|
|
|
|
(parse-error src pos
|
|
|
|
|
"unexpected closing parenthesis")
|
|
|
|
|
(list (- depth 1)
|
|
|
|
|
(append (car stack) (list xs))
|
|
|
|
|
(cdr stack))))
|
|
|
|
|
|
|
|
|
|
;; strings and symbols (we cheat and just consider everything,
|
|
|
|
|
;; including numbers and such, to be symbols) are just copied
|
|
|
|
|
;; in place
|
|
|
|
|
(("string" "symbol") (list depth
|
|
|
|
|
(append xs (list token))
|
|
|
|
|
stack))
|
|
|
|
|
|
|
|
|
|
;; we should never encounter anything else unless there's a bug
|
|
|
|
|
;; in the tokenizer or we forget a token type above
|
|
|
|
|
(else (parse-error
|
|
|
|
|
src pos (string-append
|
|
|
|
|
"unexpected token `" type "'"))))))
|
|
|
|
|
(list 0 (list) (list)) ; initial 0 depth; empty tree; expr stack
|
|
|
|
|
toks))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; lex the input SRC and pass it to `toks->ast' to generate the AST;
|
|
|
|
|
;; if the depth is non-zero after we're done, then we're unbalanced.
|
|
|
|
|
(let* ((toks (lex src 0))
|
|
|
|
|
(ast (toks->ast toks)))
|
|
|
|
|
(if (zero? (ast-depth ast))
|
|
|
|
|
(ast-tree ast)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; if we terminate at a non-zero depth, that means there ar still
|
|
|
|
|
;; open sexps
|
2017-09-02 01:30:13 -04:00
|
|
|
|
(error (string-append
|
|
|
|
|
"unexpected end of input at depth "
|
|
|
|
|
(ast-depth ast))))))
|
|
|
|
|
|
|
|
|
|
|
2017-12-08 23:58:15 -05:00
|
|
|
|
;; Compile Birth Lisp AST into ECMAScript.
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;;
|
|
|
|
|
;; The AST can be generated with `parse-lisp'.
|
2017-12-08 23:58:15 -05:00
|
|
|
|
(define (birth->ecmascript ast)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; Generate ECMAScript-friendly name from the given id.
|
|
|
|
|
;;
|
|
|
|
|
;; A subset of special characters that are acceptable in Scheme are
|
|
|
|
|
;; converted in an identifiable manner; others are simply converted to `$'
|
|
|
|
|
;; in a catch-all and therefore could result in conflicts and cannot be
|
|
|
|
|
;; reliably distinguished from one-another. Remember: this is temporary
|
|
|
|
|
;; code.
|
|
|
|
|
(define (tname->id name)
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(if (es:match (es:regexp "^\\d+$") name)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
name
|
|
|
|
|
(string-append
|
2017-12-12 01:03:37 -05:00
|
|
|
|
"$$" (es:replace (es:regexp "[^a-zA-Z0-9_]" "g")
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(lambda (c)
|
|
|
|
|
(case c
|
|
|
|
|
(("-") "$_$")
|
|
|
|
|
(("?") "$7$")
|
|
|
|
|
(("@") "$a$")
|
|
|
|
|
(("!") "$b$")
|
|
|
|
|
((">") "$g$")
|
|
|
|
|
(("#") "$h$")
|
|
|
|
|
(("*") "$k$")
|
|
|
|
|
(("<") "$l$")
|
|
|
|
|
(("&") "$n$")
|
|
|
|
|
(("%") "$o$")
|
|
|
|
|
(("+") "$p$")
|
|
|
|
|
(("=") "$q$")
|
|
|
|
|
(("^") "$v$")
|
|
|
|
|
(("/") "$w$")
|
|
|
|
|
(("$") "$$")
|
|
|
|
|
(else "$")))
|
|
|
|
|
name))))
|
|
|
|
|
|
|
|
|
|
;; Join a list of strings XS on a delimiter DELIM
|
|
|
|
|
(define (join delim xs)
|
|
|
|
|
(if (pair? xs)
|
|
|
|
|
(fold (lambda (x str)
|
|
|
|
|
(string-append str delim x))
|
|
|
|
|
(car xs)
|
|
|
|
|
(cdr xs))
|
|
|
|
|
""))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Compile parameter list.
|
|
|
|
|
;;
|
|
|
|
|
;; This simply takes the value of the symbol and outputs it (formatted),
|
|
|
|
|
;; delimited by commas.
|
|
|
|
|
(define (params->es params)
|
|
|
|
|
(join ", " (map (lambda (t)
|
|
|
|
|
(tname->id (token-value t)))
|
|
|
|
|
params)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Compile body s-expressions into ECMAScript
|
|
|
|
|
;;
|
|
|
|
|
;; This produces a 1:1 mapping of body XS s-expressions to ES statements,
|
|
|
|
|
;; recursively. The heavy lifting is done by `sexp->es'.
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(define (body->es xs ret)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; recursively process body XS until we're out of pairs
|
|
|
|
|
(if (not (pair? xs))
|
|
|
|
|
""
|
|
|
|
|
(let* ((x (car xs))
|
|
|
|
|
(rest (cdr xs))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(more? (or (not ret) (pair? rest))))
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; the result is a semicolon-delimited string of statements, with
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
;; the final statement prefixed with `return' unless (not ret)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(string-append
|
|
|
|
|
" "
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(if more? "" "return ") ; prefix with `return' if last body exp
|
|
|
|
|
(sexp->es x) ";" ; process current body expression
|
|
|
|
|
(if (pair? rest) "\n" "")
|
|
|
|
|
(body->es rest ret))))) ; recurse
|
2017-09-21 13:37:16 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Compile procedure definition into an ES function definition
|
|
|
|
|
;;
|
|
|
|
|
;; This will fail if the given token is not a `define'.
|
|
|
|
|
(define (cdfn t)
|
|
|
|
|
;; e.g. (define (foo ...) body)
|
|
|
|
|
(let* ((dfn (cadr t))
|
|
|
|
|
(id (tname->id (token-value (car dfn))))
|
|
|
|
|
(params (params->es (cdr dfn)))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(body (body->es (cddr t) #t)))
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; this is the final format---each procedure becomes its own function
|
|
|
|
|
;; definition in ES
|
|
|
|
|
(string-append
|
|
|
|
|
"function " id "(" params ")\n{\n" body "\n};")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Function/procedure aliases and special forms
|
|
|
|
|
;;
|
|
|
|
|
;; And here we have what is probably the most grotesque part of this file.
|
|
|
|
|
;;
|
|
|
|
|
;; This map allows for a steady transition---items can be removed as they
|
|
|
|
|
;; are written in Prebirth Lisp. This should give us a sane (but still
|
|
|
|
|
;; simple) environment with which we can start to self-host.
|
|
|
|
|
;;
|
|
|
|
|
;; String values are simple function aliases. Function values take over
|
|
|
|
|
;; the compilation of that function and allow for defining special forms
|
|
|
|
|
;; (in place of macro support). The first argument FN is the name of the
|
2017-12-15 00:40:10 -05:00
|
|
|
|
;; function/procedure/form, and ARGS is the list of arguments.
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;;
|
|
|
|
|
;; These are by no means meant to be solid implementations; notable
|
|
|
|
|
;; deficiencies are documented, but don't expect this to work properly in
|
|
|
|
|
;; every case. They will be replaced with proper R7RS implementations in
|
2017-12-15 00:40:10 -05:00
|
|
|
|
;; the future (post-Rebirth).
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(define (fnmap fn args t)
|
|
|
|
|
(case fn
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(("es:console")
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(string-append "console.log(" (map sexp->es args) ")"))
|
|
|
|
|
|
2017-12-11 22:24:19 -05:00
|
|
|
|
;; only expands `else' clauses; this is just to facilitate its use
|
2017-12-05 00:35:54 -05:00
|
|
|
|
;; moving forward
|
2017-12-11 22:24:19 -05:00
|
|
|
|
(("cond-expand")
|
|
|
|
|
(join "" (map (lambda (clause)
|
|
|
|
|
(let ((feature (token-value (car clause)))
|
|
|
|
|
(body (cdr clause)))
|
|
|
|
|
(if (string=? feature "else")
|
|
|
|
|
(body->es body #f)
|
|
|
|
|
"")))
|
|
|
|
|
args)))
|
2017-12-05 00:35:54 -05:00
|
|
|
|
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
;; yes, there are more important things to do until we get to the
|
|
|
|
|
;; point where it's worth implementing proper tail calls
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(("es:while")
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(let ((pred (car args))
|
|
|
|
|
(body (cdr args)))
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(__whilebrk){"
|
|
|
|
|
"while (" (sexp->es pred) "){\n"
|
|
|
|
|
(body->es body #f) " if (__whilebrk) break;\n"
|
|
|
|
|
"}\n"
|
|
|
|
|
"})(false)")))
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(("es:break") "__whilebrk=true")
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; fortunately ES6+ has native symbol support :)
|
|
|
|
|
;; we don't (yet?) need list quoting in Prebirth
|
|
|
|
|
(("quote")
|
|
|
|
|
(if (pair? (cdr args))
|
|
|
|
|
(error "quoting lists is not yet supported; sorry!")
|
|
|
|
|
(string-append "Symbol.for('" (sexp->es args) "')")))
|
|
|
|
|
|
|
|
|
|
(("define") (cdfn t))
|
|
|
|
|
|
|
|
|
|
(("lambda")
|
|
|
|
|
(let ((fnargs (car args))
|
|
|
|
|
(body (cdr args)))
|
|
|
|
|
(string-append
|
|
|
|
|
"function(" (join ", " (map sexp->es fnargs)) "){\n"
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(body->es body #t)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
"}")))
|
|
|
|
|
|
|
|
|
|
;; simple if statement with optional else, wrapped in a self-executing
|
|
|
|
|
;; function to simplify code generation (e.g. returning an if)
|
|
|
|
|
(("if")
|
|
|
|
|
(let ((pred (car args))
|
|
|
|
|
(t (cadr args))
|
|
|
|
|
(f (and (pair? (cddr args))
|
|
|
|
|
(caddr args))))
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(){"
|
|
|
|
|
"if (_truep(" (sexp->es pred) ")){return " (sexp->es t) ";}"
|
|
|
|
|
(if (pair? f)
|
|
|
|
|
(string-append "else{return " (sexp->es f) ";}")
|
|
|
|
|
"")
|
|
|
|
|
"})()")))
|
|
|
|
|
|
|
|
|
|
;; and short-circuits, so we need to implement it as a special form
|
|
|
|
|
;; rather than an alias
|
|
|
|
|
(("and")
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(__and){\n"
|
|
|
|
|
(join "" (map (lambda (expr)
|
|
|
|
|
(string-append
|
|
|
|
|
"__and = " (sexp->es expr) "; "
|
|
|
|
|
"if (!_truep(__and)) return false;\n"))
|
|
|
|
|
args))
|
|
|
|
|
"return __and;})()"))
|
|
|
|
|
|
|
|
|
|
;; or short-circuits, so we need to implement it as a special form
|
|
|
|
|
;; rather than an alias
|
|
|
|
|
(("or")
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(__or){\n"
|
|
|
|
|
(join "" (map (lambda (expr)
|
|
|
|
|
(string-append
|
|
|
|
|
"__or = " (sexp->es expr) "; "
|
|
|
|
|
"if (_truep(__or)) return __or;\n"))
|
|
|
|
|
args))
|
|
|
|
|
"return false;})()"))
|
|
|
|
|
|
|
|
|
|
;; (let ((binding val) ...) ...body), compiled as a self-executing
|
|
|
|
|
;; function which allows us to easily represent the return value of
|
|
|
|
|
;; the entire expression while maintaining local scope
|
|
|
|
|
(("let*")
|
|
|
|
|
(let ((bindings (car args))
|
|
|
|
|
(body (cdr args)))
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(){\n"
|
|
|
|
|
(join "" (map (lambda (binding)
|
|
|
|
|
(let ((var (car binding))
|
|
|
|
|
(init (cadr binding)))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(string-append " let " (sexp->es var)
|
2017-09-21 13:37:16 -04:00
|
|
|
|
" = " (sexp->es init) ";\n")))
|
|
|
|
|
bindings))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(body->es body #t) "\n"
|
2017-09-21 13:37:16 -04:00
|
|
|
|
" })()")))
|
|
|
|
|
|
|
|
|
|
;; similar to the above, but variables cannot reference one-another
|
|
|
|
|
(("let")
|
|
|
|
|
(let* ((bindings (car args))
|
|
|
|
|
(body (cdr args))
|
|
|
|
|
(fparams (join ", " (map sexp->es
|
|
|
|
|
(map car bindings))))
|
|
|
|
|
(fargs (join ", " (map sexp->es
|
|
|
|
|
(map cadr bindings)))))
|
|
|
|
|
(string-append "(function(" fparams "){\n"
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(body->es body #t) "\n"
|
2017-09-21 13:37:16 -04:00
|
|
|
|
"})(" fargs ")")))
|
|
|
|
|
|
|
|
|
|
;; and here I thought Prebirth Lisp would be simple...but having
|
|
|
|
|
;; `case' support really keeps things much more tidy, so here we are
|
|
|
|
|
;; (note that it doesn't support the arrow form, nor does it support
|
|
|
|
|
;; expressions as data)
|
|
|
|
|
(("case")
|
|
|
|
|
(let ((key (car args))
|
|
|
|
|
(clauses (cdr args)))
|
|
|
|
|
(string-append
|
|
|
|
|
"(function(){const _key=" (sexp->es key) ";\n"
|
|
|
|
|
"switch (_key){\n"
|
|
|
|
|
(join ""
|
|
|
|
|
(map (lambda (data exprs)
|
|
|
|
|
(string-append
|
|
|
|
|
(if (and (token? data)
|
|
|
|
|
(string=? "else" (token-lexeme data)))
|
|
|
|
|
"default:\n"
|
|
|
|
|
(join ""
|
|
|
|
|
(map (lambda (datum)
|
|
|
|
|
(string-append
|
|
|
|
|
"case " (sexp->es datum) ":\n"))
|
|
|
|
|
data)))
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(body->es exprs #t) "\n"))
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(map car clauses)
|
|
|
|
|
(map cdr clauses)))
|
|
|
|
|
"}})()")))
|
|
|
|
|
|
birth,prebirth: Non-recursive lexing to prevent stack exhaustion
This needs to run in the browser too, where we have no control over stack
limits.
* build-aux/bootstrap/birth.scm
(lex): Non-recursive strategy (loop with mutable list).
(make-token): Update doc. Produce list of token, new string, and
position. Don't recurse.
(body->es): Add `ret' param. Only produce `return' statement if new param
is set.
(cdfn): Use it.
(fnmap)
[js:while, js:break]: Add forms.
[lambda, let, case]: Use new `body->es' `ret' param.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
(prebirth->ecmascript): Adjust libprebirth path to be relative to self.
* build-aux/bootstrap/libprebirth.js
($$append$b$): Add `append!' procedure.
($$js$regexp, $$js$match, $$js$replace): Move a few lines up.
(fs): Provide stub if `require' is not defined.
* build-aux/bootstrap/prebirth.js
(_lex): Non-recursive strategy (loop with array-appending).
(_token): No more mutual recursion with `#_lex'. Return new string
and position.
(_bodyToEs): Add `ret' param. Only produce `return' statement if new
param is set.
(fnmap) [js:while, js:break]: Add forms.
[let*]: Define JS variables in output using `let' instead of `const' to
permit mutating with new `set!' form. Use new `body->es' `ret' param.
[set!]: Add form.
2017-10-09 00:59:11 -04:00
|
|
|
|
(("set!")
|
|
|
|
|
(let ((varid (car args))
|
|
|
|
|
(val (cadr args)))
|
|
|
|
|
(string-append (sexp->es varid) " = " (sexp->es val))))
|
|
|
|
|
|
2017-09-21 13:37:16 -04:00
|
|
|
|
;; normal procedure application
|
|
|
|
|
(else (let* ((idfn (tname->id fn))
|
|
|
|
|
(argstr (join ", " (map sexp->es args))))
|
|
|
|
|
(string-append idfn "(" argstr ")")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Convert s-expressions or scalar into ECMAScript
|
|
|
|
|
;;
|
|
|
|
|
;; T may be either an array of tokens or a primitive token (e.g. string,
|
|
|
|
|
;; symbol). This procedure is applied recursively to T as needed if T is
|
|
|
|
|
;; a list.
|
|
|
|
|
(define (sexp->es t)
|
|
|
|
|
(if (not (list? t))
|
|
|
|
|
(error "unexpected non-list for sexp->es token"))
|
|
|
|
|
|
|
|
|
|
(if (token? t)
|
|
|
|
|
(case (token-type t)
|
|
|
|
|
;; strings output as-is (note that we don't escape double quotes,
|
|
|
|
|
;; because the method of escaping them is the same in Scheme as it
|
|
|
|
|
;; is in ECMAScript---a backslash)
|
|
|
|
|
(("string") (string-append "\"" (token-value t) "\""))
|
|
|
|
|
|
|
|
|
|
;; symbols have the same concerns as procedure definitions: the
|
|
|
|
|
;; identifiers generated need to be ES-friendly
|
|
|
|
|
(("symbol") (tname->id (token-value t)))
|
|
|
|
|
|
|
|
|
|
(else (error
|
|
|
|
|
(string-append
|
|
|
|
|
"cannot compile unknown token `" (token-type t) "'"))))
|
|
|
|
|
|
|
|
|
|
;; otherwise, process the expression
|
|
|
|
|
(fnmap (token-value (car t))
|
|
|
|
|
(cdr t)
|
|
|
|
|
t)))
|
|
|
|
|
|
|
|
|
|
;; output libprebirth and compiled output, wrapped in a self-executing
|
|
|
|
|
;; function to limit scope
|
|
|
|
|
(string-append "(function(){"
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(es:file->string "libprebirth.js") "\n\n"
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(join "\n\n" (map sexp->es ast))
|
|
|
|
|
"})();"))
|
|
|
|
|
|
|
|
|
|
|
2017-08-31 01:01:06 -04:00
|
|
|
|
;; at this point, this program can parse itself and output a CST (sans
|
|
|
|
|
;; whitespace)
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(es:console (birth->ecmascript
|
2017-09-21 13:37:16 -04:00
|
|
|
|
(parse-lisp
|
2017-12-12 01:03:37 -05:00
|
|
|
|
(es:file->string "/dev/stdin"))))
|