eval/inject improved; testing won t fail on non existing javascript engine
This commit is contained in:
+57
-34
@@ -22,10 +22,13 @@
|
||||
|
||||
@bold{js-maker} is a small, syntax-driven JavaScript generator for writing a
|
||||
practical JavaScript subset in Racket notation. It provides two macros,
|
||||
@racket[js] and @racket[js/expression]. Both macros run at expansion time and
|
||||
return JavaScript source code as a string. The generated JavaScript can then be
|
||||
embedded in a page, written to a file, tested with Node or another JavaScript
|
||||
engine, or used as part of a larger code-generation workflow.
|
||||
@racket[js] and @racket[js/expression]. In the ordinary case the macros
|
||||
expand to a string containing JavaScript source code. When the source contains
|
||||
@racket[(inject racket-expr)] or its historical alias @racket[(eval racket-expr)],
|
||||
the macros instead expand to a Racket expression that computes the JavaScript
|
||||
source string at run time. The generated JavaScript can then be embedded in a
|
||||
page, written to a file, tested with Node or another JavaScript engine, or used
|
||||
as part of a larger code-generation workflow.
|
||||
|
||||
The package is deliberately not a full Racket compiler. It recognizes a
|
||||
well-defined set of Racket-like forms and maps them to JavaScript while trying
|
||||
@@ -60,9 +63,12 @@ Inside function bodies, the last expression is returned automatically unless it
|
||||
is already a statement form such as @racket[return], @racket[define],
|
||||
@racket[set!], @racket[while], or @racket[for]. At the top level of a
|
||||
@racket[js] form, js-maker also returns the value of a final value-producing
|
||||
form, such as @racket[let], @racket[begin], @racket[if], or
|
||||
@racket[with-handlers]. This makes @racket[js] output suitable as the body of a
|
||||
WebView @tt{runJavaScript} wrapper function.
|
||||
form, such as @racket[let], @racket[begin], or @racket[if]. This makes
|
||||
@racket[js] output suitable as the body of a WebView @tt{runJavaScript}
|
||||
wrapper function. A top-level @racket[with-handlers] is treated as a statement
|
||||
so that a catch handler used for side effects does not prematurely return from
|
||||
the surrounding wrapper. Use @racket[js/expression] when the value of a
|
||||
@racket[with-handlers] form itself is needed.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -71,7 +77,7 @@ For example:
|
||||
(displayln
|
||||
(js
|
||||
(let ([el (send document getElementById 'test)])
|
||||
(set! (js-dot el innerHTML) (eval html))
|
||||
(set! (js-dot el innerHTML) (inject html))
|
||||
#t))))
|
||||
]
|
||||
|
||||
@@ -107,12 +113,14 @@ self-call.
|
||||
@section{Mental model}
|
||||
|
||||
The generator is best understood as a source-to-source translator over syntax.
|
||||
The input is converted to datum form and matched against the supported surface
|
||||
Most input is converted to datum form and matched against the supported surface
|
||||
language. The Racket code is normally not evaluated. The deliberate exception
|
||||
is @racket[(eval racket-expr)], which interpolates a Racket value into the
|
||||
JavaScript source text. The expression is evaluated in the lexical context of
|
||||
the @racket[js] or @racket[js/expression] use and its value is emitted as a
|
||||
JavaScript literal. This has a few important consequences:
|
||||
is @racket[(inject racket-expr)], and the compatible older spelling
|
||||
@racket[(eval racket-expr)]. These forms mark a Racket expression whose value
|
||||
must be serialized as a JavaScript literal while the generated source string is
|
||||
being constructed. The expression is evaluated in the lexical context of the
|
||||
@racket[js] or @racket[js/expression] use. This has a few important
|
||||
consequences:
|
||||
|
||||
@compact-items[
|
||||
@item{Only forms known to js-maker are translated specially. Unknown calls are
|
||||
@@ -128,19 +136,24 @@ JavaScript literal. This has a few important consequences:
|
||||
helper functions/IIFEs to preserve semantics.}
|
||||
]
|
||||
|
||||
@subsection{Racket value interpolation with eval}
|
||||
@subsection{Racket value interpolation with inject}
|
||||
|
||||
The form @racket[(eval racket-expr)] is an interpolation escape hatch inherited
|
||||
from the original transformer. It evaluates @racket[racket-expr] as Racket in
|
||||
the use-site lexical context and then splices the resulting value into the
|
||||
JavaScript source as a literal. This makes surrounding Racket bindings visible
|
||||
to the interpolation expression.
|
||||
The form @racket[(inject racket-expr)] is the preferred interpolation escape
|
||||
hatch. It evaluates @racket[racket-expr] as Racket in the use-site lexical
|
||||
context and then splices the resulting value into the JavaScript source as a
|
||||
literal. This makes surrounding Racket bindings visible to the interpolation
|
||||
expression.
|
||||
|
||||
The older spelling @racket[(eval racket-expr)] is still accepted as an alias for
|
||||
compatibility with existing code. New code should prefer @racket[inject],
|
||||
because the form is not JavaScript @tt{eval} and does not evaluate JavaScript
|
||||
source text.
|
||||
|
||||
@(rkt+js
|
||||
#<<RKT
|
||||
(let ([x 10]
|
||||
[y 20])
|
||||
(js (let ([a (eval (* x y))])
|
||||
(js (let ([a (inject (* x y))])
|
||||
(return (* a a)))))
|
||||
RKT
|
||||
#<<JS
|
||||
@@ -153,20 +166,26 @@ JS
|
||||
|
||||
@(rkt+js
|
||||
#<<RKT
|
||||
(js/expression (array (eval (+ 1 2))
|
||||
(eval (string-append "a" "b"))))
|
||||
(js/expression (array (inject (+ 1 2))
|
||||
(inject (string-append "a" "b"))))
|
||||
RKT
|
||||
#<<JS
|
||||
[3, "ab"]
|
||||
JS
|
||||
)
|
||||
|
||||
This is not JavaScript @tt{eval}. To call JavaScript @tt{eval}, call the
|
||||
JavaScript function explicitly, for example @racket[(send window eval "1 + 2")].
|
||||
Racket-side @racket[eval] is best used for constants, generated literal data,
|
||||
and small configuration values that are known while the JavaScript source is
|
||||
being constructed. It should not be used for run-time browser state, DOM access,
|
||||
or user input.
|
||||
Neither @racket[inject] nor its alias @racket[eval] is JavaScript @tt{eval}.
|
||||
To call JavaScript @tt{eval}, call the JavaScript function explicitly, for
|
||||
example @racket[(send window eval "1 + 2")]. Racket-side interpolation is best
|
||||
used for constants, generated literal data, and small configuration values that
|
||||
are known while the JavaScript source is being constructed. It should not be
|
||||
used for run-time browser state, DOM access, or untrusted user input.
|
||||
|
||||
The name @racket[inject] was chosen over a name such as @racket[subst] because
|
||||
it is not raw textual substitution. js-maker serializes the Racket value to a
|
||||
JavaScript literal, including string escaping, booleans, numbers, lists, vectors,
|
||||
hashes, symbols, keywords, characters, @racket[#f], @racket[#t], @racket[null],
|
||||
and @racket[(void)].
|
||||
|
||||
@section{Supported expression and statement forms}
|
||||
|
||||
@@ -184,7 +203,9 @@ both, depending on context:
|
||||
@item{@racket[when], @racket[unless], @racket[while], @racket[for],
|
||||
@racket[for/list], @racket[for/vector], and @racket[for/fold].}
|
||||
@item{@racket[with-handlers] for the generic @racket[exn?] case.}
|
||||
@item{@racket[(eval racket-expr)] for Racket-side value interpolation into JavaScript literals.}
|
||||
@item{@racket[(inject racket-expr)] for Racket-side value interpolation into
|
||||
JavaScript literals; @racket[(eval racket-expr)] is accepted as a
|
||||
compatibility alias.}
|
||||
@item{Interop forms such as @racket[send], @racket[new], @racket[js-ref],
|
||||
@racket[js-dot], @racket[set-prop!], @racket[delete-prop!],
|
||||
@racket[js-delete], @racket[array], @racket[object],
|
||||
@@ -728,11 +749,13 @@ with a JavaScript executor. The executor module searches for engines such as
|
||||
Node, Deno, Bun, QuickJS, V8 @tt{d8}, JavaScriptCore @tt{jsc}, SpiderMonkey
|
||||
@tt{js}, and an optional Chromium fallback. Node is the preferred default.
|
||||
|
||||
If no JavaScript engine is available, tests are generated but execution is
|
||||
skipped with clear warnings and a successful exit status. This is intentional
|
||||
so package tests do not fail merely because a JavaScript runtime is missing.
|
||||
Set @tt{JSMAKER_REQUIRE_ENGINE=1} or @tt{JSMAKER_REQUIRE_NODE=1} to make a
|
||||
missing engine a hard failure.
|
||||
If no JavaScript engine is available, tests are generated and the framework
|
||||
uses an explicit @tt{non-failing-javascript-stub}. The stub prints notes to
|
||||
stdout, does not execute the generated JavaScript, and exits successfully.
|
||||
This is intentional so package tests do not fail merely because a JavaScript
|
||||
runtime is missing, especially in package-server environments. Set
|
||||
@tt{JSMAKER_REQUIRE_ENGINE=1} or @tt{JSMAKER_REQUIRE_NODE=1} to make a missing
|
||||
engine a hard failure.
|
||||
|
||||
Useful commands are:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user