6.8 KiB
js-maker
A syntax-driven Racket-to-JavaScript macro module.
This js-maker module was started as part of racket-webview, and has since evolved under supervision of the author using ChatGPT as AI agent.
Layout
js-maker/
main.rkt public macro module
info.rkt package metadata and package test entry
scrbl/
js-maker.scrbl Scribble documentation
testing/
jsmaker-executors.rkt JavaScript engine discovery/execution
jsmaker-test-framework.rkt JS regression framework
jsmaker-test-runner.rkt old-name compatibility wrapper
jsmaker-regression.rkt core expression tests
jsmaker-regexp-regression.rkt regexp tests
jsmaker-program-regression.rkt larger program tests
jsmaker-regressions.rkt aggregate test entry
demo/
show-jsmaker-output.rkt
show-optimized.rkt
Added language support
This package includes conservative support for:
(with-handlers ([exn? handler]) body ...), translated to JavaScripttry/catch. Only genericexn?predicates are accepted.- Gregor-style local names such as
date,time,moment,parse-date,parse-time,parse-moment,date->string,time->string,->year,->month,->day,->hours,->minutes,->seconds,->js-date, andjs-date->datetime. Import prefixes are deliberately not hardcoded; the compiler matches on the local identifier name after anyprefix:part.
Run tests
From the directory above jsmaker:
raco make jsmaker/main.rkt jsmaker/testing/jsmaker-regressions.rkt \
jsmaker/scrbl/jsmaker.scrbl
racket jsmaker/testing/jsmaker-regressions.rkt
raco test -p jsmaker
The test framework looks for JavaScript engines such as node, deno,
bun, qjs, d8, jsc, js. Chromium is only used when explicitly selected
or when JSMAKER_BROWSER_FALLBACK=1 is set.
When no JavaScript engine is available, the tests generate the JavaScript test
files and use an explicit non-failing-javascript-stub. The stub prints notes
to stdout, does not execute the generated JavaScript, and succeeds unless
JSMAKER_REQUIRE_ENGINE or JSMAKER_REQUIRE_NODE is set. This avoids package
server failures caused solely by a missing JavaScript runtime.
Useful environment variables:
JSMAKER_ENGINE=auto|node|deno|bun|qjs|d8|jsc|js|chromium
JSMAKER_ENGINE_PATH=/path/to/executable
JSMAKER_NODE=/path/to/node
JSMAKER_REQUIRE_ENGINE=1
JSMAKER_ENGINE_TIMEOUT_SECONDS=15
JSMAKER_BROWSER_FALLBACK=1
Suggested start prompt for future work
Use this prompt when asking ChatGPT to make a new Racket module or extend this one:
Work on a Racket module/package in a versioned build directory.
Important:
- Always create a new subdirectory for the deliverable, for example
/mnt/data/<project>-build-NNN/<collection-name>.
- Do not work directly in /mnt/data with loose files that have the same names;
avoid version confusion by copying and patching everything inside that build
directory.
- Keep the package structure stable:
- main.rkt for the public module;
- testing/ for test infrastructure and regression tests;
- demo/ for demonstration files;
- info.rkt for package metadata and test entry points.
- Adjust require paths to that structure before testing.
- Test with Racket itself, for example:
/tmp/racket/bin/raco make <collection>/main.rkt <collection>/testing/<tests>.rkt
/tmp/racket/bin/racket <collection>/testing/<tests>.rkt
- If JavaScript execution is needed, use a separate executor/test-framework
module. Tests must not fail solely because node/deno/bun/qjs is missing; in
that case they should use an explicit non-failing JavaScript stub that reports
this to stdout, unless a REQUIRE environment variable is set.
- Do not use shell-based internet access for dependencies. If packages are
needed, fetch them through the rktsndbx bootstrap/package-index flow.
- Deliver a zip file containing exactly the tested build directory.
- Briefly report which commands were run, what the test results were, and which
zip contains the tested result.
Latest tested fix
This build includes the with-handlers callee-position fix for inline lambda
handlers, including rest-argument handlers such as (lambda args ...). It also
fixes top-level js statement-context handling in general: js now emits
program/statement text and does not invent an implicit top-level return. This
keeps snippets valid when they are passed directly to WebView
runJavaScript/evaluateJavaScript, where a top-level JavaScript return is
a syntax error. Use js/expression when a generated JavaScript value is needed.
The build also adds a Racket-like division-by-zero runtime check for /, so the
generic exn? handler subset can catch (/ 10 0).
JavaScript use case demos
The package includes a larger set of JavaScript use case snippets in
demo/js-usecases.rkt. They are written in the Racket surface syntax accepted
by js and compiled to JavaScript by the macro. The generated JavaScript is
also written to demo/js-usecases.generated.js.
The corresponding regression tests live in testing/jsmaker-usecases.rkt and
are included by testing/jsmaker-regressions.rkt. The test framework now awaits
Promise-valued tests, so asynchronous examples such as the Fetch API can be
checked with Node as well.
Covered use cases include random numbers, Set, JavaScript falsey values,
currying, object destructuring, setInterval/clearInterval, object property
get/set/delete, string concatenation order, Object.freeze/Object.seal,
switch/case, classes with constructor defaults, sorting objects, array deletion
techniques, Bubble Sort, recursive Binary Search, Map counting, DOM HTML
access, anagram checks, pair-sum checks, and Fetch API result/error handling.
Use-case documentation
The file scrbl/usecases.scrbl documents the JavaScript use cases from
demo/js-usecases.rkt. Each use case is shown as Racket/js-maker source next
to representative generated JavaScript, followed by the behavior covered by the
regression test.
The use-case tests in testing/jsmaker-usecases.rkt intentionally use
js/expression for the test calls wherever possible. Raw JavaScript is kept
only for small test-harness preambles such as fake timers, fake DOM objects, and
fake fetch.
Hash regression tests
This build adds testing/jsmaker-hash-regression.rkt, covering common hash operations such as hash, make-hash, hash-ref, hash-set, hash-set!, hash-remove, hash-remove!, hash-update, hash-update!, hash-clear, hash-clear!, hash-copy, hash-keys, hash-values, hash->list, hash-map, and hash-for-each. The current JavaScript backend represents hashes as plain JavaScript objects, so this is a practical string/symbol-key subset rather than a full Racket hash-table implementation for arbitrary keys.