A mostly AI coded js-maker, supervised by me.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
#lang racket/base
|
||||
|
||||
(require json
|
||||
"jsmaker-test-framework.rkt"
|
||||
"jsmaker-executors.rkt"
|
||||
"../demo/dom-exercises.rkt")
|
||||
|
||||
(define (dom-preamble paragraph-text)
|
||||
(format #<<JS
|
||||
const state = { afterParagraph: [], afterHeading: [] };
|
||||
const paragraph = {
|
||||
innerHTML: ~a,
|
||||
get textContent() { return String(this.innerHTML).replace(/<[^>]*>/g, ''); },
|
||||
set textContent(v) { this.innerHTML = String(v); },
|
||||
insertAdjacentHTML: (position, html) => { state.afterParagraph.push([position, html]); }
|
||||
};
|
||||
const heading = {
|
||||
insertAdjacentHTML: (position, html) => { state.afterHeading.push([position, html]); }
|
||||
};
|
||||
const document = {
|
||||
querySelector: (selector) => {
|
||||
if (selector === 'p') return paragraph;
|
||||
if (selector === 'h1') return heading;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
JS
|
||||
(jsexpr->string paragraph-text)))
|
||||
|
||||
(define tests
|
||||
(list
|
||||
(js-program-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."))
|
||||
|
||||
(js-program-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."))
|
||||
|
||||
(js-program-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."))
|
||||
|
||||
(js-program-test
|
||||
'dom-ex04-count-words-after-heading
|
||||
exercise04
|
||||
"state.afterHeading"
|
||||
(jsexpr->string '(("afterend" "<p>4 words</p>")))
|
||||
#:preamble (dom-preamble "These are four words"))
|
||||
|
||||
(js-program-test
|
||||
'dom-ex05-replace-punctuation-faces
|
||||
exercise05
|
||||
"paragraph.innerHTML"
|
||||
(jsexpr->string "Really🤔 Yes😲 No🤔")
|
||||
#:preamble (dom-preamble "Really? Yes! No?"))))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-dom-exercises tests "/tmp/jsmaker-dom-exercises.js" #:engine engine)
|
||||
@@ -0,0 +1,164 @@
|
||||
#lang racket/base
|
||||
|
||||
(require racket/file
|
||||
racket/list
|
||||
racket/match
|
||||
racket/port
|
||||
racket/string)
|
||||
|
||||
(provide js-engine?
|
||||
js-engine-name
|
||||
js-engine-path
|
||||
js-engine-kind
|
||||
js-engine-version
|
||||
js-run-result?
|
||||
js-run-result-engine
|
||||
js-run-result-status
|
||||
js-run-result-stdout
|
||||
js-run-result-stderr
|
||||
js-run-result-success?
|
||||
known-js-engine-names
|
||||
find-js-engine
|
||||
run-js-file)
|
||||
|
||||
(struct js-engine (name path kind version-args) #:transparent)
|
||||
(struct js-run-result (engine status stdout stderr) #:transparent)
|
||||
|
||||
(define (path-string/non-empty? v)
|
||||
(and (string? v) (not (string=? v ""))))
|
||||
|
||||
(define (executable-file? p)
|
||||
(and p (file-exists? p)))
|
||||
|
||||
(define (getenv/path name)
|
||||
(define v (getenv name))
|
||||
(and (path-string/non-empty? v) (string->path v)))
|
||||
|
||||
(define engine-specs
|
||||
;; name aliases kind version-args extra-paths
|
||||
'((node ("node" "nodejs") plain ("--version")
|
||||
("/opt/nvm/versions/node/v22.16.0/bin/node" "/usr/local/bin/node" "/usr/bin/node"))
|
||||
(deno ("deno") plain ("--version") ())
|
||||
(bun ("bun") plain ("--version") ())
|
||||
(qjs ("qjs" "quickjs") plain ("-v") ())
|
||||
(d8 ("d8") plain ("--version") ())
|
||||
(jsc ("jsc") plain ("--version") ())
|
||||
(js ("js") plain ("--version") ())
|
||||
(chromium ("chromium" "chromium-browser" "google-chrome" "google-chrome-stable") chromium ("--version")
|
||||
("/usr/bin/chromium" "/usr/bin/chromium-browser" "/usr/bin/google-chrome"))))
|
||||
|
||||
(define (known-js-engine-names)
|
||||
(map car engine-specs))
|
||||
|
||||
(define (lookup-engine-spec name)
|
||||
(define wanted (string-downcase (format "~a" name)))
|
||||
(for/first ([spec (in-list engine-specs)]
|
||||
#:when (or (string=? wanted (symbol->string (car spec)))
|
||||
(member wanted (cadr spec))))
|
||||
spec))
|
||||
|
||||
(define (candidate-paths spec)
|
||||
(match spec
|
||||
[(list name aliases kind version-args extra-paths)
|
||||
(append
|
||||
(if (eq? name 'node)
|
||||
(filter values (list (getenv/path "JSMAKER_NODE")))
|
||||
'())
|
||||
(filter values (list (getenv/path "JSMAKER_ENGINE_PATH")))
|
||||
(filter values (map find-executable-path aliases))
|
||||
(map string->path extra-paths))]))
|
||||
|
||||
(define (make-engine-from-spec spec)
|
||||
(match spec
|
||||
[(list name aliases kind version-args extra-paths)
|
||||
(for/first ([p (in-list (candidate-paths spec))]
|
||||
#:when (executable-file? p))
|
||||
(js-engine name p kind version-args))]))
|
||||
|
||||
(define (find-js-engine [requested (or (getenv "JSMAKER_ENGINE") "auto")])
|
||||
(define req (string-downcase requested))
|
||||
(cond
|
||||
[(or (string=? req "") (string=? req "auto"))
|
||||
(define browser-fallback? (getenv "JSMAKER_BROWSER_FALLBACK"))
|
||||
(or (for*/first ([spec (in-list engine-specs)]
|
||||
#:unless (and (not browser-fallback?) (eq? (car spec) 'chromium))
|
||||
[engine (in-value (make-engine-from-spec spec))]
|
||||
#:when engine)
|
||||
engine)
|
||||
#f)]
|
||||
[else
|
||||
(define spec (lookup-engine-spec req))
|
||||
(and spec (make-engine-from-spec spec))]))
|
||||
|
||||
(define (capture-timeout-seconds)
|
||||
(define v (getenv "JSMAKER_ENGINE_TIMEOUT_SECONDS"))
|
||||
(cond
|
||||
[(and (path-string/non-empty? v) (string->number v)) => values]
|
||||
[else 15]))
|
||||
|
||||
(define (capture path args)
|
||||
(with-handlers ([exn:fail? (lambda (e) (values 127 "" (exn-message e)))])
|
||||
(define-values (proc stdout stdin stderr)
|
||||
(apply subprocess #f #f #f path args))
|
||||
(define waiter (thread (lambda () (subprocess-wait proc))))
|
||||
(define finished? (sync/timeout (capture-timeout-seconds) waiter))
|
||||
(unless finished?
|
||||
(subprocess-kill proc #t)
|
||||
(thread-wait waiter))
|
||||
(values (if finished? (subprocess-status proc) 124)
|
||||
(port->string stdout)
|
||||
(string-append (port->string stderr)
|
||||
(if finished? "" "\nJavaScript engine timed out and was killed.\n")))))
|
||||
|
||||
(define (js-engine-version engine)
|
||||
(define-values (status stdout stderr)
|
||||
(capture (js-engine-path engine) (js-engine-version-args engine)))
|
||||
(define s (string-trim (if (string=? stdout "") stderr stdout)))
|
||||
(and (zero? status) (not (string=? s "")) s))
|
||||
|
||||
(define (plain-run-args engine js-path)
|
||||
(case (js-engine-name engine)
|
||||
[(deno) (list "run" "--quiet" js-path)]
|
||||
[else (list js-path)]))
|
||||
|
||||
(define (copy-js-as-browser-html js-path)
|
||||
(define html-path (build-path (find-system-path 'temp-dir)
|
||||
(format "jsmaker-browser-~a.html" (current-inexact-milliseconds))))
|
||||
(define js (file->string js-path))
|
||||
(call-with-output-file html-path
|
||||
#:exists 'replace
|
||||
(lambda (out)
|
||||
(displayln "<!doctype html><meta charset=\"utf-8\"><pre id=\"out\"></pre>" out)
|
||||
(displayln "<script>" out)
|
||||
(displayln "const __out = document.getElementById('out');" out)
|
||||
(displayln "const print = (...xs) => { __out.append(xs.join(' ') + '\\n'); };" out)
|
||||
(displayln "console.log = (...xs) => print(...xs);" out)
|
||||
(displayln "console.error = (...xs) => print(...xs);" out)
|
||||
(displayln "try {" out)
|
||||
(display js out)
|
||||
(displayln "\ndocument.documentElement.setAttribute('data-jsmaker-status', 'ok');" out)
|
||||
(displayln "} catch (e) {" out)
|
||||
(displayln " print('ERROR\\t' + (e && e.stack ? e.stack : String(e)));" out)
|
||||
(displayln " document.documentElement.setAttribute('data-jsmaker-status', 'fail');" out)
|
||||
(displayln "}" out)
|
||||
(displayln "</script>" out)))
|
||||
html-path)
|
||||
|
||||
(define (chromium-run engine js-path)
|
||||
(define html-path (copy-js-as-browser-html js-path))
|
||||
(define url (string-append "file://" (path->string html-path)))
|
||||
(define args (list "--headless" "--disable-gpu" "--no-sandbox" "--dump-dom" url))
|
||||
(define-values (status stdout stderr) (capture (js-engine-path engine) args))
|
||||
(define browser-fail? (regexp-match? #rx"data-jsmaker-status=\"fail\"" stdout))
|
||||
(js-run-result engine (if browser-fail? 1 status) stdout stderr))
|
||||
|
||||
(define (run-js-file engine js-path)
|
||||
(case (js-engine-kind engine)
|
||||
[(chromium) (chromium-run engine js-path)]
|
||||
[else
|
||||
(define-values (status stdout stderr)
|
||||
(capture (js-engine-path engine) (plain-run-args engine js-path)))
|
||||
(js-run-result engine status stdout stderr)]))
|
||||
|
||||
(define (js-run-result-success? result)
|
||||
(zero? (js-run-result-status result)))
|
||||
@@ -0,0 +1,118 @@
|
||||
#lang racket/base
|
||||
|
||||
(require "../main.rkt"
|
||||
"jsmaker-executors.rkt"
|
||||
"jsmaker-test-framework.rkt")
|
||||
|
||||
(define dom-preamble
|
||||
(string-append
|
||||
"const window = {};\n"
|
||||
"const logs = [];\n"
|
||||
"const attrs = {};\n"
|
||||
"const console = { log: (...xs) => logs.push(xs.length === 1 ? xs[0] : xs) };\n"
|
||||
"const document = {\n"
|
||||
" getElementById: (id) => id === 'hi'\n"
|
||||
" ? { setAttribute: (k, v) => { attrs[k] = v; }, getAttribute: (k) => attrs[k] || '' }\n"
|
||||
" : false\n"
|
||||
"};"))
|
||||
|
||||
(define t1-program
|
||||
(js (set! window.myfunc (λ (x)
|
||||
(let* ((el (send document getElementById 'hi))
|
||||
(old (send el getAttribute "x"))
|
||||
(y (+ old (* x x))))
|
||||
(send el setAttribute "x" (+ y "")))
|
||||
(send console log "dit set attribute x on element hi")))))
|
||||
|
||||
(define t2-program
|
||||
(js (define (f x)
|
||||
(if (and (> x 10) (< x 15))
|
||||
(begin (console.log x)
|
||||
(return x))
|
||||
(return (* x x))))))
|
||||
|
||||
(define t3-program
|
||||
(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)))))))))
|
||||
|
||||
(define tail-loop-expression
|
||||
(js/expression (let loop ([i 0] [s 0])
|
||||
(if (< i 5)
|
||||
(loop (+ i 1) (+ s i))
|
||||
s))))
|
||||
|
||||
(unless (regexp-match? #rx"while \\(true\\)" tail-loop-expression)
|
||||
(error 'jsmaker-program-regression
|
||||
"tail-recursive named let was not lowered to while(true): ~a"
|
||||
tail-loop-expression))
|
||||
|
||||
(unless (regexp-match? #rx"\n " t1-program)
|
||||
(error 'jsmaker-program-regression
|
||||
"generated statement code does not appear to contain indentation: ~a"
|
||||
t1-program))
|
||||
|
||||
(unless (regexp-match? #rx"if \\(\\(x > 10\\) && \\(x < 15\\)\\)" t2-program)
|
||||
(error 'jsmaker-program-regression
|
||||
"boolean and should compile directly in if test: ~a"
|
||||
t2-program))
|
||||
|
||||
(define program-tests
|
||||
(list
|
||||
(js-program-test
|
||||
'dom-style-set-attribute
|
||||
t1-program
|
||||
"(() => { attrs.x = 'old:'; window.myfunc(4); return [attrs.x, logs]; })()"
|
||||
"[\"old:16\",[\"dit set attribute x on element hi\"]]"
|
||||
#:preamble dom-preamble)
|
||||
|
||||
(js-program-test
|
||||
'define-if-return-console
|
||||
t2-program
|
||||
"(() => { const a = f(12); const b = f(5); return [a, b, logs]; })()"
|
||||
"[12,25,[12]]"
|
||||
#:preamble "const logs = []; const console = { log: (...xs) => logs.push(xs.length === 1 ? xs[0] : xs) };" )
|
||||
|
||||
(js-program-test
|
||||
'define-send-map-return
|
||||
t3-program
|
||||
"(() => { const r = f(1, 2, 3); return [r, logs]; })()"
|
||||
"[[11,12,13],[[1,2,3]]]"
|
||||
#:preamble "const logs = []; const console = { log: (...xs) => logs.push(xs.length === 1 ? xs[0] : xs) };" )
|
||||
|
||||
(js-expression-test 'named-let-tail-big
|
||||
(js/expression (let loop ([i 0] [s 0])
|
||||
(if (< i 100000)
|
||||
(loop (+ i 1) (+ s 1))
|
||||
s)))
|
||||
"100000")
|
||||
|
||||
(js-expression-test 'named-let-tail-accumulator tail-loop-expression "10")
|
||||
|
||||
(js-expression-test 'explicit-while-form
|
||||
(js/expression (let ([i 0] [s 0])
|
||||
(while (< i 4)
|
||||
(set! s (+ s i))
|
||||
(set! i (+ i 1)))
|
||||
s))
|
||||
"6")
|
||||
|
||||
(js-expression-test 'bare-return
|
||||
(js/expression ((lambda (x) (if x (return) (return 7))) #t))
|
||||
"undefined")
|
||||
|
||||
(js-expression-test 'implicit-last-expression-return
|
||||
(js/expression ((lambda (x) (+ x 10)) 5))
|
||||
"15")
|
||||
|
||||
(js-program-test
|
||||
'with-handlers-rest-lambda-statement
|
||||
(js (with-handlers ([exn? (λ args (displayln (length args)))])
|
||||
(/ 10 0)))
|
||||
"logs"
|
||||
"[1]"
|
||||
#:preamble "const logs = []; const console = { log: (...xs) => logs.push(xs.length === 1 ? xs[0] : xs) };")))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-program-regression program-tests "/tmp/jsmaker-program-regression.js" #:engine engine)
|
||||
@@ -0,0 +1,24 @@
|
||||
#lang racket/base
|
||||
|
||||
(require "../main.rkt"
|
||||
"jsmaker-executors.rkt"
|
||||
"jsmaker-test-framework.rkt")
|
||||
|
||||
(define tests
|
||||
(list
|
||||
(js-expression-test 'regexp-literal-test (js/expression (regexp-match? #rx"a+" "baac")) "true")
|
||||
(js-expression-test 'regexp-match-basic (js/expression (regexp-match #rx"a+" "baac")) "[\"aa\"]")
|
||||
(js-expression-test 'regexp-match-captures (js/expression (regexp-match #rx"(a)(b)?" "a")) "[\"a\",\"a\",false]")
|
||||
(js-expression-test 'pregexp-match-digits (js/expression (regexp-match #px"([a-z]+)-([0-9]+)" "abc-123")) "[\"abc-123\",\"abc\",\"123\"]")
|
||||
(js-expression-test 'regexp-match-star (js/expression (regexp-match* #rx"a+" "baacaa")) "[\"aa\",\"aa\"]")
|
||||
(js-expression-test 'regexp-split (js/expression (regexp-split #rx"," "a,b,,c")) "[\"a\",\"b\",\"\",\"c\"]")
|
||||
(js-expression-test 'regexp-replace (js/expression (regexp-replace #rx"a" "banana" "X")) "\"bXnana\"")
|
||||
(js-expression-test 'regexp-replace-star (js/expression (regexp-replace* #rx"a" "banana" "X")) "\"bXnXnX\"")
|
||||
(js-expression-test 'regexp-replace-backref (js/expression (regexp-replace #rx"([a-z]+)-([0-9]+)" "abc-123" "\\2/\\1")) "\"123/abc\"")
|
||||
(js-expression-test 'regexp-replace-full (js/expression (regexp-replace #rx"a+" "baac" "[\\0]")) "\"b[aa]c\"")
|
||||
(js-expression-test 'regexp-pattern-string (js/expression (regexp-match "a+" "baac")) "[\"aa\"]")
|
||||
(js-expression-test 'regexp-quote (js/expression (regexp-quote "a+b*c?")) "\"a\\\\+b\\\\*c\\\\?\"")
|
||||
(js-expression-test 'regexp-match-positions (js/expression (regexp-match-positions #rx"(a)(b)?" "xa")) "[[1,2],[1,2],false]")))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-regexp-regression tests "/tmp/jsmaker-regexp-regression.js" #:engine engine)
|
||||
@@ -0,0 +1,110 @@
|
||||
#lang racket/base
|
||||
|
||||
(require racket/string
|
||||
"../main.rkt"
|
||||
"jsmaker-executors.rkt"
|
||||
"jsmaker-test-framework.rkt")
|
||||
|
||||
(define direct-and (js/expression (and (> x 10) (< x 15))))
|
||||
(unless (string=? direct-and "((x > 10) && (x < 15))")
|
||||
(error 'jsmaker-regression "expected direct && generation, got: ~a" direct-and))
|
||||
|
||||
(define direct-or (js/expression (or (< x 10) (> x 20))))
|
||||
(unless (string=? direct-or "((x < 10) || (x > 20))")
|
||||
(error 'jsmaker-regression "expected direct || generation, got: ~a" direct-or))
|
||||
|
||||
(define direct-chain (js/expression (< x y 10)))
|
||||
(unless (string=? direct-chain "((x < y) && (y < 10))")
|
||||
(error 'jsmaker-regression "expected direct pairwise comparison, got: ~a" direct-chain))
|
||||
|
||||
(define effectful-chain (js/expression (< x (f y) 10)))
|
||||
(unless (regexp-match? #rx"__cmp" effectful-chain)
|
||||
(error 'jsmaker-regression "effectful chained comparison should use temporaries, got: ~a" effectful-chain))
|
||||
|
||||
(define simple-let-star
|
||||
(js (let* ((x 10)
|
||||
(y (+ x x)))
|
||||
(return y))))
|
||||
|
||||
(define with-handlers-rest-lambda-program
|
||||
(js (with-handlers ([exn? (λ args (displayln args))])
|
||||
(/ 10 0))))
|
||||
(unless (regexp-match? #rx"\\(function\\(\\.\\.\\.args\\)" with-handlers-rest-lambda-program)
|
||||
(error 'jsmaker-regression
|
||||
"with-handlers rest-lambda handler should be parenthesized in callee position, got: ~a"
|
||||
with-handlers-rest-lambda-program))
|
||||
(unless (not (regexp-match? #rx"catch[^{]*\\{\\n function\\(" with-handlers-rest-lambda-program))
|
||||
(error 'jsmaker-regression
|
||||
"with-handlers emitted a function declaration in statement position, got: ~a"
|
||||
with-handlers-rest-lambda-program))
|
||||
(unless (regexp-match? #rx"let x = 10;" simple-let-star)
|
||||
(error 'jsmaker-regression "simple let* should emit direct let for x, got: ~a" simple-let-star))
|
||||
(unless (regexp-match? #rx"let y = .*x.*x.*;" simple-let-star)
|
||||
(error 'jsmaker-regression "simple let* should emit direct dependent let for y, got: ~a" simple-let-star))
|
||||
(unless (not (regexp-match? #rx"__let_star_value" simple-let-star))
|
||||
(error 'jsmaker-regression "simple let* should not use tempvars, got: ~a" simple-let-star))
|
||||
|
||||
(define tests
|
||||
(list
|
||||
(js-expression-test 'if-zero (js/expression (if 0 1 2)) "1")
|
||||
(js-expression-test 'if-false (js/expression (if #f 1 2)) "2")
|
||||
(js-expression-test 'and-false (js/expression (and #f 5)) "false")
|
||||
(js-expression-test 'and-zero (js/expression (and 0 5)) "5")
|
||||
(js-expression-test 'or-empty-string (js/expression (or "" 5)) "\"\"")
|
||||
(js-expression-test 'direct-and-boolean (format "((x) => ~a)(12)" direct-and) "true")
|
||||
(js-expression-test 'direct-or-boolean (format "((x) => ~a)(5)" direct-or) "true")
|
||||
(js-expression-test 'chain-lt (js/expression (< 1 2 3)) "true")
|
||||
(js-expression-test 'chain-lt-false (js/expression (< 1 3 2)) "false")
|
||||
(js-expression-test 'let-expr (js/expression (let ([x 2] [y 3]) (+ x y))) "5")
|
||||
(js-expression-test 'let-star-sequential-binding (js/expression (let* ([x 10] [y (+ x x)]) y)) "20")
|
||||
(js-expression-test 'let-star-dependent-shadowing (js/expression (let ([x 4]) (let* ([x x] [y x]) (+ x y)))) "8")
|
||||
(js-expression-test 'named-let (js/expression (let loop ([i 0] [s 0]) (if (< i 5) (loop (+ i 1) (+ s i)) s))) "10")
|
||||
(js-expression-test 'for-list (js/expression (for/list ([x (in-range 5)] #:when (odd? x)) (* x x))) "[1,9]")
|
||||
(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 'substring (js/expression (substring "abcdef" 1 4)) "\"bcd\"")
|
||||
(js-expression-test 'equal-list (js/expression (equal? (list 1 2) (list 1 2))) "true")
|
||||
(js-expression-test 'cond-test-only (js/expression (cond [0] [else 2])) "0")
|
||||
(js-expression-test 'cond-arrow (js/expression (cond [(+ 1 2) => (lambda (x) (+ x 10))] [else 0])) "13")
|
||||
(js-expression-test 'cond-false-arrow (js/expression (cond [#f => (lambda (x) (+ x 10))] [else 7])) "7")
|
||||
(js-expression-test 'rest-lambda (js/expression ((lambda xs (length xs)) 1 2 3)) "3")
|
||||
(js-expression-test 'dotted-lambda (js/expression ((lambda (a . xs) (+ a (length xs))) 10 20 30)) "12")
|
||||
(js-expression-test 'let-values-one (js/expression (let-values ([(x) 5]) (+ x 1))) "6")
|
||||
(js-expression-test 'let-values-many (js/expression (let-values ([(x y) (values 2 3)]) (+ x y))) "5")
|
||||
(js-expression-test 'let-tdz (js/expression (let ([x 4]) (let ([x x]) (+ x 1)))) "5")
|
||||
(js-expression-test 'let-star-tdz (js/expression (let ([x 4]) (let* ([x x] [y x]) (+ x y)))) "8")
|
||||
(js-expression-test 'division-normal
|
||||
(js/expression (/ 20 2 2))
|
||||
"5")
|
||||
(js-expression-test 'with-handlers-division-by-zero
|
||||
(js/expression (with-handlers ([exn? (lambda args (string-append "caught:" (exn-message (car args))))])
|
||||
(/ 10 0)))
|
||||
"\"caught:division by zero\"")
|
||||
(js-expression-test 'with-handlers-generic-exn
|
||||
(js/expression (with-handlers ([exn? (lambda (e) (string-append "caught:" (exn-message e)))])
|
||||
(error "boom")))
|
||||
"\"caught:boom\"")
|
||||
(js-expression-test 'with-handlers-no-error
|
||||
(js/expression (with-handlers ([exn? (lambda (e) 99)])
|
||||
(+ 20 22)))
|
||||
"42")
|
||||
(js-expression-test 'gregor-prefix-date-string
|
||||
(js/expression (date->string (foo:date 2026 5 25)))
|
||||
"\"2026-05-25\"")
|
||||
(js-expression-test 'gregor-prefix-time-string
|
||||
(js/expression (time->string (bar:time 8 9 10)))
|
||||
"\"08:09:10\"")
|
||||
(js-expression-test 'gregor-prefix-moment-fields
|
||||
(js/expression (list (baz:->year (baz:parse-moment "2026-05-25T08:09:10" "yyyy-MM-dd'T'HH:mm:ss"))
|
||||
(baz:->month (baz:parse-moment "2026-05-25T08:09:10"))
|
||||
(baz:->day (baz:parse-moment "2026-05-25T08:09:10"))))
|
||||
"[2026,5,25]")
|
||||
(js-expression-test 'gregor-js-date-conversion
|
||||
(js/expression (list (q:->year (js-date->datetime (q:->js-date (q:date 2026 5 25))))
|
||||
(q:date? (q:date 2026 5 25))
|
||||
(q:moment? (q:moment 2026 5 25 8 9 10))))
|
||||
"[2026,true,true]")))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-core-regression tests "/tmp/jsmaker-core-regression.js" #:engine engine)
|
||||
@@ -0,0 +1,7 @@
|
||||
#lang racket/base
|
||||
|
||||
(require "jsmaker-regression.rkt"
|
||||
"jsmaker-regexp-regression.rkt"
|
||||
"jsmaker-program-regression.rkt"
|
||||
"jsmaker-dom-exercises.rkt"
|
||||
"jsmaker-usecases.rkt")
|
||||
@@ -0,0 +1,88 @@
|
||||
#lang racket/base
|
||||
|
||||
(require racket/format
|
||||
racket/string
|
||||
"jsmaker-executors.rkt")
|
||||
|
||||
(provide js-expression-test
|
||||
js-program-test
|
||||
write-js-test-file
|
||||
run-jsmaker-regression
|
||||
warning-line)
|
||||
|
||||
(define (warning-line who fmt . args)
|
||||
(displayln (string-append "WARNING: " (apply format fmt args))
|
||||
(current-error-port)))
|
||||
|
||||
(define (js-expression-test name expr expected)
|
||||
(list name expr expected))
|
||||
|
||||
(define (js-program-test name program check-expr expected #:preamble [preamble ""])
|
||||
(list name
|
||||
(format "(() => {~a~a~areturn (~a); })()"
|
||||
(if (string=? preamble "") "" (string-append "\n" preamble "\n"))
|
||||
program
|
||||
(if (regexp-match? #rx"\n$" program) "" "\n")
|
||||
check-expr)
|
||||
expected))
|
||||
|
||||
(define (write-js-test-file js-path tests)
|
||||
(call-with-output-file js-path
|
||||
#:exists 'replace
|
||||
(lambda (out)
|
||||
(displayln "const tests = [];" out)
|
||||
(displayln "const __normalize = (v) => v === undefined ? 'undefined' : JSON.stringify(v);" out)
|
||||
(displayln "const __print = (...xs) => {" out)
|
||||
(displayln " if (typeof console !== 'undefined' && console.log) console.log(...xs);" out)
|
||||
(displayln " else if (typeof print === 'function') print(xs.join(' '));" out)
|
||||
(displayln "};" out)
|
||||
(for ([t tests])
|
||||
(define name (symbol->string (car t)))
|
||||
(define expr (cadr t))
|
||||
(define expected (caddr t))
|
||||
(fprintf out "tests.push([~s, () => (~a), ~s]);\n" name expr expected))
|
||||
(displayln "(async () => {" out)
|
||||
(displayln " let failed = 0;" out)
|
||||
(displayln " for (const [name, thunk, expected] of tests) {" out)
|
||||
(displayln " let value;" out)
|
||||
(displayln " try { value = await Promise.resolve(thunk()); }" out)
|
||||
(displayln " catch (e) { value = { __thrown: String(e && e.message !== undefined ? e.message : e) }; }" out)
|
||||
(displayln " const actual = __normalize(value);" out)
|
||||
(displayln " const ok = actual === expected;" out)
|
||||
(displayln " __print((ok ? 'ok' : 'FAIL') + '\\t' + name + '\\t' + actual);" out)
|
||||
(displayln " if (!ok) failed++;" out)
|
||||
(displayln " }" out)
|
||||
(displayln " if (failed !== 0) throw new Error(failed + ' jsmaker test(s) failed');" out)
|
||||
(displayln "})().catch((e) => {" out)
|
||||
(displayln " __print(e && e.stack ? e.stack : String(e));" out)
|
||||
(displayln " if (typeof process !== 'undefined' && process.exit) process.exit(1);" out)
|
||||
(displayln " throw e;" out)
|
||||
(displayln "});" out))))
|
||||
|
||||
(define (describe-engine engine)
|
||||
(define version (or (js-engine-version engine) "unknown version"))
|
||||
(format "~a at ~a (~a)"
|
||||
(js-engine-name engine)
|
||||
(path->string (js-engine-path engine))
|
||||
version))
|
||||
|
||||
(define (run-jsmaker-regression who tests js-path #:engine [engine (find-js-engine)])
|
||||
(write-js-test-file js-path tests)
|
||||
(cond
|
||||
[engine
|
||||
(printf "~a: using JavaScript engine ~a\n" who (describe-engine engine))
|
||||
(define result (run-js-file engine js-path))
|
||||
(display (js-run-result-stdout result))
|
||||
(unless (string=? (js-run-result-stderr result) "")
|
||||
(display (js-run-result-stderr result) (current-error-port)))
|
||||
(unless (js-run-result-success? result)
|
||||
(error who "JavaScript regression failed with ~a; see ~a"
|
||||
(js-engine-name engine) js-path))]
|
||||
[else
|
||||
(warning-line who "No JavaScript engine was found; regression tests were generated but not executed.")
|
||||
(warning-line who "Generated JavaScript test file: ~a" js-path)
|
||||
(warning-line who "Tried engines: ~a" (string-join (map symbol->string (known-js-engine-names)) ", "))
|
||||
(warning-line who "Set JSMAKER_ENGINE to auto/node/deno/bun/qjs/d8/jsc/js/chromium or set JSMAKER_ENGINE_PATH.")
|
||||
(warning-line who "For backwards compatibility, JSMAKER_NODE can point to a Node executable.")
|
||||
(when (or (getenv "JSMAKER_REQUIRE_ENGINE") (getenv "JSMAKER_REQUIRE_NODE"))
|
||||
(error who "JavaScript engine required by environment setting"))]))
|
||||
@@ -0,0 +1,12 @@
|
||||
#lang racket/base
|
||||
|
||||
(require "jsmaker-test-framework.rkt")
|
||||
|
||||
(provide run-jsmaker-node-regression)
|
||||
|
||||
;; Compatibility wrapper for the older single-engine test runner name.
|
||||
;; It now delegates to the generic framework/executor layer. When no engine
|
||||
;; is available, the framework generates the JavaScript test file and reports a
|
||||
;; skip/warning unless JSMAKER_REQUIRE_ENGINE or JSMAKER_REQUIRE_NODE is set.
|
||||
(define (run-jsmaker-node-regression who tests js-path)
|
||||
(run-jsmaker-regression who tests js-path))
|
||||
@@ -0,0 +1,191 @@
|
||||
#lang racket/base
|
||||
|
||||
(require json
|
||||
"../main.rkt"
|
||||
"jsmaker-test-framework.rkt"
|
||||
"jsmaker-executors.rkt"
|
||||
"../demo/js-usecases.rkt")
|
||||
|
||||
(define timer-preamble
|
||||
#<<JS
|
||||
const intervals = {};
|
||||
let nextIntervalId = 1;
|
||||
function setInterval(cb, ms) {
|
||||
const id = nextIntervalId++;
|
||||
intervals[id] = { cb, ms, active: true };
|
||||
return id;
|
||||
}
|
||||
function clearInterval(id) {
|
||||
if (intervals[id]) intervals[id].active = false;
|
||||
}
|
||||
function runInterval(id, times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
if (!intervals[id] || !intervals[id].active) break;
|
||||
intervals[id].cb();
|
||||
}
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
(define dom-preamble
|
||||
#<<JS
|
||||
const body = { innerHTML: "<p>A</p>" };
|
||||
const root = { innerHTML: "<p>A</p>" };
|
||||
const document = {
|
||||
body,
|
||||
querySelector: (selector) => selector === "body" ? body : false,
|
||||
getElementById: (id) => id === "root" ? root : false
|
||||
};
|
||||
JS
|
||||
)
|
||||
|
||||
(define fetch-preamble
|
||||
#<<JS
|
||||
function fetch(url) {
|
||||
if (url === "/ok") {
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve({ title: "Done" })
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error("network"));
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
(define tests
|
||||
(list
|
||||
(js-program-test
|
||||
'use-random-number-1-to-5
|
||||
usecase-random-number
|
||||
(js/expression (randomBetween1And5))
|
||||
(jsexpr->string 5)
|
||||
#:preamble "Math.random = () => 0.80;")
|
||||
|
||||
(js-program-test
|
||||
'use-unique-values-set
|
||||
usecase-unique-values
|
||||
(js/expression (uniqueValues (array 1 2 2 3 1)))
|
||||
(jsexpr->string '(1 2 3)))
|
||||
|
||||
(js-program-test
|
||||
'use-six-falsey-values
|
||||
usecase-falsey-values
|
||||
(js/expression (send (falseyValues) map (lambda (x) (return (Boolean x)))))
|
||||
(jsexpr->string '(#f #f #f #f #f #f)))
|
||||
|
||||
(js-program-test
|
||||
'use-currying-simple
|
||||
usecase-currying
|
||||
(js/expression ((add 2) 3))
|
||||
(jsexpr->string 5))
|
||||
|
||||
(js-program-test
|
||||
'use-object-destructuring
|
||||
usecase-object-destructuring
|
||||
(js/expression (describePerson (object 'name "Ada" 'age 37)))
|
||||
(jsexpr->string "Ada:37"))
|
||||
|
||||
(js-program-test
|
||||
'use-timer-clear-interval
|
||||
usecase-timer-interval
|
||||
(js/expression
|
||||
(let* ([t (startTimer)])
|
||||
(runInterval t.id 5)
|
||||
(array t.id (send t getTicks) (js-dot (js-ref intervals t.id) active))))
|
||||
(jsexpr->string '(1 3 #f))
|
||||
#:preamble timer-preamble)
|
||||
|
||||
(js-program-test
|
||||
'use-object-get-set-delete-prop
|
||||
usecase-object-props
|
||||
(js/expression (objectProps))
|
||||
(jsexpr->string '(1 1 1 2 3 #f)))
|
||||
|
||||
(js-program-test
|
||||
'use-string-concat-order
|
||||
usecase-string-concat-order
|
||||
(js/expression (concatOrder))
|
||||
(jsexpr->string '("33" "123")))
|
||||
|
||||
(js-program-test
|
||||
'use-freeze-vs-seal
|
||||
usecase-freeze-vs-seal
|
||||
(js/expression (freezeVsSeal))
|
||||
(jsexpr->string '(1 9 #t #t #t)))
|
||||
|
||||
(js-program-test
|
||||
'use-switch-example
|
||||
usecase-switch
|
||||
(js/expression (array (switchExample 1) (switchExample 2) (switchExample 9)))
|
||||
(jsexpr->string '("one" "two-or-three" "other")))
|
||||
|
||||
(js-program-test
|
||||
'use-class-constructor-default
|
||||
usecase-class-constructor
|
||||
(js/expression (classExample))
|
||||
(jsexpr->string '("Hello world" "Hello Ada")))
|
||||
|
||||
(js-program-test
|
||||
'use-sort-objects-by-property
|
||||
usecase-sort-objects-by-property
|
||||
(js/expression
|
||||
(send (sortByProperty (array (object 'age 30) (object 'age 20) (object 'age 25)) "age")
|
||||
map
|
||||
(lambda (x) (return x.age))))
|
||||
(jsexpr->string '(20 25 30)))
|
||||
|
||||
(js-program-test
|
||||
'use-delete-array-elements-four-ways
|
||||
usecase-delete-array-elements
|
||||
(js/expression (deleteArrayWays (array "a" "b" "c")))
|
||||
(jsexpr->string '(("a" "c") ("a" "c") ("a" "c") (#f 3))))
|
||||
|
||||
(js-program-test
|
||||
'use-bubble-sort
|
||||
usecase-bubble-sort
|
||||
(js/expression (bubbleSort (array 5 1 4 2 8)))
|
||||
(jsexpr->string '(1 2 4 5 8)))
|
||||
|
||||
(js-program-test
|
||||
'use-binary-search-recursive
|
||||
usecase-binary-search
|
||||
(js/expression
|
||||
(array (binarySearch (array 1 3 5 7 9) 7 0 4)
|
||||
(binarySearch (array 1 3 5 7 9) 4 0 4)))
|
||||
(jsexpr->string '(3 -1)))
|
||||
|
||||
(js-program-test
|
||||
'use-map-count-occurrences
|
||||
usecase-map-count-occurrences
|
||||
(js/expression (countOccurrences (array "a" "b" "a" "c" "b" "a")))
|
||||
(jsexpr->string '(("a" 3) ("b" 2) ("c" 1))))
|
||||
|
||||
(js-program-test
|
||||
'use-get-html-three-ways
|
||||
usecase-get-html-three-ways
|
||||
(js/expression (getHtmlThreeWays))
|
||||
(jsexpr->string '("<p>A</p>" "<p>A</p>" "<p>A</p>"))
|
||||
#:preamble dom-preamble)
|
||||
|
||||
(js-program-test
|
||||
'use-anagram
|
||||
usecase-anagram
|
||||
(js/expression (array (canArrange "listen" "silent")
|
||||
(canArrange "abc" "abd")))
|
||||
(jsexpr->string '(#t #f)))
|
||||
|
||||
(js-program-test
|
||||
'use-pairs-equal-target
|
||||
usecase-pairs-equal-target
|
||||
(js/expression (pairsEqualTarget (array 1 2 3 4 3 5) 6))
|
||||
(jsexpr->string '((2 4) (3 3) (1 5))))
|
||||
|
||||
(js-program-test
|
||||
'use-fetch-api-results-errors
|
||||
usecase-fetch-api
|
||||
(js/expression (send Promise all (array (loadTitle "/ok") (loadTitle "/fail"))))
|
||||
"[{\"ok\":true,\"title\":\"Done\"},{\"ok\":false,\"message\":\"network\"}]"
|
||||
#:preamble fetch-preamble)))
|
||||
|
||||
(define engine (find-js-engine))
|
||||
(run-jsmaker-regression 'jsmaker-usecases tests "/tmp/jsmaker-usecases.js" #:engine engine)
|
||||
Reference in New Issue
Block a user