js-tranform documentented.
This commit is contained in:
@@ -281,6 +281,9 @@
|
||||
(syntax-case stx ()
|
||||
((_ (a c b1 b2))
|
||||
(cond
|
||||
((eq? (syntax->datum #'a) 'define) #'(js-def c (b1 b2)))
|
||||
((eq? (syntax->datum #'a) 'lambda) #'(js-lambda c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'λ) #'(js-lambda c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'if) #'(js2-if c b1 b2))
|
||||
((memq (syntax->datum #'a) js-ops) #'(js-op a c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'let*) #'(js2-let* c b1 b2))
|
||||
@@ -321,6 +324,7 @@
|
||||
((eq? (syntax->datum #'a) 'return) #'(js-return b))
|
||||
((eq? (syntax->datum #'a) 'quote) #'(js-quote b))
|
||||
((eq? (syntax->datum #'a) 'eval) #'(js-eval b))
|
||||
((eq? (syntax->datum #'a) 'list) #'(js-op a b))
|
||||
;string-append
|
||||
; "\"" (esc-double-quote (format "~a" 'b)) "\""))
|
||||
(else
|
||||
@@ -334,6 +338,8 @@
|
||||
(cond
|
||||
((eq? (syntax->datum #'a) 'let*) #'(js2-let* c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'define) #'(js-def c (b1 ...)))
|
||||
((eq? (syntax->datum #'a) 'lambda) #'(js-lambda c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'λ) #'(js-lambda c b1 ...))
|
||||
((memq (syntax->datum #'a) js-ops) #'(js-op a c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'begin) #'(js-begin c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'let) #'(error "let is not supported in js context, use let*"))
|
||||
@@ -392,10 +398,11 @@
|
||||
#|
|
||||
(define t1
|
||||
(js (set! window.myfunc (λ (x)
|
||||
(let* ((el (document.getElementById 'hi))
|
||||
(let* ((el (send document getElementById 'hi))
|
||||
(y (* x x)))
|
||||
(el.setAttribute "x" (+ y ""))
|
||||
(send el setAttribute "x" (+ y ""))
|
||||
)
|
||||
(send console log "dit set attribute x on element hi")
|
||||
)
|
||||
)))
|
||||
|
||||
@@ -408,6 +415,33 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define t3 (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)))))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
==>
|
||||
function f(x, y, z) {
|
||||
console.log([ x].concat([ y].concat([ z])));
|
||||
{
|
||||
let l = [ x].concat([ y].concat([ z]));
|
||||
return (l.map(function (a) { return (a + 10);
|
||||
;
|
||||
}
|
||||
));
|
||||
;
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
|#
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
#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.
|
||||
@@ -17,6 +17,7 @@
|
||||
@include-section["wv-dialog.scrbl"]
|
||||
@include-section["wv-element.scrbl"]
|
||||
@include-section["wv-input.scrbl"]
|
||||
@include-section["js-transform.scrbl"]
|
||||
@include-section["racket-webview.scrbl"]
|
||||
@include-section["racket-webview-qt.scrbl"]
|
||||
@include-section["rgba.scrbl"]
|
||||
|
||||
Reference in New Issue
Block a user