A mostly AI coded js-maker, supervised by me.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
*.html
|
||||
*.js
|
||||
*.css
|
||||
*.bak
|
||||
*.scrbl~
|
||||
@@ -0,0 +1,245 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
racket/list
|
||||
racket/string
|
||||
"../main.rkt"))
|
||||
|
||||
@title{jsmaker}
|
||||
@author+email["Hans Dijkema" ""]
|
||||
|
||||
@defmodule[jsmaker]
|
||||
|
||||
The @racketmodname[jsmaker] collection provides two syntax forms that
|
||||
translate a practical subset of Racket expressions to JavaScript source code.
|
||||
The translation is syntax-driven: the Racket expression is not evaluated, but
|
||||
is inspected by the macro and emitted as a JavaScript string.
|
||||
|
||||
The goal is not to implement a complete Racket compiler. The goal is a useful
|
||||
and predictable source generator for small pieces of JavaScript, including
|
||||
callbacks, browser-facing functions, simple data processing, regular
|
||||
expressions, and some date/time helper forms.
|
||||
|
||||
@section{Public API}
|
||||
|
||||
@defform[(js form ...)]{
|
||||
Translates one or more Racket forms to JavaScript statement code. The result
|
||||
is a string.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(define (f x)
|
||||
(if (and (> x 10) (< x 15))
|
||||
(begin
|
||||
(console.log x)
|
||||
(return x))
|
||||
(return (* x x)))))
|
||||
]
|
||||
|
||||
The generated JavaScript is statement-oriented:
|
||||
|
||||
@codeblock{
|
||||
function f(x) {
|
||||
if ((x > 10) && (x < 15)) {
|
||||
console.log(x);
|
||||
return x;
|
||||
} else {
|
||||
return (x * x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inside function bodies, the final expression is returned automatically unless
|
||||
an explicit @racket[return] form is used.
|
||||
}
|
||||
|
||||
@defform[(js/expression form)]{
|
||||
Translates a single Racket expression to a JavaScript expression string.
|
||||
|
||||
@racketblock[
|
||||
(js/expression
|
||||
(let loop ([i 0] [acc 0])
|
||||
(if (< i 5)
|
||||
(loop (+ i 1) (+ acc i))
|
||||
acc)))
|
||||
]
|
||||
|
||||
Tail-recursive named @racket[let] loops are lowered to JavaScript
|
||||
@tt{while (true)} loops when the recursive call is in tail position.
|
||||
}
|
||||
|
||||
@section{Supported expression subset}
|
||||
|
||||
The supported subset includes literals, identifiers, function calls,
|
||||
@racket[lambda], @racket[λ], @racket[define], @racket[set!], @racket[if],
|
||||
@racket[begin], @racket[begin0], @racket[cond], @racket[case], @racket[let],
|
||||
@racket[let*], @racket[letrec], @racket[let-values], @racket[let*-values],
|
||||
named @racket[let], @racket[when], @racket[unless], @racket[while],
|
||||
@racket[for], @racket[for/list], @racket[for/vector], and
|
||||
@racket[for/fold].
|
||||
|
||||
The common arithmetic, comparison, list/vector/string, hash, and higher-order
|
||||
forms used in the regression suite are also supported. Examples include
|
||||
@racket[+], @racket[-], @racket[*], @racket[/], @racket[quotient],
|
||||
@racket[remainder], @racket[<], @racket[<=], @racket[>], @racket[>=],
|
||||
@racket[=], @racket[equal?], @racket[and], @racket[or], @racket[not],
|
||||
@racket[list], @racket[vector], @racket[cons], @racket[append],
|
||||
@racket[map], @racket[filter], @racket[foldl], @racket[foldr],
|
||||
@racket[substring], @racket[string-append], @racket[hash], and
|
||||
@racket[hash-ref].
|
||||
|
||||
@section{Truthiness and boolean simplification}
|
||||
|
||||
Racket treats only @racket[#f] as false. JavaScript treats many values as
|
||||
false. The generator therefore preserves Racket truthiness where needed.
|
||||
When a form is known to produce a JavaScript boolean, the generator emits
|
||||
simpler JavaScript. For example:
|
||||
|
||||
@racketblock[
|
||||
(js/expression (and (> x 10) (< x 15)))
|
||||
]
|
||||
|
||||
emits:
|
||||
|
||||
@codeblock{
|
||||
((x > 10) && (x < 15))
|
||||
}
|
||||
|
||||
A chained comparison such as @racket[(< x y 10)] is emitted directly when all
|
||||
reused operands are simple:
|
||||
|
||||
@codeblock{
|
||||
((x < y) && (y < 10))
|
||||
}
|
||||
|
||||
When an intermediate expression might have side effects, the generator uses
|
||||
temporaries so the expression is evaluated once and in order.
|
||||
|
||||
@section{Regular expressions}
|
||||
|
||||
Racket @tt{#rx} and common @tt{#px} patterns are translated to
|
||||
JavaScript @tt{RegExp} values where the syntax is compatible. The generator
|
||||
supports @racket[regexp?], @racket[pregexp?], @racket[regexp-match],
|
||||
@racket[regexp-match?], @racket[regexp-match*],
|
||||
@racket[regexp-match-positions], @racket[regexp-split],
|
||||
@racket[regexp-replace], @racket[regexp-replace*], and
|
||||
@racket[regexp-quote].
|
||||
|
||||
Match results are normalized to Racket-like values: a failed match becomes
|
||||
@tt{false}, successful matches become JavaScript arrays, and an unmatched
|
||||
optional capture becomes @tt{false}.
|
||||
|
||||
The regexp support is deliberately conservative. Known incompatible constructs
|
||||
such as inline option groups and atomic groups are rejected instead of being
|
||||
silently miscompiled.
|
||||
|
||||
@section{Exceptions}
|
||||
|
||||
A small @racket[with-handlers] subset is supported:
|
||||
|
||||
@racketblock[
|
||||
(js/expression
|
||||
(with-handlers ([exn? (lambda (e)
|
||||
(string-append "caught:" (exn-message e)))])
|
||||
(error "boom")))
|
||||
]
|
||||
|
||||
The generated JavaScript uses @tt{try}/@tt{catch}. Only a generic
|
||||
@racket[exn?] predicate is supported. Handler procedures are emitted as
|
||||
function expressions in callee position, so rest-argument handlers such as
|
||||
@racket[(lambda args ...)] are valid JavaScript. Division by zero in
|
||||
@racket[/] is checked at run time and throws a JavaScript @tt{Error}, which
|
||||
this subset can catch with @racket[with-handlers]. The generator does not
|
||||
model Racket's exception hierarchy, continuable exceptions, exception marks,
|
||||
or the full exact/inexact numeric distinction.
|
||||
|
||||
@section{Gregor-style date and time helpers}
|
||||
|
||||
The generator includes a small JavaScript-side representation for a subset of
|
||||
Gregor-style date/time operations. Prefixes are not hardcoded. A call such as
|
||||
@racket[(g:date 2026 5 25)], @racket[(gregor:date 2026 5 25)], or
|
||||
@racket[(date 2026 5 25)] is matched by the local name @racket[date].
|
||||
|
||||
Supported local names include @racket[date], @racket[time], @racket[datetime],
|
||||
@racket[moment], @racket[parse-date], @racket[parse-time],
|
||||
@racket[parse-datetime], @racket[parse-moment], @racket[string->date],
|
||||
@racket[string->time], @racket[string->datetime], @racket[date->string],
|
||||
@racket[time->string], @racket[datetime->string], @racket[moment->string],
|
||||
@racket[date?], @racket[time?], @racket[datetime?], @racket[moment?],
|
||||
@racket[->year], @racket[->month], @racket[->day], @racket[->hours],
|
||||
@racket[->minutes], @racket[->seconds], @racket[->js-date], and
|
||||
@racket[js-date->datetime].
|
||||
|
||||
Plain dates and times are represented as tagged JavaScript objects rather than
|
||||
native @tt{Date} objects, avoiding accidental timezone shifts. Use
|
||||
@racket[->js-date] when a native JavaScript @tt{Date} is desired.
|
||||
|
||||
@section{JavaScript interop forms}
|
||||
|
||||
Several forms are emitted as direct JavaScript interop:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@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[(object key value ...)] emits a JavaScript object literal.}
|
||||
@item{@racket[(array value ...)] emits a JavaScript array literal.}]
|
||||
|
||||
|
||||
@section{JavaScript use case demos}
|
||||
|
||||
The @filepath{demo/js-usecases.rkt} file contains a set of practical JavaScript
|
||||
examples written in the Racket surface syntax accepted by @racket[js]. The
|
||||
module also writes @filepath{demo/js-usecases.generated.js}, which contains the
|
||||
generated JavaScript snippets wrapped as callable demo functions.
|
||||
|
||||
The corresponding tests are in @filepath{testing/jsmaker-usecases.rkt} and are
|
||||
included by @filepath{testing/jsmaker-regressions.rkt}. These tests execute the
|
||||
generated JavaScript with the configured JavaScript executor. Promise-valued
|
||||
results are awaited by the test framework, which allows examples such as Fetch
|
||||
API success/error handling to be checked directly.
|
||||
|
||||
The use case set covers random numbers, @tt{Set}, JavaScript falsey values,
|
||||
currying, object destructuring, intervals, object property get/set/delete,
|
||||
string concatenation order, @tt{Object.freeze} and @tt{Object.seal}, switch/case,
|
||||
classes with constructor defaults, sorting objects, array deletion techniques,
|
||||
Bubble Sort, recursive Binary Search, @tt{Map} counting, DOM HTML access,
|
||||
anagram checks, pair-sum checks, and Fetch API result/error handling.
|
||||
|
||||
@section{Testing}
|
||||
|
||||
The regression suite lives in @filepath{testing/}. The main entry point is
|
||||
@filepath{testing/jsmaker-regressions.rkt}. The test framework searches for a
|
||||
JavaScript engine such as Node, Deno, Bun, QuickJS, or another supported
|
||||
executor. If no engine is available, the JavaScript test files are generated
|
||||
and execution is skipped with warnings. This skip is intentional so package
|
||||
tests do not fail on systems without a JavaScript runtime.
|
||||
|
||||
The usual command is:
|
||||
|
||||
@codeblock{
|
||||
raco test jsmaker/testing/jsmaker-regressions.rkt
|
||||
}
|
||||
|
||||
@section{Private compatibility files}
|
||||
|
||||
The @filepath{private/} directory contains legacy helper material from the
|
||||
source project. The current public @racketmodname[jsmaker] module, demos, and
|
||||
regression tests do not require these helper files. They are kept in the
|
||||
package layout for compatibility and are omitted from compilation and the test
|
||||
entry point in @filepath{info.rkt}.
|
||||
|
||||
@section{Limitations}
|
||||
|
||||
This package is not a full Racket compiler. It does not expand arbitrary
|
||||
Racket macros, implement modules, contracts, classes, continuations, parameters,
|
||||
or the full numeric tower. Unsupported forms should fail explicitly rather than
|
||||
silently generate JavaScript with different semantics.
|
||||
|
||||
@section{Use-case documentation}
|
||||
|
||||
The practical JavaScript examples are documented separately in
|
||||
@other-doc['(lib "jsmaker/scrbl/usecases.scrbl")]. That document shows each
|
||||
use case as Racket/js-maker source next to representative generated
|
||||
JavaScript, and lists the behavior covered by the regression test.
|
||||
@@ -0,0 +1,584 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require scribble/core
|
||||
(for-label racket/base
|
||||
"../main.rkt"))
|
||||
|
||||
@(define (side-by-side racket-source js-source)
|
||||
(tabular #:style 'boxed #:sep (hspace 2)
|
||||
(list (list (bold "Racket / js-maker") (bold "Generated JavaScript"))
|
||||
(list (verbatim racket-source) (verbatim js-source)))))
|
||||
|
||||
@(define (tested s)
|
||||
(nested #:style 'inset (bold "Tested behavior: ") s))
|
||||
|
||||
@title{jsmaker JavaScript Use Cases}
|
||||
@author+email["Hans Dijkema" ""]
|
||||
|
||||
@defmodule[jsmaker/demo/js-usecases]
|
||||
|
||||
This document describes the practical JavaScript use cases in
|
||||
@filepath{demo/js-usecases.rkt}. Each implementation is written as a Racket
|
||||
snippet using @racket[js] and is tested by compiling it to JavaScript and
|
||||
executing that JavaScript with the configured test executor. The corresponding
|
||||
tests are in @filepath{testing/jsmaker-usecases.rkt}.
|
||||
|
||||
The tests intentionally use @racket[js/expression] for their calls wherever
|
||||
possible. Raw JavaScript remains only in small harness preambles, such as fake
|
||||
@tt{setInterval}, fake DOM objects, and fake @tt{fetch}.
|
||||
|
||||
@section{Running the examples}
|
||||
|
||||
Generate the JavaScript examples with:
|
||||
|
||||
@codeblock{
|
||||
racket demo/js-usecases.rkt
|
||||
}
|
||||
|
||||
Run the use-case regression tests with:
|
||||
|
||||
@codeblock{
|
||||
racket testing/jsmaker-usecases.rkt
|
||||
}
|
||||
|
||||
@section{Use cases}
|
||||
|
||||
@subsection{1. Random number between 1 and 5}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (randomBetween1And5)
|
||||
(return (+ (send Math floor (* (send Math random) 5)) 1))))
|
||||
RKT
|
||||
#<<JS
|
||||
function randomBetween1And5() {
|
||||
return (Math.floor((Math.random() * 5)) + 1);
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "With Math.random fixed at 0.80, the generated function returns 5.")
|
||||
|
||||
@subsection{2. Unique values with Set}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (uniqueValues xs)
|
||||
(return (send Array from (new Set xs)))))
|
||||
RKT
|
||||
#<<JS
|
||||
function uniqueValues(xs) {
|
||||
return Array.from(new Set(xs));
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The array [1, 2, 2, 3, 1] becomes [1, 2, 3].")
|
||||
|
||||
@subsection{3. Six falsey JavaScript values}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (falseyValues)
|
||||
(return (array #f 0 "" js-null js-undefined js-NaN))))
|
||||
RKT
|
||||
#<<JS
|
||||
function falseyValues() {
|
||||
return [false, 0, "", null, undefined, NaN];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "Mapping JavaScript Boolean over the returned values yields six false values.")
|
||||
|
||||
@subsection{4. Currying}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (add x)
|
||||
(return (lambda (y)
|
||||
(return (+ x y))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function add(x) {
|
||||
return function(y) {
|
||||
return (x + y);
|
||||
};
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The generated call add(2)(3) returns 5; the test call itself is produced with js/expression.")
|
||||
|
||||
@subsection{5. Object destructuring}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (describePerson person)
|
||||
(let-object ([name 'name]
|
||||
[age 'age 0])
|
||||
person
|
||||
(return (string-append name ":" (number->string age))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function describePerson(person) {
|
||||
return (() => {
|
||||
const { name: name, age: age = 0 } = person;
|
||||
return (name + ":" + String(age));
|
||||
})();
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The object { name: \"Ada\", age: 37 } is rendered as \"Ada:37\".")
|
||||
|
||||
@subsection{6. Escaping a timer interval}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (startTimer)
|
||||
(let* ([ticks 0]
|
||||
[intervalId #f])
|
||||
(set! intervalId
|
||||
(setInterval (lambda ()
|
||||
(set! ticks (+ ticks 1))
|
||||
(when (= ticks 3)
|
||||
(clearInterval intervalId)))
|
||||
10))
|
||||
(return (object 'id intervalId
|
||||
'getTicks (lambda () (return ticks)))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function startTimer() {
|
||||
{
|
||||
let ticks = 0;
|
||||
let intervalId = false;
|
||||
intervalId = setInterval(function() {
|
||||
ticks = (ticks + 1);
|
||||
if ((ticks === 3)) clearInterval(intervalId);
|
||||
return undefined;
|
||||
}, 10);
|
||||
return {id: intervalId, getTicks: function() { return ticks; }};
|
||||
}
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "A fake timer runs the callback five times, but clearInterval stops it at tick 3.")
|
||||
|
||||
@subsection{7. Get, set, and delete object properties}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (objectProps)
|
||||
(let* ([obj (object 'a 1)]
|
||||
[a1 obj.a]
|
||||
[a2 (js-ref obj "a")])
|
||||
(let-object ([a3 'a]) obj
|
||||
(set! obj.b 2)
|
||||
(set-prop! obj "c" 3)
|
||||
(delete-prop! obj "a")
|
||||
(return (array a1 a2 a3 obj.b (js-ref obj "c")
|
||||
(send Object hasOwn obj "a")))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function objectProps() {
|
||||
let obj = {a: 1};
|
||||
let a1 = obj.a;
|
||||
let a2 = obj["a"];
|
||||
return (() => {
|
||||
const { a: a3 } = obj;
|
||||
obj.b = 2;
|
||||
obj["c"] = 3;
|
||||
delete obj["a"];
|
||||
return [a1, a2, a3, obj.b, obj["c"], Object.hasOwn(obj, "a")];
|
||||
})();
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The three reads produce 1, the two writes produce 2 and 3, and the deleted property is absent.")
|
||||
|
||||
@subsection{8. String concatenation order}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (concatOrder)
|
||||
(return (array (+ 1 2 "3")
|
||||
(+ "1" 2 3)))))
|
||||
RKT
|
||||
#<<JS
|
||||
function concatOrder() {
|
||||
return [(1 + 2 + "3"), ("1" + 2 + 3)];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The generated function returns [\"33\", \"123\"].")
|
||||
|
||||
@subsection{9. Object.freeze versus Object.seal}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (freezeVsSeal)
|
||||
(let* ([frozen (send Object freeze (object 'a 1))]
|
||||
[sealed (send Object seal (object 'a 1))])
|
||||
(set! frozen.a 9)
|
||||
(set! sealed.a 9)
|
||||
(delete-prop! sealed "a")
|
||||
(return (array frozen.a sealed.a
|
||||
(send Object isFrozen frozen)
|
||||
(send Object isSealed sealed)
|
||||
(send Object hasOwn sealed "a"))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function freezeVsSeal() {
|
||||
let frozen = Object.freeze({a: 1});
|
||||
let sealed = Object.seal({a: 1});
|
||||
frozen.a = 9;
|
||||
sealed.a = 9;
|
||||
delete sealed["a"];
|
||||
return [frozen.a, sealed.a, Object.isFrozen(frozen),
|
||||
Object.isSealed(sealed), Object.hasOwn(sealed, "a")];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The frozen value stays 1; the sealed value changes to 9 but remains present.")
|
||||
|
||||
@subsection{10. Switch example}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (switchExample n)
|
||||
(case n
|
||||
[(1) (return "one")]
|
||||
[(2 3) (return "two-or-three")]
|
||||
[else (return "other")]))))
|
||||
RKT
|
||||
#<<JS
|
||||
function switchExample(n) {
|
||||
switch (n) {
|
||||
case 1: return "one";
|
||||
case 2:
|
||||
case 3: return "two-or-three";
|
||||
default: return "other";
|
||||
}
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The generated function maps 1, 2, and 9 to the three expected branches.")
|
||||
|
||||
@subsection{11. Class constructor with a default value}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define-class Greeter
|
||||
(constructor ([name "world"])
|
||||
(set! this.name name))
|
||||
(method greet ()
|
||||
(return (string-append "Hello " this.name))))
|
||||
|
||||
(define (classExample)
|
||||
(let* ([a (new Greeter)]
|
||||
[b (new Greeter "Ada")])
|
||||
(return (array (send a greet) (send b greet))))))
|
||||
RKT
|
||||
#<<JS
|
||||
class Greeter {
|
||||
constructor(name = "world") {
|
||||
this.name = name;
|
||||
}
|
||||
greet() {
|
||||
return ("Hello " + this.name);
|
||||
}
|
||||
}
|
||||
function classExample() {
|
||||
let a = new Greeter();
|
||||
let b = new Greeter("Ada");
|
||||
return [a.greet(), b.greet()];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The default constructor value and explicit constructor argument both work.")
|
||||
|
||||
@subsection{12. Sort objects by a property}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (sortByProperty xs prop)
|
||||
(return (send (send xs slice)
|
||||
sort
|
||||
(lambda (a b)
|
||||
(return (- (js-ref a prop) (js-ref b prop))))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function sortByProperty(xs, prop) {
|
||||
return xs.slice().sort(function(a, b) {
|
||||
return (a[prop] - b[prop]);
|
||||
});
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "Sorting age objects by age yields [20, 25, 30].")
|
||||
|
||||
@subsection{13. Four ways to delete or remove an array element}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (deleteArrayWays xs)
|
||||
(let* ([a1 (send xs slice)]
|
||||
[a2 (send xs slice)]
|
||||
[a3 (send xs slice)]
|
||||
[a4 (send xs slice)])
|
||||
(send a1 splice 1 1)
|
||||
(set! a2 (send a2 filter (lambda (x i) (return (not (= i 1))))))
|
||||
(set! a3 (send (send a3 slice 0 1) concat (send a3 slice 2)))
|
||||
(delete-prop! a4 1)
|
||||
(return (array a1 a2 a3
|
||||
(array (send Object hasOwn a4 "1") (length a4)))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function deleteArrayWays(xs) {
|
||||
let a1 = xs.slice(), a2 = xs.slice(), a3 = xs.slice(), a4 = xs.slice();
|
||||
a1.splice(1, 1);
|
||||
a2 = a2.filter(function(x, i) { return !(i === 1); });
|
||||
a3 = a3.slice(0, 1).concat(a3.slice(2));
|
||||
delete a4[1];
|
||||
return [a1, a2, a3, [Object.hasOwn(a4, "1"), a4.length]];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "splice, filter, and slice/concat remove the item; delete leaves a hole and preserves length.")
|
||||
|
||||
@subsection{14. Bubble sort}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (bubbleSort xs)
|
||||
(let* ([a (send xs slice)]
|
||||
[n (length a)])
|
||||
(while (> n 1)
|
||||
(let* ([i 1])
|
||||
(while (< i n)
|
||||
(when (> (list-ref a (- i 1)) (list-ref a i))
|
||||
(let* ([tmp (list-ref a (- i 1))])
|
||||
(vector-set! a (- i 1) (list-ref a i))
|
||||
(vector-set! a i tmp)))
|
||||
(set! i (+ i 1))))
|
||||
(set! n (- n 1)))
|
||||
(return a))))
|
||||
RKT
|
||||
#<<JS
|
||||
function bubbleSort(xs) {
|
||||
let a = xs.slice();
|
||||
let n = a.length;
|
||||
while ((n > 1)) {
|
||||
let i = 1;
|
||||
while ((i < n)) {
|
||||
if ((a[(i - 1)] > a[i])) {
|
||||
let tmp = a[(i - 1)];
|
||||
a[(i - 1)] = a[i];
|
||||
a[i] = tmp;
|
||||
}
|
||||
i = (i + 1);
|
||||
}
|
||||
n = (n - 1);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "Sorting [5, 1, 4, 2, 8] yields [1, 2, 4, 5, 8].")
|
||||
|
||||
@subsection{15. Recursive binary search}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (binarySearch xs target low high)
|
||||
(if (> low high)
|
||||
(return -1)
|
||||
(let* ([mid (send Math floor (/ (+ low high) 2))]
|
||||
[value (list-ref xs mid)])
|
||||
(cond
|
||||
[(= value target) (return mid)]
|
||||
[(< value target) (return (binarySearch xs target (+ mid 1) high))]
|
||||
[else (return (binarySearch xs target low (- mid 1)))])))))
|
||||
RKT
|
||||
#<<JS
|
||||
function binarySearch(xs, target, low, high) {
|
||||
if ((low > high)) return -1;
|
||||
let mid = Math.floor(((low + high) / 2));
|
||||
let value = xs[mid];
|
||||
if ((value === target)) return mid;
|
||||
if ((value < target)) return binarySearch(xs, target, (mid + 1), high);
|
||||
return binarySearch(xs, target, low, (mid - 1));
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "Searching 7 returns index 3; searching 4 returns -1.")
|
||||
|
||||
@subsection{16. Count occurrences with Map}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (countOccurrences xs)
|
||||
(let* ([counts (new Map)])
|
||||
(for ([x (in-list xs)])
|
||||
(if (send counts has x)
|
||||
(send counts set x (+ (send counts get x) 1))
|
||||
(send counts set x 1)))
|
||||
(return (send Array from (send counts entries))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function countOccurrences(xs) {
|
||||
let counts = new Map();
|
||||
for (const x of xs) {
|
||||
if (counts.has(x)) counts.set(x, (counts.get(x) + 1));
|
||||
else counts.set(x, 1);
|
||||
}
|
||||
return Array.from(counts.entries());
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "The array [\"a\", \"b\", \"a\", \"c\", \"b\", \"a\"] yields [[\"a\",3],[\"b\",2],[\"c\",1]].")
|
||||
|
||||
@subsection{17. Get HTML in three ways}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (getHtmlThreeWays)
|
||||
(return (array document.body.innerHTML
|
||||
(js-dot (send document querySelector "body") innerHTML)
|
||||
(js-ref (send document getElementById "root") "innerHTML")))))
|
||||
RKT
|
||||
#<<JS
|
||||
function getHtmlThreeWays() {
|
||||
return [document.body.innerHTML,
|
||||
document.querySelector("body").innerHTML,
|
||||
document.getElementById("root")["innerHTML"]];
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "A fake DOM returns the same HTML string through all three access forms.")
|
||||
|
||||
@subsection{18. Anagram / rearrangement check}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (sortChars s)
|
||||
(return (send (send (send s split "") sort) join "")))
|
||||
|
||||
(define (canArrange stringA stringB)
|
||||
(return (string=? (sortChars stringA) (sortChars stringB)))))
|
||||
RKT
|
||||
#<<JS
|
||||
function sortChars(s) {
|
||||
return s.split("").sort().join("");
|
||||
}
|
||||
function canArrange(stringA, stringB) {
|
||||
return (sortChars(stringA) === sortChars(stringB));
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "listen/silent returns true; abc/abd returns false.")
|
||||
|
||||
@subsection{19. Pairs equal to a target, without repeats}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (pairsEqualTarget xs target)
|
||||
(let* ([seen (new Set)]
|
||||
[used (new Set)]
|
||||
[out (array)])
|
||||
(for ([x (in-list xs)])
|
||||
(let* ([y (- target x)])
|
||||
(if (and (send seen has y)
|
||||
(not (send used has x))
|
||||
(not (send used has y)))
|
||||
(begin
|
||||
(send out push (array y x))
|
||||
(send used add x)
|
||||
(send used add y))
|
||||
(send seen add x))))
|
||||
(return out))))
|
||||
RKT
|
||||
#<<JS
|
||||
function pairsEqualTarget(xs, target) {
|
||||
let seen = new Set();
|
||||
let used = new Set();
|
||||
let out = [];
|
||||
for (const x of xs) {
|
||||
let y = (target - x);
|
||||
if (seen.has(y) && !(used.has(x)) && !(used.has(y))) {
|
||||
out.push([y, x]); used.add(x); used.add(y);
|
||||
} else {
|
||||
seen.add(x);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "For [1, 2, 3, 4, 3, 5] and target 6 the returned pairs are [[2,4],[3,3],[1,5]].")
|
||||
|
||||
@subsection{20. Fetch API with result and error handling}
|
||||
|
||||
@(side-by-side
|
||||
#<<RKT
|
||||
(js
|
||||
(define (loadTitle url)
|
||||
(return
|
||||
(send
|
||||
(send
|
||||
(send (fetch url)
|
||||
then
|
||||
(lambda (response)
|
||||
(return (send response json))))
|
||||
then
|
||||
(lambda (data)
|
||||
(return (object 'ok #t 'title data.title))))
|
||||
catch
|
||||
(lambda (err)
|
||||
(return (object 'ok #f 'message err.message)))))))
|
||||
RKT
|
||||
#<<JS
|
||||
function loadTitle(url) {
|
||||
return fetch(url)
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) { return {ok: true, title: data.title}; })
|
||||
.catch(function(err) { return {ok: false, message: err.message}; });
|
||||
}
|
||||
JS
|
||||
)
|
||||
|
||||
@(tested "A fake fetch resolves /ok to {ok:true,title:\"Done\"} and rejects /fail to {ok:false,message:\"network\"}.")
|
||||
Reference in New Issue
Block a user