eval/inject improved; testing won t fail on non existing javascript engine

This commit is contained in:
2026-05-28 00:07:21 +02:00
parent 8fe47a7ed4
commit e89ab88670
6 changed files with 185 additions and 66 deletions
+51 -19
View File
@@ -8,9 +8,10 @@
(provide js js/expression)
;; Convert a Racket value produced by (eval <racket-expr>) inside a js form to
;; a JavaScript literal string. This is a Racket -> JavaScript interpolation
;; boundary, not JavaScript eval.
;; Convert a Racket value produced by (inject <racket-expr>) or the
;; historical alias (eval <racket-expr>) inside a js form to a JavaScript
;; literal string. This is a Racket -> JavaScript interpolation boundary,
;; not JavaScript eval.
(define (jsmaker-runtime-escape-js-string s)
(define out (open-output-string))
(for ([ch (in-string s)])
@@ -261,8 +262,9 @@
(format "__~a_~a" prefix tmp-counter))
;; Runtime interpolation escape hatch. Before compiling the syntax to a
;; datum, the public macros replace each (eval <racket-expr>) subform by a
;; unique placeholder symbol. The generated macro result is then a Racket
;; datum, the public macros replace each (inject <racket-expr>) subform by a
;; unique placeholder symbol. The historical alias (eval <racket-expr>) is
;; kept for compatibility. The generated macro result is then a Racket
;; expression that replaces those placeholders by JavaScript literals computed
;; from <racket-expr> in the use-site lexical context. This is intentionally
;; different from JavaScript eval, which remains available as
@@ -276,27 +278,49 @@
(define (fresh-eval-placeholder)
(set! counter (add1 counter))
(string->symbol (format "__rkt_eval_slot_~a__" counter)))
(define (walk d)
(match d
[(list 'quote _) d]
[(list 'quasiquote _) d]
[(list 'eval expr)
(define ph (fresh-eval-placeholder))
(set! evals (append evals (list (list (symbol->string ph) expr))))
ph]
[(? pair?) (cons (walk (car d)) (walk (cdr d)))]
[(? vector?) (list->vector (map walk (vector->list d)))]
[_ d]))
(values (walk (syntax->datum stx)) evals))
(define (syntax-head-symbol x)
(define xs (syntax->list x))
(and xs
(pair? xs)
(identifier? (car xs))
(syntax-e (car xs))))
(define (walk-pair p)
(cond
[(null? p) '()]
[(pair? p) (cons (walk (car p)) (walk-pair (cdr p)))]
[else (walk p)]))
(define (walk x)
(define e (if (syntax? x) (syntax-e x) x))
(cond
[(syntax? x)
(case (syntax-head-symbol x)
[(quote quasiquote) (syntax->datum x)]
[(inject eval)
(define xs (syntax->list x))
(cond
[(and xs (= (length xs) 2))
(define ph (fresh-eval-placeholder))
(set! evals (append evals (list (list (symbol->string ph) (cadr xs)))))
ph]
[else (walk-pair e)])]
[else
(cond
[(pair? e) (walk-pair e)]
[(vector? e) (list->vector (map walk (vector->list e)))]
[else e])])]
[(pair? e) (walk-pair e)]
[(vector? e) (list->vector (map walk (vector->list e)))]
[else e]))
(values (walk stx) evals))
(define (wrap-compiled-js stx compiled evals)
(for/fold ([acc #`#,compiled]) ([ev (in-list evals)])
(define placeholder (first ev))
(define racket-expr-datum (second ev))
(define racket-expr-stx (second ev))
#`(string-replace #,acc
#,placeholder
(jsmaker-runtime-value->js
#,(datum->syntax stx racket-expr-datum)))))
#,racket-expr-stx))))
;; Racket conditionals treat only #f as false. JavaScript would also
;; reject 0, "", null and undefined in condition position, so every
@@ -1408,6 +1432,8 @@ if (~a !== false) return ~a;" tmp (compile-expr arg) tmp tmp)))
(format "(~a = ~a)" (compile-assignment-target target) (compile-expr rhs))]
[(list 'return) "undefined"]
[(list 'return e) (compile-expr e)]
[(list 'inject racket-expr)
(compile-racket-eval racket-expr)]
[(list 'eval racket-expr)
(compile-racket-eval racket-expr)]
[(list f args ...) (compile-call f args)]
@@ -1424,6 +1450,12 @@ if (~a !== false) return ~a;" tmp (compile-expr arg) tmp tmp)))
[(list 'delete-prop! _ ...) #f]
[(list 'while _ ...) #f]
[(list 'for _ ...) #f]
;; Top-level js is statement/program output. with-handlers may contain
;; a handler used only for side effects; compiling it as an implicit
;; tail return would emit return statements inside the generated
;; try/catch and prematurely leave the caller's wrapper. Use
;; js/expression when the value of with-handlers is needed.
[(list 'with-handlers _ ...) #f]
[_ #t]))
(define (compile-top forms)