tail returns and complex stuff
This commit is contained in:
@@ -123,6 +123,15 @@
|
|||||||
(define s2 (regexp-replace* #rx"[^A-Za-z0-9_$]" s1 "_"))
|
(define s2 (regexp-replace* #rx"[^A-Za-z0-9_$]" s1 "_"))
|
||||||
(if (regexp-match? #rx"^[A-Za-z_$]" s2) s2 (string-append "_" s2)))
|
(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)
|
(define (compile-assignment-target target)
|
||||||
(match target
|
(match target
|
||||||
[(? symbol?) (id->js target)]
|
[(? symbol?) (id->js target)]
|
||||||
@@ -398,6 +407,14 @@
|
|||||||
(format "if (~a) ~a\nreturn undefined;" (compile-expr c) (block (compile-body body)))]
|
(format "if (~a) ~a\nreturn undefined;" (compile-expr c) (block (compile-body body)))]
|
||||||
[(list 'unless c body ...)
|
[(list 'unless c body ...)
|
||||||
(format "if (!(~a)) ~a\nreturn undefined;" (compile-expr c) (block (compile-body 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))]))
|
[_ (format "return ~a;" (compile-expr d))]))
|
||||||
|
|
||||||
(define (compile-stmt d)
|
(define (compile-stmt d)
|
||||||
@@ -414,6 +431,8 @@
|
|||||||
(format "let ~a = ~a;" (id->js id) (compile-expr rhs))]
|
(format "let ~a = ~a;" (id->js id) (compile-expr rhs))]
|
||||||
[(list 'define-values ids rhs)
|
[(list 'define-values ids rhs)
|
||||||
(format "let [~a] = ~a;" (compile-arg-list ids) (compile-expr 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)
|
[(list 'set! target rhs)
|
||||||
(format "~a = ~a;" (compile-assignment-target target) (compile-expr rhs))]
|
(format "~a = ~a;" (compile-assignment-target target) (compile-expr rhs))]
|
||||||
[(list 'return)
|
[(list 'return)
|
||||||
@@ -1383,6 +1402,8 @@ if (~a !== false) return ~a;" tmp (compile-expr arg) tmp tmp)))
|
|||||||
(parens (compile-delete-target obj key))]
|
(parens (compile-delete-target obj key))]
|
||||||
[(list 'set-prop! obj key val)
|
[(list 'set-prop! obj key val)
|
||||||
(format "(~a[~a] = ~a)" (compile-expr obj) (compile-expr key) (compile-expr 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)
|
[(list 'set! target rhs)
|
||||||
(format "(~a = ~a)" (compile-assignment-target target) (compile-expr rhs))]
|
(format "(~a = ~a)" (compile-assignment-target target) (compile-expr rhs))]
|
||||||
[(list 'return) "undefined"]
|
[(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)]
|
[(list f args ...) (compile-call f args)]
|
||||||
[_ (literal->js d)]))
|
[_ (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)
|
(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)
|
(define-syntax (js stx)
|
||||||
(syntax-case stx ()
|
(syntax-case stx ()
|
||||||
|
|||||||
+39
-3
@@ -16,7 +16,7 @@
|
|||||||
(list (verbatim rkt) (verbatim js)))))
|
(list (verbatim rkt) (verbatim js)))))
|
||||||
|
|
||||||
@title{js-maker: a Syntax-Driven Racket-to-JavaScript Generator}
|
@title{js-maker: a Syntax-Driven Racket-to-JavaScript Generator}
|
||||||
@author+email["Hans Dijkema" "hans@dijkewijk.nl"]
|
@author+email["Hans Dijkema" ""]
|
||||||
|
|
||||||
@defmodule[jsmaker]
|
@defmodule[jsmaker]
|
||||||
|
|
||||||
@@ -58,7 +58,32 @@ function square(x) {
|
|||||||
|
|
||||||
Inside function bodies, the last expression is returned automatically unless it
|
Inside function bodies, the last expression is returned automatically unless it
|
||||||
is already a statement form such as @racket[return], @racket[define],
|
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)]{
|
@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[(send obj method arg ...)] emits @tt{obj.method(arg, ...)}.}
|
||||||
@item{@racket[(new cls arg ...)] emits @tt{new cls(arg, ...)}.}
|
@item{@racket[(new cls arg ...)] emits @tt{new cls(arg, ...)}.}
|
||||||
@item{@racket[(js-ref obj key)] emits @tt{obj[key]}.}
|
@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[(set-prop! obj key value)] emits @tt{obj[key] = value}.}
|
||||||
@item{@racket[(delete-prop! obj key)] and @racket[(js-delete obj key)] emit
|
@item{@racket[(delete-prop! obj key)] and @racket[(js-delete obj key)] emit
|
||||||
JavaScript @tt{delete}.}
|
JavaScript @tt{delete}.}
|
||||||
@@ -719,6 +750,7 @@ The package uses this layout:
|
|||||||
js-maker/
|
js-maker/
|
||||||
main.rkt
|
main.rkt
|
||||||
info.rkt
|
info.rkt
|
||||||
|
private/
|
||||||
testing/
|
testing/
|
||||||
demo/
|
demo/
|
||||||
scrbl/
|
scrbl/
|
||||||
@@ -727,6 +759,10 @@ js-maker/
|
|||||||
@filepath{main.rkt} is the public module. @filepath{testing/} contains the
|
@filepath{main.rkt} is the public module. @filepath{testing/} contains the
|
||||||
executor and regression framework. @filepath{demo/} contains generated-code
|
executor and regression framework. @filepath{demo/} contains generated-code
|
||||||
examples. @filepath{scrbl/} contains this reference and the use-case document.
|
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}
|
@section{Limitations and non-goals}
|
||||||
|
|
||||||
|
|||||||
@@ -27,42 +27,43 @@ const document = {
|
|||||||
JS
|
JS
|
||||||
(jsexpr->string paragraph-text)))
|
(jsexpr->string paragraph-text)))
|
||||||
|
|
||||||
|
(define (side-effect-expr preamble program check-expr)
|
||||||
|
(format "(() => {~a
|
||||||
|
(function() {
|
||||||
|
~a
|
||||||
|
})();
|
||||||
|
return (~a);
|
||||||
|
})()"
|
||||||
|
preamble
|
||||||
|
program
|
||||||
|
check-expr))
|
||||||
|
|
||||||
(define tests
|
(define tests
|
||||||
(list
|
(list
|
||||||
(js-program-test
|
(js-expression-test
|
||||||
'dom-ex01-highlight-long-words
|
'dom-ex01-highlight-long-words
|
||||||
exercise01
|
(side-effect-expr (dom-preamble "Short extraordinary words remain.") exercise01 "paragraph.innerHTML")
|
||||||
"paragraph.innerHTML"
|
(jsexpr->string "Short <span style=\"background: yellow\">extraordinary</span> words remain."))
|
||||||
(jsexpr->string "Short <span style=\"background: yellow\">extraordinary</span> words remain.")
|
|
||||||
#:preamble (dom-preamble "Short extraordinary words remain."))
|
|
||||||
|
|
||||||
(js-program-test
|
(js-expression-test
|
||||||
'dom-ex02-add-source-link
|
'dom-ex02-add-source-link
|
||||||
exercise02
|
(side-effect-expr (dom-preamble "Force ipsum text.") exercise02 "state.afterParagraph")
|
||||||
"state.afterParagraph"
|
(jsexpr->string '(("afterend" "<a href=\"https://forcemipsum.com/\">Source: ForceM Ipsum</a>"))))
|
||||||
(jsexpr->string '(("afterend" "<a href=\"https://forcemipsum.com/\">Source: ForceM Ipsum</a>")))
|
|
||||||
#:preamble (dom-preamble "Force ipsum text."))
|
|
||||||
|
|
||||||
(js-program-test
|
(js-expression-test
|
||||||
'dom-ex03-split-sentences
|
'dom-ex03-split-sentences
|
||||||
exercise03
|
(side-effect-expr (dom-preamble "First sentence. Second sentence. Third.") exercise03 "paragraph.innerHTML")
|
||||||
"paragraph.innerHTML"
|
(jsexpr->string "First sentence.<br>Second sentence.<br>Third.<br>"))
|
||||||
(jsexpr->string "First sentence.<br>Second sentence.<br>Third.<br>")
|
|
||||||
#:preamble (dom-preamble "First sentence. Second sentence. Third."))
|
|
||||||
|
|
||||||
(js-program-test
|
(js-expression-test
|
||||||
'dom-ex04-count-words-after-heading
|
'dom-ex04-count-words-after-heading
|
||||||
exercise04
|
(side-effect-expr (dom-preamble "These are four words") exercise04 "state.afterHeading")
|
||||||
"state.afterHeading"
|
(jsexpr->string '(("afterend" "<p>4 words</p>"))))
|
||||||
(jsexpr->string '(("afterend" "<p>4 words</p>")))
|
|
||||||
#:preamble (dom-preamble "These are four words"))
|
|
||||||
|
|
||||||
(js-program-test
|
(js-expression-test
|
||||||
'dom-ex05-replace-punctuation-faces
|
'dom-ex05-replace-punctuation-faces
|
||||||
exercise05
|
(side-effect-expr (dom-preamble "Really? Yes! No?") exercise05 "paragraph.innerHTML")
|
||||||
"paragraph.innerHTML"
|
(jsexpr->string "Really🤔 Yes😲 No🤔"))))
|
||||||
(jsexpr->string "Really🤔 Yes😲 No🤔")
|
|
||||||
#:preamble (dom-preamble "Really? Yes! No?"))))
|
|
||||||
|
|
||||||
(define engine (find-js-engine))
|
(define engine (find-js-engine))
|
||||||
(run-jsmaker-regression 'jsmaker-dom-exercises tests "/tmp/jsmaker-dom-exercises.js" #:engine 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 '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 '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 '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-test 'compile-time-eval-number
|
||||||
(js/expression (eval (+ 1 2)))
|
(js/expression (eval (+ 1 2)))
|
||||||
"3")
|
"3")
|
||||||
|
|||||||
Reference in New Issue
Block a user