rebirth: Replace most built-in forms with macros

This is a significant step toward abandoning the Rebirth compiler (which is
a slight, mostly unaltered version of the Birth compiler, at this point);
anything written for these macros from this point on can be re-used moving
forward, regardless of what compiler we have underneath it.  I'll continue
writing more Scheme-like abstractions moving forward to begin to normalize
the syntax as I get closer to a point where it's worth starting to create
proper R⁷RS implementations.

Things are moving along slowly, but they're moving.  I don't have a whole
lot of free time between kids and other obligations.

* build-aux/bootstrap/birth.scm (fnmap): Doc corrections.
* build-aux/bootstrap/rebirth.scm: Add more documentation.
    Add numerous macros to replace built-in forms; I'm not listing them
    here.
  (%list-item): Whitespace fix.
  (fnmap): Split into `fnmap-premacro'.
  (fnmap-premacro): New procedure.
master
Mike Gerwitz 2017-12-15 00:40:10 -05:00
parent ae7fcdbc1a
commit 49142b6630
Signed by: mikegerwitz
GPG Key ID: 8C917B7F5DC51BA2
2 changed files with 309 additions and 141 deletions

View File

@ -367,12 +367,12 @@
;; String values are simple function aliases. Function values take over ;; String values are simple function aliases. Function values take over
;; the compilation of that function and allow for defining special forms ;; 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 ;; (in place of macro support). The first argument FN is the name of the
;; function/procedure/form, and ARS is the list of arguments. ;; function/procedure/form, and ARGS is the list of arguments.
;; ;;
;; These are by no means meant to be solid implementations; notable ;; These are by no means meant to be solid implementations; notable
;; deficiencies are documented, but don't expect this to work properly in ;; deficiencies are documented, but don't expect this to work properly in
;; every case. They will be replaced with proper R7RS implementations in ;; every case. They will be replaced with proper R7RS implementations in
;; the future (Rebirth). ;; the future (post-Rebirth).
(define (fnmap fn args t) (define (fnmap fn args t)
(case fn (case fn
(("es:console") (("es:console")

View File

@ -46,11 +46,163 @@
;;; vary as much as we want from the initial implementation. See the commit ;;; vary as much as we want from the initial implementation. See the commit
;;; history for this file for more information as to how it evolved (the ;;; history for this file for more information as to how it evolved (the
;;; first commit is the direct copy before actual code changes). ;;; first commit is the direct copy before actual code changes).
;;;
;;; This file follows a narrative (from Birth to Reⁿbirth), but it's more of
;;; a choose-your-adventure-book-style narrative: order of certain
;;; definitions unfortunately matters in this simple implementation. For
;;; example, primitive macros (e.g. `if') must be defined before they are
;;; used, so those appear at the top of this file, despite their definitions
;;; not being supported until future passes.
;;;
;;; So, to begin, go to `==STEP 0=='.
;; ==Step 2== (don't start here; see Step 0 below)
;;
;; Did you read the other steps first? If not, you're out of order; skip
;; down to Step 0 first and then come back here.
;;
;; Back? Good!
;;
;; Now that we have macro support, we can start to refactor parts of the
;; compiler into macros---rather than maintaining features as part of the
;; compiler itself, we maintain them as a library used alongside the
;; program. This also has another important benefit: additional compiler
;; features resulting from these definitions do not require another Rebirth
;; compilation pass (that is, Re⁽ⁿ⁺¹⁾birth) before they are available to
;; use.
;;
;; To make sure that these macros are not thwarted by the existing `fnmap'
;; definitions, `fnmap' has been refactored to remove the respective
;; definitions using `cond-expand'; see `fnmap-premacro'.
;;
;; These are by no means meant to be solid implementations; strong
;; deficiencies exist, and don't expect this to work properly in every
;; case. They will be replaced with proper R7RS implementations in the
;; future.
;;
;; Initially, everything here was a near-exact copy of the `fnmap-premacro'
;; forms, re-arranged as needed for compilation (see limitations of
;; `cdfn-macro'), so all changes are clearly visible in the repository
;; history.
(cond-expand
(cdfn-macro
(define-macro (%es:native-apply fn . args)
(`quote
(string->es
(unquote (string-append
(token-value fn)
"(" (join "," (map sexp->es args)) ")")))))
(define-macro (es:console . args)
(`quote (%es:native-apply console.log (unquote@ args))))
(define-macro (es:error . args)
(`quote (%es:native-apply console.error (unquote@ args))))
(define-macro (es:raw . body)
(`quote
(string->es (unquote@ body))))
(define-macro (define-es-macro decl . body)
(quasiquote
(define-macro (unquote decl)
(list
(quote string->es)
(string-append (unquote@ body))))))
;; Don't worry---basic tail call support (at least for recursion) is
;; nearing, and then we can get rid of this ugly thing.
(define-es-macro (es:while pred . body)
"(function(__whilebrk){"
"while (" (sexp->es pred) "){\n"
(body->es body #f) " if (__whilebrk) break;\n"
"}\n"
"})(false)")
(define-es-macro (es:break)
"__whilebrk=true")
(define-es-macro (lambda fnargs . body)
"function(" (join ", " (map sexp->es fnargs)) "){\n"
(body->es body #t)
"}")
(define-es-macro (let* bindings . body)
"(function(){\n"
(join "" (map (lambda (binding)
(string-append
" let " (sexp->es (car binding))
" = " (sexp->es (cadr binding)) ";\n"))
bindings))
(body->es body #t) "\n"
" })()")
(define-es-macro (let bindings . body)
(let* ((fparams (join ", " (map sexp->es
(map car bindings))))
(fargs (join ", " (map sexp->es
(map cadr bindings)))))
(string-append "(function(" fparams "){\n"
(body->es body #t) "\n"
"})(" fargs ")")))
(define-es-macro (and . args)
"(function(__and){\n"
(join "" (map (lambda (expr)
(string-append
"__and = " (sexp->es expr) "; "
"if (!_truep(__and)) return false;\n"))
args))
"return __and;})()")
(define-es-macro (or . args)
"(function(__or){\n"
(join "" (map (lambda (expr)
(string-append
"__or = " (sexp->es expr) "; "
"if (_truep(__or)) return __or;\n"))
args))
"return false;})()")
(define-es-macro (if pred t . rest)
(let ((f (and (pair? rest)
(car rest))))
(string-append
"(function(){"
"if (_truep(" (sexp->es pred) ")){return " (sexp->es t) ";}"
(or (and (pair? f)
(string-append "else{return " (sexp->es f) ";}"))
"")
"})()")))
(define-es-macro (case key . clauses)
"(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)))
(body->es exprs #t) "\n"))
(map car clauses)
(map cdr clauses)))
"}})()")
(define-es-macro (set! varid val)
(sexp->es varid) " = " (sexp->es val))))
;; ==STEP 0== (start here)
;;
;; The first step in the Rebirth process is to liberate ourselves from ;; The first step in the Rebirth process is to liberate ourselves from
;; libprebirth. ;; libprebirth.
;;
;; Here we define the libprebirth primitives. When we first compile ;; Here we define the libprebirth primitives. When we first compile
;; Rebirth with Birth, `string->es' is not yet available, because it is ;; Rebirth with Birth, `string->es' is not yet available, because it is
;; only implemented in Rebirth. Further, Birth includes libprebirth in ;; only implemented in Rebirth. Further, Birth includes libprebirth in
@ -228,6 +380,8 @@
(string->es "fsdata[$$path]"))))) (string->es "fsdata[$$path]")))))
;; ==STEP 1== (see Step 0 above)
;;
;; Without macro support, anything that involves producing code with ;; Without macro support, anything that involves producing code with
;; variable structure at compile-time must be hard-coded in the ;; variable structure at compile-time must be hard-coded in the
;; compiler. Perhaps the greatest power in Lisp is the ability to extend ;; compiler. Perhaps the greatest power in Lisp is the ability to extend
@ -449,6 +603,8 @@
(lexeme (cadr item-parts))) (lexeme (cadr item-parts)))
(car (make-token type lexeme "" 0)))))))) (car (make-token type lexeme "" 0))))))))
;; (go to Step 2 above)
;; pair selection ;; pair selection
(define (cadr xs) (define (cadr xs)
@ -944,37 +1100,57 @@
(string-append idfn "(" argstr ")")))) (string-append idfn "(" argstr ")"))))
;; Function/procedure aliases and special forms ;; Primitive special forms.
;; ;;
;; And here we have what is probably the most grotesque part of this file. ;; These are forms that cannot be re-written as macros because of
;; ;; chicken-and-egg issues. Since the Rebirth compiler is temporary, we're
;; This map allows for a steady transition---items can be removed as they ;; not going to worry about getting rid of the rest of these.
;; 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 ;; String values are simple function aliases. Function values take over
;; the compilation of that function and allow for defining special forms ;; 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 ;; (in place of macro support). The first argument FN is the name of the
;; function/procedure/form, and ARS is the list of arguments. ;; function/procedure/form, and ARGS is the list of arguments.
;;
;; 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
;; the future (Rebirth).
(define (fnmap fn args t) (define (fnmap fn args t)
(case fn
;; output raw code into the compiled ECMAScript (what could go wrong?)
(("string->es")
(token-value (car args)))
;; very primitive cond-expand
(("cond-expand") (expand-cond-expand args))
;; Note that the unquote forms are only valid within a quasiquote; see
;; that procedure for the handling of those forms. Since we do not
;; support the special prefix form, we also offer "`quote" as a
;; shorthand for quasiquote.
(("quote") (quote-sexp (car args)))
(("quasiquote" "`quote") (quasiquote-sexp (car args)))
(("define") (cdfn t))
(("define-macro") (cdfn-macro t)) ; not defined until string->es cond
;; If we have macro support (`cdfn-macro'), then assume that they exist
;; and try to use them; otherwise, continue to use built-in forms, which
;; have been moved into `fnmap-premacro').
(else
(cond-expand
(cdfn-macro (apply-proc-or-macro fn args))
(else (fnmap-premacro fn args t))))))
;; Special forms to be removed on future Rebirth pass in favor of macros
;;
;; See Step 2 above for the replacement macro definitions.
(cond-expand
(cdfn-macro) ; our cond-expand does not support `else'
(else
(define (fnmap-premacro fn args t)
(case fn (case fn
(("es:console") (("es:console")
(string-append "console.log(" (map sexp->es args) ")")) (string-append "console.log(" (map sexp->es args) ")"))
(("es:error") (("es:error")
(string-append "console.error(" (map sexp->es args) ")")) (string-append "console.error(" (map sexp->es args) ")"))
;; very primitive cond-expand
(("cond-expand") (expand-cond-expand args))
;; output raw code into the compiled ECMAScript (what could go wrong?)
(("string->es")
(token-value (car args)))
;; yes, there are more important things to do until we get to the ;; yes, there are more important things to do until we get to the
;; point where it's worth implementing proper tail calls ;; point where it's worth implementing proper tail calls
(("es:while") (("es:while")
@ -988,14 +1164,6 @@
"})(false)"))) "})(false)")))
(("es:break") "__whilebrk=true") (("es:break") "__whilebrk=true")
;; note that the unquote forms are only valid within a quasiquote; see
;; that procedure for the handling of those forms
(("quote") (quote-sexp (car args)))
(("quasiquote") (quasiquote-sexp (car args)))
(("define") (cdfn t))
(("define-macro") (cdfn-macro t)) ; not defined until string->es cond
(("lambda") (("lambda")
(let ((fnargs (car args)) (let ((fnargs (car args))
(body (cdr args))) (body (cdr args)))
@ -1045,7 +1213,7 @@
;; (let ((binding val) ...) ...body), compiled as a self-executing ;; (let ((binding val) ...) ...body), compiled as a self-executing
;; function which allows us to easily represent the return value of ;; function which allows us to easily represent the return value of
;; the entire expression while maintaining local scope ;; the entire expression while maintaining local scope.
(("let*") (("let*")
(let ((bindings (car args)) (let ((bindings (car args))
(body (cdr args))) (body (cdr args)))
@ -1103,7 +1271,7 @@
(string-append (sexp->es varid) " = " (sexp->es val)))) (string-append (sexp->es varid) " = " (sexp->es val))))
;; procedure or macro ;; procedure or macro
(else (apply-proc-or-macro fn args)))) (else (apply-proc-or-macro fn args))))))
;; Convert s-expressions or scalar into ECMAScript ;; Convert s-expressions or scalar into ECMAScript