From 6ffc11d2b1fbf7fdfdd1ff4089a4ac2493bc46c0 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Thu, 28 May 2026 17:44:53 +0200 Subject: [PATCH] Robuuster returns in javascript. --- README.md | 44 ++++++++++++++++++---------------- main.rkt | 31 +++++------------------- scrbl/js-maker.scrbl | 34 ++++++++++---------------- testing/jsmaker-regression.rkt | 8 +++---- 4 files changed, 47 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 3e5e8f4..cb76d21 100644 --- a/README.md +++ b/README.md @@ -76,42 +76,46 @@ Use this prompt when asking ChatGPT to make a new Racket module or extend this one: ```text -Work on a Racket module/package in a version-locked buildmap. +Work on a Racket module/package in a versioned build directory. Important: -- Always create a new subdirectory for the release, for example +- Always create a new subdirectory for the deliverable, for example /mnt/data/-build-NNN/. -- Do not work directly in /mnt/data with individual files of the same name; - avoid version confusion by copying/patching everything into that buildmap. -- Keep the package structure consistent: +- 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 match that structure before testing. +- Adjust require paths to that structure before testing. - Test with Racket itself, for example: /tmp/racket/bin/raco make /main.rkt /testing/.rkt /tmp/racket/bin/racket /testing/.rkt -- If JavaScript is required, use a separate executor/test-framework module. - Tests must not fail simply because node/deno/bun/qjs is missing; they must - skip with clear warnings unless a REQUIRE environment variable has been set. -- Do not use shell-based internet access for dependencies. If packages are needed, retrieve - them via the rktsndbx bootstrap/package-index flow. -- After completion, provide a zip file containing exactly the tested buildmap. -- Briefly report which commands were executed, what the test results were, - and which zip file contains the tested result. +- 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 for `with-handlers`, so a -side-effecting catch handler does not prematurely return from the surrounding -JavaScript wrapper. Use `js/expression` when the value of a `with-handlers` -form itself 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)`. +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 diff --git a/main.rkt b/main.rkt index 69fc48f..e826666 100644 --- a/main.rkt +++ b/main.rkt @@ -1439,32 +1439,13 @@ if (~a !== false) return ~a;" tmp (compile-expr arg) tmp tmp))) [(list f args ...) (compile-call f args)] [_ (literal->js d)])) - (define (top-tail-returnable? d) - (match d - [(list 'define _ ...) #f] - [(list 'define-values _ ...) #f] - [(list 'define-class _ ...) #f] - [(list 'set! _ ...) #f] - [(list 'vector-set! _ ...) #f] - [(list 'set-prop! _ ...) #f] - [(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) - (cond - [(null? forms) ""] - [(top-tail-returnable? (last forms)) - (compile-body forms #:return-last? #t)] - [else - (compile-body forms #:return-last? #f)]))) + ;; A js form produces JavaScript program/statement text. It must be valid + ;; when handed directly to a WebView run/evaluateJavaScript entry point, so + ;; it may not invent top-level return statements. Return insertion still + ;; happens in expression contexts and function bodies, where JavaScript + ;; return is syntactically valid. + (compile-body forms #:return-last? #f))) (define-syntax (js stx) (syntax-case stx () diff --git a/scrbl/js-maker.scrbl b/scrbl/js-maker.scrbl index dda2197..a680ae1 100644 --- a/scrbl/js-maker.scrbl +++ b/scrbl/js-maker.scrbl @@ -16,18 +16,10 @@ (list (verbatim rkt) (verbatim js))))) @title{js-maker: a Syntax-Driven Racket-to-JavaScript Generator} -@author+email["Hans Dijkema" "hans@dijkewijk.nl"] +@author+email["Hans Dijkema" ""] @defmodule[js-maker] -This module has been largely coded as an evolution to the @tt{js-transformer} setup -from @tt{racket-webview} by supervising it's evolution using AI. -It is astonishing what AI can do these days. To get the AI agent this far -on racket and testing the output by itself, I had to get @tt{racket} installed -in the coding sandbox of the AI agent. -See @hyperlink["https://racket.discourse.group/t/a-small-experiment-bootstrapping-racket-into-a-chatgpt-sandbox-at-openai/4238"]{the discourse article} -about that. - @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]. In the ordinary case the macros @@ -70,13 +62,12 @@ function square(x) { 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], 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. +@racket[js] form, however, js-maker emits statement code and does not invent an +implicit @tt{return}. This keeps the generated code valid when it is handed +directly to a WebView @tt{runJavaScript} or @tt{evaluateJavaScript} entry point, +where a top-level JavaScript @tt{return} would be an @tt{Illegal return} +syntax error. Use @racket[js/expression] when a generated JavaScript value is +needed. For example: @@ -95,7 +86,7 @@ emits JavaScript similar to: { let el = document.getElementById("test"); el.innerHTML = "

Hi

"; - return true; + true; } } } @@ -161,14 +152,15 @@ source text. #< { let a = 200; return (a * a); -} +})() JS ) diff --git a/testing/jsmaker-regression.rkt b/testing/jsmaker-regression.rkt index 2202460..4bec1b4 100644 --- a/testing/jsmaker-regression.rkt +++ b/testing/jsmaker-regression.rkt @@ -37,9 +37,9 @@ (runtime-eval-macro-function 't "BEE")) (unless (and (regexp-match? #rx"getElementById\\(\"t\"\\)" runtime-eval-macro-program) (regexp-match? #rx"innerHTML = \"BEE\"" runtime-eval-macro-program) - (regexp-match? #rx"return true;" runtime-eval-macro-program)) + (not (regexp-match? #rx"return true;" runtime-eval-macro-program))) (error 'jsmaker-regression - "runtime eval inside a user macro should keep use-site lexical bindings, got: ~a" + "runtime eval inside a user macro should keep use-site lexical bindings and should not emit an implicit top-level return, got: ~a" runtime-eval-macro-program)) (define-syntax with-id->el/inject @@ -58,9 +58,9 @@ (runtime-inject-macro-function 't "BEE")) (unless (and (regexp-match? #rx"getElementById\\(\"t\"\\)" runtime-inject-macro-program) (regexp-match? #rx"innerHTML = \"BEE\"" runtime-inject-macro-program) - (regexp-match? #rx"return true;" runtime-inject-macro-program)) + (not (regexp-match? #rx"return true;" runtime-inject-macro-program))) (error 'jsmaker-regression - "runtime inject inside a user macro should keep use-site lexical bindings, got: ~a" + "runtime inject inside a user macro should keep use-site lexical bindings and should not emit an implicit top-level return, got: ~a" runtime-inject-macro-program)) (define simple-let-star