#lang scribble/manual @(require (for-label racket/base "../private/js-transform.rkt")) @title{JavaScript Transformation} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @defmodule[racket-webview/private/js-transform] This module provides a small compile-time DSL for generating JavaScript source text from Racket-like forms. It is used internally by the webview implementation to construct JavaScript snippets in a structured way, without having to write the complete snippet as one raw string. The module exports only @racket[js]. The other forms described in this section are DSL forms recognized by @racket[js]; they are not exported as separate bindings. @defform[ (js js-statement ...) ]{ Transforms each @racket[js-statement] into JavaScript source text. The result is a string. The generated string is not evaluated by Racket and no JavaScript is executed by this module. Each top-level statement is emitted with a trailing semicolon and newline. The resulting string can be passed to the webview layer for injection or execution in the browser context. For example: @racketblock[ (define source (js (set! window.myfunc (λ (x) (let* ((el (send document getElementById 'hi)) (y (* x x))) (send el setAttribute "x" (+ y ""))) (send console log "did set attribute x on element hi"))))) ] This generates JavaScript source that assigns a function to @tt{window.myfunc}. The function looks up a DOM element, calculates a value, sets an attribute, and writes a message to the JavaScript console. } @section{Primitive values} Numbers are emitted as JavaScript numeric literals. Strings are emitted as double-quoted JavaScript strings, with embedded double quotes escaped. Identifiers are emitted as JavaScript names. For example: @racketblock[ (js (send console log "hello") (send console log 42)) ] Symbols can be quoted to produce JavaScript string values: @racketblock[ (js (send console log 'hello)) ] @section{Function calls and method calls} A form that is not recognized as a special DSL form is treated as a JavaScript function call: @racketblock[ (js (alert "Hello from Racket")) ] This generates a JavaScript call to @tt{alert}. Method calls can be written with @racket[send]: @racketblock[ (js (send document getElementById 'hi)) ] The @racket[send] form has the shape: @racketblock[ (send object method arg ...) ] and generates a JavaScript method call of the form: @codeblock|{ object.method(arg, ...) }| For example, @racket[(send document getElementById 'hi)] generates a call shaped like: @codeblock|{ document.getElementById("hi") }| Using @racket[send] keeps the method-call structure explicit in the DSL. It is usually clearer than writing dotted JavaScript names directly as function names. @section{Operators} The DSL supports a small set of JavaScript infix operators. @itemlist[ #:style 'compact @item{@racket[(+ a b ...)], @racket[(- a b ...)], @racket[(* a b ...)] and @racket[(/ a b ...)] generate arithmetic infix expressions.} @item{@racket[(and a b ...)] and @racket[(or a b ...)] generate JavaScript @tt{&&} and @tt{||} expressions.} @item{@racket[(> a b)], @racket[(< a b)], @racket[(>= a b)], @racket[(<= a b)], @racket[(== a b)], @racket[(=== a b)] and @racket[(!= a b)] generate JavaScript comparison expressions.} ] For example: @racketblock[ (js (define (inside-range x) (if (and (> x 10) (< x 15)) (return x) (return (* x x))))) ] @section{Definitions and assignments} @defform[(define (name arg ...) body ...)]{ Generates a JavaScript function declaration. @racketblock[ (js (define (square x) (return (* x x)))) ] This produces a JavaScript function named @tt{square}. } @defform[(set! name expr)]{ Generates a JavaScript assignment. @racketblock[ (js (set! window.answer 42)) ] This produces an assignment to @tt{window.answer}. } @section{Functions} @defform[(lambda (arg ...) body ...)]{ Generates a JavaScript function expression. } @defform[(λ (arg ...) body ...)]{ Equivalent to @racket[lambda]. } For example: @racketblock[ (js (set! window.square (λ (x) (return (* x x))))) ] A function body may contain more than one DSL statement: @racketblock[ (js (set! window.f (λ (x) (send console log x) (return (* x x))))) ] @section{Control flow and statement blocks} @defform[(if condition then-expr else-expr)]{ Generates a JavaScript @tt{if}/@tt{else} statement. The condition is translated as a JavaScript expression. The two branches are translated as JavaScript statements. @racketblock[ (js (define (f x) (if (> x 0) (return x) (return (- 0 x))))) ] } @defform[(begin body ...)]{ Generates a JavaScript block. @racketblock[ (js (if (> x 10) (begin (send console log x) (return x)) (return 0))) ] } @defform[(return expr)]{ Generates a JavaScript @tt{return} statement. @racketblock[ (js (define (id x) (return x))) ] } @section{Sequential bindings} @defform[(let* ((id expr) ...) body ...)]{ Generates a JavaScript block with sequential @tt{let} declarations, followed by the generated body statements. @racketblock[ (js (let* ((x 10) (y (* x x))) (send console log y) (return y))) ] The plain @racket[let] form is intentionally not supported in a JavaScript context. Use @racket[let*] instead, so that the generated JavaScript bindings remain explicitly sequential. } @section{Lists} @defform[(list expr ...)]{ Generates a JavaScript array literal. @racketblock[ (js (send console log (list 1 2 3))) ] } @defform[(cons expr list-expr)]{ Generates JavaScript that prepends a value to an array-like expression by concatenating a one-element array with the translated list expression. For example: @racketblock[ (js (send console log (cons x (cons y (list z))))) ] This generates JavaScript shaped like: @codeblock|{ console.log([ x].concat([ y].concat([ z]))); }| } @section{Embedding Racket values} @defform[(eval value)]{ Embeds a simple Racket value as a JavaScript literal at expansion time. The supported values are strings, symbols, numbers and lists containing those same kinds of values. @racketblock[ (define names '(alice bob charlie)) (js (send console log (eval names))) ] The value is converted to JavaScript source text before the generated JavaScript is returned. } @section{Example} The following example combines function definition, method calls, list construction, sequential bindings and a lambda expression passed to a JavaScript method: @racketblock[ (define source (js (define (f x y z) (send console log (cons x (cons y (list z)))) (let* ((l (cons x (cons y (list z))))) (return (send l map (λ (a) (return (+ a 10))))))))) ] This generates a JavaScript function that logs an array, constructs the same array in a local binding, maps over it, and returns the mapped result. @section{Limitations} This transformer is intentionally small. It is not a complete JavaScript parser, not a JavaScript evaluator and not a general Racket-to-JavaScript compiler. Unrecognized forms are generally treated as JavaScript function calls. This keeps the implementation compact, but it also means that some mistakes may produce JavaScript source that only fails when the browser runs the generated code. The generated source is intended for small snippets used by the webview layer, not for translating arbitrary Racket programs to JavaScript.