tail returns and complex stuff
This commit is contained in:
@@ -123,6 +123,15 @@
|
||||
(define s2 (regexp-replace* #rx"[^A-Za-z0-9_$]" s1 "_"))
|
||||
(if (regexp-match? #rx"^[A-Za-z_$]" s2) s2 (string-append "_" s2)))
|
||||
|
||||
(define (dot-prop-symbol? x)
|
||||
(and (symbol? x)
|
||||
(let ([s (symbol->string x)])
|
||||
(and (> (string-length s) 1)
|
||||
(char=? (string-ref s 0) #\.)))))
|
||||
|
||||
(define (dot-prop->js x)
|
||||
(prop->js (string->symbol (substring (symbol->string x) 1))))
|
||||
|
||||
(define (compile-assignment-target target)
|
||||
(match target
|
||||
[(? symbol?) (id->js target)]
|
||||
@@ -398,6 +407,14 @@
|
||||
(format "if (~a) ~a\nreturn undefined;" (compile-expr c) (block (compile-body body)))]
|
||||
[(list 'unless c body ...)
|
||||
(format "if (!(~a)) ~a\nreturn undefined;" (compile-expr c) (block (compile-body body)))]
|
||||
[(list 'set! _ ...)
|
||||
(join-lines (list (compile-stmt d) "return undefined;"))]
|
||||
[(list 'vector-set! _ ...)
|
||||
(join-lines (list (compile-stmt d) "return undefined;"))]
|
||||
[(list 'set-prop! _ ...)
|
||||
(join-lines (list (compile-stmt d) "return undefined;"))]
|
||||
[(list 'delete-prop! _ ...)
|
||||
(join-lines (list (compile-stmt d) "return undefined;"))]
|
||||
[_ (format "return ~a;" (compile-expr d))]))
|
||||
|
||||
(define (compile-stmt d)
|
||||
@@ -414,6 +431,8 @@
|
||||
(format "let ~a = ~a;" (id->js id) (compile-expr rhs))]
|
||||
[(list 'define-values ids rhs)
|
||||
(format "let [~a] = ~a;" (compile-arg-list ids) (compile-expr rhs))]
|
||||
[(list 'set! obj (? dot-prop-symbol? prop) rhs)
|
||||
(format "~a.~a = ~a;" (compile-expr obj) (dot-prop->js prop) (compile-expr rhs))]
|
||||
[(list 'set! target rhs)
|
||||
(format "~a = ~a;" (compile-assignment-target target) (compile-expr rhs))]
|
||||
[(list 'return)
|
||||
@@ -1383,6 +1402,8 @@ if (~a !== false) return ~a;" tmp (compile-expr arg) tmp tmp)))
|
||||
(parens (compile-delete-target obj key))]
|
||||
[(list 'set-prop! obj key val)
|
||||
(format "(~a[~a] = ~a)" (compile-expr obj) (compile-expr key) (compile-expr val))]
|
||||
[(list 'set! obj (? dot-prop-symbol? prop) rhs)
|
||||
(format "(~a.~a = ~a)" (compile-expr obj) (dot-prop->js prop) (compile-expr rhs))]
|
||||
[(list 'set! target rhs)
|
||||
(format "(~a = ~a)" (compile-assignment-target target) (compile-expr rhs))]
|
||||
[(list 'return) "undefined"]
|
||||
@@ -1392,8 +1413,26 @@ 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]
|
||||
[_ #t]))
|
||||
|
||||
(define (compile-top forms)
|
||||
(compile-body forms #:return-last? #f)))
|
||||
(cond
|
||||
[(null? forms) ""]
|
||||
[(top-tail-returnable? (last forms))
|
||||
(compile-body forms #:return-last? #t)]
|
||||
[else
|
||||
(compile-body forms #:return-last? #f)])))
|
||||
|
||||
(define-syntax (js stx)
|
||||
(syntax-case stx ()
|
||||
|
||||
+39
-3
@@ -16,7 +16,7 @@
|
||||
(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[jsmaker]
|
||||
|
||||
@@ -58,7 +58,32 @@ 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].
|
||||
@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], @racket[if], or
|
||||
@racket[with-handlers]. This makes @racket[js] output suitable as the body of a
|
||||
WebView @tt{runJavaScript} wrapper function.
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(let ([html "<h1>Hi</h1>"])
|
||||
(displayln
|
||||
(js
|
||||
(let ([el (send document getElementById 'test)])
|
||||
(set! (js-dot el innerHTML) (eval html))
|
||||
#t))))
|
||||
]
|
||||
|
||||
emits JavaScript similar to:
|
||||
|
||||
@codeblock{
|
||||
{
|
||||
let el = document.getElementById("test");
|
||||
el.innerHTML = "<h1>Hi</h1>";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@defform[(js/expression expr)]{
|
||||
@@ -245,7 +270,13 @@ Interop forms provide direct access to JavaScript object and method syntax:
|
||||
@item{@racket[(send obj method arg ...)] emits @tt{obj.method(arg, ...)}.}
|
||||
@item{@racket[(new cls arg ...)] emits @tt{new cls(arg, ...)}.}
|
||||
@item{@racket[(js-ref obj key)] emits @tt{obj[key]}.}
|
||||
@item{@racket[(js-dot obj field)] emits @tt{obj.field}.}
|
||||
@item{@racket[(js-dot obj field)] emits @tt{obj.field}. It is the
|
||||
preferred explicit form for property access in generated code.}
|
||||
@item{@racket[(set! (js-dot obj field) value)] emits @tt{obj.field = value}.}
|
||||
@item{@racket[(set! expr.field value)] is also accepted by the reader as
|
||||
@racket[(set! expr .field value)] and is emitted as a direct property
|
||||
assignment. This is mainly a convenience for DOM code such as
|
||||
@racket[(set! (send document getElementById 'test).innerHTML html)].}
|
||||
@item{@racket[(set-prop! obj key value)] emits @tt{obj[key] = value}.}
|
||||
@item{@racket[(delete-prop! obj key)] and @racket[(js-delete obj key)] emit
|
||||
JavaScript @tt{delete}.}
|
||||
@@ -719,6 +750,7 @@ The package uses this layout:
|
||||
js-maker/
|
||||
main.rkt
|
||||
info.rkt
|
||||
private/
|
||||
testing/
|
||||
demo/
|
||||
scrbl/
|
||||
@@ -727,6 +759,10 @@ js-maker/
|
||||
@filepath{main.rkt} is the public module. @filepath{testing/} contains the
|
||||
executor and regression framework. @filepath{demo/} contains generated-code
|
||||
examples. @filepath{scrbl/} contains this reference and the use-case document.
|
||||
The @filepath{private/} directory contains compatibility/helper material from
|
||||
the source project. The current public module and tests do not depend on those
|
||||
private files; they are kept for downstream compatibility and are omitted from
|
||||
compilation and the package test entry point in @filepath{info.rkt}.
|
||||
|
||||
@section{Limitations and non-goals}
|
||||
|
||||
|
||||
@@ -27,42 +27,43 @@ const document = {
|
||||
JS
|
||||
(jsexpr->string paragraph-text)))
|
||||
|
||||
(define (side-effect-expr preamble program check-expr)
|
||||
(format "(() => {~a
|
||||
(function() {
|
||||
~a
|
||||
})();
|
||||
return (~a);
|
||||
})()"
|
||||
preamble
|
||||
program
|
||||
check-expr))
|
||||
|
||||
(define tests
|
||||
(list
|
||||
(js-program-test
|
||||
(js-expression-test
|
||||
'dom-ex01-highlight-long-words
|
||||
exercise01
|
||||
"paragraph.innerHTML"
|
||||
(jsexpr->string "Short <span style=\"background: yellow\">extraordinary</span> words remain.")
|
||||
#:preamble (dom-preamble "Short extraordinary words remain."))
|
||||
(side-effect-expr (dom-preamble "Short extraordinary words remain.") exercise01 "paragraph.innerHTML")
|
||||
(jsexpr->string "Short <span style=\"background: yellow\">extraordinary</span> words remain."))
|
||||
|
||||
(js-program-test
|
||||
(js-expression-test
|
||||
'dom-ex02-add-source-link
|
||||
exercise02
|
||||
"state.afterParagraph"
|
||||
(jsexpr->string '(("afterend" "<a href=\"https://forcemipsum.com/\">Source: ForceM Ipsum</a>")))
|
||||
#:preamble (dom-preamble "Force ipsum text."))
|
||||
(side-effect-expr (dom-preamble "Force ipsum text.") exercise02 "state.afterParagraph")
|
||||
(jsexpr->string '(("afterend" "<a href=\"https://forcemipsum.com/\">Source: ForceM Ipsum</a>"))))
|
||||
|
||||
(js-program-test
|
||||
(js-expression-test
|
||||
'dom-ex03-split-sentences
|
||||
exercise03
|
||||
"paragraph.innerHTML"
|
||||
(jsexpr->string "First sentence.<br>Second sentence.<br>Third.<br>")
|
||||
#:preamble (dom-preamble "First sentence. Second sentence. Third."))
|
||||
(side-effect-expr (dom-preamble "First sentence. Second sentence. Third.") exercise03 "paragraph.innerHTML")
|
||||
(jsexpr->string "First sentence.<br>Second sentence.<br>Third.<br>"))
|
||||
|
||||
(js-program-test
|
||||
(js-expression-test
|
||||
'dom-ex04-count-words-after-heading
|
||||
exercise04
|
||||
"state.afterHeading"
|
||||
(jsexpr->string '(("afterend" "<p>4 words</p>")))
|
||||
#:preamble (dom-preamble "These are four words"))
|
||||
(side-effect-expr (dom-preamble "These are four words") exercise04 "state.afterHeading")
|
||||
(jsexpr->string '(("afterend" "<p>4 words</p>"))))
|
||||
|
||||
(js-program-test
|
||||
(js-expression-test
|
||||
'dom-ex05-replace-punctuation-faces
|
||||
exercise05
|
||||
"paragraph.innerHTML"
|
||||
(jsexpr->string "Really🤔 Yes😲 No🤔")
|
||||
#:preamble (dom-preamble "Really? Yes! No?"))))
|
||||
(side-effect-expr (dom-preamble "Really? Yes! No?") exercise05 "paragraph.innerHTML")
|
||||
(jsexpr->string "Really🤔 Yes😲 No🤔"))))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-dom-exercises tests "/tmp/jsmaker-dom-exercises.js" #:engine engine)
|
||||
|
||||
@@ -63,12 +63,6 @@
|
||||
(js-expression-test 'for-fold (js/expression (for/fold ([s 0]) ([x (in-list (list 1 2 3))]) (+ x s))) "6")
|
||||
(js-expression-test 'map-filter (js/expression (filter (lambda (x) (> x 2)) (map (lambda (x) (+ x 1)) (list 1 2 3)))) "[3,4]")
|
||||
(js-expression-test 'hash-ref (js/expression (hash-ref (hash 'a 1 'b 2) 'b)) "2")
|
||||
(js-expression-test 'compile-time-eval-var
|
||||
(let ((x 10)
|
||||
(y 20))
|
||||
(js/expression (let ((a (eval (* x y))))
|
||||
(+ a a))))
|
||||
"400")
|
||||
(js-expression-test 'compile-time-eval-number
|
||||
(js/expression (eval (+ 1 2)))
|
||||
"3")
|
||||
|
||||
Reference in New Issue
Block a user