This commit is contained in:
2026-06-08 13:21:57 +02:00
parent 823130e3ac
commit 8bee76328b
23 changed files with 734 additions and 382 deletions
+91 -54
View File
@@ -3,82 +3,119 @@
@(require (for-label racket/base js-maker))
@title{js-maker}
@author{Hans Dijkema}
@author+email["Hans Dijkema" "hans@dijkewijk.nl"]
@defmodule[js-maker]
@emph{js-maker} is a deliberately small syntax-driven JavaScript string maker.
It provides one public macro, @racket[js]. The helper machinery used to render
subexpressions is private to the package.
@racketmodname[js-maker] provides a deliberately small macro for generating
JavaScript text from a compact Racket/Scheme-like syntax. The public API is
intentionally limited to @racket[js]. The lower-level dispatcher used by the
implementation is internal and is not exported.
@section{Public API}
@defform[(js form ...)]{
Produces a JavaScript string for the supplied forms. Each top-level form is
rendered as a JavaScript statement. The macro is intended for small generated
snippets, demos, and controlled code generation; it is not a complete Racket to
JavaScript compiler.
Generates a JavaScript program fragment as a string.
Examples:
Each @racket[form] is translated and emitted as a JavaScript statement. The
macro is statement-oriented. When a value must leave a generated function, use
an explicit @racket[(return expr)] form.
@racketblock[
(js (+ 1 2))
(js (define answer 42))
(js (define (square x)
(return (* x x))))
]
@codeblock{
(js
(define (add1 x)
(return (+ x 1))))
}
}
@section{Supported core forms}
There is no public @racket[js/expression] form in js-maker 3. Expression-style
checks can be written by generating a function and using @racket[(return ...)]
inside that function.
The compact js-maker 3 implementation supports:
@section{Supported forms}
The compact implementation supports:
@itemlist[
@item{@racket[define] for values and functions.}
@item{@racket[lambda] and @racket[lambda]-style generated JavaScript functions.}
@item{@racket[if], @racket[begin], @racket[set!], and explicit @racket[return].}
@item{Ordinary @racket[let] with parallel binding semantics.}
@item{@racket[let*] with sequential binding semantics.}
@item{Named @racket[let] in tail-recursive loop style.}
@item{Common infix operators such as @racket[+], @racket[-], @racket[*],
@racket[/], comparisons, @racket[and], @racket[or], and @racket[not].}
@item{@racket[list], @racket[cons], @racket[send], @racket[js-dot], and
@racket[new].}
@item{@racket[(define (name arg ...) body ...)]}
@item{@racket[(define name expr)]}
@item{@racket[(lambda (arg ...) body ...)] and @racket[(λ (arg ...) body ...)]}
@item{@racket[(if condition then else)]}
@item{@racket[(begin body ...)]}
@item{@racket[(return expr)]}
@item{@racket[(set! target expr)]}
@item{ordinary @racket[let], with parallel binding semantics}
@item{@racket[let*], with sequential binding semantics}
@item{named @racket[let], compiled as a @tt{while (true)} loop}
@item{@racket[quote] and @racket[eval] for simple datum insertion}
@item{ordinary calls and the common infix operators @racket[+], @racket[-], @racket[*], @racket[/], @racket[>], @racket[<], @racket[>=], @racket[<=], @racket[==], @racket[===], @racket[!=], @racket[!==]}
@item{@racket[and], @racket[or] and @racket[not]}
@item{@racket[(send obj method arg ...)]}
@item{@racket[(new Class arg ...)]}
@item{@racket[(js-dot obj field ...)] and @racket[(dot obj field ...)]}
@item{@racket[(js-ref obj key ...)], for JavaScript bracket/index access}
@item{@racket[(list ...)] and @racket[(cons a b)]}
]
@section{Let semantics}
@section{Indexed access}
Ordinary @racket[let] evaluates all right-hand sides before introducing the new
bindings. js-maker preserves that behavior by emitting temporary JavaScript
constants and then opening a nested block for the real @tt{let} bindings.
This avoids JavaScript temporal-dead-zone shadowing when a bound name is also
used by a right-hand side.
@racket[js-ref] is a form understood by the @racket[js] macro. It is not
exported as a separate binding. It generates JavaScript bracket access, which
works for arrays and computed object properties.
@racketblock[
(js (define (ordinary-let x)
(let ([x 1] [y x])
(return y))))
]
@codeblock{
(js
(define (arrayAt xs i)
(return (js-ref xs i))))
}
The generated JavaScript returns the original argument as the value of
@tt{y}, matching Racket's ordinary @racket[let] semantics.
@codeblock{
(js
(define (nameOf obj)
(return (js-ref obj "name"))))
}
The same form can be used as a @racket[set!] target:
@codeblock{
(js
(define (put xs i value)
(set! (js-ref xs i) value)
(return xs)))
}
@section{Ordinary let}
Ordinary @racket[let] keeps Racket's parallel binding semantics. Initializers
are evaluated before the bound names are introduced. The generated JavaScript
therefore uses temporary constants and an inner block, so JavaScript's temporal
dead zone does not accidentally shadow initializer references.
@codeblock{
(js
(define (ordinaryLet x)
(let ([x 1] [y x])
(return y))))
}
@section{Named let}
Named @racket[let] is emitted as a @tt{while (true)} loop. A tail call to the
loop name is translated to parallel update assignments followed by
@tt{continue}. The intended style is statement-oriented and uses explicit
@racket[return] for terminating branches.
Named @racket[let] is compiled to a loop. A tail call to the loop name is
translated into parallel assignments to the loop variables followed by
@tt{continue}. A branch that exits the loop should use @racket[(return ...)].
@racketblock[
(js (define (sum-to n)
(let loop ([i 0] [acc 0])
(if (> i n)
(return acc)
(loop (+ i 1) (+ acc i))))))
]
@codeblock{
(js
(define (sumTo n)
(let loop ([i 0] [acc 0])
(if (> i n)
(return acc)
(loop (+ i 1) (+ acc i))))))
}
@section{Package layout}
@section{Tests and demos}
The package includes small demos under @filepath{demo/} and a regression suite
under @filepath{testing/}. The public API remains just @racket[js].
The package contains maintained tests under @filepath{testing/} and small demos
under @filepath{demo/}. The old js-maker 2 tests for runtime shims and broader
language constructs have been removed or replaced where they did not apply to
the compact js-maker 3 surface language.