Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Introduction
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt."
— Famous Author
Key Concepts
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam, quis nostrum exercitationem
Corporis suscipit laboriosam, nisi ut aliquid ex ea commodi
Quis autem vel eum iure reprehenderit qui in ea voluptate
At vero eos et accusamus et iusto odio dignissimos ducimus
💡 Pro Tip
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae.
Code Example
{`let example = () => {
let value = computeValue();
let result = transform(value);
process(result);
};`}
Conclusion
Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
By using our services, you agree to these terms. Please read them carefully.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
;
};
};
module ArticleContent = {
[@react.component]
let make = () => {
{React.string(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
)}
{React.string("Introduction")}
{React.string(
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
)}
{React.string(
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.",
)}
{React.string(
"\"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.\"",
)}
{React.string("— Famous Author")}
{React.string("Key Concepts")}
{React.string(
"Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.",
)}
{React.string(
"Ut enim ad minima veniam, quis nostrum exercitationem",
)}
{React.string(
"Corporis suscipit laboriosam, nisi ut aliquid ex ea commodi",
)}
{React.string(
"Quis autem vel eum iure reprehenderit qui in ea voluptate",
)}
{React.string(
"At vero eos et accusamus et iusto odio dignissimos ducimus",
)}
{React.string("💡 Pro Tip")}
{React.string(
"Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae.",
)}
{React.string("Code Example")}
{React.string(
{|let example = () => {
let value = computeValue();
let result = transform(value);
process(result);
};|},
)}
{React.string("Conclusion")}
{React.string(
"Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.",
)}
;
};
};
module Sidebar = {
[@react.component]
let make = () => {
let relatedPosts = [|
"Understanding Server-Side Rendering",
"The Future of Web Development",
"Performance Optimization Tips",
"Building Scalable Applications",
"Modern JavaScript Frameworks",
|];
let tags = [|
"React",
"SSR",
"Performance",
"JavaScript",
"OCaml",
"Web Development",
"Tutorial",
|];
;
};
};
module CommentsSection = {
[@react.component]
let make = (~comments) => {
;
};
};
module Page = {
[@react.component]
let make = () => {
{React.string("Analytics Dashboard")}
;
};
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/DeepTree.re
================================================
/* Scenario: Deep Tree
50+ levels deep component tree
Purpose: Test deep recursion and call stack performance
*/
module Wrapper = {
[@react.component]
let make = (~depth, ~maxDepth, ~children) => {
let percentage = float_of_int(depth) /. float_of_int(maxDepth) *. 100.0;
{React.string(Printf.sprintf("Level %d (%.0f%%)", depth, percentage))}
children
;
};
};
let rec renderDepth = (current, max) =>
if (current >= max) {
;
};
};
module SelectField = {
[@react.component]
let make = (~id, ~label, ~options, ~required=true) => {
;
};
};
module TextareaField = {
[@react.component]
let make = (~id, ~label, ~rows=4, ~required=true, ~placeholder="") => {
;
};
};
module CheckboxField = {
[@react.component]
let make = (~id, ~label, ~description=?) => {
{switch (description) {
| Some(desc) =>
{React.string(desc)}
| None => React.null
}}
;
};
};
module RadioGroup = {
[@react.component]
let make = (~name, ~label, ~options) => {
;
};
};
module FormSection = {
[@react.component]
let make = (~title, ~description=?, ~children) => {
{React.string(title)}
{switch (description) {
| Some(desc) =>
{React.string(desc)}
| None => React.null
}}
children
;
};
};
module ProgressSteps = {
[@react.component]
let make = (~steps, ~currentStep) => {
;
};
};
module PersonalInfoForm = {
[@react.component]
let make = () => {
;
};
};
module AddressForm = {
[@react.component]
let make = () => {
;
};
};
module EmploymentForm = {
[@react.component]
let make = () => {
;
};
};
module PreferencesForm = {
[@react.component]
let make = () => {
{React.string("Notification Preferences")}
;
};
};
module AdditionalInfoForm = {
[@react.component]
let make = () => {
;
};
};
module TermsForm = {
[@react.component]
let make = () => {
{React.string(
"By using our services, you agree to these terms. Please read them carefully.",
)}
{React.string(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
)}
{React.string(
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
)}
{React.string(
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.",
)}
;
};
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/PropsHeavy.re
================================================
/* Scenario: Props Heavy
Components with many HTML attributes/props
Purpose: Test attribute serialization performance
*/
module HeavyDiv = {
[@react.component]
let make = (~id, ~children) => {
children
;
};
};
module HeavyInput = {
[@react.component]
let make = (~id, ~label) => {
{React.string(Printf.sprintf("Column %d", col + 1))}
),
)}
{React.array(
Array.init(rows, row =>
{React.array(
Array.init(cols, col =>
{React.string(
Printf.sprintf("R%dC%d", row + 1, col + 1),
)}
),
)}
),
)}
;
};
};
/* Different sizes for comparison */
module Small = {
[@react.component]
let make = () => {
{React.array(
Array.init(10, i =>
),
)}
;
};
};
module Medium = {
[@react.component]
let make = () => {
{React.array(
Array.init(50, i =>
),
)}
;
};
};
module Large = {
[@react.component]
let make = () => {
;
};
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/ShallowTree.re
================================================
/* Scenario: Shallow Tree
5 components deep with multiple props each
Purpose: Test prop passing and shallow component hierarchies
*/
module Level5 = {
[@react.component]
let make = (~title, ~subtitle, ~active, ~count) => {
{React.string(title)}
{React.string(subtitle)}
{React.int(count)}
;
};
};
module Level4 = {
[@react.component]
let make = (~title, ~description, ~isHighlighted, ~itemCount) => {
;
};
};
module Level3 = {
[@react.component]
let make = (~groupName, ~expanded, ~totalItems) => {
{React.string(groupName)}
;
};
};
module Level2 = {
[@react.component]
let make = (~sectionTitle, ~isVisible) => {
{React.string(sectionTitle)}
;
};
};
module Level1 = {
[@react.component]
let make = (~pageTitle) => {
{React.string(pageTitle)}
;
};
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/Table.re
================================================
/* Scenario: Table Rendering
Real-world data table patterns
Purpose: Test realistic table rendering performance
*/
type user = {
id: int,
name: string,
email: string,
role: string,
status: [
| `active
| `inactive
| `pending
],
department: string,
joinDate: string,
salary: float,
manager: option(string),
projects: int,
};
let generateUsers = count => {
let departments = [|
"Engineering",
"Design",
"Product",
"Marketing",
"Sales",
"HR",
|];
let roles = [|
"Engineer",
"Senior Engineer",
"Lead",
"Manager",
"Director",
|];
let statuses = [|`active, `inactive, `pending|];
let firstNames = [|
"Alice",
"Bob",
"Charlie",
"Diana",
"Eve",
"Frank",
"Grace",
"Henry",
|];
let lastNames = [|
"Smith",
"Johnson",
"Williams",
"Brown",
"Jones",
"Garcia",
"Miller",
|];
Array.init(
count,
i => {
let id = i + 1;
let firstName = firstNames[i mod Array.length(firstNames)];
let lastName = lastNames[i mod Array.length(lastNames)];
{
id,
name: Printf.sprintf("%s %s", firstName, lastName),
email:
Printf.sprintf(
"%s.%s@company.com",
String.lowercase_ascii(firstName),
String.lowercase_ascii(lastName),
),
role: roles[i mod Array.length(roles)],
status: statuses[i mod Array.length(statuses)],
department: departments[i mod Array.length(departments)],
joinDate:
Printf.sprintf(
"2%03d-%02d-%02d",
20 + i mod 5,
1 + i mod 12,
1 + i mod 28,
),
salary: 50000.0 +. float_of_int(i * 1000),
manager:
i mod 5 == 0 ? None : Some(Printf.sprintf("Manager %d", i / 5)),
projects: i mod 10 + 1,
};
},
);
};
module StatusBadge = {
[@react.component]
let make = (~status) => {
let (bgColor, textColor, label) =
switch (status) {
| `active => ("bg-green-100", "text-green-800", "Active")
| `inactive => ("bg-red-100", "text-red-800", "Inactive")
| `pending => ("bg-yellow-100", "text-yellow-800", "Pending")
};
{React.string(label)}
;
};
};
module TableRow = {
[@react.component]
let make = (~user, ~isEven) => {
;
};
};
/* Different sizes for comparison */
module Table10 = {
let users = generateUsers(10);
[@react.component]
let make = () => ;
};
module Table50 = {
let users = generateUsers(50);
[@react.component]
let make = () => ;
};
module Table100 = {
let users = generateUsers(100);
[@react.component]
let make = () => ;
};
module Table500 = {
let users = generateUsers(500);
[@react.component]
let make = () => ;
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/Trivial.re
================================================
/* Scenario: Trivial
Baseline test - simplest possible component
Purpose: Measure baseline overhead of React rendering
*/
[@react.component]
let make = () =>
{React.string("Hello World")}
;
================================================
FILE: benchmark/scenarios/WideTree.re
================================================
/* Scenario: Wide Tree
Many siblings at the same level (tests list/array rendering)
Purpose: Test horizontal scaling and sibling handling
*/
module Card = {
[@react.component]
let make = (~id, ~title, ~description, ~price, ~rating, ~inStock) => {
{inStock
?
{React.string("In Stock")}
:
{React.string("Out of Stock")}
}
;
};
};
let generateItems = count =>
Array.init(
count,
i => {
let id = i + 1;
let title = Printf.sprintf("Product %d", id);
let description =
Printf.sprintf(
"This is the description for product %d. It contains useful information.",
id,
);
let price = 9.99 +. float_of_int(i mod 100);
let rating = 3.0 +. float_of_int(i mod 20) /. 10.0;
let inStock = i mod 7 != 0;
(id, title, description, price, rating, inStock);
},
);
module Wide10 = {
let items = generateItems(10);
[@react.component]
let make = () => {
;
};
};
[@react.component]
let make = () => ;
================================================
FILE: benchmark/scenarios/dune
================================================
(library
(name benchmark_scenarios)
(libraries
server-reason-react.react
server-reason-react.reactDom
server-reason-react.js)
(preprocess
(pps server-reason-react.ppx server-reason-react.melange_ppx)))
================================================
FILE: benchmark/streaming/dune
================================================
(executable
(name streaming_bench)
(libraries
unix
lwt
lwt.unix
server-reason-react.js
server-reason-react.react
server-reason-react.reactDom
benchmark_scenarios)
(preprocess
(pps server-reason-react.ppx lwt_ppx)))
================================================
FILE: benchmark/streaming/streaming_bench.ml
================================================
(** Streaming Benchmark for server-reason-react
Measures:
- Time to first byte (TTFB)
- Time to full render
- Chunk sizes
- Memory during streaming *)
let measure_time_us f =
let start = Unix.gettimeofday () in
let result = f () in
let stop = Unix.gettimeofday () in
let time_us = (stop -. start) *. 1_000_000.0 in
(result, time_us)
let format_time_us us =
if us < 1000.0 then Printf.sprintf "%.2fµs" us
else if us < 1_000_000.0 then Printf.sprintf "%.2fms" (us /. 1000.0)
else Printf.sprintf "%.2fs" (us /. 1_000_000.0)
type scenario_result = {
name : string;
avg_static_time_us : float;
avg_string_time_us : float;
output_bytes : int;
throughput_mb_s : float;
}
let measure_render_methods ~name ~iterations render_element =
Printf.printf "Measuring %s...\n%!" name;
let static_times = ref 0.0 in
let string_times = ref 0.0 in
let output_bytes = ref 0 in
for _ = 1 to iterations do
let element = render_element () in
let html_static, static_time = measure_time_us (fun () -> ReactDOM.renderToStaticMarkup element) in
static_times := !static_times +. static_time;
let _html_string, string_time = measure_time_us (fun () -> ReactDOM.renderToString element) in
string_times := !string_times +. string_time;
output_bytes := String.length html_static
done;
let avg_static = !static_times /. float_of_int iterations in
let avg_string = !string_times /. float_of_int iterations in
let bytes = !output_bytes in
let throughput = float_of_int bytes /. 1_000_000.0 /. (avg_static /. 1_000_000.0) in
{
name;
avg_static_time_us = avg_static;
avg_string_time_us = avg_string;
output_bytes = bytes;
throughput_mb_s = throughput;
}
let print_result r =
Printf.printf "\n%s\n" (String.make 60 '-');
Printf.printf "Scenario: %s\n" r.name;
Printf.printf "%s\n" (String.make 60 '-');
Printf.printf "renderToStaticMarkup: %s\n" (format_time_us r.avg_static_time_us);
Printf.printf "renderToString: %s\n" (format_time_us r.avg_string_time_us);
Printf.printf "Output size: %d bytes (%.2f KB)\n" r.output_bytes (float_of_int r.output_bytes /. 1024.0);
Printf.printf "Throughput: %.2f MB/s\n" r.throughput_mb_s;
Printf.printf "String overhead: %.1f%%\n"
((r.avg_string_time_us -. r.avg_static_time_us) /. r.avg_static_time_us *. 100.0)
let print_comparison_table results =
Printf.printf "\n%s\n" (String.make 90 '=');
Printf.printf "COMPARISON TABLE\n";
Printf.printf "%s\n" (String.make 90 '=');
Printf.printf "%-20s %12s %12s %10s %12s\n" "Scenario" "Static" "String" "Size" "Throughput";
Printf.printf "%s\n" (String.make 90 '-');
List.iter
(fun r ->
Printf.printf "%-20s %12s %12s %9dB %10.1fMB/s\n" r.name (format_time_us r.avg_static_time_us)
(format_time_us r.avg_string_time_us) r.output_bytes r.throughput_mb_s)
results;
Printf.printf "%s\n" (String.make 90 '=')
let main () =
let iterations = 100 in
Printf.printf "Streaming/Render Benchmark for server-reason-react\n";
Printf.printf "Iterations per scenario: %d\n\n" iterations;
let scenarios =
let open Benchmark_scenarios in
[
("Trivial", fun () -> Benchmark_scenarios.Trivial.make (Benchmark_scenarios.Trivial.makeProps ()));
("ShallowTree", fun () -> ShallowTree.make (ShallowTree.makeProps ()));
("DeepTree10", fun () -> DeepTree.Depth10.make (DeepTree.Depth10.makeProps ()));
("DeepTree50", fun () -> DeepTree.Depth50.make (DeepTree.Depth50.makeProps ()));
("WideTree10", fun () -> WideTree.Wide10.make (WideTree.Wide10.makeProps ()));
("WideTree100", fun () -> WideTree.Wide100.make (WideTree.Wide100.makeProps ()));
("WideTree500", fun () -> WideTree.Wide500.make (WideTree.Wide500.makeProps ()));
("Table10", fun () -> Table.Table10.make (Table.Table10.makeProps ()));
("Table100", fun () -> Table.Table100.make (Table.Table100.makeProps ()));
("Table500", fun () -> Table.Table500.make (Table.Table500.makeProps ()));
("PropsSmall", fun () -> PropsHeavy.Small.make (PropsHeavy.Small.makeProps ()));
("PropsMedium", fun () -> PropsHeavy.Medium.make (PropsHeavy.Medium.makeProps ()));
("Ecommerce24", fun () -> Ecommerce.Products24.make (Ecommerce.Products24.makeProps ()));
("Ecommerce48", fun () -> Ecommerce.Products48.make (Ecommerce.Products48.makeProps ()));
("Dashboard", fun () -> Dashboard.make (Dashboard.makeProps ()));
("Blog50", fun () -> Blog.Blog50.make (Blog.Blog50.makeProps ()));
("Form", fun () -> Form.make (Form.makeProps ()));
]
in
let results =
List.map
(fun (name, render_element) ->
let result = measure_render_methods ~name ~iterations render_element in
print_result result;
result)
scenarios
in
print_comparison_table results;
Lwt.return ()
let () = Lwt_main.run (main ())
================================================
FILE: demo/README.md
================================================
## Requirements
- npm (and run `npm install` from the root of the project)
- [watchexec](https://github.com/watchexec/watchexec)
# Usage
From the root of the project, run
```bash
# 1 terminal to compile the code
make demo-build-watch
# 2 terminal to run the server
make demo-serve-watch
```
# fs explanation
The app consist of 3 folders: `universal`, `server` and `client`, which contains each compilation target defined by dune.
## `client/`
A folder that contains the code executed in the client only. It's defined in dune as a `melange.emit` to emit JavaScript from Reason via Melange. It's a a tiny entrypoint to render `Shared_js.App` component.
```re
switch (ReactDOM.querySelector("#root")) {
| Some(el) => ReactDOM.render(, el)
| None => ()
};
```
## `server/`
An executable that expose a HTTP server using [dream](https://aantron.github.io/dream). It serves a different routes, all of them written in React and send it as a string with `ReactDOM.renderToString` or as an `application/octet-stream` with [`Dream.stream`](https://aantron.github.io/dream/#streams).
## `universal/`
This folder contains a library for shared code between `client` and `server`. dune generates two sub-libraries `Shared_js` and `Shared_native` (by using `copy_files#`) with separate dependencies and preprocessors for each:
```dune
; demo/universal/js/dune
(library
(name shared_js)
(modes melange)
(libraries reason-react)
(preprocess (pps reason-react-ppx)))
(copy_files# "../*.re")
```
```dune
; demo/universal/native/dune
(library
(name shared_native)
(modes native)
(libraries
server-reason-react.react
server-reason-react.reactDom)
(preprocess
(pps server-reason-react.ppx)))
```
`shared_js` is used on the `client/dune` melange.emit to be compiled by Melange while `shared_native` is used in the `server/dune` executable compiled by OCaml
================================================
FILE: demo/client/DummyRouterRSC.re
================================================
module DOM = Webapi.Dom;
module Location = DOM.Location;
module History = DOM.History;
module ReadableStream = Webapi.ReadableStream;
[@mel.scope "window"] [@mel.set]
external setNavigate: (Webapi.Dom.Window.t, string => unit) => unit =
"__navigate";
external readable_stream: ReadableStream.t =
"window.srr_stream.readable_stream";
let fetchApp = url => {
let headers =
Fetch.HeadersInit.make({ "Accept": "application/react.component" });
Fetch.fetchWithInit(
url,
Fetch.RequestInit.make(~method_=Fetch.Get, ~headers, ()),
);
};
let callServer = (path: string, args) => {
let headers =
Fetch.HeadersInit.make({
"Accept": "application/react.action",
"ACTION_ID": path,
});
ReactServerDOMEsbuild.encodeReply(args)
|> Js.Promise.then_(body => {
let body = Fetch.BodyInit.make(body);
Fetch.fetchWithInit(
"/",
Fetch.RequestInit.make(~method_=Fetch.Post, ~headers, ~body, ()),
)
|> Js.Promise.then_(result => {
let body = Fetch.Response.body(result);
ReactServerDOMEsbuild.createFromReadableStream(body);
});
});
};
module App = {
let initialRSCModel =
ReactServerDOMEsbuild.createFromReadableStream(
~callServer,
readable_stream,
);
[@react.component]
let make = () => {
let initialElement = React.Experimental.usePromise(initialRSCModel);
let (layout, setLayout) = React.useState(() => initialElement);
let navigate = search => {
let location = DOM.window->DOM.Window.location;
let origin = Location.origin(location);
let pathname = Location.pathname(location);
let currentSearch = Location.search(location);
let currentParams = URL.SearchParams.makeExn(currentSearch);
let newSearchParams = Js.Dict.empty();
URL.SearchParams.forEach(currentParams, (value, key) => {
Js.Dict.set(newSearchParams, key, value)
});
let newParams = URL.SearchParams.makeExn(search);
URL.SearchParams.forEach(newParams, (value, key) => {
Js.Dict.set(newSearchParams, key, value)
});
let finalSearch =
newSearchParams
|> Js.Dict.entries
|> URL.SearchParams.makeWithArray
|> URL.SearchParams.toString;
if (currentSearch == "?" ++ finalSearch) {
();
} else {
let finalURL =
URL.makeExn(origin ++ pathname)
->URL.setSearchAsString(finalSearch);
let response = fetchApp(URL.toString(finalURL));
ReactServerDOMEsbuild.createFromFetch(response)
|> Js.Promise.then_(element => {
History.pushState(
History.state(DOM.history),
"",
URL.toString(finalURL),
DOM.history,
);
setLayout(_ => element);
Js.Promise.resolve();
})
|> ignore;
();
};
};
/* Publish navigate fn into window.__navigate */
setNavigate(Webapi.Dom.window, navigate);
{
Js.log(error);
{React.string("Something went wrong")}
;
}}>
layout
;
};
};
let document: option(Webapi.Dom.Element.t) = [%mel.raw "window.document"];
let body =
Webapi.Dom.document
->Webapi.Dom.Document.asHtmlDocument
->Option.bind(Webapi.Dom.HtmlDocument.body);
switch (document) {
| Some(element) =>
React.startTransition(() => {
let _ = ReactDOM.Client.hydrateRoot(element, );
();
})
| None => Js.log("Root element not found")
};
================================================
FILE: demo/client/HydrateRoot.re
================================================
let element = Webapi.Dom.Document.querySelector("#root", Webapi.Dom.document);
switch (element) {
| Some(el) =>
let _ = ReactDOM.Client.hydrateRoot(el, );
();
| None => Js.log("No root element found")
};
================================================
FILE: demo/client/NestedRouterRSC.re
================================================
module ReadableStream = Webapi.ReadableStream;
external readable_stream: ReadableStream.t =
"window.srr_stream.readable_stream";
let document: option(Webapi.Dom.Element.t) = [%mel.raw "window.document"];
let callServer = (path: string, args) => {
let headers =
Fetch.HeadersInit.make({
"Accept": "application/react.action",
"ACTION_ID": path,
});
ReactServerDOMEsbuild.encodeReply(args)
|> Js.Promise.then_(body => {
let body = Fetch.BodyInit.make(body);
Fetch.fetchWithInit(
"/",
Fetch.RequestInit.make(~method_=Fetch.Post, ~headers, ~body, ()),
)
|> Js.Promise.then_(result => {
let body = Fetch.Response.body(result);
ReactServerDOMEsbuild.createFromReadableStream(body);
});
});
};
let initialRSCModel =
ReactServerDOMEsbuild.createFromReadableStream(
~callServer,
readable_stream,
);
module ClientApp = {
[@react.component]
let make = () => {
let initialElement = React.Experimental.usePromise(initialRSCModel);
initialElement;
};
};
switch (document) {
| Some(element) =>
React.startTransition(() => {
let _ = ReactDOM.Client.hydrateRoot(element, );
();
})
| None => Js.log("Root element not found")
};
================================================
FILE: demo/client/RenderRoot.re
================================================
module Dom = Webapi.Dom;
let element = Dom.Document.querySelector("#root", Dom.document);
switch (element) {
| Some(el) =>
let root = ReactDOM.Client.createRoot(el);
ReactDOM.Client.render(root, );
| None => Js.log("No root element found")
};
================================================
FILE: demo/client/ServerOnlyRSC.re
================================================
let root =
Webapi.Dom.document
|> Webapi.Dom.Document.querySelector("#root")
|> Option.get;
let root = ReactDOM.Client.createRoot(root);
let headers =
Fetch.HeadersInit.make({ "Accept": "application/react.component" });
let fetch =
Fetch.fetchWithInit(
Routes.serverOnlyRSC,
Fetch.RequestInit.make(~method_=Fetch.Get, ~headers, ()),
);
ReactServerDOMEsbuild.createFromFetch(fetch)
|> Js.Promise.then_(app => {
ReactDOM.Client.render(root, app);
Js.Promise.resolve();
})
|> ignore;
================================================
FILE: demo/client/SinglePageRSC.re
================================================
let callServer = (path: string, args) => {
let headers =
Fetch.HeadersInit.make({
"Accept": "application/react.action",
"ACTION_ID": path,
});
ReactServerDOMEsbuild.encodeReply(args)
|> Js.Promise.then_(body => {
let body = Fetch.BodyInit.make(body);
Fetch.fetchWithInit(
"/",
Fetch.RequestInit.make(~method_=Fetch.Post, ~headers, ~body, ()),
)
|> Js.Promise.then_(result => {
let body = Fetch.Response.body(result);
ReactServerDOMEsbuild.createFromReadableStream(body);
});
});
};
let root =
Webapi.Dom.document
|> Webapi.Dom.Document.querySelector("#root")
|> Option.get;
let root = ReactDOM.Client.createRoot(root);
let headers =
Fetch.HeadersInit.make({ "Accept": "application/react.component" });
let fetch =
Fetch.fetchWithInit(
Routes.singlePageRSC,
Fetch.RequestInit.make(~method_=Fetch.Get, ~headers, ()),
);
ReactServerDOMEsbuild.createFromFetch(~callServer, fetch)
|> Js.Promise.then_(app => {
ReactDOM.Client.render(root, app);
Js.Promise.resolve();
})
|> ignore;
================================================
FILE: demo/client/build.mjs
================================================
import Esbuild from "esbuild";
import Path from "path";
import extractClientComponents from "../../packages/esbuild-plugin/plugin.mjs";
async function build(entryPoints, { env, output, extract, mockWebpackRequire }) {
const outfile = output;
const outdir = Path.dirname(outfile);
const splitting = true;
const bootstrapOutput = Path.join(Path.dirname(outfile), "bootstrap.js");
let plugins = [];
if (extract) {
plugins.push(
extractClientComponents({
target: "app",
mockWebpackRequire,
bootstrapOutput,
entrypoints: ["SinglePageRSC.re.js", "DummyRouterRSC.re.js", "NestedRouterRSC.re.js"],
}),
);
}
const isDev = env === "development";
try {
const result = await Esbuild.build({
entryPoints,
entryNames: "[name]",
bundle: true,
logLevel: "debug",
platform: "browser",
format: "esm",
splitting,
outdir,
plugins,
write: true,
treeShaking: isDev ? false : true,
minify: isDev ? false : true,
define: {
"process.env.NODE_ENV": `"${env}"`,
"__DEV__": `"${isDev}"`, /* __DEV__ is used by react-client code */
},
});
entryPoints.forEach((entryPoint) => {
console.log('Build completed successfully for "' + entryPoint + '"');
});
return result;
} catch (error) {
console.error("\nBuild failed:", error);
process.exit(1);
}
}
function parseArgv(argv) {
const args = argv.slice(2);
const result = { _: [] };
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith("--")) {
const longArg = arg.slice(2);
if (longArg.includes("=")) {
const [key, value] = longArg.split("=");
result[key] = parseValue(value);
} else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
result[longArg] = parseValue(args[++i]);
} else {
result[longArg] = true;
}
} else if (arg.startsWith("-")) {
const shortArg = arg.slice(1);
if (shortArg.includes("=")) {
const [key, value] = shortArg.split("=");
result[key] = parseValue(value);
} else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
result[shortArg] = parseValue(args[++i]);
} else {
for (const char of shortArg) {
result[char] = true;
}
}
} else {
result._.push(parseValue(arg));
}
}
return result;
}
function parseValue(value) {
if (value === "true") return true;
if (value === "false") return false;
if (value === "null") return null;
if (!isNaN(value)) return Number(value);
return value;
}
function camelCaseKeys(obj) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
value,
]),
);
}
const flags = parseArgv(process.argv);
const options = camelCaseKeys(flags);
const entryPoints = options._;
build(entryPoints, options);
================================================
FILE: demo/client/dune
================================================
(env
(_
(env-vars
("DEMO_ENV" "development"))))
(melange.emit
(enabled_if
(= %{profile} dev))
(target app)
(module_systems
(es6 re.js))
(libraries
melange
reason-react
server-reason-react.react-server-dom-esbuild
server-reason-react.url_js
melange.dom
melange-webapi
melange-fetch
demo_shared_js
nested_router_js)
(preprocess
(pps browser_ppx -js reason-react-ppx melange.ppx)))
(rule
(enabled_if
(= %{profile} dev))
(alias client)
(deps
(package server-reason-react)
(alias_rec melange)
(:script build.mjs)
(:entrypoints
"app/demo/client/HydrateRoot.re.js"
"app/demo/client/RenderRoot.re.js"
"app/demo/client/SinglePageRSC.re.js"
"app/demo/client/DummyRouterRSC.re.js"
"app/demo/client/NestedRouterRSC.re.js"
"app/demo/client/ServerOnlyRSC.re.js")
(source_tree node_modules)
(file package.json)
(source_tree ../../packages/esbuild-plugin))
(action
(run
node
%{script}
%{entrypoints}
--output=app/demo/client/
--extract=true
--env=%{env:DEMO_ENV='production'})))
================================================
FILE: demo/client/package.json
================================================
{
"name": "client",
"version": "0.0.0",
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@pedrobslisboa/react-client": "^19.1.0",
"esbuild": "^0.21.4",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-server-dom-webpack": "^19.1.0"
}
}
================================================
FILE: demo/dream-nested-router/dune
================================================
(test
(name test_router_rsc)
(enabled_if
(= %{profile} dev))
(libraries
alcotest
dream
react
js
server-reason-react.url_native
nested_router_native)
(preprocess
(pps melange_native_ppx)))
================================================
FILE: demo/dream-nested-router/js/HistoryCache.re
================================================
/**
* HistoryCache is a module that caches the pages.
* It's used to avoid fetching the same page again when navigating back and forward.
* For FullPage, we cache the whole page element.
* For SubRoute, we cache only the sub-route element.
*/
type page =
| FullPage(React.element)
| SubRoute(React.element);
module Make = (Config: {
type key;
}) => {
type t = {
cache: Hashtbl.t(Config.key, page),
keyQueue: Queue.t(Config.key),
maxSize: int,
};
let create = (~maxSize=10, ()) => {
cache: Hashtbl.create(maxSize),
keyQueue: Queue.create(),
maxSize,
};
let set = (t, ~key, ~page) => {
if (!Hashtbl.mem(t.cache, key)) {
if (Queue.length(t.keyQueue) >= t.maxSize) {
let oldestKey = Queue.take(t.keyQueue);
Hashtbl.remove(t.cache, oldestKey);
};
Queue.add(key, t.keyQueue);
};
Hashtbl.replace(t.cache, key, page);
};
let get = (t, ~key) => {
Hashtbl.find_opt(t.cache, key);
};
};
================================================
FILE: demo/dream-nested-router/js/HistoryState.re
================================================
module DOM = Webapi.Dom;
module History = DOM.History;
/**
* Melange webapi don't set state type, so we use Obj.magic to cast it to the correct type while the PR is not merged.
* https://github.com/melange-community/melange-webapi/blob/80c6ededd06cc66b75445d1ed5c855e050b156a0/src/Webapi/Dom/Webapi__Dom__History.re#L2
* PR: https://github.com/melange-community/melange-webapi/pull/29
*/
[@platform js]
type t = History.state;
let fromEvent = event =>
DOM.Event.target(event)
->DOM.EventTarget.unsafeAsWindow
->DOM.Window.history
->History.state;
let toJs: History.state => Js.t({..}) = state => state |> Obj.magic;
let fromJs: Js.t({..}) => History.state = state => state |> Obj.magic;
[@platform js]
let push = (state, path) => {
History.pushState(state, "", path, DOM.history);
let _ =
DOM.EventTarget.dispatchEvent(
DOM.Event.make("popstate"),
DOM.Window.asEventTarget(DOM.window),
);
();
};
[@platform js]
let replace = (state, path) => {
History.replaceState(state, "", path, DOM.history);
let _ =
DOM.EventTarget.dispatchEvent(
DOM.Event.make("popstate"),
DOM.Window.asEventTarget(DOM.window),
);
();
};
================================================
FILE: demo/dream-nested-router/js/VirtualHistory.re
================================================
/**
Virtual History is a state of routes that the client has visited.
It's used to store the routes path and renderPage function.
This is how the client knows which route is rendered, and which subroute needs to get rendered as pageconsumer (children)
let state = [{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/student",
renderPage: (pageElement) => {...},
}]
When the client visits /student/:id, we find the parent route (/student) with VirtualHistory.find and we call the renderPage function to update the page/subroutes.
The virtual state history will be updated to:
[
{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/student",
renderPage: (pageElement) => {...},
},
{
path: "/student/:id",
renderPage: (pageElement) => {...},
},
]
*/
type route = {
path: string,
renderPage: React.element => unit,
};
let state = ref([]);
/* When a route is visited, we add it to the virtual state history */
let push = (~path, ~renderPage): unit => {
let filteredRoutes = List.filter(route => route.path != path, state^);
state :=
filteredRoutes
@ [
{
path,
renderPage,
},
];
};
let find = (path: string) => {
List.find_opt(route => route.path == path, state^);
};
let cleanup = () => {
state := [];
};
let cleanPathState = path => {
state :=
List.filter(
route => route.path |> String.length <= (path |> String.length),
state^,
);
};
let getAllRoutes = () => {
state^;
};
================================================
FILE: demo/dream-nested-router/js/dune
================================================
(library
(name nested_router_js)
(enabled_if
(= %{profile} dev))
(modes melange)
(wrapped false)
(libraries
reason-react
server-reason-react.react-server-dom-esbuild
melange-webapi
melange.belt
melange.js
melange-fetch
melange.dom
server-reason-react.url_js
server-reason-react.rsc
server-reason-react.runtime)
(preprocess
(pps
browser_ppx
-js
server-reason-react.ppx
-melange
-shared-folder-prefix=js/
server-reason-react.rsc.ppx
melange.ppx
reason-react-ppx)))
(copy_files
(files "../native/shared/*.re"))
================================================
FILE: demo/dream-nested-router/native/README.md
================================================
# Nested Router
The Nested Router allows us to navigate through the application while requesting only the minimum required to render the page.
To understand how this navigation works, we must understand the concepts behind it:
## Route
A route is composed of a **Layout**, a **page**, and **sub-routes**.
A layout is the UI shared across sub-routes. On navigation, layouts preserve state, remain interactive, and do not rerender, while the page is the dynamic content of the current route and is replaced by the sub-route content when navigate to.
The sub-route, as the name suggests, is a route composed of the previous one.
As we can see in the sample below:
On this sample, we can see the `/`, `/students`, and `/students/lola` pages.
We can divide those into:
- "/"
- Main Layout (Top bar) (Orange)
- Main Page (Orange)
- "/student"
- Main Layout (Orange)
- Students Layout (Sidebar) (Red)
- Students Page (Red)
- "/student/lola"
- Main Layout (Top bar) (Orange)
- Students Layout (Sidebar) (Red)
- Student Page (Blue)
As we can see, the Main Layout is present on all pages because it is inherited on sub-routes; as such, the Students Layout is present on "/student" and "/student/lola". On the other hand, the "page" is dynamic, defined by the current route.
### How does the control of the route happen?
The main feature of having the control of the route is to be able to render the page content dynamically, based on the current route, so we can request the minimum required to render the page content, keeping the layouts static and only rendering the page content when the route changes.
For example:
- Current route -> /
```reason
module MainLayout = {
[@react.component]
let make = (~children) => {
children
}
}
module MainPage = {
[@react.component]
let make = (~children) => {
"Home"
}
}
/**
Visual Representation of the MainLayout and MainPage components
*/
```
- Target route -> /students
```reason
module StudentsLayout = {
[@react.component]
let make = (~children) => {
children
}
}
module StudentsPage = {
[@react.component]
let make = (~children) => {
"Students"
}
}
/**
Visual Representation of the StudentsLayout and StudentsPage components
*/
```
In that case we only need the sub-route content, the /students, composed of StudentsLayout and StudentsPage, taking over the MainPage component as the page content. The MainLayout keeps the same, no rerender as the Layout is server component.
To make this work we have a state that stores the route, and a function that updates it when the user navigates to a new route.
The module responsible for this is the `Route` module. All routes are Rendered through the `Route` component.
For example:
```reason
} layout={} />
} layout={} />
```
As soon we navigate to "/students", inside the Route component of the "/" path, it will render the `` component in the place of the `MainPage` component inside the "/" Route component, setting the route state content to the ``.
⚠️ **Important** Route is a client component, so the props must respect the React rules for client components, which means that we can't pass a function component as a prop.
```reason
} layout={(~children) => } />
```
This will cause an error because the function component is not client prop.
#### So how can we update the MainPage to StudentsPage when the user navigates to "/students" if the cannot call layout(~childre=) inside the Route component?
The workaround for it is to use the Context API to pass the children to the layout, through a `Consumer`
```reason
module PageConsumer = {
[@react.client.component]
let make = () => {
let value = React.useContext(context);
value;
};
}
[@react.client.component]
let make =
(~path: string, ~layout: React.element, ~page: option(React.element)) => {
let (page, setPage) =
React.useState(() => page |> Option.value(~default=React.null));
let%browser_only renderPage = pageElement => setPage(_ => pageElement);
layout
;
};
```
That way we send to the client the page content, and the layout will be rendered as: `layout(~children=)`.
We can dynamically update the page content by calling the `renderPage` function, updating the page content on the layout. With minimum re-renders.
### Virtual History
To make all this work, we need to keep track of the visited routes so we can identify the parent route and the sub-route to render the correct page content.
```reason
type branch = {
path: string,
renderPage: React.element => unit,
};
let state = ref([]);
```
That way we can find the route by calling the `find` function with the path of the route, and render the page content by calling the `renderPage` function.
For example
```reason
// Current Virtual History state
[
{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/students",
renderPage: (pageElement) => {...},
},
]
// navigating from "/students" to "/students/:name":
let navigate = (~to: string) => {
// ...
let route = VirtualHistory.find("/students");
route.renderPage(} />);
// ...
}
// After navigating to "/students/:name" the Virtual History state will be updated to:
[
{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/students",
renderPage: (pageElement) => {...},
},
{
path: "/students/:name",
renderPage: (pageElement) => {...},
},
]
// navigating from "/students/lola" to "/students":
let navigate = (~to: string) => {
// ...
let route = VirtualHistory.find("/");
route.renderPage(} layout={} />);
// ...
}
// After navigating to "/" the Virtual History state will be updated to:
[
{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/students",
renderPage: (pageElement) => {...},
},
]
```
On every Route component, we push the route to the virtual history, and render the page content by calling the `renderPage` function. That way the Virtual History state is always up to date with the current route.
```reason
[@react.client.component]
let make =
(~path: string, ~layout: React.element, ~page: option(React.element)) => {
let (page, setPage) =
React.useState(() => page |> Option.value(~default=React.null));
let%browser_only renderPage = pageElement => setPage(_ => pageElement);
(
if (isFirstRender.current) {
isFirstRender.current = false;
VirtualHistory.push(~path, ~renderPage);
}
);
layout
;
};
```
## Dynamic Routes
Dynamic routes are routes that have a dynamic parameter, like "/students/:name". That allows us to render the same route with different content based on the dynamic parameter.
For example:
```reason
module StudentPage = {
[@react.client.component]
let make = (~dynamicParams: DynamicParams.t) => {
let name = DynamicParams.find("name", dynamicParams);
"Student " ++ id
};
};
```
The dynamic parameters can also be accessed in any client component by using the `Router.use` hook.
```reason
let {dynamicParams, ..._} = Router.use();
let name = DynamicParams.find("name", dynamicParams);
```
## Router
To control the routes navigation, we need to use the `Router` component, which provides the dynamic params, url and navigation function to the application.
```reason
[@react.client.component]
let make = () => {
let {navigate, dynamicParams, url, _} = Router.use();
};
```
### Navigation
The navigate function takes care of identifying the sub-route path to render the correct page content, so we request only the minimum required to render the page content.
Example:
```reason
// current route: /students
navigate(~to="/students/lola");
// Parent route: /students
// Sub-route: /lola
// Request: /students/lola?toSubRoute=/lola
```
In the sample above, the request is for the `/students/lola` route (In the server it falls into the `/students/:name` route) but we only want to render the `/lola` route, thats why we send the `toSubRoute` query param with the sub-route path. On the Dream handler, we split the target to get the sub-route path and the parent route path. In that case, the parent route path is `/students` and the sub-route path is `/:name`.
We then return the `/:name` route and update the Virtual History item `/students` to render the `/:name` route. Updating the Virtual History state to:
```reason
[
{
path: "/",
renderPage: (pageElement) => {...},
},
{
path: "/students",
renderPage: (pageElement) => {...},
},
{
path: "/students/:name",
renderPage: (pageElement) => {...},
},
]
```
## Route definitions
After knowing how the navigation works, we can now understand how to define the routes.
We declare the routes as a tree of routes, where each route can have a layout and a page.
```reason
type route = {
path: string,
layout: option(React.element),
page: option(React.element),
subRoutes: option(list(route)),
};
type routeDefinitionsTree = {
mainLayout: React.element,
mainPage: React.element,
routes: list(route),
};
let routeDefinitionsTree = {
mainLayout: ,
mainPage: ,
routes: [
{
path: "/",
layout: Some(),
page: Some(),
subRoutes: Some([
{
path: "/students",
layout: Some(),
page: Some(),
subRoutes: Some([
{
path: "/students/:name",
layout: None,
page: Some(),
subRoutes: None,
},
]),
},
]),
},
],
};
```
⚠️ The routeDefinitionsTree as the name suggests is a tree of routes, so it starts from a branch and goes down to the leaves, the main branch is the "/", so we don't need to define the "/" branch. Also, the MainLayout and MainPage are special as they don't have dynamic params.
It's from the route definitions tree that the Dream handler generates the routes paths and find which route to render based on the current path.
```reason
let routesPaths = [
"/",
"/students",
"/students/:name",
];
let route = routes |> RouterRSC.getRoute(~request, ~definition="/students/:name", routes);
/** Result:
}
page={
Some(
}
page={
Some(
}
page={Some()}
/>
)
}
/>
)
}
/>
*/
```
# IMPROVEMENTS
- Type safe routes (ppx_deriving_routes?)
- Loading state
- 404 state
================================================
FILE: demo/dream-nested-router/native/RouterRSC.re
================================================
/**
* RouterRSC is a module that provides the helpers to build the route and the layout component from the route definitions.
*/
module type MAIN_LAYOUT = {
[@react.component]
let make: (~children: React.element, unit) => React.element;
};
module type MAIN_PAGE = {
[@react.component]
let make: (~query: URL.SearchParams.t, unit) => React.element;
};
/**
* A layout is the UI that is shared between multiple pages.
* On navigation, layouts preserve state, remain interactive, and do not rerender.
* Why there is no queryParams in the layout?
* As it does not rerender on navigation, it cannot access search params which would otherwise become stale.
*/
module type LAYOUT = {
[@react.component]
let make:
(~children: React.element, ~params: DynamicParams.t, unit) => React.element;
};
/**
* A page is the UI that is rendered on a specific route.
*/
module type PAGE = {
[@react.component]
let make:
(~params: DynamicParams.t, ~query: URL.SearchParams.t, unit) =>
React.element;
};
module type NOT_FOUND = {
[@react.component]
let make: (~path: string, unit) => React.element;
};
module type LOADING = {
[@react.component]
let make: unit => React.element;
};
type routeConfig = {
path: string,
layout: option(module LAYOUT),
page: option(module PAGE),
loading: option(module LOADING),
/**
* children is a list of routes that are nested within the current route.
* It is used to render a specific UI within a parent route layout.
* A sub-route "takes" the parent page place in the layout.
*/
children: list(routeConfig),
};
type t = {
layout: option(module MAIN_LAYOUT),
page: (module MAIN_PAGE),
notFound: option(module NOT_FOUND),
loading: option(module LOADING),
routes: list(routeConfig),
};
let route = (~path, ~layout=?, ~page=?, ~loading=?, children, ()) => {
path,
layout,
page,
loading,
children,
};
let make = (~layout=?, ~page, ~notFound=?, ~loading=?, routes) => {
layout,
page,
notFound,
loading,
routes,
};
let extractDynamicParam = (request, segment) => {
String.starts_with(segment, ~prefix=":")
? {
let key = segment->String.sub(1, String.length(segment) - 1);
Some((key, Dream.param(request, key)));
}
: None;
};
let renderPage = (~pageOpt, ~loadingOpt, ~globalLoading, ~params, ~query) => {
switch (pageOpt) {
| None => React.null
| Some(page) =>
module Page = (val page: PAGE);
let pageElement = ;
let loading =
switch (loadingOpt, globalLoading) {
| (Some(_), _) => loadingOpt
| (None, Some(_)) => globalLoading
| _ => None
};
switch (loading) {
| None => pageElement
| Some(loading) =>
module Loading = (val loading: LOADING);
}> pageElement ;
};
};
};
let renderMainPage = (~page, ~globalLoading, ~query) => {
module Page = (val page: MAIN_PAGE);
let pageElement = ;
switch (globalLoading) {
| None => pageElement
| Some(loading) =>
module Loading = (val loading: LOADING);
}> pageElement ;
};
};
module DefaultMainLayout = {
[@react.component]
let make = (~children) => children;
};
let renderMainLayout = (~layoutOpt, ~children) => {
module Layout = (
val layoutOpt
|> Option.value(
~default=(module DefaultMainLayout): (module MAIN_LAYOUT),
)
);
children ;
};
let renderNotFound = (~notFound, ~path) => {
switch (notFound) {
| None => React.null
| Some(notFound) =>
module NotFound = (val notFound: NOT_FOUND);
;
};
};
/**
* Returns the React.element for the given path definition from the routes tree.
* Example:
* - definition: /students/:id
* - React.element returned:
* }
* pageconsumer={
* }
* pageconsumer={}
* />
* }
* />
*/
let getRoute =
(
~initialDynamicParams=DynamicParams.create(),
~globalLoading=None,
~definition: string,
~request: Dream.request,
routes: list(routeConfig),
) => {
let pathSegments =
String.split_on_char('/', definition)
|> List.filter(segment => segment != "");
let query =
Dream.all_queries(request)
|> Array.of_list
|> URL.SearchParams.makeWithArray;
// Goes through the route definitions to find the correct route from the definition
let rec aux =
(
routes: list(routeConfig),
pathSegments,
parentPath,
currentDynamicParams,
)
: option(React.element) => {
switch (routes, pathSegments) {
| ([route, ...restRoutes], [segment, ...restSegments]) =>
let currentRoutePath = parentPath ++ route.path;
/**
* The page and layout have only access to
* the dynamic params of the current route and the parent route.
* So we append the current dynamic params to the parent dynamic params.
* Example:
* - Path: /classroom/:classroom_id
* - Parent dynamic params: [("classroom_id", "1")]
* - Path: /student/:student_id
* - Request: /classroom/1/student/1
* - Dynamic params: [("student_id", "1"), ("classroom_id", "1")]
*/
let dynamicParams =
extractDynamicParam(request, segment)
|> Option.map(((key, value)) =>
DynamicParams.add(currentDynamicParams, key, value)
)
|> Option.value(~default=currentDynamicParams);
let renderLayout =
switch (route.layout) {
| Some(layout) =>
module Layout = (val layout: LAYOUT);
;
| None =>
renderPage(
~pageOpt=route.page,
~loadingOpt=route.loading,
~globalLoading,
~params=dynamicParams,
~query,
)
};
if (route.path == "/" ++ segment) {
let pageconsumer =
switch (route.children) {
| [] => None
| children =>
Some(
aux(children, restSegments, currentRoutePath, dynamicParams)
|> Option.value(
~default=
renderPage(
~pageOpt=route.page,
~loadingOpt=route.loading,
~globalLoading,
~params=dynamicParams,
~query,
),
),
)
};
Some(
,
);
} else {
aux(restRoutes, pathSegments, parentPath, dynamicParams);
};
// No match
| _ => None
};
};
aux(routes, pathSegments, "", initialDynamicParams);
};
/**
* Returns the React.element for a specific sub route for the given path definitions
* using the parents segments to find the correct component
* Example:
* - parentPath: /students
* - subRoutePath: /:id
* - React.element returned:
* }
* pageconsumer={}
* />
*/
let getSubRoute =
(
~request: Dream.request,
~parentDefinition: string,
~subRouteDefinition: string,
~globalLoading=None,
routes: list(routeConfig),
) => {
let query =
Dream.all_queries(request)
|> Array.of_list
|> URL.SearchParams.makeWithArray;
let parentPathSegments =
String.split_on_char('/', parentDefinition)
|> List.filter(segment => segment != "");
// Goes through the parent route definitions to find the correct route from the subRoutePath to render
let rec aux = (routes, parentSegments, currentDynamicParams) => {
switch (routes, parentSegments) {
// When the parent segments are empty, we start rendering the route for the given subRoutePath
| (routes, []) =>
getRoute(
~initialDynamicParams=currentDynamicParams,
~definition=subRouteDefinition,
~request,
~globalLoading,
routes,
)
| (
[routeDefinition, ...restRouteDefinitions],
[parentRouteDefinitionSegment, ...restParentRouteDefinitionSegments],
) =>
let dynamicParams =
/**
* The page and layout have only access to
* the dynamic params of the current route and the parent route.
* So we append the current dynamic params to the parent dynamic params.
* Example:
* - Path: /classroom/:classroom_id
* - Parent dynamic params: [("classroom_id", "1")]
* - Path: /student/:student_id
* - Request: /classroom/1/student/1
* - Dynamic params: [("student_id", "1"), ("classroom_id", "1")]
*/
extractDynamicParam(request, parentRouteDefinitionSegment)
|> Option.map(((key, value)) =>
DynamicParams.add(currentDynamicParams, key, value)
)
|> Option.value(~default=currentDynamicParams);
if (routeDefinition.path == "/" ++ parentRouteDefinitionSegment) {
switch (routeDefinition.children) {
| [] =>
switch (routeDefinition.page) {
| None => None
| Some(_) =>
Some(
renderPage(
~pageOpt=routeDefinition.page,
~loadingOpt=routeDefinition.loading,
~globalLoading,
~params=dynamicParams,
~query,
),
)
}
| children =>
aux(children, restParentRouteDefinitionSegments, dynamicParams)
};
} else {
aux(restRouteDefinitions, parentSegments, dynamicParams);
};
| _ => None
};
};
aux(routes, parentPathSegments, DynamicParams.create());
};
/**
Generate all possible routes paths from a given list of routes
Example:
- Routes: [
{ path: "/student", children: [{ path: "/student/:student_id", children: [] }] },
{ path: "/classroom", children: [{ path: "/classroom/:classroom_id", children: [] }] },
]
- Routes paths: ["/student", "/student/:student_id", "/classroom", "/classroom/:classroom_id"]
*/
let generated_routes_paths = (~routes: list(routeConfig)) => {
let rec aux =
(routes: list(routeConfig), parentPath: string): list(string) => {
switch (routes) {
| [] => []
| [route, ...remainingRoutes] =>
let fullPath = parentPath ++ route.path;
let childRoutes =
switch (route.children) {
| [] => []
| children => aux(children, fullPath)
};
[fullPath] @ childRoutes @ aux(remainingRoutes, parentPath);
};
};
aux(routes, "");
};
let buildUrlFromRequest = request => {
let protocol = Dream.tls(request) ? "https" : "http";
let host = Dream.header(request, "Host") |> Option.value(~default="");
let target = Dream.target(request);
Printf.sprintf("%s://%s%s", protocol, host, target) |> URL.makeExn;
};
let renderSubRouteModel =
(
~request,
~parentRouteDefinition /* students */,
~subRouteDefinition /* :id */,
~dynamicParams,
~globalLoading,
~notFound,
routes,
) => {
let parentRoute = parentRouteDefinition == "" ? "/" : parentRouteDefinition;
let element =
routes
|> getSubRoute(
~request,
~parentDefinition=parentRoute,
~subRouteDefinition,
~globalLoading,
)
|> Option.value(
~default=renderNotFound(~notFound, ~path=Dream.target(request)),
);
DreamRSC.stream_model_value(
~location=Dream.target(request),
React.Model.Element(
element
,
),
);
};
let renderRouteModel =
(~request, ~routeDefinition, ~dynamicParams, routeDefinitions) => {
let globalLoading = routeDefinitions.loading;
let parentRoute = routeDefinition == "" ? "/" : routeDefinition;
let pageconsumer = {
let isRoot = routeDefinition ++ "/" == "/";
Some(
if (isRoot) {
renderMainPage(
~page=routeDefinitions.page,
~globalLoading,
~query=
Dream.all_queries(request)
|> Array.of_list
|> URL.SearchParams.makeWithArray,
);
} else {
routeDefinitions.routes
|> getRoute(~request, ~definition=routeDefinition, ~globalLoading)
|> Option.value(
~default=
renderNotFound(
~notFound=routeDefinitions.notFound,
~path=Dream.target(request),
),
);
},
);
};
DreamRSC.stream_model_value(
~location=Dream.target(request),
React.Model.Element(
,
)}
pageconsumer
/>
,
),
);
};
// Render full route HTML (for initial page load)
let renderRouteHtml =
(
~request,
~routeDefinition,
~dynamicParams,
~bootstrapModules,
~document,
routeDefinitions,
) => {
let globalLoading = routeDefinitions.loading;
let url = buildUrlFromRequest(request);
DreamRSC.stream_html(
~bootstrapModules,
document(
~children=
,
)}
pageconsumer={
let isRoot = routeDefinition ++ "/" == "/";
Some(
if (isRoot) {
renderMainPage(
~page=routeDefinitions.page,
~globalLoading,
~query=
Dream.all_queries(request)
|> Array.of_list
|> URL.SearchParams.makeWithArray,
);
} else {
routeDefinitions.routes
|> getRoute(
~request,
~definition=routeDefinition,
~globalLoading,
)
|> Option.value(
~default=
renderNotFound(
~notFound=routeDefinitions.notFound,
~path=Dream.target(request),
),
);
},
);
}
/>
,
),
);
};
let routeDefinitionsHandlers =
(~bootstrapModules, ~document, ~routeDefinitions, basePath, handler) => {
let routesPaths = [
"/",
...generated_routes_paths(~routes=routeDefinitions.routes),
];
routesPaths
|> List.map(path => {
let normalizedPath = path == "/" ? "" : path;
[
handler(
basePath ++ normalizedPath ++ "/",
request => {
Dream.log("Redirecting to /demo%s", normalizedPath);
let query = Dream.target(request) |> Dream.split_target |> snd;
Dream.redirect(
request,
basePath ++ normalizedPath ++ "?" ++ query,
);
},
),
handler(
basePath ++ normalizedPath,
request => {
let dynamicParams: DynamicParams.t =
/**
* Route definition: /students/:id/grades/:grade_id
* Current path: /students/123/grades/456
* Dynamic params: [("id", "123"), ("grade_id", "456")]
*/
normalizedPath
|> String.split_on_char('/')
|> List.filter_map(extractDynamicParam(request))
|> Array.of_list;
switch (Dream.query(request, "toSubRoute")) {
| Some(subRoutePath) =>
/**
* When the user navigates to a sub-route path (Example: /grades/456) from a parent route path (Example: /students/123), we need to find this sub-route definition (grades/:grade_id)
* and the parent route definition (students/:id) so we can match it on the renderSubRouteModel function.
* To find the sub-route definition, we need to find the index of the sub-route path in the current route definition from the subRoutePath.
* Then split the current route definition into the sub-route definition and the parent route definition.
* Request: https://localhost:3000/students/123/grades/456?toSubRoute=/grades/456
* The toSubRoute means that from the current path, the user wants to navigate from /students/123 to /grades/456.
* Route definition that matches the current path: /students/:id/grades/:grade_id (server-side only)
* Sub-route target: ["grades", "456"] (?toSubRoute=/grades/456) -> Length: 2
* Split ["students", ":id", "grades", ":grade_id"] into:
* - ["students", ":id"] (parent route definition)
* - ["grades", ":grade_id"] (sub-route definition)
*/
let subRoutePathnamesIndex =
(normalizedPath |> String.split_on_char('/') |> List.length)
- (subRoutePath |> String.split_on_char('/') |> List.length);
// Split the route definition into the parent route definition and the sub route definition
let (parentRouteDefinition, subRouteDefinition) =
normalizedPath
|> String.split_on_char('/')
|> List.fold_left(
((parent, sub, remaining), segment) =>
if (remaining > 0) {
([segment, ...parent], sub, remaining - 1);
} else {
(parent, [segment, ...sub], remaining);
},
([], [], subRoutePathnamesIndex),
)
|> (
((parent, sub, _)) => (
List.rev(parent) |> String.concat("/"),
List.rev(sub) |> String.concat("/"),
)
);
renderSubRouteModel(
~request,
~parentRouteDefinition,
~subRouteDefinition,
~dynamicParams,
~globalLoading=routeDefinitions.loading,
~notFound=routeDefinitions.notFound,
routeDefinitions.routes,
);
| None =>
/* If the request has the header application/react.component, we render the full route as model */
let isModelRequest =
Dream.header(request, "Accept")
== Some("application/react.component");
if (isModelRequest) {
routeDefinitions
|> renderRouteModel(
~request,
~routeDefinition=normalizedPath,
~dynamicParams,
);
} else {
renderRouteHtml(
~bootstrapModules,
~request,
~routeDefinition=normalizedPath,
~dynamicParams,
~document,
routeDefinitions,
);
};
};
},
),
];
})
|> List.flatten;
};
================================================
FILE: demo/dream-nested-router/native/RouterRSC.rei
================================================
module type MAIN_LAYOUT = {
[@react.component]
let make: (~children: React.element, unit) => React.element;
};
module type MAIN_PAGE = {
[@react.component]
let make: (~query: URL.SearchParams.t, unit) => React.element;
};
module type LAYOUT = {
[@react.component]
let make:
(~children: React.element, ~params: DynamicParams.t, unit) => React.element;
};
module type PAGE = {
[@react.component]
let make:
(~params: DynamicParams.t, ~query: URL.SearchParams.t, unit) =>
React.element;
};
module type NOT_FOUND = {
[@react.component]
let make: (~path: string, unit) => React.element;
};
module type LOADING = {
[@react.component]
let make: unit => React.element;
};
type routeConfig;
type t;
let route:
(
~path: string,
~layout: (module LAYOUT)=?,
~page: (module PAGE)=?,
~loading: (module LOADING)=?,
list(routeConfig),
unit
) =>
routeConfig;
let make:
(
~layout: (module MAIN_LAYOUT)=?,
~page: (module MAIN_PAGE),
~notFound: (module NOT_FOUND)=?,
~loading: (module LOADING)=?,
list(routeConfig)
) =>
t;
let getRoute:
(
~initialDynamicParams: DynamicParams.t=?,
~globalLoading: option(module LOADING)=?,
~definition: string,
~request: Dream.request,
list(routeConfig)
) =>
option(React.element);
let getSubRoute:
(
~request: Dream.request,
~parentDefinition: string,
~subRouteDefinition: string,
~globalLoading: option(module LOADING)=?,
list(routeConfig)
) =>
option(React.element);
let generated_routes_paths: (~routes: list(routeConfig)) => list(string);
let buildUrlFromRequest: Dream.request => URL.t;
let routeDefinitionsHandlers:
(
~bootstrapModules: list(string),
~document: (~children: React.element) => React.element,
~routeDefinitions: t,
string,
(string, Dream.handler) => Dream.route
) =>
list(Dream.route);
================================================
FILE: demo/dream-nested-router/native/dune
================================================
(include_subdirs unqualified)
(library
(name nested_router_native)
(enabled_if
(= %{profile} dev))
(wrapped false)
(flags :standard -w -26-27) ; browser_only removes code from the server, making this warning necessary
(libraries
react
reactDOM
dream
js
server-reason-react.url_native
server-reason-react.rsc-native
server-reason-react.runtime
webapi
lwt
server-reason-react.fetch
dream_rsc)
(preprocess
(pps
lwt_ppx
melange_native_ppx
server-reason-react.ppx
-shared-folder-prefix=/native/shared/
server-reason-react.browser_ppx
server-reason-react.rsc-native.ppx)))
================================================
FILE: demo/dream-nested-router/native/shared/DynamicParams.re
================================================
[@deriving rsc]
type t = array((string, string));
let create = () => [||];
let add = (t, key, value) => {
Array.append(t, [|(key, value)|]);
};
let find = (paramKey, t) =>
if (Array.length(t) == 0) {
None;
} else {
Array.find_map(
((key, value)) => {key == paramKey ? Some(value) : None},
t,
);
};
================================================
FILE: demo/dream-nested-router/native/shared/NavigationResponse.re
================================================
type navigationCallback =
(
~parentRoute: string,
~dynamicParams: DynamicParams.t,
~element: React.element
) =>
unit;
let internalContext: React.Context.t(option(navigationCallback)) =
React.createContext(None);
let internalProvider = React.Context.provider(internalContext);
[@react.client.component]
let make =
(
~parentRoute: string,
~dynamicParams: DynamicParams.t,
~children: React.element,
) => {
let callback = React.useContext(internalContext);
switch%platform (Runtime.platform) {
| Client =>
React.useLayoutEffect0(() => {
switch (callback) {
| Some(cb) => cb(~parentRoute, ~dynamicParams, ~element=children)
| None => ()
};
None;
})
| Server => ()
};
React.null;
};
================================================
FILE: demo/dream-nested-router/native/shared/Route.re
================================================
/**
* Route is the component that renders the route and provides the renderPage function to update page/subroutes when the route is navigated to.
* It push the route to the virtual history when mounted.\
*
* As the is a client component, we cannot pass to the component the layout as a function component (~children: React.element) => React.element,
* so we need to pass the layout as a React.element and use the Provider to pass the children to the layout.
* That workaround allow us to update the page/subroutes when the route is nested.
*
* Path: /about/contact
*
* Example:
* }
* pageconsumer={
* }
* pageconsumer={
* }
* pageconsumer={}
* />
* }
* />
* }
*
* Visual representation of the route tree:
*
*
*
*
*
*
*
*/
type t = React.element;
let context = React.createContext(React.null);
module PageConsumer = {
[@react.client.component]
let make = () => {
let value = React.useContext(context);
value;
};
};
module Provider = {
let provider = React.Context.provider(context);
[@react.client.component]
let make = (~value: React.element, ~children: React.element) => {
switch%platform (Runtime.platform) {
| Client =>
React.createElement(
provider,
{
"value": value,
"children": children,
},
)
| Server => provider(React.Context.makeProps(~value, ~children, ()))
};
};
};
[@react.client.component]
let make =
(
~path: string,
~layout: React.element,
~pageconsumer: option(React.element),
) => {
let (pageconsumer, setPageConsumer) =
React.useState(() => pageconsumer |> Option.value(~default=React.null));
let isFirstRender = React.useRef(true);
let (cachedNodeKey, setCachedNodeKey) = React.useState(() => path);
let%browser_only renderPage = pageElement => {
setPageConsumer(_ => pageElement);
// This is a hack to force a re-render of the route by changing the key
// Is there a better way to do this?
setCachedNodeKey(_ => Js.Date.now() |> string_of_float);
};
/**
* push the route to the virtual history.
* The renderPage function is used to update the page/subroutes.
*/
(
switch%platform (Runtime.platform) {
| Client =>
if (isFirstRender.current) {
isFirstRender.current = false;
VirtualHistory.push(~path, ~renderPage);
}
| Server => ()
}
);
/**
* pageconsumer is the component that will be rendered as
* the child of the current route, representing the page/subroute content. It's the value of 'children' on
* the layout component.
* layout is the component that remains the same across all subroutes.
* Ref: https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts
*/
pageconsumer }>
layout
;
};
================================================
FILE: demo/dream-nested-router/native/shared/Router.re
================================================
/**
* Router is a component that provides the router context to the application.
* It provides the dynamic params, url and navigation function to the application.
* On navigation, it fetches the route component and updates the dynamic params.
* Depending on the mode (revalidate or not), it either updates the whole page or the specific route component.
*/
exception No_provider(string);
module DOM = Webapi.Dom;
module Location = DOM.Location;
module History = DOM.History;
type url = URL.t;
let url_to_rsc = url => url |> URL.toString |> RSC.Primitives.string_to_rsc;
let url_of_rsc = rsc => URL.makeExn(rsc |> RSC.Primitives.string_of_rsc);
[@platform js]
let watchUrl = callback => {
let watcherID = _ =>
callback(URL.makeExn(Location.href(DOM.window->DOM.Window.location)));
DOM.EventTarget.addEventListener(
"popstate",
watcherID,
DOM.Window.asEventTarget(DOM.window),
);
watcherID;
};
[@platform js]
let unwatchUrl = watcherID => {
DOM.EventTarget.removeEventListener(
"popstate",
watcherID,
DOM.Window.asEventTarget(DOM.window),
);
};
[@platform js]
module HistoryCache = {
module HistoryCacheConfig = {
type key = {
.
"path": string,
"dynamicParams": DynamicParams.t,
"parentRoute": string,
};
};
module HistoryCache = HistoryCache.Make(HistoryCacheConfig);
let cache = HistoryCache.create();
let set = (~key, ~page) => {
HistoryCache.set(cache, ~key, ~page);
};
let get = (~key) => {
HistoryCache.get(cache, ~key);
};
};
/**
* Compares two paths and returns the sub-route path between them.
* Example:
* - path1: /students/123
* - path2: /students/123/grades/456
* - Returns: /grades/456
*/
[@platform js]
let findSubRoutePath = (path1, path2) => {
let splitPath = path => path |> String.split_on_char('/') |> List.tl;
let rec findSubRoutePath = (p1, p2, acc) => {
switch (p1, p2) {
| ([h1, ...t1], [h2, ...t2]) when h1 == h2 =>
findSubRoutePath(t1, t2, acc)
| (_, remaining) => remaining |> String.concat("/")
};
};
findSubRoutePath(splitPath(path1), splitPath(path2), "");
};
let%browser_only splitPathAndQuery = to_ => {
switch (to_ |> String.split_on_char('?')) {
| [path, queryParams, ..._] => (path, Some(queryParams))
| _ => (to_, None)
};
};
let%browser_only buildQueryString = (~prefix, queryParamsOpt) => {
queryParamsOpt |> Option.map(q => prefix ++ q) |> Option.value(~default="");
};
let%browser_only fetchComponent = endpoint => {
let headers =
Fetch.HeadersInit.make({ "Accept": "application/react.component" });
Fetch.fetchWithInit(
endpoint,
Fetch.RequestInit.make(~method_=Fetch.Get, ~headers, ()),
)
|> Js.Promise.then_(response => {
let body = Fetch.Response.body(response);
ReactServerDOMEsbuild.createFromReadableStream(body);
});
};
[@platform js]
type pendingNavigation = {
revalidate: bool,
path: string,
shouldReplace: bool,
};
type t =
(~replace: bool=?, ~revalidate: bool=?, ~shallow: bool=?, string) => unit;
type router = {
navigate: t,
params: DynamicParams.t,
url: URL.t,
pathname: string,
searchParams: URL.SearchParams.t,
isNavigating: bool,
};
let context: React.Context.t(option(router)) = React.createContext(None);
let provider = React.Context.provider(context);
let use = () => {
switch (React.useContext(context)) {
| Some(context) => context.navigate
| None => raise(No_provider("Router.use() requires the Router component"))
};
};
let useRouter = () => {
switch (React.useContext(context)) {
| Some(context) => context
| None =>
raise(No_provider("Router.useRouter() requires the Router component"))
};
};
[@react.client.component]
let make =
(
~serverUrl: url,
~initialDynamicParams: DynamicParams.t,
~children: React.element,
) => {
let (element, setElement) = React.useState(() => children);
let (pendingNavigationResponse, setPendingNavigationResponse) =
React.useState(() => React.null);
let (url, setUrl) = React.useState(() => serverUrl);
let (dynamicParams, setDynamicParams) =
React.useState(() => initialDynamicParams);
let setDynamicParams = params => setDynamicParams(_ => params);
let pathname = URL.pathname(url);
let searchParams = URL.searchParams(url);
React.useEffect0(() => {
let watcherId = watchUrl(url => setUrl(_ => url));
Some(() => unwatchUrl(watcherId));
});
let (cachedNodeKey, setCachedNodeKey) = React.useState(() => "");
let (isNavigating, setIsNavigating) = React.useState(() => false);
let pendingNavigationRef = React.useRef(None);
let%browser_only renderFullPage = element => {
/**
* This is a hack to force a re-render of the route by changing the key
* react-router do something similar
* Is there a better way to do this?
*/
setCachedNodeKey(_ => Js.Date.now() |> string_of_float);
setElement(_ => element);
VirtualHistory.cleanup();
};
let%browser_only renderSubRoute = (~parentRoute, element) => {
let virtualHistoryRoute =
VirtualHistory.find(parentRoute)
|> Option.value(~default=VirtualHistory.state^ |> List.hd);
VirtualHistory.cleanPathState(virtualHistoryRoute.path);
virtualHistoryRoute.renderPage(element);
};
let%browser_only handleNavigationResponse =
(~parentRoute, ~dynamicParams, ~element) => {
switch (pendingNavigationRef.current) {
| Some({ revalidate, path, shouldReplace }) =>
setDynamicParams(dynamicParams);
let historyState = {
"dynamicParams": dynamicParams,
"parentRoute": parentRoute,
"path": path,
};
let _ =
shouldReplace
? HistoryState.replace(HistoryState.fromJs(historyState), path)
: HistoryState.push(HistoryState.fromJs(historyState), path);
let _ =
if (revalidate) {
HistoryCache.set(~key=historyState, ~page=FullPage(element));
renderFullPage(element);
} else {
HistoryCache.set(~key=historyState, ~page=SubRoute(element));
renderSubRoute(~parentRoute, element);
};
pendingNavigationRef.current = None;
setIsNavigating(_ => false);
setPendingNavigationResponse(_ => React.null);
| None => ()
};
};
let%browser_only navigate =
(
~replace as shouldReplace=false,
~revalidate=false,
~shallow=false,
to_,
) => {
let curPath = Location.pathname(DOM.window->DOM.Window.location);
let (toPath, queryParamsOpt) = splitPathAndQuery(to_);
/**
* Identify the sub-route path from the current path to the target path
* Example:
* 1.
* - Current path: /students/123
* - Target path: /students/123/grades/456
* - Sub-route path: /grades/456
* - Endpoint: /students/123/grades/456?toSubRoute=/grades/456
* - We only receive the /grades/456 component to render in the /students/123 route
* 2.
* - Current path: /students/123/grades/456
* - Target path: /about/contact
* - Sub-route path: "" (No sub-route)
* - Endpoint: /about/contact?toSubRoute=
* - We receive the /about/contact component to render in the /.
*/
let subRoutePath = findSubRoutePath(curPath, toPath);
let endpoint =
if (revalidate) {
toPath ++ buildQueryString(~prefix="?", queryParamsOpt);
} else {
toPath
++ "?toSubRoute="
++ subRoutePath
++ buildQueryString(~prefix="&", queryParamsOpt);
};
if (shallow) {
();
} else {
setIsNavigating(_ => true);
pendingNavigationRef.current =
Some({
revalidate,
path: to_,
shouldReplace,
});
let _ =
fetchComponent(endpoint)
|> Js.Promise.then_((navigationResponse: React.element) => {
setPendingNavigationResponse(_ => navigationResponse);
Js.Promise.resolve();
})
|> Js.Promise.catch(error => {
pendingNavigationRef.current = None;
setIsNavigating(_ => false);
Js.Promise.reject(Obj.magic(error));
});
();
};
();
};
// Initialize cache and history state after hydration
React.useEffect0(() => {
let curPath = Location.pathname(DOM.window->DOM.Window.location);
let historyState = {
"dynamicParams": dynamicParams,
"path": curPath,
"parentRoute": curPath,
};
HistoryCache.set(~key=historyState, ~page=FullPage(element));
/**
* Replace the history state set by the browser to our own implementation.
*/
HistoryState.replace(HistoryState.fromJs(historyState), curPath);
None;
});
// Listen to the popstate event and handle the history navigation.
React.useEffect0(() => {
let watcherId = event =>
/**
* Event is trusted when it was generated by the user agent, not by EventTarget.dispatchEvent.
* https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted
*/
(
if (DOM.Event.isTrusted(event)) {
let historyState: {
.
"dynamicParams": DynamicParams.t,
"path": string,
"parentRoute": string,
} =
event->HistoryState.fromEvent->HistoryState.toJs;
let dynamicParams = historyState##dynamicParams;
let parentRoute = historyState##parentRoute;
setDynamicParams(dynamicParams);
switch (HistoryCache.get(~key=historyState)) {
| Some(FullPage(page)) => renderFullPage(page)
| Some(SubRoute(page)) => renderSubRoute(~parentRoute, page)
| None =>
/**
* If we don't find the cached page, we navigate to the path and replace the history state.
* That may happen when the user refreshes the page, as the cache is in-memory or when the cache was cleared from the cache history due to the max cache size.
*/
navigate(~replace=true, historyState##path)
};
}
);
DOM.EventTarget.addEventListener(
"popstate",
watcherId,
DOM.Window.asEventTarget(DOM.window),
);
Some(
() =>
DOM.EventTarget.removeEventListener(
"popstate",
watcherId,
DOM.Window.asEventTarget(DOM.window),
),
);
});
let routerValue =
Some({
navigate,
params: dynamicParams,
url,
pathname,
searchParams,
isNavigating,
});
{switch%platform () {
| Client =>
React.createElement(
NavigationResponse.internalProvider,
{
"value": Some(handleNavigationResponse),
"children":
React.createElement(
provider,
{
"value": routerValue,
"children":
React.array([|element, pendingNavigationResponse|]),
},
),
},
)
| Server =>
NavigationResponse.internalProvider(
React.Context.makeProps(
~value=None,
~children=
provider(
React.Context.makeProps(
~value=
Some({
navigate: (~replace=?, ~revalidate=?, ~shallow=?, _) =>
failwith("navigate isn't supported on server"),
params: dynamicParams,
url,
pathname,
searchParams,
isNavigating,
}),
~children=element,
(),
),
),
(),
),
)
}}
;
};
================================================
FILE: demo/dream-nested-router/native/shared/Router.rei
================================================
exception No_provider(string);
type url = URL.t;
let url_to_rsc: url => RSC.t;
let url_of_rsc: RSC.t => url;
type t =
(~replace: bool=?, ~revalidate: bool=?, ~shallow: bool=?, string) => unit;
let use: unit => t;
type router = {
navigate: t,
params: DynamicParams.t,
url: URL.t,
pathname: string,
searchParams: URL.SearchParams.t,
isNavigating: bool,
};
let useRouter: unit => router;
[@react.client.component]
let make:
(
~serverUrl: url,
~initialDynamicParams: DynamicParams.t,
~children: React.element
) =>
React.element;
================================================
FILE: demo/dream-nested-router/test_router_rsc.ml
================================================
let test title fn = Alcotest.test_case title `Quick fn
let assert_bool left right = Alcotest.check Alcotest.bool "should be equal" right left
let assert_option_string left right = Alcotest.check Alcotest.(option string) "should be equal" right left
let assert_string_list left right = Alcotest.check Alcotest.(list string) "should be equal" right left
let run_request ~route ~target f =
let handler_called = ref false in
let handler request =
handler_called := true;
f request;
Dream.empty `OK
in
let handler_with_router = Dream.router [ Dream.get route handler ] in
let _response = Dream.test handler_with_router (Dream.request ~target "") in
assert_bool !handler_called true
let get_route_dynamic_params () =
let seen_id = ref None in
let seen_query = ref None in
let module Page = struct
let makeProps ~params ~query () : < params : DynamicParams.t ; query : URL.SearchParams.t > Js.t =
object
method params = params
method query = query
end
let make ?key:_ props =
seen_id := DynamicParams.find "id" props#params;
seen_query := URL.SearchParams.get props#query "q";
React.null
end in
let routes =
[ RouterRSC.route ~path:"/students" [ RouterRSC.route ~path:"/:id" ~page:(module Page : RouterRSC.PAGE) [] () ] () ]
in
run_request ~route:"/students/:id" ~target:"/students/123?q=cat" (fun request ->
let result = RouterRSC.getRoute ~definition:"/students/:id" ~request routes in
assert_bool (Option.is_some result) true);
assert_option_string !seen_id (Some "123");
assert_option_string !seen_query (Some "cat")
let get_sub_route_dynamic_params () =
let seen_id = ref None in
let seen_grade_id = ref None in
let module Page = struct
let makeProps ~params ~query () : < params : DynamicParams.t ; query : URL.SearchParams.t > Js.t =
object
method params = params
method query = query
end
let make ?key:_ props =
seen_id := DynamicParams.find "id" props#params;
seen_grade_id := DynamicParams.find "grade_id" props#params;
React.null
end in
let routes =
[
RouterRSC.route ~path:"/students"
[
RouterRSC.route ~path:"/:id"
[
RouterRSC.route ~path:"/grades"
[ RouterRSC.route ~path:"/:grade_id" ~page:(module Page : RouterRSC.PAGE) [] () ]
();
]
();
]
();
]
in
run_request ~route:"/students/:id/grades/:grade_id" ~target:"/students/123/grades/456" (fun request ->
let result =
RouterRSC.getSubRoute ~request ~parentDefinition:"/students/:id" ~subRouteDefinition:"/grades/:grade_id" routes
in
assert_bool (Option.is_some result) true);
assert_option_string !seen_id (Some "123");
assert_option_string !seen_grade_id (Some "456")
let generated_routes_paths () =
let routes =
[
RouterRSC.route ~path:"/students" [ RouterRSC.route ~path:"/:id" [] () ] ();
RouterRSC.route ~path:"/teachers" [] ();
]
in
let actual = RouterRSC.generated_routes_paths ~routes in
let expected = [ "/students"; "/students/:id"; "/teachers" ] in
assert_string_list actual expected
let () =
Alcotest.run "RouterRSC"
[
( "RouterRSC",
[
test "getRoute" get_route_dynamic_params;
test "getSubRoute" get_sub_route_dynamic_params;
test "generated_routes_paths" generated_routes_paths;
] );
]
================================================
FILE: demo/dream-rsc/DreamRSC.re
================================================
module RequestContext = {
type pending_cookie = {
name: string,
value: string,
expires: option(float),
max_age: option(float),
domain: option(string),
path: option(string),
secure: option(bool),
http_only: option(bool),
same_site:
option(
[
| `Strict
| `Lax
| `None
],
),
};
type phase =
| Render
| Action(ref(list(pending_cookie)));
let request_key: Lwt.key(Dream.request) = Lwt.new_key();
let phase_key: Lwt.key(phase) = Lwt.new_key();
let get_request = () =>
switch (Lwt.get(request_key)) {
| Some(request) => request
| None =>
failwith(
"RequestContext.get_request: no request context. "
++ "This function must be called inside a server component or server function.",
)
};
let get_header = name => Dream.header(get_request(), name);
let get_cookie = (~decrypt=false, name) =>
Dream.cookie(~decrypt, get_request(), name);
let set_cookie =
(
~expires=?,
~max_age=?,
~domain=?,
~path=?,
~secure=?,
~http_only=?,
~same_site=?,
name,
value,
) =>
switch (Lwt.get(phase_key)) {
| Some(Action(pending)) =>
pending :=
[
{
name,
value,
expires,
max_age,
domain,
path,
secure,
http_only,
same_site,
},
...pending^,
]
| Some(Render) =>
failwith(
"RequestContext.set_cookie: cookies can only be modified in a server function (action), not during render.",
)
| None =>
failwith(
"RequestContext.set_cookie: no request context. "
++ "This function must be called inside a server function.",
)
};
};
let with_render_context = (request, f) =>
Lwt.with_value(RequestContext.request_key, Some(request), () =>
Lwt.with_value(RequestContext.phase_key, Some(RequestContext.Render), f)
);
let with_action_context = (request, f) => {
let pending = ref([]);
let run = () =>
Lwt.with_value(RequestContext.request_key, Some(request), () =>
Lwt.with_value(
RequestContext.phase_key,
Some(RequestContext.Action(pending)),
f,
)
);
(pending, run);
};
let serialize_pending_cookies = pending =>
pending
|> List.rev
|> List.map((cookie: RequestContext.pending_cookie) => {
let header_value =
Dream.to_set_cookie(
~expires=?cookie.expires,
~max_age=?cookie.max_age,
~domain=?cookie.domain,
~path=?cookie.path,
~secure=?cookie.secure,
~http_only=?cookie.http_only,
~same_site=?cookie.same_site,
cookie.name,
cookie.value,
);
("Set-Cookie", header_value);
});
let require_action_id = actionId =>
switch (actionId) {
| Some(id) => Ok(id)
| None =>
Error(
"Missing ACTION_ID header, this request was not created by server-reason-react",
)
};
let dispatch_handler = (~lookup, actionId, dispatch) =>
switch (require_action_id(actionId)) {
| Error(msg) => Lwt.fail_with(msg)
| Ok(actionId) =>
switch (lookup(actionId)) {
| None => Lwt.fail_with("Action " ++ actionId ++ " is not registered")
| Some(handler) => dispatch(actionId, handler)
}
};
let dreamFormDataToJs = formData => {
let formDataJs = Js.FormData.make();
formData
|> List.iter(((name, value)) => {
let (_filename, value) = value |> List.hd;
Js.FormData.append(formDataJs, name, `String(value));
});
formDataJs;
};
let handleFormRequest = (~lookup, actionId, formData) => {
let formDataJs = dreamFormDataToJs(formData);
switch (ReactServerDOM.decodeFormDataReply(formDataJs)) {
| Error(msg) => Lwt.fail_with(msg)
| Ok((args, formData)) =>
dispatch_handler(~lookup, actionId, (actionId, handler) =>
switch (handler) {
| ReactServerDOM.FormData(handler) => handler(args, formData)
| ReactServerDOM.Body(_) =>
Lwt.fail_with(
"Action "
++ actionId
++ " is registered as Body handler but received FormData request",
)
}
)
};
};
let handleRequestBody = (~lookup, request, actionId) => {
let%lwt body = Dream.body(request);
switch (ReactServerDOM.decodeReply(body)) {
| Error(msg) => Lwt.fail_with(msg)
| Ok(args) =>
dispatch_handler(~lookup, actionId, (actionId, handler) =>
switch (handler) {
| ReactServerDOM.Body(handler) => handler(args)
| ReactServerDOM.FormData(_) =>
Lwt.fail_with(
"Action "
++ actionId
++ " is registered as FormData handler but received JSON body request",
)
}
)
};
};
let handleNoJsFormRequest = (~lookup, formDataJs) => {
switch (ReactServerDOM.decodeAction(formDataJs)) {
| Some((actionId, userFormData)) =>
switch (lookup(actionId)) {
| None => Lwt.fail_with("Action " ++ actionId ++ " is not registered")
| Some(handler) =>
switch (handler) {
| ReactServerDOM.FormData(handler) => handler([||], userFormData)
| ReactServerDOM.Body(handler) =>
/* No-JS form submissions don't carry serialized args; the form data is the entire payload */
handler([||])
}
}
| None =>
Lwt.fail_with("No ACTION_ID header and no $ACTION_* keys in FormData")
};
};
let handleRequest = (~lookup, request) => {
let actionId = Dream.header(request, "ACTION_ID");
let contentType = Dream.header(request, "Content-Type");
switch (contentType) {
| Some(contentType)
when String.starts_with(contentType, ~prefix="multipart/form-data") =>
switch%lwt (Dream.multipart(request, ~csrf=false)) {
| `Ok(formData) =>
switch (actionId) {
| Some(_) =>
/* JS-enabled path: ACTION_ID header present */
handleFormRequest(~lookup, actionId, formData)
| None =>
/* No-JS path: check FormData for $ACTION_* keys */
handleNoJsFormRequest(~lookup, dreamFormDataToJs(formData))
}
| _ =>
Lwt.fail_with(
"Missing form data, this request was not created by server-reason-react",
)
}
| _ => handleRequestBody(~lookup, request, actionId)
};
};
let streamFunctionResponse = (~debug=false, ~lookup, request) => {
let (pending, run) =
with_action_context(request, () => handleRequest(~lookup, request));
/* Run the action. On success we keep pending cookies; on failure we discard them.
Either way we capture the outcome as a promise for create_action_response,
which serializes both successes and failures into the RSC stream
(rather than letting failures become HTTP 500s). */
let%lwt (action_promise, cookie_headers) =
Lwt.catch(
() => {
let%lwt result = run();
let cookies = serialize_pending_cookies(pending^);
Lwt.return((Lwt.return(result), cookies));
},
exn => {
pending := [];
Lwt.return((Lwt.fail(exn), []));
},
);
Dream.stream(
~headers=[
("Content-Type", "application/react.action"),
...cookie_headers,
],
stream => {
let%lwt () =
ReactServerDOM.create_action_response(
~debug,
~subscribe=
chunk => {
if (debug) {
Dream.log("Action response");
Dream.log("%s", chunk);
};
let%lwt () = Dream.write(stream, chunk);
Dream.flush(stream);
},
action_promise,
);
Dream.flush(stream);
},
);
};
let is_react_component_header = str =>
String.equal(str, "application/react.component");
let stream_model_value = (~debug=false, ~location, app) =>
Dream.stream(
~headers=[
("Content-Type", "application/react.component"),
("X-Content-Type-Options", "nosniff"),
("X-Location", location),
],
stream => {
let%lwt () =
ReactServerDOM.render_model_value(
~debug,
~subscribe=
chunk => {
if (debug) {
Dream.log("Chunk");
Dream.log("%s", chunk);
};
let%lwt () = Dream.write(stream, chunk);
Dream.flush(stream);
},
app,
);
Dream.flush(stream);
},
);
let stream_model = (~debug=false, ~location, app) =>
stream_model_value(~debug, ~location, React.Model.Element(app));
let stream_html =
(
~debug=false,
~skipRoot=false,
~bootstrapScriptContent=?,
~bootstrapScripts=[],
~bootstrapModules=[],
app,
) => {
Dream.stream(
~headers=[("Content-Type", "text/html")],
stream => {
let%lwt (html, subscribe) =
ReactServerDOM.render_html(
~skipRoot,
~bootstrapScriptContent?,
~bootstrapScripts,
~bootstrapModules,
~debug,
app,
);
let%lwt () = Dream.write(stream, html);
let%lwt () = Dream.flush(stream);
let%lwt () =
subscribe(chunk => {
if (debug) {
Dream.log("Chunk");
Dream.log("%s", chunk);
};
let%lwt () = Dream.write(stream, chunk);
Dream.flush(stream);
});
Dream.flush(stream);
},
);
};
let createFromRequest =
(
~debug=false,
~disableSSR=false,
~layout=children => children,
~bootstrapModules=[],
~bootstrapScripts=[],
~bootstrapScriptContent="",
element,
request,
) =>
with_render_context(request, () =>
switch (Dream.header(request, "Accept")) {
| Some(accept) when is_react_component_header(accept) =>
stream_model(~debug, ~location=Dream.target(request), element)
| _ =>
stream_html(
~debug,
~skipRoot=disableSSR,
~bootstrapScriptContent,
~bootstrapScripts,
~bootstrapModules,
layout(element),
)
}
);
================================================
FILE: demo/dream-rsc/DreamRSC.rei
================================================
/** Dream integration for React Server Components.
Provides request context (cookies, headers) accessible from server
components and server functions via ambient [Lwt.key] storage,
plus streaming helpers for RSC rendering and action dispatch. */;
module RequestContext: {
/** {2 Reading request data}
These functions are available in both server components (render phase)
and server functions (action phase). They raise if called outside
a request context. */;
/** Returns the current [Dream.request]. Raises if no request context. */
let get_request: unit => Dream.request;
/** Read a request header by name. */
let get_header: string => option(string);
/** Read a cookie from the request.
@param decrypt whether to decrypt the cookie value (default: false) */
let get_cookie: (~decrypt: bool=?, string) => option(string);
/** {2 Writing cookies}
Only available during the action phase (server functions).
Raises during render or outside a request context. */;
/** Queue a [Set-Cookie] header on the action response.
Raises during render (matching Next.js [ReadonlyRequestCookiesError]). */
let set_cookie:
(
~expires: float=?,
~max_age: float=?,
~domain: string=?,
~path: string=?,
~secure: bool=?,
~http_only: bool=?,
~same_site:
[
| `Strict
| `Lax
| `None
]
=?,
string,
string
) =>
unit;
};
/** {1 Streaming} */;
/** Render a React element as a full HTML page or RSC model stream,
depending on the request's [Accept] header.
Installs a render-phase request context: [RequestContext.get_*] is
available, [RequestContext.set_cookie] raises. */
let createFromRequest:
(
~debug: bool=?,
~disableSSR: bool=?,
~layout: React.element => React.element=?,
~bootstrapModules: list(string)=?,
~bootstrapScripts: list(string)=?,
~bootstrapScriptContent: string=?,
React.element,
Dream.request
) =>
Lwt.t(Dream.response);
/** Handle a server function POST request.
Installs an action-phase request context: both [RequestContext.get_*]
and [RequestContext.set_cookie] are available. Pending cookies are
serialized as [Set-Cookie] response headers. If the action raises,
pending cookies are discarded.
@param lookup maps an action ID to a registered [ReactServerDOM.server_function]
@param debug enable debug logging (default: false) */
let streamFunctionResponse:
(
~debug: bool=?,
~lookup: string => option(ReactServerDOM.server_function),
Dream.request
) =>
Lwt.t(Dream.response);
/** Stream a [React.model_value] as an RSC model response. */
let stream_model_value:
(~debug: bool=?, ~location: string, React.model_value) =>
Lwt.t(Dream.response);
/** Stream a [React.element] as an RSC model response. */
let stream_model:
(~debug: bool=?, ~location: string, React.element) => Lwt.t(Dream.response);
/** Stream a [React.element] as an HTML response with optional
bootstrap scripts for client hydration. */
let stream_html:
(
~debug: bool=?,
~skipRoot: bool=?,
~bootstrapScriptContent: string=?,
~bootstrapScripts: list(string)=?,
~bootstrapModules: list(string)=?,
React.element
) =>
Lwt.t(Dream.response);
================================================
FILE: demo/dream-rsc/dune
================================================
(library
(name dream_rsc)
(wrapped false)
(libraries dream react reactDOM js lwt lwt.unix)
(preprocess
(pps lwt_ppx)))
================================================
FILE: demo/dune
================================================
(rule
(alias demo)
(enabled_if
(= %{profile} "dev"))
(deps
server/server.exe
(alias_rec client))
(action
(progn
; we want dune to write the file but not attach any fsevents listeners to it,
; so that watchexec can read from it without issues.
; this means no (target), no (with-stdout-to), just a bash command with stdout
; redirect inside a string
(bash "date > %{project_root}/../../demo/.running/built_at.txt"))))
(install
(section bin)
(enabled_if
(= %{profile} dev))
(files
("./node_modules/@tailwindcss/cli/dist/index.mjs" as tailwind)))
(rule
(target output.css)
(enabled_if
(= %{profile} dev))
(alias client)
(deps
(source_tree ./)
(source_tree ./../server)
(:config tailwind.config.js)
(:input styles.css))
(action
(progn
(run tailwind --config=%{config} --input=%{input} --output=%{target}))))
================================================
FILE: demo/package.json
================================================
{
"name": "server-reason-react-demo",
"version": "0.0.0",
"description": "",
"dependencies": {
"@tailwindcss/cli": "^4.1.4",
"tailwindcss": "^4.1.4"
}
}
================================================
FILE: demo/server/db/notes.json
================================================
[
{
"id": 0,
"title": "Lorem ipsum for markdown, exists",
"content": "# Aethere conterminus nec est damno\n\nLorem markdownum patiente clade retenta, domos facta cacumine nostris coniunx aspergine intraverat. Petit **et**, est est recens invitaque refert asper vigoris undis sacerdos.\n\nEt undis laetos Caystros intellege est auras corpus, montes ambit tum formae pellitis [et inque](#lenta-argo). Sit dicentum nondum, Dorceus debita attonitum nulla cornua vestem si auras.\n\n## Dummodo in veretur argenti plenissima quoque damnare\n\nSic quae, aula fortibus fratribus longoque abiit mea cava commune spectant uno telis hiemem. Quibus vestigia pugnat prolisque Caesareos bracchia caesae, victoria citi colubris totos penates usum hirta. Perituraque inest promittat Procris mille famaque ursa, hamadryadas rapuere moxque amorem. Domui comites adspicit tabellae euhoe matri duxere dei Dianae Aegyptia celebrare. Veniat gestasset levavit: oras cursus arcebatque quid, herba caput tum praecepta.\n\nUrget quod dixi idemque. Timuitque hortis dubiaque meo per cantu admissum manibus lapis minimamque simulacraque currere licet, Fortuna reliquit massa. Positus tenet.\n\nLusisse Hymenaeon terrore referebat mortale in Pelopeia, facienda positoque bibisset. Per totum, virginibus dumque cornua modumque domus arma ecquid hoc meo [tertius hic](#mater). Coniugis laudis, *fertur*, postquam nostro, mihi mortale fessam illa quater autumni per sapiens, albentes hippomene, et.\n\n## Non sacra tibi superare circa\n\nEst per viro est, nec in trunca causa. Viso placet, cadunt quaque ignorant verbenis loquor exceptas in, summe iuga nectare. Mihi domus segetes, ferro, in quodque litoraque dixit, mediis bacis, egit, qua meae iram Boeotaque.\n\nCuspide accipit poterat, spreto haec quoque? Turpe quae, iacentia esset fissa vivum, an lacertis ire; spumas. Quis quod concidit Alcmene. Attollo mollia metalla adest terris **cultosque prompsit celsum** minima, saepe. Et mi est laverat totis, videndo dedimus capientur: iamque.\n\nHumanas nec, adest hanc iaculum; Phrygiae vae deinde quae neque quodque Nesse caelum chlamydem tamen generosos? Genuit puer placabilis tamen, invito et nervos tuam hoc.\n\nOpus nec. Motu omne vates negate fluere, nec sic membra Hennaeis pleno, arcana toto non. Quicquid se opta saepe! Tibi litis sunt saliente herba. Stamina huius sceptra iuvenes turbasti et mihi votique qua tanta, uno super ero vacuus fluminis tepida.",
"updated_at": 1716604800
},
{
"id": 1,
"title": "Our markdown parser is poor, don't stress it plz",
"content": "\n# Murra acta una cretum refert\n## Undas pati incola cognoscit Arethusa\nPatrium utentem: illa tempora; reddit seque, ab fuga notas Charaxi! Mater bibulas o mollia elisi veribus,\n[virtute](http: //www.de.com/iuncta-iactanti.html) tutus sub nam strictumque gens animasse,\n[anni inde](http: //aera.org/) illuc tellus. Munus generosos militis quoque sit.\n## Semper plura tempora tantae effodit cervix subito\nUllam vero: duris mea bellis pulvere! Cervice placidi, ignes, Laelapa pectore languentique fugitque; utroque Medusaeo. Pereat des, argento gemmas praestantes Amore referre. Tamen lanugine novercae frigora miser cum.\nPallas iam solet salix transit; causa fugio animalibus currant verba aversata, faciam tenus, unda. Opem tereti lecto ferventibus pater: Festa fictus nihil: mea quidem quem quodque leonis ad urbes: deus? Tantae corpus; o audit vox animam peteret presso sua quatere Venus.\n## Quem et recondidit puer conlapsa currus\nCeycis mallem bracchia. Minimas si invitae et catulo in detestatur fuit, dea pares, viscera flebant Elin solet annis frondescere sordidus. Laborem ut Troezena grandia at certans posses et Minyeides **nobis** tracti natae.\n## Posse ulla templo Iove aliter\nPictae aeraque sceptro, stupet, levi nec amans hoc breve inplet, gravidamque, locus. Foribusque simul, caput amplexa, silvis titulis: e removit aderat: orbae frenis, ingenium ardua gradere **esse**. Sine et *pessima percusso* est tener aduncis funibus claro: *sumptis* hostibus et, ore venti iamdudum. Laniare novis scopulis, tu priora veniens, nec quodque se novorum tribuam nomine?\n\n## Quodque cervicibus luces\nDedit socios esset, exarsit et movere Saturnia pudici, herede. Nec optima, non hanc spisso, sum gladii qua descendunt **noceat altoque me**. Patrium utentem: illa tempora; reddit seque, ab fuga notas Charaxi.\nPede tota, ligati: subduci succedit animas recessit inde aut, salva, ista. Artes carebat nutrix, arte primus sceptra accipis subit manibusque.\n> Rudentes quaque nec error tecta aves sic obsistere, ignis non nisi expalluit quater harpen; domus. Sine exilibus caerula quo modico et imagine, cana cognoscenda pars torvum cupidine membra: Achilli negabamus manu nec? Tenebant et rubigine tremuere deorum, ora. [Quae agmine patriaeque](http: //incurvatasortemque.io/) fuerit obverterat quoque; sum reticere; huic quaque **adspergine exsangues**! Protegat **verso** fama limite [ligno dextera](http: //intendens-in.net/lentinavis.html), lusisse at haeserat > pro exarsit deae: magni hamo altior.\n",
"updated_at": 1716604800
},
{
"id": 2,
"title": "Another important note",
"content": "# Ad Latous\n\n## Verba nostra\n\nRemovere vicimus quid nisi fluctibus Dictys. Tutus ictu amborum iniere inque, quod, omnes, neu pariter Andraemone nequiquam quod suo; luctantia. Feris ara fusum reliquit spirat longique alitibus, ab capillis movi persequitur.\n\n## Crimina Fames\n\nErat qui quodque decusque te tibi nil volucrem [in audaci](#in-et) obliquis **rebus tacuit**. Virtute est annis arma aequora, tenet vellem Eurydices dixerunt supplice animal.\n\n## Ab ego saxum ab tecta tympana mentita\n\nMei mutabit lacerata. Voti aguntur teloque, adest *vocabant*, unde defecto habet.\n\n- Ait pondere\n- Flamma putares cursu genitore plagas conabar manibus\n- Guttis recepta\n- Dixit electus\n- Exaudi tremulo\n- Natique duroque intrat sperat\n\n## Damnarat velox acerris mihi invitus celebrantur mali\n\nRemovere vicimus quid nisi. Est sed in neque patietur foramen exi haec ait. Ter laverat sociis quasque potitur si [est columnis](#dominoque) tempora dum audito et omnia *Pharosque est*.\n\n```cgiKeyboard(myspace_blacklist_streaming.ebook.browserUatBcc(5 / 45, ofRwSecondary), ccd);```\n\nFunda **Gorgoneum tenera ardet** condar viros cannae sequiturque *claro* quicumque. Serpens innumeraeque Cereris foret agitat socios gravem aquae nescisse, deus acie. Demissaque unum dubitabile erepta sanguinea surgis scindere illic meae credidit **dummodo maius** dat aures Illyricos coercet concipe roganti repetitaque.\n",
"updated_at": 1716804800
},
{
"id": 3,
"title": "RSC with navigation, yuhu! 🎉",
"content": "# Scrobibus luctu sunt cognoscenda erat iuvenis\n\n## Ciboque exuit quoque toris portae sed equos\n\nStygiam colle porrigit et stipite\ncuraque muneris. Aram labens admonitu prensam status, vox undis et\n**percussit**, quoque; nec mando ripae!\n\n## Per nec rudentes auras\n\nIxionis talia, nam de quaerere limine, illa non neu flevit! Suis cui nec esset\nquid crura. Quae fore uterum summa.\n\n## Populo refluitque deprensa\n\nPergama et fuerunt signa commemorare ecce, non ferit, impetus ab sustinui resto,\npiscem *inductus*, quem. Manum cruentior obruit. Alis Epiros; tum alti aurum\nvideri *et siqua tecta*, vitamque vellera quam superatus per matris mollescat,\n*si*.\n\nMei [signa](#satis-in-illo) evitata Elin flumina; divum\n[puer](#figuram-tot-vocari), reppulit ira arcton. Epulis ut incepti quod. Ter\naliis acta *ira*: obstitit!\n\n## Ipse forte ille remittat\n\nIpse que, nexu vana sequar fui opus perstant! Post hospes.\n\nUt quae illi vidit et in me, *sonumque coniunx* gravitate montis legum pars? Ait\ne addidit guttur, **habitantque** saxum Mopsopium innuba et Peragit sedisti et\nglaebis, ambitae quo currere. Ante per ignem; infantem inpositus tu enim qui:\nhostis mihi mirum euntem, quid? Spicis et frontem repressit deinde, ut residens\nbella vocatum [plumbum](#leto-per-his). Voce documenta stant, inhonorati viaeque\nvidet iterum sanguine, aras veste futuri, argumenta arcus milite non, non?\n\nMaenades Turnusque consulat morsu, sive *mille tenuere ossibus*. Amor duo, ecce\nimperio muneraque contemptus quodcumque quam tetigere tibi, petit, ubi aurumque\n**rogant**. Loca nubes colla ademit: cognoscenda atque. Funereum habebit dixit\nest gemitum viroque Megareus quibus: bracchia signa meus, filia; lucem decent\ntacito?\n",
"updated_at": 1716904800
}
]
================================================
FILE: demo/server/dune
================================================
(include_subdirs qualified)
(executable
(enabled_if
(= %{profile} "dev"))
(name server)
(libraries
dream
dream_rsc
demo_shared_native
nested_router_native
server-reason-react.url_native
react
reactDOM
html
js
lwt.unix
str
unix
belt
yojson)
(preprocess
(pps
server-reason-react.ppx
server-reason-react.melange_ppx
server-reason-react.rsc-native.ppx
lwt_ppx)))
================================================
FILE: demo/server/pages/Comments.re
================================================
module Post = {
[@react.component]
let make = () => {
{React.string(
"Notice how HTML for comments 'streams in' before the JavaScript (or React) has loaded on the page. In fact, the demo is entirely rendered in the server and doesn't use client-side JavaScript at all",
)}
{React.string("This demo is ")}
{React.string("artificially slowed down")}
{React.string(" while loading the comments data.")}
;
};
};
module Data = {
let delay = 4.0;
let fakeData = [
"Wait, it doesn't wait for React to load?",
"How does this even work?",
"I like marshmallows",
"!1!1!1! This is a comment",
"This is actually static from the server",
"But, imagine it's dynamic",
];
let get = () => fakeData;
let cached = ref(false);
let destroy = () => cached := false;
let promise = () => {
cached.contents
? Lwt.return(fakeData)
: {
let%lwt () = Lwt_unix.sleep(delay);
cached.contents = true;
Lwt.return(fakeData);
};
};
};
module Comments = {
[@react.async.component]
let make = () => {
let comments = React.Experimental.usePromise(Data.promise());
Lwt.return(
{comments
|> List.mapi((i, comment) =>
{React.string(comment)}
)
|> React.list}
,
);
};
};
module Page = {
[@react.component]
let make = () => {
{React.string("Rendering React.Suspense on the server")}
;
};
};
module App = {
[@react.async.component]
let make = (~selectedId, ~isEditing, ~searchText, ~sleep) => {
Lwt.return(
"server-reason-react notes"
"migrated from "
"reactjs/server-components-demo"
" with (server)-reason-react and Melange"
}>
,
);
};
};
let handler = request => {
let selectedId =
Dream.query(request, "selectedId")
|> Option.map(string => int_of_string_opt(string))
|> Option.value(~default=None);
let isEditing =
Dream.query(request, "isEditing")
|> Option.map(v => v == "true")
|> Option.value(~default=false);
let ssr =
Dream.query(request, "ssr")
|> Option.map(v => v == "false")
|> Option.value(~default=true);
let searchText =
Dream.query(request, "searchText") |> Option.value(~default="");
let sleep =
Dream.query(request, "sleep")
->Option.bind(Float.of_string_opt)
->Option.bind(value =>
if (value < 0.) {
None;
} else {
Some(value);
}
);
DreamRSC.createFromRequest(
~disableSSR=!ssr,
~bootstrapModules=["/static/demo/DummyRouterRSC.re.js"],
,
request,
);
};
================================================
FILE: demo/server/pages/Home.re
================================================
let handler = _request => {
let app =
{React.string("server-reason-react's demos")}
"This is a list of links to all the demos for server-reason-react's features"
"If you want to learn more about server-reason-react, check out the "
"documentation"
" or "
"repository"
"."
};
};
================================================
FILE: demo/server/pages/ServerOnlyRSC.re
================================================
let handler = request => {
let app =
{React.string(
"The client will fetch the server component from the server and run createFromFetch",
)}
{React.string("asking for the current time (in seconds) since")}
{React.string("00:00:00 GMT, Jan. 1, 1970")}
;
DreamRSC.createFromRequest(
~bootstrapModules=["/static/demo/ServerOnlyRSC.re.js"],
~layout=children => children ,
app,
request,
);
};
================================================
FILE: demo/server/pages/SidebarNote.re
================================================
[@react.component]
let make = (~note: Note.t) => {
let lastUpdatedAt =
if (Date.is_today(note.updated_at)) {
Date.format_time(note.updated_at);
} else {
Date.format_date(note.updated_at);
};
let summary =
note.content |> Markdown.extract_text |> Markdown.summarize(~words=20);
{switch (String.trim(summary)) {
| "" => {React.string("(No content)")}
| s => s
}}
}>
{note.title} lastUpdatedAt ;
};
================================================
FILE: demo/server/pages/SinglePageRSC.re
================================================
module Section = {
[@react.component]
let make = (~title, ~children, ~description=?) => {
{React.string(title)}
{switch (description) {
| Some(description) =>
description
| None => React.null
}}
children
;
};
};
module ExpandedContent = {
[@react.component]
let make = (~id, ~content: string, ~updatedAt: float, ~title: string) => {
let lastUpdatedAt =
if (Date.is_today(updatedAt)) {
Date.format_time(updatedAt);
} else {
Date.format_date(updatedAt);
};
let summary =
content |> Markdown.extract_text |> Markdown.summarize(~words=20);
{switch (String.trim(summary)) {
| "" => {React.string("(No content)")}
| s => s
}}
}>
title lastUpdatedAt ;
};
};
module CacheDemo = {
let calls = ref(0);
let get =
React.cache(label => {
calls.contents = calls.contents + 1;
label ++ " #" ++ Int.to_string(calls.contents);
});
};
module Page = {
[@react.async.component]
let make = () => {
let promiseIn2 =
Lwt.bind(Lwt_unix.sleep(2.0), _ =>
Lwt.return("Solusionao in 2 seconds!")
);
let promiseIn4 =
Lwt.bind(Lwt_unix.sleep(4.0), _ =>
Lwt.return("Solusionao in 4 seconds!")
);
let cachedValueFirst = CacheDemo.get("Cached value");
let cachedValueSecond = CacheDemo.get("Cached value");
Lwt.return(
{React.string(
"Server side rendering server components and client components",
)}
{React.string(
"React server components. Lazy loading of client components. Client props encodings, such as promises, React elements, and primitive types.",
)}
{"First call: " ++ cachedValueFirst} {"Second call: " ++ cachedValueSecond} {React.string("H E A D E R")} )}
string_list=["Item 1", "Item 2"]
promise=promiseIn2>
{React.string(
"This footer is a React.element as a server component into client prop, yay!",
)}
{React.string("H E A D E R")} )}
string_list=["Item 1", "Item 2"]
promise=promiseIn2>
{React.string(
"This footer is a React.element as a server component into client prop, yay!",
)}
{React.string("Server functions")}
,
);
};
};
module App = {
[@react.component]
let make = () => {
;
};
};
let handler = request =>
DreamRSC.createFromRequest(
~bootstrapModules=["/static/demo/SinglePageRSC.re.js"],
~layout=
children =>
children
,
,
request,
);
================================================
FILE: demo/server/server.re
================================================
let debug = Sys.getenv_opt("DEMO_ENV") == Some("development");
// Allow GET and POST from the same handler enables progressive enhancement.
// When JS is disabled, the browser will make a POST request into the same page (instead of a GET). The server should handle the form action and return the page.
// When JS is enabled, the page will make a POST request to the server with the action ID and the server will return the action response.
let getAndPost = (path, handler) =>
Dream.scope(
"/",
[],
[
Dream.get(path, handler),
Dream.post(
path,
DreamRSC.streamFunctionResponse(
~debug,
~lookup=FunctionReferences.get,
),
),
],
);
let server =
Dream.logger(
Dream.router([
getAndPost("/", Pages.Home.handler),
Dream.get("/demo", req => Dream.redirect(req, "/")),
Dream.get(
"/output.css",
Dream.from_filesystem("./_build/default/demo", "output.css"),
),
Dream.get(
"/static/**",
Dream.static("./_build/default/demo/client/app"),
),
getAndPost(Routes.renderToString, _request =>
Dream.html(
ReactDOM.renderToString(
,
),
)
),
getAndPost(Routes.renderToStaticMarkup, _request =>
Dream.html(
ReactDOM.renderToStaticMarkup(
,
),
)
),
getAndPost(Routes.renderToStream, Pages.Comments.handler),
getAndPost(Routes.singlePageRSC, Pages.SinglePageRSC.handler),
getAndPost(Routes.dummyRouterRSC, Pages.DummyRouterRSC.handler),
getAndPost(Routes.serverOnlyRSC, Pages.ServerOnlyRSC.handler),
...getAndPost
|> RouterRSC.routeDefinitionsHandlers(
"/demo/router",
~bootstrapModules=["/static/demo/NestedRouterRSC.re.js"],
~document=
(~children) =>
Pages.NestedRouter.Document.make(
Pages.NestedRouter.Document.makeProps(~children, ()),
),
~routeDefinitions=Pages.NestedRouter.routeDefinitions,
),
]),
);
let interface = {
switch (Sys.getenv_opt("SERVER_INTERFACE")) {
| Some(env) => env
| None => "localhost"
};
};
Dream.run(~port=8080, ~interface, server);
================================================
FILE: demo/styles.css
================================================
@import "tailwindcss";
/* Since we use dynamic classNames on Theme.re, we need to inline the colors here. This can cause tailwind to not generate some classes if there's a missing variant here or we add new colors. */
@source inline("{hover:,}{text,bg,border}-[#FFC53D]");
@source inline("{hover:,}{text,bg,border}-[#080808]");
@source inline("{hover:,}{text,bg,border}-[#0F0F0F]");
@source inline("{hover:,}{text,bg,border}-[#151515]");
@source inline("{hover:,}{text,bg,border}-[#191919]");
@source inline("{hover:,}{text,bg,border}-[#1E1E1E]");
@source inline("{hover:,}{text,bg,border}-[#252525]");
@source inline("{hover:,}{text,bg,border}-[#2A2A2A]");
@source inline("{hover:,}{text,bg,border}-[#313131]");
@source inline("{hover:,}{text,bg,border}-[#3A3A3A]");
@source inline("{hover:,}{text,bg,border}-[#484848]");
@source inline("{hover:,}{text,bg,border}-[#6E6E6E]");
@source inline("{hover:,}{text,bg,border}-[#B4B4B4]");
@source inline("{hover:,}{text,bg,border}-[#EEEEEE]");
@source inline("{hover:,}{text,bg,border}-[#F5F5F5]");
@source inline("{hover:,}{text,bg,border}-[#FFFFFF]");
@source inline("{hover:,focus:,active:,disabled:,}{text,bg,border}-primary");
:root {
background-color: #151515;
}
================================================
FILE: demo/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
content: {
files: ["./client/*.re","./server/*.re", "./universal/*.re"],
},
plugins: {},
}
================================================
FILE: demo/universal/js/Dream.re
================================================
let log = Js.log2;
================================================
FILE: demo/universal/js/dune
================================================
(include_subdirs unqualified)
(library
(name demo_shared_js)
(modes melange)
(wrapped false)
(libraries
reason-react
server-reason-react.react-server-dom-esbuild
server-reason-react.runtime
melange-webapi
melange.belt
melange.js
melange-fetch
melange.dom
server-reason-react.url_js
server-reason-react.rsc
nested_router_js)
(melange.runtime_deps
../../../packages/react-server-dom-esbuild/ReactServerDOMEsbuild.js)
(preprocess
(pps
browser_ppx
-js
server-reason-react.ppx
-melange
-shared-folder-prefix=js/
server-reason-react.rsc.ppx
melange.ppx
reason-react-ppx)))
(copy_files
(files "../native/shared/*.re"))
================================================
FILE: demo/universal/native/DB.re
================================================
open Lwt.Syntax;
let repoRoot = () => {
let exeDir = Filename.dirname(Sys.executable_name);
let rec findRoot = dir =>
if (Filename.basename(dir) == "_build") {
Filename.dirname(dir);
} else {
let parent = Filename.dirname(dir);
if (parent == dir) {
Sys.getcwd();
} else {
findRoot(parent);
};
};
findRoot(exeDir);
};
let runtimeRoot = () => {
let (/) = Filename.concat;
repoRoot() / "demo" / ".running";
};
let runtimeDbDir = () => {
let (/) = Filename.concat;
runtimeRoot() / "db";
};
let dbPath = file => {
let (/) = Filename.concat;
runtimeDbDir() / file;
};
let sourcePath = file => {
let (/) = Filename.concat;
repoRoot() / "demo" / "server" / "db" / file;
};
let readPath = path => {
switch%lwt (Lwt_io.with_file(~mode=Lwt_io.Input, path, Lwt_io.read)) {
| v => Lwt_result.return(v)
| exception e =>
Dream.log("Error reading file %s: %s", path, Printexc.to_string(e));
Lwt.return_error(Printexc.to_string(e));
};
};
let writePath = (path, content) => {
switch%lwt (
Lwt_io.with_file(~mode=Lwt_io.Output, path, c => Lwt_io.write(c, content))
) {
| () => Lwt_result.return()
| exception e =>
Dream.log("Error writing file %s: %s", path, Printexc.to_string(e));
Lwt.return_error(Printexc.to_string(e));
};
};
let ensureDir = dir =>
if (Sys.file_exists(dir)) {
Lwt_result.return();
} else {
switch (Unix.mkdir(dir, 0o755)) {
| () => Lwt_result.return()
| exception e =>
Dream.log(
"Error creating directory %s: %s",
dir,
Printexc.to_string(e),
);
Lwt.return_error(Printexc.to_string(e));
};
};
let ensureDbDir = () => {
let runtimeRoot = runtimeRoot();
let runtimeDbDir = runtimeDbDir();
switch%lwt (ensureDir(runtimeRoot)) {
| Ok () => ensureDir(runtimeDbDir)
| Error(e) => Lwt.return_error(e)
};
};
let ensureDbFile = file => {
let path = dbPath(file);
let source = sourcePath(file);
switch%lwt (ensureDbDir()) {
| Error(e) => Lwt.return_error(e)
| Ok () =>
if (Sys.file_exists(path)) {
Lwt_result.return();
} else if (Sys.file_exists(source)) {
switch%lwt (readPath(source)) {
| Ok(content) => writePath(path, content)
| Error(e) => Lwt.return_error(e)
};
} else {
writePath(path, "[]");
}
};
};
let readFile = file => {
let path = dbPath(file);
switch%lwt (ensureDbFile(file)) {
| Ok () => readPath(path)
| Error(e) => Lwt.return_error(e)
};
};
let writeFile = (file, content) => {
let path = dbPath(file);
switch%lwt (ensureDbDir()) {
| Ok () => writePath(path, content)
| Error(e) => Lwt.return_error(e)
};
};
let parseNote = (note: Yojson.Safe.t): option(Note.t) =>
switch (note) {
| `Assoc(fields) =>
let id =
fields |> List.assoc("id") |> Yojson.Safe.to_string |> int_of_string;
let title = fields |> List.assoc("title") |> Yojson.Safe.Util.to_string;
let content =
fields |> List.assoc("content") |> Yojson.Safe.Util.to_string;
let updated_at =
fields
|> List.assoc("updated_at")
|> Yojson.Safe.to_string
|> float_of_string;
Some({
id,
title,
content,
updated_at,
});
| _ => None
};
let parseNotes = json => {
switch (Yojson.Safe.from_string(json)) {
| `List(notes) => notes |> List.filter_map(parseNote) |> Result.ok
| _ => Result.error("Invalid notes file format")
| exception _ => Result.error("Invalid JSON format format")
};
};
let serializeNote = (note: Note.t): Yojson.Safe.t =>
`Assoc([
("id", `Int(note.id)),
("title", `String(note.title)),
("content", `String(note.content)),
("updated_at", `Float(note.updated_at)),
]);
let serializeNotes = (notes: list(Note.t)): string =>
`List(notes |> List.map(serializeNote)) |> Yojson.Safe.pretty_to_string;
let readNotesCached =
React.cache(sleep => {
Dream.log("[DB.readNotes] Fetching all notes from disk");
let%lwt () =
switch (sleep) {
| Some(0.)
| None => Lwt.return()
| Some(delay) => Lwt_unix.sleep(delay)
};
switch%lwt (readFile("./notes.json")) {
| Ok(json) => Lwt_result.lift(parseNotes(json))
| Error(_) => Lwt.return_error("Error reading notes file")
/* When something fails, treat it as an empty note db */
| exception _error => Lwt.return_ok([])
};
});
let readNotes = (~sleep=None, ()) => readNotesCached(sleep);
let findOne =
React.cache(((notes, id)) => {
switch (notes |> List.find_opt((note: Note.t) => note.id == id)) {
| Some(note) => Lwt_result.return(note)
| None =>
Lwt_result.fail("Note with id " ++ Int.to_string(id) ++ " not found")
}
});
let insertNote = (~title, ~content, notes) => {
let id = List.length(notes);
let note: Note.t = {
id,
title,
content,
updated_at: Unix.time(),
};
(note, [note, ...notes]);
};
let addNote = (~title, ~content) => {
let%lwt notes = readNotes();
let notes =
Result.map(
notes => {
let (note, notes) = insertNote(~title, ~content, notes);
(note, notes);
},
notes,
);
Lwt_result.lift(notes |> Result.map(((note, _)) => note));
};
let createNote = (~title, ~content) => {
let%lwt notes = readNotes();
switch (notes) {
| Ok(notes) =>
let (note, updatedNotes) = insertNote(~title, ~content, notes);
switch%lwt (writeFile("./notes.json", serializeNotes(updatedNotes))) {
| Ok () => Lwt_result.return(note)
| Error(e) => Lwt_result.fail(e)
};
| Error(e) => Lwt_result.fail(e)
};
};
let editNote = (~id, ~title, ~content) => {
let%lwt notes = readNotes();
switch (notes) {
| Ok(notes) =>
let updatedNotes =
notes
|> List.map((currentNote: Note.t) =>
if (currentNote.id == id) {
{
...currentNote,
title,
content,
updated_at: Unix.time(),
};
} else {
currentNote;
}
);
let editedNote =
updatedNotes |> List.find((note: Note.t) => note.id == id);
switch%lwt (writeFile("./notes.json", serializeNotes(updatedNotes))) {
| Ok () => Lwt_result.return(editedNote)
| Error(e) => Lwt_result.fail(e)
};
| Error(e) => Lwt_result.fail(e)
};
};
let deleteNote = id => {
let%lwt notes = readNotes();
let notes =
Result.map(
notes => notes |> List.filter((note: Note.t) => note.id != id),
notes,
);
Lwt_result.lift(notes);
};
let fetchNoteCached =
React.cache(((sleep, id)) => {
Dream.log("[DB.fetchNote] Fetching note id=%d from disk", id);
let%lwt () =
switch (sleep) {
| Some(delay) => Lwt_unix.sleep(delay)
| None => Lwt.return()
};
let* notes = readNotes(~sleep, ());
switch (notes) {
| Ok(notes) => findOne((notes, id))
| Error(e) => Lwt_result.fail(e)
};
});
let fetchNote = (~sleep=None, id) => fetchNoteCached((sleep, id));
================================================
FILE: demo/universal/native/Date.re
================================================
let is_today = date => {
let now = Unix.localtime(Unix.time());
let d = Unix.localtime(date);
now.tm_year == d.tm_year
&& now.tm_mon == d.tm_mon
&& now.tm_mday == d.tm_mday;
};
let format_time = date => {
let t = Unix.localtime(date);
let hour = t.tm_hour mod 12;
let hour =
if (hour == 0) {
12;
} else {
hour;
};
let ampm =
if (t.tm_hour >= 12) {
"pm";
} else {
"am";
};
Printf.sprintf("%d:%02d %s", hour, t.tm_min, ampm);
};
let format_date = date => {
let t = Unix.localtime(date);
Printf.sprintf("%d/%d/%02d", t.tm_mon + 1, t.tm_mday, t.tm_year mod 100);
};
================================================
FILE: demo/universal/native/FunctionReferences.re
================================================
type t = Hashtbl.t(string, ReactServerDOM.server_function);
let registry = Hashtbl.create(10);
let register = Hashtbl.add(registry);
let get = Hashtbl.find_opt(registry);
================================================
FILE: demo/universal/native/FunctionReferences.rei
================================================
include ReactServerDOM.FunctionReferences;
================================================
FILE: demo/universal/native/Markdown.re
================================================
module List = {
include List;
let take = (lst, n) => {
let rec aux = (lst, n, acc) =>
switch (lst, n) {
| ([], _)
| (_, 0) => List.rev(acc)
| ([x, ...xs], n) => aux(xs, n - 1, [x, ...acc])
};
aux(lst, n, []);
};
};
let convert_headings = text => {
text
|> Str.global_replace(Str.regexp("^#### \\(.*\\)$"), "
");
};
let convert_emphasis = text => {
text
|> Str.global_replace(
Str.regexp("\\*\\*\\([^*]*\\)\\*\\*"),
"\\1",
)
|> Str.global_replace(
Str.regexp("__\\([^_]*\\)__"),
"\\1",
)
|> Str.global_replace(Str.regexp("\\*\\([^*]*\\)\\*"), "\\1")
|> Str.global_replace(Str.regexp("_\\([^_]*\\)_"), "\\1");
};
let convert_code = text => {
text
|> Str.global_replace(
Str.regexp("```\\([^`]*\\)```"),
"
\\1
",
)
|> Str.global_replace(Str.regexp("`\\([^`]*\\)`"), "\\1");
};
let convert_links = text => {
text
|> Str.global_replace(
Str.regexp("\\[\\([^]]*\\)\\](\\([^)]*\\))"),
"\\1",
);
};
let convert_lists = text => {
let lines = String.split_on_char('\n', text);
let process_line = line => {
switch (line) {
| line when Str.string_match(Str.regexp("^-\\s*\\(.*\\)$"), line, 0) =>
"
" ++ Str.matched_group(1, line) ++ "
"
| line when Str.string_match(Str.regexp("^\\+\\s*\\(.*\\)$"), line, 0) =>
"
" ++ Str.matched_group(1, line) ++ "
"
| line when Str.string_match(Str.regexp("^\\*\\s*\\(.*\\)$"), line, 0) =>
"
" ++ Str.matched_group(1, line) ++ "
"
| line
when Str.string_match(Str.regexp("^\\d+\\.\\s*\\(.*\\)$"), line, 0) =>
"
" ++ Str.matched_group(1, line) ++ "
"
| _ => line
};
};
let wrap_consecutive_items = lines => {
let rec aux = (acc, current_list, lines) => {
switch (current_list, lines) {
| ([], []) => List.rev(acc)
| ([hd, ...tl], []) =>
List.rev([
"
;
};
================================================
FILE: demo/universal/native/shared/NoteListSkeleton.re
================================================
[@react.component]
let make = () => {
;
};
================================================
FILE: demo/universal/native/shared/NotePreview.re
================================================
[@react.component]
let make = (~body: string) => {
;
};
================================================
FILE: demo/universal/native/shared/NoteSkeleton.re
================================================
[@react.component]
let make = (~isEditing as _) => {
"Loading..."
;
};
================================================
FILE: demo/universal/native/shared/Promise_renderer.re
================================================
[@warning "-33"];
module Reader = {
[@react.component]
let make = (~promise: Js.Promise.t(string)) => {
let value = React.Experimental.usePromise(promise);
let%browser_only onMouseOver = _ev => {
Js.log("Over the promise!");
};
value
;
};
};
[@react.client.component]
let make = (~promise: Js.Promise.t(string)) => {
{React.string("Loading...")}
}>
;
};
================================================
FILE: demo/universal/native/shared/RR.re
================================================
[@platform native]
include {
let useStateValue = initialState => {
let setValueStatic = _newState => ();
(initialState, setValueStatic);
};
};
[@platform js]
include {
[@mel.module "react"]
external useState:
(unit => 'state) => ('state, (. ('state => 'state)) => unit) =
"useState";
let useStateValue = initialState => {
let (state, setState) = useState(_ => initialState);
let setValueStatic = newState => setState(. _ => newState);
(state, setValueStatic);
};
};
================================================
FILE: demo/universal/native/shared/RequestContextDemo.re
================================================
let buttonClass = "font-mono border-2 py-1 px-2 rounded-lg bg-yellow-950 border-yellow-700 text-yellow-200 hover:bg-yellow-800";
[@react.client.component]
let make = () => {
let (sessionUser, setSessionUser) = RR.useStateValue("");
let (userAgent, setUserAgent) = RR.useStateValue("");
let (cookieResult, setCookieResult) = RR.useStateValue("");
let (nameInput, setNameInput) = RR.useStateValue("Lola");
let (isLoading, setIsLoading) = RR.useStateValue(false);
{React.string("Read cookies & headers")}
{isLoading ? "Loading..." : sessionUser}
{userAgent != ""
?
{"User-Agent: " ++ userAgent}
: React.null}
{React.string("Set a cookie")}
{
let value = React.Event.Form.target(e)##value;
setNameInput(value);
}}
className="font-mono border-2 py-1 px-2 rounded-lg bg-gray-900 border-gray-700 text-gray-200"
placeholder="Enter a name"
/>
{cookieResult != ""
?
cookieResult
: React.null}
{cookieResult != ""
?
"Click 'Read request context' again to see the updated cookie value."
: React.null}
;
};
================================================
FILE: demo/universal/native/shared/Routes.re
================================================
let home = "/";
let renderToStaticMarkup = "/demo/renderToStaticMarkup";
let renderToString = "/demo/renderToString";
let renderToStream = "/demo/renderToStream";
let serverOnlyRSC = "/demo/serverOnlyRSC";
let singlePageRSC = "/demo/singlePageRSC";
let dummyRouterRSC = "/demo/dummyRouterRSC";
let dummyRouterRSCNoSSR = "/demo/dummyRouterRSC?ssr=false";
let router = "/demo/router";
let links = [|
(
"renderToString",
"Server side render a component (React.element) defining a static document into a string, the client rerenders the component (createRoot / render)",
renderToString,
),
(
"renderToStaticMarkup",
"Server side render a component (React.element) defining a document into a markup string (contains a few differences on the output compared to the renderToString version). The client hydrates it with the same component (hydrateRoot)",
renderToStaticMarkup,
),
(
"renderToStream",
"Server side render into a stream. A comments page that loads without any additional client-side code and just Suspense + streaming the HTML",
renderToStream,
),
(
"serverOnlyRSC",
"A client fetching a single react server component with createFromFetch",
serverOnlyRSC,
),
(
"singlePageRSC",
"A single page to with server components and SSR (with hydration), client components to test all props serialisation, including React.element and Js.Promise",
singlePageRSC,
),
(
"dummyRouterRSC",
"A dummy implementation of a router (only a few queryStrings) as a single page app. Server components with SSR, client components and Suspense + React.use",
dummyRouterRSC,
),
(
"dummyRouterRSC - without SSR",
"The same demo as dummyRouterRSC but without SSR. It SSR the shell of the page (head, body, etc), but not the app itself.",
dummyRouterRSCNoSSR,
),
(
"nestedRouterRSC",
"A nested router with server components and SSR, client components and Suspense + React.use. It uses the same design as the dummyRouterRSC but with a more complex structure that can handle nested routes and dynamic segments.",
router,
),
|];
module Menu = {
[@react.component]
let make = () => {
;
};
================================================
FILE: demo/universal/native/shared/Static_small.re
================================================
[@react.component]
let make = () =>
{React.string("This is Light Server Component")}
{React.string("Heavy Server Component")}
;
================================================
FILE: demo/universal/native/shared/Text.re
================================================
type size =
| XSmall
| Small
| Medium
| Large
| XLarge
| XXLarge
| XXXLarge;
let size_to_string = size =>
switch (size) {
| XSmall => "text-xs"
| Small => "text-sm"
| Medium => "text-base"
| Large => "text-lg"
| XLarge => "text-xl"
| XXLarge => "text-2xl"
| XXXLarge => "text-3xl"
};
type weight =
| Thin
| Light
| Regular
| Semibold
| Bold
| Extrabold
| Black;
type align =
| Left
| Center
| Right
| Justify;
[@react.component]
let make =
(
~color=Theme.Color.Gray12,
~size: size=Small,
~weight: weight=Regular,
~align=Left,
~children,
~role=?,
) => {
let className =
Cx.make([
Theme.text(color),
size_to_string(size),
switch (weight) {
| Thin => "font-thin"
| Light => "font-light"
| Regular => "font-normal"
| Semibold => "font-semibold"
| Bold => "font-bold"
| Extrabold => "font-extrabold"
| Black => "font-black"
},
switch (align) {
| Left => "text-left"
| Right => "text-right"
| Justify => "text-justify"
| Center => "text-center"
},
]);
{React.string(children)} ;
};
================================================
FILE: demo/universal/native/shared/Textarea.re
================================================
[@react.component]
let make = (~rows=10, ~value, ~onChange, ~id="", ~placeholder="") =>
;
================================================
FILE: demo/universal/native/shared/Theme.re
================================================
type align = [
| `start
| `center
| `end_
];
type justify = [
| `around
| `between
| `evenly
| `start
| `center
| `end_
];
module Media = {
let onDesktop = rules => {
String.concat(" md:", rules);
};
};
module Color = {
type t =
| None
| Transparent
| Gray0
| Gray1
| Gray2
| Gray3
| Gray4
| Gray5
| Gray6
| Gray7
| Gray8
| Gray9
| Gray10
| Gray11
| Gray12
| Gray13
| Gray14
| Primary;
let oneScaleUp = color => {
switch (color) {
| Gray0 => Gray1
| Gray1 => Gray2
| Gray2 => Gray3
| Gray3 => Gray4
| Gray4 => Gray5
| Gray5 => Gray6
| Gray6 => Gray7
| Gray7 => Gray8
| Gray8 => Gray9
| Gray9 => Gray10
| Gray10 => Gray11
| Gray11 => Gray12
| Gray12 => Gray13
| Gray13 => Gray14
| Gray14 => Gray14
| _ => color
};
};
let primary = "#FFC53D";
let gray0 = "#080808";
let gray1 = "#0F0F0F";
let gray2 = "#151515";
let gray3 = "#191919";
let gray4 = "#1E1E1E";
let gray5 = "#252525";
let gray6 = "#2A2A2A";
let gray7 = "#313131";
let gray8 = "#3A3A3A";
let gray9 = "#484848";
let gray10 = "#6E6E6E";
let gray11 = "#B4B4B4";
let gray12 = "#EEEEEE";
let gray13 = "#F5F5F5";
let gray14 = "#FFFFFF";
let brokenWhite = gray10;
let white = gray12;
let black = gray1;
let fadedBlack = gray3;
};
let none = "none";
type kind =
| Text
| Background
| Border;
let to_string = kind =>
switch (kind) {
| Text => "text"
| Background => "bg"
| Border => "border"
};
let color = (~kind, value) =>
switch ((value: Color.t)) {
| None => to_string(kind) ++ "-none"
| Transparent => to_string(kind) ++ "-transparent"
| Gray0 => to_string(kind) ++ "-[" ++ Color.gray0 ++ "]"
| Gray1 => to_string(kind) ++ "-[" ++ Color.gray1 ++ "]"
| Gray2 => to_string(kind) ++ "-[" ++ Color.gray2 ++ "]"
| Gray3 => to_string(kind) ++ "-[" ++ Color.gray3 ++ "]"
| Gray4 => to_string(kind) ++ "-[" ++ Color.gray4 ++ "]"
| Gray5 => to_string(kind) ++ "-[" ++ Color.gray5 ++ "]"
| Gray6 => to_string(kind) ++ "-[" ++ Color.gray6 ++ "]"
| Gray7 => to_string(kind) ++ "-[" ++ Color.gray7 ++ "]"
| Gray8 => to_string(kind) ++ "-[" ++ Color.gray8 ++ "]"
| Gray9 => to_string(kind) ++ "-[" ++ Color.gray9 ++ "]"
| Gray10 => to_string(kind) ++ "-[" ++ Color.gray10 ++ "]"
| Gray11 => to_string(kind) ++ "-[" ++ Color.gray11 ++ "]"
| Gray12 => to_string(kind) ++ "-[" ++ Color.gray12 ++ "]"
| Gray13 => to_string(kind) ++ "-[" ++ Color.gray13 ++ "]"
| Gray14 => to_string(kind) ++ "-[" ++ Color.gray14 ++ "]"
| Primary => to_string(kind) ++ "-[" ++ Color.primary ++ "]"
};
let text = value => color(~kind=Text, value);
let background = value => color(~kind=Background, value);
let border = value => color(~kind=Border, value);
let hover = value =>
switch (value) {
| [] => ""
| [value] => " hover:" ++ value
| values => " hover:" ++ String.concat(" hover:", values)
};
let button =
Cx.make([
"px-4 py-1 border-2 rounded-md",
"transition-[background-color] duration-250 ease-out",
border(Color.Gray5),
text(Color.Gray12),
hover([background(Color.Gray6), border(Color.Gray7)]),
]);
================================================
FILE: documentation/browser_ppx.mld
================================================
{0 Exclude client code from the native build}
[browser_only] is the ppx to exclude client code from the server build and conditionally execute code based on each platform.
For example, if you're using [Webapi] to query the DOM and extract some data from it. This code should only run on the client, and there's no equivalent or fallback on the server.
The ppx expose the [[%browser_only]] extension and [[@browser_only]] attribute that can be used to discard functions and values, and [[switch%platform]] to conditionally compile and execute code based on the platform.
{1 Example}
{[
let%browser_only countDomNodes = (id) => {
let elements = Webapi.Element.querySelector("#" ++ id);
let arr_elements = Webapi.Element.toArray(elements);
Array.length(arr_elements);
}
]}
{[
switch%platform (Runtime.platform) {
| Server => print_endline("This prints to the terminal")
| Client => Js.log("This prints to the console")
};
]}
{1 Installation}
Add [server-reason-react.browser_ppx] into to your pps field under a dune stanzas (melange.emit, libraries or executable) in your dune files.
You would need to add it on both "server" and "client" dune files. Adding the [-js] flag [server-reason-react.browser_ppx -js] for the client and without the flag for the server:
{[
; server exectuable
(executable
(name server)
(preprocess
(pps server-reason-react.browser_ppx)))
; melange emitting JavaScript
(melange.emit
(target app)
(preprocess
(pps server-reason-react.browser_ppx -js)))
]}
{1 Usage}
{2 let%browser_only to discard functions}
{[
let%browser_only countDomNodes = (id) => {
let elements = Webapi.Element.querySelector("#" ++ id);
let arr_elements = Webapi.Element.toArray(elements);
Array.length(arr_elements);
};
]}
The method tagged by [browser_only] and it will keep the function for the client build, but will be discarded for the server build. On the server build, the ppx transforms the body of function into a [Runtime.Impossible_in_ssr] exception.
If this function ever runs on the server accidentally, it will raise the exception. If this exception isn't caught, the server will obviously crash. This situation is very unlikely to happen, but in case of not being sure, it's good to be prepared for it and add a try catch block.
There may be other cases where catching the exception might be useful. For example, if you want to provide a default value or a fallback.
Following with the example from above:
{[
let%browser_only countDomNodes = (id) => {
let elements = Webapi.Element.querySelector("#" ++ id);
let arr_elements = Webapi.Element.toArray(elements);
Array.length(arr_elements);
}
let main = id =>
switch (countDomNodes(id)) {
| exception Runtime.Impossible_in_ssr(_message) => 0
};
]}
Now, the function [main] will return 0 if the function [countDomNodes] raises the [Runtime.Impossible_in_ssr] exception, and is "safe" (as in, it won't crash) to run on the server.
{2 switch%platform to conditionally execute code based on the platform}
[switch%platform] allows to conditionally execute code based on the platform. There are some cases where you need to run a specific code only on the server or only on the client.
An example is worth a thousand words:
{[
switch%platform (Runtime.platform) {
| Server => print_endline("This prints to the terminal")
| Client => Js.log("This prints to the console")
};
]}
Because Reason (and also OCaml) is a language where everything is an expression, not only can execute code, but any expression can be part of the switch.
{[
let howManyColumns =
switch%platform (Runtime.platform) {
| Server => 0
| Client => 12
};
]}
Note that the expression is evaluated for each platform, but the type needs to be the same for all the branches.
{2 [[@platform]] attribute}
The [[@platform]] attribute allows to specify code blocks that should only be included in the JavaScript or native build, respectively. The [[@platform]] attribute works the same way as the [[switch%platform]], but applied to entire modules.
Again, this is useful when you have code that is specific to one platform and should not be included in the other, but all packaged into a single module.
For example, you can define two modules, but only one of them should be kept in the final build based on the platform.
{[
[@platform js]
module X = {
type t = Js.Json.t;
let a = 2 + 2;
};
[@platform native]
module Y = {
type t = Js.Json.t;
let a = 4 + 4;
};
]}
When compiling with the `-js` flag, only the block with [[@platform js]] (module X) is kept, and when compiling without it, only the block with [[@platform native]] (module Y) is kept.
If you name the modules the same, the compiler won't complain, since you would get a single module available in both targets, respectively.
{[
[@platform js]
module X = {
type t = Js.Json.t;
let a = 2 + 2;
};
[@platform native]
module X = {
type t = Yojson.Basic.t;
let a = 4 + 4;
};
]}
================================================
FILE: documentation/dune
================================================
(documentation
(package server-reason-react)
(mld_files
index
get-started
universal-code
how-to-organise-universal-code
browser_ppx
externals-melange-attributes
ssr-and-hydration))
(install
(section doc)
(files
(ssr-and-hydration-pipeline.png
as
odoc-pages/ssr-and-hydration-pipeline.png)
(ssr-and-hydration-pipeline-fixed.png
as
odoc-pages/ssr-and-hydration-pipeline-fixed.png))
(package server-reason-react))
================================================
FILE: documentation/externals-melange-attributes.mld
================================================
{0 Externals and melange attributes}
[melange.ppx] is designed to preprocess Melange programs (simplifying code generation for common use cases like generating bindings or code from types). It's not compatible with native, but if you want to share a module with [melange.ppx] we provide a drop-in replacement called: [server-reason-react.melange_ppx].
Most of the features are shimmed to not work on the server and the compiler will warn to wrap it in [browser_only] expressions.
{1 [server-reason-react.melange_ppx] supports}
{2 All [mel.] attributes}
mel.* attributes are stripped out of the native build, and transformed into raising functions to raise at server runtime.
{2 Enables pipe_first [->]}
Pipe first is the operator to apply a function to a value where data is passed as the first argument. [->] is a convenient operator that allows you to "flip" your code inside-out.
It's not supported in native OCaml, but [server-reason-react.melange_ppx] enables it and works as expected.
{2 Supports RegExp [[%re "/regex/"]]}
Transforms [[%re ...]] into [Js.Re.t] from [server-reason-react.js] and it uses a C implementation of the regex engine from QuickJS from {{:https://github.com/ml-in-barcelona/quickjs.ml}quickjs.ml}. (Experimental)
{2 Debugger [%debugger]}
It removes the debugger in native. It's a noop on the server context, and it's pretty uncommon it's usage.
{2 Supports Js.t (object access [##] and mel.obj)}
{[
let john = {"name": "john", "age": 99};
/* The type of john is `{ . "age": int, "name": string }` which represents a
JavaScript Object. */
let name = john##name;
]}
https://melange.re/v3.0.0/communicate-with-javascript.html#using-js-t-objects
Object creation and object field access is designed to interact with JavaScript Objects, in native we reperesent those as OCaml Objects (which are very different) and [server-reason-react-ppx.melange_ppx] proviedes the implementation to make it work. (Experimental)
{2 Supports [\[@@deriving jsConverter\]]}
The [jsConverter] deriver generates conversion functions between OCaml variants and JavaScript-friendly representations (integers for regular variants, strings for polymorphic variants).
{b Regular variants:}
{[
type action = Click | Submit | Cancel [@@deriving jsConverter]
(* Generates:
val actionToJs : action -> int
val actionFromJs : int -> action option *)
]}
{b Polymorphic variants:}
{[
type state = [`Idle | `Loading | `Error] [@@deriving jsConverter]
(* Generates:
val stateToJs : state -> string
val stateFromJs : string -> state option *)
]}
{b With [\@mel.as] to customize values:}
{[
type action = Click | Submit [@mel.as 3] | Cancel [@@deriving jsConverter]
(* Click = 0, Submit = 3, Cancel = 4 *)
]}
{b With [{ newType }] for abstract types:}
{[
type action = Click | Submit [@@deriving jsConverter { newType }]
(* Generates an abstract type [abs_action] and:
val actionToJs : action -> abs_action
val actionFromJs : abs_action -> action *)
]}
Note: Only variants without payloads are supported. Variants with payloads like [Submit of int] will produce a compile-time error.
{1 Usage}
To use [server-reason-react.melange_ppx] you need to add it to your dune's pps field:
{[ (preprocess (pps server-reason-react.melange_ppx)) ]}
================================================
FILE: documentation/get-started.mld
================================================
{0 Get started}
This page explains the different modules available in [server-reason-react] and how to use them.
It assumes a minimum understanding of:
- {{:https://reasonml.github.io/docs/en/what-and-why}Reason}
- {{:reasonml.github.io/reason-react/}reason-react} (the react.js bindings)
- {{:https://melange.re/v3.0.0/what-is-melange.html}Melange} (the JavaScript compiler)
- {{:https://dune.readthedocs.io/en/stable}dune} (the build system)
{1 Installation}
{2:install-opam From opam's registry (recommended)}
{[
opam install server-reason-react
]}
{2:install-source From source}
To use the development version, install via opam pinning:
{[
opam pin server-reason-react.dev "https://github.com/ml-in-barcelona/server-reason-react.git#main" -y
]}
{1 Setup}
Add to your dune file:
{[
(libraries (server-reason-react.react server-reason-react.reactDom))
(preprocess (pps server-reason-react.ppx))
]}
{1 Usage}
[server-reason-react] provides {!React} and {!ReactDOM} modules with the same interface as [reason-react], including JSX transformation via [server-reason-react.ppx]. Components follow the standard [reason-react] API as explained in their {{:https://reasonml.github.io/reason-react/docs/en/components}official documentation}.
Here's a simple component:
{[
module Greetings = {
[@react.component]
let make = (~name) => {
{React.string("Hello " ++ name)}
};
};
]}
Components are functions that return a [React.element] and are annotated with [@react.component]. By convention, they are named `make`. When used in JSX, they can be written without the `make` prefix, using just the module name.
Here's a longer component with state:
{[
module Counter = {
[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
};
};
]}
Hooks like [React.useState] or [React.useEffect] are available but are no-ops when running on the server. Since components don't re-render on the server. Hooks like [React.useCallback] and [React.useMemo] have no memoization and return values just once.
{1 Server-side Rendering}
The main difference from [reason-react] is the ability to render on the server using {!ReactDOM}. The module provides three rendering methods:
{2 renderToString/renderToStaticMarkup}
[ReactDOM.renderToString] renders a React tree as a HTML string:
{[
let html = ReactDOM.renderToString();
]}
[ReactDOM.renderToStaticMarkup] renders a non-interactive React tree (can't be hydrated on the client):
{[
let html = ReactDOM.renderToStaticMarkup();
]}
{2 renderToStream}
[ReactDOM.renderToStream] renders a React tree as a {{:https://ocsigen.org/lwt/3.1.0/api/Lwt_stream}Lwt_stream} of type [Lwt_stream.t(string)]:
{[
let%lwt (stream, abort) = ReactDOM.renderToStream();
stream |> Lwt_stream.iter_s((chunk => {
let%lwt () = Dream.write(response_stream, chunk);
Dream.flush(response_stream);
}));
]}
Note: [Lwt] is required. See {{:https://github.com/ml-in-barcelona/server-reason-react/issues/205}this issue} for details.
{1 React Server Components}
React Server Components (RSC) is an architecture that allows you to render React components exclusively on the server, using server-side code (such as query the database or access the filesystem). It also, allows to differentiate server and client components (those components that require interactivity). Making sure that server ones are stripped from the JavaScript bundle sent to the client, while client components are loaded only when needed.
There's a entire area of improvements that RSC bring to the table, such as decreasing the bundle size by lazy loading client components, remove data fetching with useEffect hooks (by passing just promises), streaming the result of the server rendering of the page or stream the RSC payload, removes state by lifting it to the URL, to name the most notable ones.
This library supports it, but many pieces are being polished right now, check the {{:https://github.com/ml-in-barcelona/server-reason-react/tree/main/demo}demo folder} for more information or the {{:https://github.com/ml-in-barcelona/server-reason-react/issues/204}umbrella issue}.
================================================
FILE: documentation/how-to-organise-universal-code.mld
================================================
{0 How to organise universal code}
While using [server-reason-react] it's important to know how to organise the code. Sometimes you may want to have components that are shared between the client and the server, and sometimes you want to have components that are only used by the client or by the server.
In this guide will show how to setup the dune files accordingly.
{1:copy_files The copy_files hack}
In order to reuse the same code, you can use {{:https://dune.readthedocs.io/en/stable/reference/dune/copy_files.html}(copy_files ...)}. It seems hacky, and eventually we will have better ways of doing so, but is the method I found to be more reliable in terms of developer experience, mostly editor support and error messages.
{[
- src
- client/
- dune
- server/
- shared/
- dune
]}
{[
(* client/dune *)
(library
(name url_js)
(modes melange)
(libraries melange.js)
(wrapped false)
(modules Url)
(preprocess (pps melange.ppx))
(copy_files#
(source_only)
(mode fallback) ; `mode fallback` means you can override files in the client folder
(files "../native/shared/**.{re,rei}"))
]}
{[
(* server/dune *)
(library
(name url_native)
(modes native)
(modules Url)
(wrapped false))
]}
Here's the {{:https://github.com/ml-in-barcelona/server-reason-react/tree/main/demo/universal} universal demo}
{1:components reason-react and server-reason-react}
Asuming you want to share react.components between the client and the server, you can use the same technique as above.
{[
(library
(name shared_js)
(modes melange)
(libraries reason_react melange.belt bs_webapi)
(wrapped false)
(preprocess
(pps melange.ppx reason-react-ppx)))
(copy_files# "../native/shared/*.re")
(library
(name shared_native)
(modes native)
(libraries
server-reason-react.react
server-reason-react.reactDom
server-reason-react.belt
server-reason-react.webapi)
(wrapped false)
(preprocess
(pps
server-reason-react.ppx
server-reason-react.browser_ppx
server-reason-react.melange_ppx)))
(copy_files# "../*.re")
]}
This will expose all modules under a `Shared` module. You can then use those modules in both the client and the server.
{[
// client.re
switch (ReactDOM.querySelector("#root")) {
| Some(el) =>
let root = ReactDOM.Client.hydrateRoot(el);
ReactDOM.Client.hydrate(, root);
| None => Js.log("Can't find a 'root' element")
};
]}
{[
// server.re
// Given a random server library, and a random Page component
module Page = {
[@react.component]
let make = (~children, ~scripts) => {
{React.string("Server Reason React demo")}
children
;
};
};
// ...
req => {
let html = ReactDOM.renderToString();
Httpd.Response.make_string(Ok(html));
}
]}
{1:virtual Note on virtual_libraries}
There's a better mechanism of doing the same thing by dune, which is {{:https://dune.readthedocs.io/en/stable/variants.html}Virtual libraries}.
However, there are a few limitations on virtual libraries:
- {b Require all types to be abstract}
- There are some {{:https://dune.readthedocs.io/en/stable/variants.html#limitations}known limitations}
- {{:https://github.com/ocaml/dune/issues/7104}Some inconsistent behaviour}
I found that this mechanism is not as reliable as copy_files, and it's not well supported by editors. I would recommend to use copy_files instead, while we explore better ways of doing so with the dune team.
{1:future Future}
We know that the copy_file hack is not the best way, and we are exploring better ways of doing so with the dune.
Current efforts are focused on an RFC, to enable single-context Universal Libraries {{:https://github.com/ocaml/dune/issues/10630}dune#10630}.
================================================
FILE: documentation/index.mld
================================================
@children_order get-started universal-code how-to-organise-universal-code browser_ppx externals-melange-attributes
{0 server-reason-react}
{{:https://github.com/ml-in-barcelona/server-reason-react}server-reason-react} is a Native implementation of React's Server-side rendering (SSR) and React Server Components (RSC) architecture for {{:https://reasonml.github.io/}Reason}.
server-reason-react is designed to be used with {{:https://reasonml.github.io/reason-react//}reason-react} and {{:https://github.com/melange-re/melange}Melange}. Together it enables developers to write efficient React components using a single language, type-safe and performant, while building for both native executables and JavaScript.
{2 Features}
{ul
{- {b Server-side rendering HTML} with [ReactDOM.renderToString]/[ReactDOM.renderToStaticMarkup]}
{- Server-side rendering {b streaming HTML} with [ReactDOM.renderToStream] (similar to react@18 [renderToReadableStream])}
{- Includes {b [React.Suspense]} and {b [React.use()]} implementations}
{- {b server-reason-react-ppx} - A ppx transformation to support JSX on native}
{- All reason-react interface is either implemented or stubbed (some of the methods, like React.useState need to be stubbed because they aren't used on the server!)}
{- {b React Server Components} - A ReactServerDOM module for streaming RSC payload, an esbuild plugin to enhance the bundle with client-components mappings, a Dream middleware to serve the RSC endpoint and a dummy implementation of a router (still {{:https://github.com/ml-in-barcelona/server-reason-react/issues/204}work in progress})}
}
{b Warning:} This repo contains a few parts that are considered experimental and there's no guarantee of stability. Most of the stable parts are used in production at ahrefs.com, app.ahrefs.com and wordcount.com. Check each module's documentation for more details.
{2 Why}
There are plenty of motives for it, the main one is that {{:https://ahrefs.com}ahrefs} (the company I work for) needs it. We use OCaml for the backend and Reason (with React) for the frontend. We wanted to take advantage of the same features from React.js in the server as well.
Currently 100% of the public site ({{:https://ahrefs.com}ahrefs.com}), the shell part of the dashboard ({{:https://app.ahrefs.com}app.ahrefs.com}) and {{:https://wordcount.com}wordcount.com} are rendered on the server with [server-reason-react].
What made us create this library was mostly:
{ul
{- Use the same language (Reason) for both server and client}
{- Embrace server-client integrations such as type-safe routing, JSON decoding/encoding, sharing types and logic, while keep enjoying functional programming patterns}
{- Native performance is better than JavaScript performance (Node.js, Bun, Deno)}
{- Writing React from a different language than JavaScript, but still using the brilliant pieces from the ecosystem}
{- Exploration of OCaml effects and React}
{- Further exploration with OCaml multicore, direct-style and concurrency with React features such as async components, React.use or Suspense}
}
Explained more about the motivation in {{:https://sancho.dev/blog/server-side-rendering-react-in-ocaml}this blog post} and also in my talk about {{:https://www.youtube.com/watch?v=Oy3lZl2kE-0&t=92s&ab_channel=FUNOCaml}{b Universal react in OCaml} at fun-ocaml 2024} and {{:https://www.youtube.com/watch?v=e3qY-Eg9zRY&ab_channel=ReactAlicante}{b Server side rendering React natively with Reason} at ReactAlicante 2023}
{2 Other libraries inside this repo}
Aside from the core ([React], [ReactDOM] and [ReactServerDOM]), server-reason-react repo contains some common melange libraries to ensure components are universal. Some of them are reimplementations in native of those libraries, and others are new implementations. Currently they are part of the repository, but eventually will be moved out to their own opam packages and repositories.
{table
{tr {th Name} {th Description} {th Melange equivalent library}}
{tr {td {{:https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_only.html}[server-reason-react.browser_ppx]}}
{td A ppx to discard code for each platform with different attributes: [let%browser_only], [switch%platform] and [@platform]}
{td }}
{tr {td {{:https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/server-reason-react.url_native/URL/index.html}[server-reason-react.url_js] and [server-reason-react.url_native]}}
{td Universal URL module: binds to [window.URL] in browser, implemented with {{:https://github.com/mirage/ocaml-uri}[opam-uri]} in native}
{td }}
{tr {td {{:https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/externals-melange-attributes.html}[server-reason-react.melange_ppx]}}
{td A ppx to add the melange attributes to native code}
{td {{:https://melange.re/v4.0.0/}melange.ppx}}}
{tr {td [server-reason-react.promise]}
{td Vendored version of {{:https://github.com/aantron/promise}aantron/promise} with melange support {{:https://github.com/aantron/promise/pull/80}PR#80}}
{td {{:https://github.com/aantron/promise}promise}}}
{tr {td [server-reason-react.belt]}
{td Implementation of Belt for native {{:https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/server-reason-react.belt_native/Belt/index.html}API reference}}
{td {{:https://melange.re/v4.0.0/api/ml/melange/Belt}melange.belt}}}
{tr {td [server-reason-react.js]}
{td Implementation of [Js] library for native (unsafe/incomplete). Check the issue {{:https://github.com/ml-in-barcelona/server-reason-react/issues/110}#110} for more details}
{td {{:https://melange.re/v4.0.0/api/ml/melange/Js}melange.js}}}
{tr {td [server-reason-react.fetch]}
{td Stub of fetch with browser_ppx to compile in native}
{td {{:https://github.com/melange-community/melange-fetch}melange.fetch}}}
{tr {td [server-reason-react.webapi]}
{td Stub version of Webapi library for native code compilation}
{td {{:https://github.com/melange-community/melange-webapi}melange-webapi}}}
{tr {td [server-reason-react.dom]}
{td Stub version of Dom library for native code compilation}
{td {{:https://melange.re/v4.0.0/}melange-dom}}}
}
{1:guides Guides}
{ol
{li {{!page-"get-started"}Get started}}
{li {{!page-"universal-code"}What does universal code mean?}}
{li {{!page-"how-to-organise-universal-code"}How to organise universal code}}
{li {{!page-"browser_ppx"}Exclude client code from the native build}}
{li {{!page-"externals-melange-attributes"}Externals and melange attributes}}
{li {{!page-"ssr-and-hydration"}SSR and hydration}}
}
{2 Core API}
Those are the core libraries of [server-reason-react].
{!modules: React ReactDOM}
{2:next Next}
{{!page-"get-started"}Get started}
================================================
FILE: documentation/ssr-and-hydration.mld
================================================
{0 Server-side rendering and hydration}
{1 How does React.js work with SSR from server-reason-react?}
[server-reason-react] via [ReactDOM.renderToString], [ReactDOM.renderToStaticMarkup] or [ReactDOM.renderToStream] generate the HTML markup on the server. When the page loads in the browser, React.js performs a process called "hydration".
Hydration attaches event handlers into the existing HTML elements, using reason-react's [ReactDOM.hydrateRoot]. In case of not having hydration and just using render (via [ReactDOM.renderRoot]), React will re-render the entire page from scratch, which may cause layout shifts and be slower than hydration.
For hydration to work correctly, the initial markup from the server must match exactly what React loads on the client. React will throw a hydration error if there is a mismatch.
For example:
- server renders: [Hello from server]
- client renders: [Hello from client]
With this mismatch, hydration will fail and React will re-render on the client. This will result in worse performance and user experience, like layout shifts and other annoyances.
Read more about it in the [React documentation](https://react.dev/reference/react-dom/client/hydrateRoot).
{1 Hydrate error}
Commonly, hydrate errors will appear in the console as:
{%html:
Text content does not match server-rendered HTML.
%}
{2 Example}
Let's start by triggering a hydration error on purpose. Take a look at this code:
{@reasonml[
[@react.component]
let make = () => {
let backgroundColor = switch%platform () {
| Server => "red"
| Client => "blue"
};
;
};
]}
We use [switch%platform] to provide different values on the server and client. To understand how [switch%platform] works, look at the {{!page-"browser_ppx"} browser_ppx page}.
And let's see the diagram below, which shows how the code will behave:
{%html:%}
* React to complain about a hydration error because of the content of className attribute from the server is [className="red"] (IV) and what it expects on the client during hydration is [className="blue"] (V).
To fix this, the server and client render must provide the same markup.
{2 Possible solution: check if the client is mounted}
There are cases where your server doesn't have all the information needed to render the same markup on the client. For example, the client can access the DOM and run some calculations like [window.innerWidth] or [getBoundingClientRect].
The trick here is to make sure the server-render and the first client-render have the same markup, while the second client-render can have any value. This way, React will be happy with the first client-render and will not throw a hydration error.
To make it happen, we can check if the client is mounted and then change apply the final value. This is often done with a hook that will be executed only after the client is mounted.
{@reasonml[
module UseMounted = {
let use = () => {
let (isMounted, setIsMounted) = React.useState(() => false);
React.useEffect0(() => {
// The useEffect hook will be executed only on the client and after the hydration
setIsMounted(_ => true);
None;
});
isMounted;
};
};
[@react.component]
let make = () => {
let isClientMounted = UseMounted.use();
let backgroundColor = isClientMounted ? "blue" : "red"
;
};
]}
With a code like this one, the flow will be:
{%html:%}
** As we can see, isClientMounted is false on III, IV and V and eventually true on VI. So there is no difference between III and IV, and React will be happy with it.
In this case, we don't even need [switch%platform], as [useEffects] only runs on client and the value is provided on the client.
{1 Some helpers that you can create}
Those helpers are useful, but [server-reason-react] doesn't provide any, this code should live in your project.
{2 UseMounted}
[UseMounted] is a hook to check if the component has mounted.
{@reasonml[
let use = () => {
let (isMounted, setIsMounted) = React.useState(() => false);
React.useEffect0(() => {
setIsMounted(_ => true);
None;
});
isMounted;
};
]}
{3 When using it:}
You should use [UseMounted] whenever you have to deal with variables inside a component. For different JSX, take a look at {{!section-"server-or-client-render"} } or {{!section-"client-only"} }.
Example:
{@reasonml[
let isFocusable =
isClientMounted ? children->isFocusableElement : false;
]}
Even with [isClientMounted], the content {b MUST} compile on [native]. If the client content is not [native] compatible, you can use [%browser_only].
{@reasonml[
let%browser_only getAnswer = () => 42
let answer = isClientMounted ? getAnswer() : 0;
]}
{@text[
1 | let answer = isClientMounted ? getAnswer() : 0;
^^^^^^^^^^^
**Error** (alert browser_only): File.getAnswer
This expression is marked to only run on the browser where JavaScript can run. You can only use it inside a let%browser_only function.
]}
As you can see, [getAnswer] is a [browser_only] content and it must run under a [%browser_only]:
{@reasonml[
let%browser_only getAnswer = () => 42
let answer = isClientMounted ? [%browser_only () => getAnswer()]() : 0;
]}
{3 Combine [switch%platform] and [%browser_only] with [isClientMounted]}
[switch%platform] and [browser_only] are extensions that help us discarting parts of the same file for both native and JavaScript without breaking the compiler (read more about them here: {{!page-"browser_ppx"} browser_ppx page}).
So we should use [switch%platform] when we don't have a single way to provide the same value. If there is a way to provide the same value, but not in a single way, you'll need [switch%platform] like following:
{@reasonml[
let foo = switch%platform () {
| Server => foo_native()
| Client => foo_client()
}
]}
Here, [foo] will have the same value on both targets. There's no need for [isClientMounted] because Hydrate will never throw an error.
The usage of [isClientMounted] forces the execution of the code to behave proeprly, but it does not avoid compiler error because the code needs to compile on both targets. So, you will need [isClientMounted] and [browser_only] together:
{@reasonml[
let%browser_only foo_cant_compile_on_native = () => "Hey"
let value = isClientMounted
? [%browser_only () => foo_cant_compile_on_native()]
: "Yah"
]}
{%html:
✏️ Note
Remember, switch%platform is a helper to be used only when we don't have an alternative. However, we are constantly providing new universal content on server-reason-react, so you can ping us when you find something not universal that you think could be.
%}
{2:server-or-client-render component}
{@reasonml[
[@react.component]
let make = (~server: unit => React.element, ~client: unit => React.element) => {
let isClientMounted = UseMounted.use();
switch (isClientMounted) {
| false => server()
| true => client()
};
};
]}
[ServerOrClientRender] is a React Component that helps to provide different JSX markup on server and client powered by [UseMounted] to avoid hydrate issues.
Example:
{@reasonml[
[@react.component]
let make = () => {
}
client={() => }
/>;
};
]}
Again, even with [ServerOrClientRender], the content {b MUST} compile on [native]. If the client content is not [native] compatible, you must use [%browser_only].
{@reasonml[
// Imagine a BlueComponent.re being a client only component
// Foo.re: a universal file
}
client={() => }
/>
]}
{@text[
1 | }
client={
[%browser_only () => ]
}
/>
]}
{2:client-only }
The [ClientOnly] component is built on top of [ServerOrClientRender]. It provides a helper to easily apply client-side content.
{[
[@react.component]
let make = (~children: unit => React.element) => {
RR.null} />;
};
]}
Use [] when the server JSX can be empty ([RR.null]) and only want to render JSX on the client side.
{@reasonml[
{() => React.string("Hello World")}
]}
Again, we should use [%browser_only] even when using [ClientOnly] since the content {b MUST} compile on [native].
================================================
FILE: documentation/universal-code.mld
================================================
{0 What does universal code mean}
One of the goals of [server-reason-react] is to make easier to write code that can be shared between native and JavaScript.
A library (or module) is universal if:
- Compiles correctly for both platforms
- Exposes a common interface to both platforms
- Respects the semantics of the library on each platform
This is what we call universal code, but let me explain each point a bit better
{2 Compiles correctly for both platforms}
One of the first challenges of sharing code is that both platforms have different APIs available. You can't use browser's APIs on the server, for example [document.querySelectorAll]. Also, you can't use server related APIs on the client such as any filsystem operations, for example [Unix.getpid].
In this aspect server-reason-react is not much different than Node.js. For example, Node.js doesn't provide the global window/document objects in Node and enforces the user to handle those cases manually. [if typeof window !== "undefined" { ... }]
In our case, those browser APIs don't exist on native, but the difference with Node.js is that we need the code to compile, meanwhile Node.js (being JIT) will raise an error at runtime if your code tries to use those APIs. In OCaml, those modules need to be present.
Which makes those modules either present but stubbed on native or discarded with [browser_ppx].
{2 Exposes a common interface to both platforms}
Exposes a common interface to both platforms but it can also expose platform specific implementations on each side. Let's give a simple example:
{[
// Let's imagine we have a module "Math" that we want to be universal
module type Math_interface = {
let sum: (int, int) => int;
};
// a "Math_native" module that implements the interface for the server
module Math_native: Math_interface = {
let sum = (a, b) => a + b;
// For the sake of the example, we want a async sum
let async = (a, b) => Lwt.return(a + b);
};
// a "Math_js" module that implements the interface for the client
// Asuming that Math_native.async is only used in native code, we don't need to implement "async"
module Math_js: Math_interface = {
let sum = (a, b) => a + b;
};
]}
This example is a bit silly, since the sum function is the same on both platforms. But it shows the idea: implement platform specific parts on each module.
{2 Respects the semantics of the library on each platform}
There's cases where the semantics of the library are different on each platform, but the behaviour is the same. Let's give a real example from the server-reason-react codebase:
{[
module ReasonReact = {
module React = {
type element; // an abstract type
// a bind to the react.js createElement function, melange will inline the function
// React.createElement when compiling to JavaScript
[@mel.module "react"]
external createElement: string => React.element = "createElement";
};
};
module ServerReasonReact = {
module React = {
// in the server-reason-react version, the element type isn't abstract
// because we need to know the kind of element to render in ReactDOM.renderToString for example. I could make it abstract on the interface, but I don't need to (for correctness is a good idea to maintain the exact interface).
type element =
| Element(string)
| Text(string)
| Component(unit => element)
| Fragment(array(element));
// createElement is a function that returns a React.element
let createElement = name => React.Model.Element(string);
};
};
]}
In both "createElement" functions the semantics are the same, but the implementation is different. There's plenty of cases like this one, but I consider those cases useful for adapting a JavaScript library to native, in a world where you start a library with universality in mind, this might not be needed.
{1:kinds Kinds of universal libraries}
{2:pure Pure universal library}
It's a library without any client or server dependency, you can have a library with all modes: [(modes native byte melange)]. This is common for type-only libraries or libraries that only rely on the standard library. I often refer to this as "pure universal" library.
For example, a library to handle remote data named [Remote_data]. Represented here as a cut down version of the library for demo purposes, you can imagine to have all necessary functions to operate on this type:
{[
(* dune *)
(library
(name RemoteData)
(modes native melange)) (* Contains both modes for melange and native *)
]}
{[
(* RemoteData.re *)
type t('data, 'error) =
| NotAsked
| InitialLoading
| Loading('data)
| Failure('error)
| Success('data);
let map = (remoteData, fn) =>
switch (remoteData) {
| NotAsked => NotAsked
| InitialLoading
| Loading(_) => InitialLoading
| Failure(error) => Failure(error)
| Success(data) => Success(fn(data))
};
let getWithDefault = (remoteData, defaultValue) =>
switch (remoteData) {
| NotAsked
| InitialLoading
| Loading(_)
| Failure(_) => defaultValue
| Success(data) => data
};
let isLoading =
fun
| InitialLoading
| Loading(_) => true
| _ => false;
]}
This library can be used in both "native" and "melagne" stanzas interchangeably.
{2:same-api Same API, different implementations}
There are some other cases where you want to expose the same API, but the implementation is different.
For example, another tiny example: you may want to have a library that exposes a function to get the current time. On the client, you may want to use the browser API, while on the server you may want to use the system time.
[dune] allows to have 2 libraries with the same name, but available in different modes. For example:
{[
(library
(name url_js)
(modes melange)
(libraries melange.js)
(modules Url)
(wrapped false))
(library
(name url_native)
(modes native)
(modules Url)
(wrapped false))
]}
[url_js] and [url_native] are two different libraries, but they expose the same module called [Url] with the same API.
Both libraries need to be [(wrapped false)] so they expose all the modules (which in this case is only [Url]) directly.
[wrapped true] means that the library is wrapped in a entry module, so the modules are exposed under the library name. In this case, [wrapped false] expose the modules directly.
{1:examples Examples of universal libraries from server-reason-react}
As explained before, [server-reason-react] exposes a few modules that aren't React itself, such as {!Belt} or {!Js}. Those are native implementations of those libraries, which the user would need to add both server-reason-react.belt and melange.belt in any library.
- {!Belt} is an implementation of [Belt] that would work on both server and client. [server-reason-react.belt] (Unstable)
- {!Js} is an half-implementation of the [Js] module from melange.js, and many parts aren't implemented and some other parts aren't possible to implement on the server (Unstable, it raises "NOT IMPLEMENTED" for missing functions). [server-reason-react.js]
- {!Webapi} is a shimmed version of [melange-webapi] that works crash at runtime if you call those APIs on the server. [server-reason-react.webapi]
================================================
FILE: dune
================================================
(dirs packages demo documentation benchmark compare)
(documentation
(package server-reason-react))
================================================
FILE: dune-project
================================================
(lang dune 3.9)
(using melange 0.1)
(using directory-targets 0.1)
(using mdx 0.4)
(cram enable)
(name server-reason-react)
(license MIT)
(maintainers "David Sancho ")
(authors "David Sancho ")
(source
(github ml-in-barcelona/server-reason-react))
(generate_opam_files true)
(implicit_transitive_deps false)
(package
(name server-reason-react)
(synopsis "Rendering React components on the server natively")
(depends
; General system dependencies
(ocaml (>= 4.14.1))
(reason (>= 3.17.2))
(melange (>= 3.0.0))
; Library dependencies
(uucp (>= 16.0.0))
(ppxlib (>= 0.36.0))
(quickjs (>= 0.4.2))
(lwt (>= 5.9.2))
(lwt_ppx (>= 2.1.0))
(uri (>= 4.2.0))
(yojson (>= 2.2.0))
(integers (>= 0.7.0))
(zarith (>= 1.14))
(uutf (>= 1.0.3))
(melange-fetch (>= 0.2.0))
(melange-json (>= 2.0.0))
(melange-json-native (>= 2.0.0))
(melange-webapi (>= 0.21.0))
(reason-react (>= 0.16.0))
; Documentation
(odoc :with-doc)
; Dev dependencies
(ocamlformat
(and
(= 0.28.1)
:with-test)) ; We use ocamlformat on the tests
(ocaml-lsp-server :with-dev-setup)
(dream
(and
(= 1.0.0~alpha8)
:with-dev-setup)) ; We use dream on the demo
(reason-react-ppx :with-dev-setup)
; Test dependencies
(alcotest :with-test)
(alcotest-lwt :with-test)
(fmt :with-test)
(merlin :with-test)
))
================================================
FILE: fly.toml
================================================
app = "server-reason-react-test"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
SERVER_INTERFACE = "0.0.0.0"
[http_service]
internal_port = 8080
force_https = true
================================================
FILE: packages/Belt/src/Belt.re
================================================
/** The stdlib shipped with Melange, but working on native */;
/** {!Belt.Id}
Provide utilities to create identified comparators or hashes for
data structures used below.
It create a unique identifier per module of
functions so that different data structures with slightly different
comparison functions won't mix
*/
module Id = Belt_Id;
/** {!Belt.Array}
{b mutable array}: Utilities functions
*/
module Array = Belt_Array;
/** {!Belt.SortArray}
The top level provides some generic sort related utilities.
It also has two specialized inner modules
{!Belt.SortArray.Int} and {!Belt.SortArray.String}
*/
module SortArray = Belt_SortArray;
/** {!Belt.MutableQueue}
An FIFO(first in first out) queue data structure
*/
module MutableQueue = Belt_MutableQueue;
/** {!Belt.MutableStack}
An FILO(first in last out) stack data structure
*/
module MutableStack = Belt_MutableStack;
/** {!Belt.List}
Utilities for List data type
*/
module List = Belt_List;
/** {!Belt.Range}
Utilities for a closed range [(from, start)]
*/
module Range = Belt_Range;
/** {!Belt.Set}
The top level provides generic {b immutable} set operations.
It also has three specialized inner modules
{!Belt.Set.Int}, {!Belt.Set.String} and
{!Belt.Set.Dict}: This module separates data from function
which is more verbose but slightly more efficient
*/
module Set = Belt_Set;
/** {!Belt.Map},
The top level provides generic {b immutable} map operations.
It also has three specialized inner modules
{!Belt.Map.Int}, {!Belt.Map.String} and
{!Belt.Map.Dict}: This module separates data from function
which is more verbose but slightly more efficient
*/
module Map = Belt_Map;
/** {!Belt.MutableSet}
The top level provides generic {b mutable} set operations.
It also has two specialized inner modules
{!Belt.MutableSet.Int} and {!Belt.MutableSet.String}
*/
module MutableSet = Belt_MutableSet;
/** {!Belt.MutableMap}
The top level provides generic {b mutable} map operations.
It also has two specialized inner modules
{!Belt.MutableMap.Int} and {!Belt.MutableMap.String}
*/
module MutableMap = Belt_MutableMap;
/** {!Belt.HashSet}
The top level provides generic {b mutable} hash set operations.
It also has two specialized inner modules
{!Belt.HashSet.Int} and {!Belt.HashSet.String}
*/
module HashSet = Belt_HashSet;
/** {!Belt.HashMap}
The top level provides generic {b mutable} hash map operations.
It also has two specialized inner modules
{!Belt.HashMap.Int} and {!Belt.HashMap.String}
*/
module HashMap = Belt_HashMap;
/** {!Belt.Option}
Utilities for option data type.
*/
module Option = Belt_Option;
/** {!Belt.Result}
Utilities for result data type.
*/;
module Result = Belt_Result;
/** {!Belt.Int}
Utilities for Int.
*/;
module Int = Belt_Int;
/** {!Belt.Float}
Utilities for Float.
*/;
module Float = Belt_Float;
================================================
FILE: packages/Belt/src/Belt_Array.ml
================================================
type 'a t = 'a array
let length = Array.length
let size = length
let getUnsafe = Array.unsafe_get
let setUnsafe = Array.unsafe_set
let get = Array.get
let getUndefined arr i = if i >= 0 && i < length arr then Js.Undefined.return (getUnsafe arr i) else Js.undefined
let get arr i = if i >= 0 && i < length arr then Some (getUnsafe arr i) else None
let getExn arr i =
(if Stdlib.not (i >= 0 && i < length arr) then
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error);
getUnsafe arr i
let set arr i v =
if i >= 0 && i < length arr then (
setUnsafe arr i v;
true)
else false
let setExn arr i v =
if Stdlib.not (i >= 0 && i < length arr) then begin
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
end;
setUnsafe arr i v
let makeUninitialized len = Array.make len Js.undefined
let makeUninitializedUnsafe len defaultVal = Array.make len defaultVal
let truncateToLengthUnsafe arr len = Stdlib.Array.sub arr 0 len
let copy = Stdlib.Array.copy
let swapUnsafe xs i j =
let tmp = getUnsafe xs i in
setUnsafe xs i (getUnsafe xs j);
setUnsafe xs j tmp
let random_int min max = Random.int (max - min) + min
let shuffleInPlace xs =
let len = length xs in
for i = 0 to len - 1 do
swapUnsafe xs i (random_int i len)
done
let shuffle xs =
let result = copy xs in
shuffleInPlace result;
result
let reverseAux xs ofs len =
for i = 0 to (len / 2) - 1 do
swapUnsafe xs (ofs + i) (ofs + len - i - 1)
done
let reverseInPlace xs =
let len = length xs in
reverseAux xs 0 len
let make l f =
if l <= 0 then [||]
else
let res = Array.make l f in
res
let reverse xs =
let len = length xs in
let result = if len > 0 then makeUninitializedUnsafe len (getUnsafe xs 0) else [||] in
for i = 0 to len - 1 do
setUnsafe result i (getUnsafe xs (len - 1 - i))
done;
result
let makeByU l f = if l <= 0 then [||] else Stdlib.Array.init l f
let makeBy l f = makeByU l (fun a -> f a)
let makeByAndShuffleU l f =
let u = makeByU l f in
shuffleInPlace u;
u
let makeByAndShuffle l f = makeByAndShuffleU l (fun a -> f a)
let range start finish =
let cut = finish - start in
if cut < 0 then [||]
else
let arr = makeUninitializedUnsafe (cut + 1) 0 in
for i = 0 to cut do
setUnsafe arr i (start + i)
done;
arr
let rangeBy start finish ~step =
let cut = finish - start in
if cut < 0 || step <= 0 then [||]
else
let nb = (cut / step) + 1 in
let arr = makeUninitializedUnsafe nb 0 in
let cur = ref start in
for i = 0 to nb - 1 do
setUnsafe arr i !cur;
cur := !cur + step
done;
arr
let zip xs ys =
let lenx, leny = (length xs, length ys) in
let len = Stdlib.min lenx leny in
let s = if len > 0 then makeUninitializedUnsafe len (getUnsafe xs 0, getUnsafe ys 0) else [||] in
for i = 0 to len - 1 do
setUnsafe s i (getUnsafe xs i, getUnsafe ys i)
done;
s
let zipByU xs ys f =
let lenx, leny = (length xs, length ys) in
let len = Stdlib.min lenx leny in
Stdlib.Array.init len (fun i -> f (getUnsafe xs i) (getUnsafe ys i))
let zipBy xs ys f = zipByU xs ys (fun a b -> f a b)
let concat = Stdlib.Array.append
let concatMany arrs =
let lenArrs = length arrs in
let totalLen = ref 0 in
let firstArrWithLengthMoreThanZero = ref None in
for i = 0 to lenArrs - 1 do
let len = length (getUnsafe arrs i) in
totalLen := !totalLen + len;
if len > 0 && !firstArrWithLengthMoreThanZero = None then firstArrWithLengthMoreThanZero := Some (getUnsafe arrs i)
done;
match !firstArrWithLengthMoreThanZero with
| None -> [||]
| Some firstArr ->
let result = makeUninitializedUnsafe !totalLen (getUnsafe firstArr 0) in
totalLen := 0;
for j = 0 to lenArrs - 1 do
let cur = getUnsafe arrs j in
for k = 0 to length cur - 1 do
setUnsafe result !totalLen (getUnsafe cur k);
incr totalLen
done
done;
result
let slice a ~offset ~len =
if len <= 0 then [||]
else
let lena = length a in
let ofs = if offset < 0 then max (lena + offset) 0 else offset in
let hasLen = lena - ofs in
let copyLength = min hasLen len in
if copyLength <= 0 then [||] else Stdlib.Array.sub a ofs copyLength
let fill a ~offset ~len v =
if len > 0 then
let lena = length a in
let ofs = if offset < 0 then max (lena + offset) 0 else offset in
let hasLen = lena - ofs in
let fillLength = min hasLen len in
if fillLength > 0 then Stdlib.Array.fill a ofs fillLength v
let blitUnsafe ~src:a1 ~srcOffset:srcofs1 ~dst:a2 ~dstOffset:srcofs2 ~len:blitLength =
if srcofs2 <= srcofs1 then
for j = 0 to blitLength - 1 do
setUnsafe a2 (j + srcofs2) (getUnsafe a1 (j + srcofs1))
done
else
for j = blitLength - 1 downto 0 do
setUnsafe a2 (j + srcofs2) (getUnsafe a1 (j + srcofs1))
done
let blit ~src:a1 ~srcOffset:ofs1 ~dst:a2 ~dstOffset:ofs2 ~len =
let lena1 = length a1 in
let lena2 = length a2 in
let srcofs1 = if ofs1 < 0 then max (lena1 + ofs1) 0 else ofs1 in
let srcofs2 = if ofs2 < 0 then max (lena2 + ofs2) 0 else ofs2 in
let blitLength = min len (min (lena1 - srcofs1) (lena2 - srcofs2)) in
if blitLength > 0 then Stdlib.Array.blit a1 srcofs1 a2 srcofs2 blitLength
let forEachU a f =
for i = 0 to length a - 1 do
f (getUnsafe a i)
done
let forEach a f = forEachU a (fun a -> f a)
let mapU a f = Stdlib.Array.map f a
let map a f = mapU a (fun a -> f a)
let keepU a f =
let l = length a in
let r = if l > 0 then makeUninitializedUnsafe l (getUnsafe a 0) else [||] in
let j = ref 0 in
for i = 0 to l - 1 do
let v = getUnsafe a i in
if f v then (
setUnsafe r !j v;
incr j)
done;
truncateToLengthUnsafe r !j
let keep a f = keepU a (fun a -> f a)
let keepWithIndexU a f =
let l = length a in
let r = if l > 0 then makeUninitializedUnsafe l (getUnsafe a 0) else [||] in
let j = ref 0 in
for i = 0 to l - 1 do
let v = getUnsafe a i in
if f v i then (
setUnsafe r !j v;
incr j)
done;
truncateToLengthUnsafe r !j
let keepWithIndex a f = keepWithIndexU a (fun a -> f a)
let keepMapU a f =
let l = length a in
let r = ref None in
let j = ref 0 in
for i = 0 to l - 1 do
let v = getUnsafe a i in
match f v with
| None -> ()
| Some v ->
let r =
match !r with
| None ->
let newr = makeUninitializedUnsafe l v in
r := Some newr;
newr
| Some r -> r
in
setUnsafe r !j v;
incr j
done;
match !r with None -> [||] | Some r -> truncateToLengthUnsafe r !j
let keepMap a f = keepMapU a (fun a -> f a)
let forEachWithIndexU a f =
for i = 0 to length a - 1 do
f i (getUnsafe a i)
done
let forEachWithIndex a f = forEachWithIndexU a (fun a b -> f a b)
let mapWithIndexU a f = Stdlib.Array.mapi f a
let mapWithIndex a f = mapWithIndexU a (fun a b -> f a b)
let reduceU a x f =
let r = ref x in
for i = 0 to length a - 1 do
r := f !r (getUnsafe a i)
done;
!r
let reduce a x f = reduceU a x (fun a b -> f a b)
let reduceReverseU a x f =
let r = ref x in
for i = length a - 1 downto 0 do
r := f !r (getUnsafe a i)
done;
!r
let reduceReverse a x f = reduceReverseU a x (fun a b -> f a b)
let reduceReverse2U a b x f =
let r = ref x in
let len = min (length a) (length b) in
for i = len - 1 downto 0 do
r := f !r (getUnsafe a i) (getUnsafe b i)
done;
!r
let reduceReverse2 a b x f = reduceReverse2U a b x (fun a b c -> f a b c)
let rec everyAux arr i b len =
if i = len then true else if b (getUnsafe arr i) then everyAux arr (i + 1) b len else false
let rec someAux arr i b len = if i = len then false else if b (getUnsafe arr i) then true else someAux arr (i + 1) b len
let everyU arr b =
let len = length arr in
everyAux arr 0 b len
let every arr f = everyU arr (fun b -> f b)
let someU arr b =
let len = length arr in
someAux arr 0 b len
let some arr f = someU arr (fun b -> f b)
let rec everyAux2 arr1 arr2 i b len =
if i = len then true else if b (getUnsafe arr1 i) (getUnsafe arr2 i) then everyAux2 arr1 arr2 (i + 1) b len else false
let rec someAux2 arr1 arr2 i b len =
if i = len then false else if b (getUnsafe arr1 i) (getUnsafe arr2 i) then true else someAux2 arr1 arr2 (i + 1) b len
let every2U a b p = everyAux2 a b 0 p (min (length a) (length b))
let every2 a b p = every2U a b (fun a b -> p a b)
let some2U a b p = someAux2 a b 0 p (min (length a) (length b))
let some2 a b p = some2U a b (fun a b -> p a b)
let eqU a b p =
let lena = length a in
let lenb = length b in
if lena = lenb then everyAux2 a b 0 p lena else false
let eq a b p = eqU a b (fun a b -> p a b)
let rec everyCmpAux2 arr1 arr2 i b len =
if i = len then 0
else
let c = b (getUnsafe arr1 i) (getUnsafe arr2 i) in
if c = 0 then everyCmpAux2 arr1 arr2 (i + 1) b len else c
let cmpU a b p =
let lena = length a in
let lenb = length b in
if lena > lenb then 1 else if lena < lenb then -1 else everyCmpAux2 a b 0 p lena
let cmp a b p = cmpU a b (fun a b -> p a b)
let partitionU a f =
let l = length a in
let i = ref 0 in
let j = ref 0 in
let a1 = if l > 0 then makeUninitializedUnsafe l (getUnsafe a 0) else [||] in
let a2 = if l > 0 then makeUninitializedUnsafe l (getUnsafe a 0) else [||] in
for ii = 0 to l - 1 do
let v = getUnsafe a ii in
if f v then (
setUnsafe a1 !i v;
incr i)
else (
setUnsafe a2 !j v;
incr j)
done;
(truncateToLengthUnsafe a1 !i, truncateToLengthUnsafe a2 !j)
let partition a f = partitionU a (fun x -> f x)
let unzip = Stdlib.Array.split
let sliceToEnd a offset =
let lena = length a in
let ofs = if offset < 0 then Stdlib.max (lena + offset) 0 else offset in
let len = if lena > ofs then lena - ofs else 0 in
Stdlib.Array.init len (fun i -> getUnsafe a (ofs + i))
let flatMapU a f = concatMany (mapU a f)
let flatMap a f = flatMapU a (fun a -> f a)
let getByU a p =
let l = length a in
let i = ref 0 in
let r = ref None in
while r.contents = None && i.contents < l do
let v = getUnsafe a i.contents in
if p v then r.contents <- Some v;
i.contents <- i.contents + 1
done;
r.contents
let getBy a p = getByU a (fun[@bs] a -> p a)
let getIndexByU a p =
let l = length a in
let i = ref 0 in
let r = ref None in
while r.contents = None && i.contents < l do
let v = getUnsafe a i.contents in
if p v then r.contents <- Some i.contents;
i.contents <- i.contents + 1
done;
r.contents
let getIndexBy a p = getIndexByU a (fun a -> p a)
let reduceWithIndexU a x f =
let r = ref x in
for i = 0 to length a - 1 do
r.contents <- f r.contents (getUnsafe a i) i
done;
r.contents
let reduceWithIndex a x f = reduceWithIndexU a x (fun a b c -> f a b c)
let joinWithU a sep toString =
match length a with
| 0 -> ""
| l ->
let lastIndex = l - 1 in
let rec aux i res =
let v = getUnsafe a i in
if i = lastIndex then res ^ toString v else aux (i + 1) (res ^ toString v ^ sep)
in
aux 0 ""
let joinWith a sep toString = joinWithU a sep (fun x -> toString x)
let initU n f = Stdlib.Array.init n f
let init n f = initU n (fun i -> f i)
let push _arr _i = `Do_not_use_Array_push_in_native
================================================
FILE: packages/Belt/src/Belt_Array.mli
================================================
(***********************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the GNU Library General Public License, with *)
(* the special exception on linking described in file ../LICENSE. *)
(* *)
(***********************************************************************)
(* Adapted significantly by Authors of ReScript *)
(** {!Belt.Array} Utililites for Array functions *)
type 'a t = 'a array
val length : 'a t -> int
(** [length xs] return the size of the array *)
val size : 'a t -> int
(** {b See} {!length} *)
val get : 'a t -> int -> 'a option
(** [get arr i]
If [i <= 0 <= length arr];returns [Some value] where [value] is the item at index [i] If [i] is out of range;returns
[None]
{[
Belt.Array.get [| "a"; "b"; "c" |] 0 = Some "a";;
Belt.Array.get [| "a"; "b"; "c" |] 3 = None;;
Belt.Array.get [| "a"; "b"; "c" |] (-1) = None
]} *)
val getExn : 'a t -> int -> 'a
(** [getExn arr i]
{b raise} an exception if [i] is out of range;otherwise return the value at index [i] in [arr] *)
val getUnsafe : 'a t -> int -> 'a
(** [getUnsafe arr i]
{b Unsafe}
no bounds checking;this would cause type error if [i] does not stay within range *)
val getUndefined : 'a t -> int -> 'a Js.undefined
(** [getUndefined arr i]
It does the same thing in the runtime as {!getUnsafe}; it is {i type safe} since the return type still tracks
whether it is in range or not. On native this is represented using the same option-backed encoding as
[Js.Undefined]. *)
val set : 'a t -> int -> 'a -> bool
(** [set arr n x] modifies [arr] in place; it replaces the nth element of [arr] with [x]
@return false means not updated due to out of range *)
val setExn : 'a t -> int -> 'a -> unit
(** [setExn arr i x] {b raise} an exception if [i] is out of range *)
val setUnsafe : 'a t -> int -> 'a -> unit
val shuffleInPlace : 'a t -> unit
(** [shuffleInPlace arr] randomly re-orders the items in [arr] *)
val shuffle : 'a t -> 'a t
(** [shuffle xs]
@return a fresh array with items in original array randomly shuffled *)
val reverseInPlace : 'a t -> unit
(** [reverseInPlace arr] reverses items in [arr] in place
{[
let arr = [| 10; 11; 12; 13; 14 |]
let () = reverseInPlace arr;;
arr = [| 14; 13; 12; 11; 10 |]
]} *)
val reverse : 'a t -> 'a t
(** [reverse arr]
@return a fresh array with items in [arr] in reverse order
{[
reverse [| 10; 11; 12; 13; 14 |] = [| 14; 13; 12; 11; 10 |]
]} *)
val makeUninitialized : int -> 'a Js.undefined array
(** [makeUninitialized n] creates an array of length [n] filled with the undefined value. You must specify the type of
data that will eventually fill the array.
{[
let arr : string Js.undefined array = makeUninitialized 5;;
getExn arr 0 = Js.undefined
]} *)
val makeUninitializedUnsafe : int -> 'a -> 'a array
(** [makeUninitializedUnsafe n filler]
{b Unsafe}
Native approximation of the JavaScript [makeUninitializedUnsafe]. Since OCaml arrays must be fully initialized, the
[filler] value is used to allocate the array before callers overwrite the slots they need. Unread slots therefore
contain [filler], not JavaScript-style holes or [undefined].
{[
let arr = Belt.Array.makeUninitializedUnsafe 5 "placeholder";;
Belt.Array.setExn arr 0 "example";;
Belt.Array.getExn arr 0 = "example"
]} *)
val make : int -> 'a -> 'a t
(** [make n e] return an array of size [n] filled with value [e]
@return an empty array when [n] is negative. *)
val range : int -> int -> int t
(** [range start finish] create an inclusive array
{[
range 0 3 = [| 0; 1; 2; 3 |];;
range 3 0 = [||];;
range 3 3 = [| 3 |]
]} *)
val rangeBy : int -> int -> step:int -> int t
(** [rangeBy start finish ~step]
@return empty array when step is 0 or negative it also return empty array when [start > finish]
{[
rangeBy 0 10 ~step:3 = [| 0; 3; 6; 9 |];;
rangeBy 0 12 ~step:3 = [| 0; 3; 6; 9; 12 |];;
rangeBy 33 0 ~step:1 = [||];;
rangeBy 33 0 ~step:(-1) = [||];;
rangeBy 3 12 ~step:(-1) = [||];;
rangeBy 3 3 ~step:0 = [||];;
rangeBy 3 3 ~step:1 = [| 3 |]
]} *)
val makeByU : int -> ((int -> 'a)[@bs]) -> 'a t
val makeBy : int -> (int -> 'a) -> 'a t
(** [makeBy n f]
return an empty array when [n] is negative return an array of size [n] populated by [f i] start from [0] to [n - 1]
{[
makeBy 5 (fun i -> i) = [| 0; 1; 2; 3; 4 |];;
makeBy 5 (fun i -> i * i) = [| 0; 1; 4; 9; 16 |]
]} *)
val makeByAndShuffleU : int -> ((int -> 'a)[@bs]) -> 'a t
val makeByAndShuffle : int -> (int -> 'a) -> 'a t
(** [makeByAndShuffle n f]
Equivalent to [shuffle (makeBy n f)] *)
val zip : 'a t -> 'b array -> ('a * 'b) array
(** [zip a b]
Create an array of pairs from corresponding elements of [a] and [b]. Stop with the shorter array
{[
zip [| 1; 2 |] [| 3; 4; 5 |] = [| (1, 3); (2, 4) |]
]} *)
val zipByU : 'a t -> 'b array -> (('a -> 'b -> 'c)[@bs]) -> 'c array
val zipBy : 'a t -> 'b array -> ('a -> 'b -> 'c) -> 'c array
(** [zipBy xs ys f]
Create an array by applying [f] to corresponding elements of [xs] and [ys] Stops with shorter array
Equivalent to [map (zip xs ys) (fun (a,b) -> f a b) ]
{[
zipBy [| 1; 2; 3 |] [| 4; 5 |] (fun a b -> (2 * a) + b) = [| 6; 9 |]
]} *)
val unzip : ('a * 'b) array -> 'a t * 'b array
(** [unzip a] takes an array of pairs and creates a pair of arrays. The first array contains all the first items of the
pairs; the second array contains all the second items.
{[
unzip [| (1, 2); (3, 4) |] = ([| 1; 3 |], [| 2; 4 |]);;
unzip [| (1, 2); (3, 4); (5, 6); (7, 8) |] = ([| 1; 3; 5; 7 |], [| 2; 4; 6; 8 |])
]} *)
val concat : 'a t -> 'a t -> 'a t
(** [concat xs ys]
@return
a fresh array containing the concatenation of the arrays [v1] and [v2];so even if [v1] or [v2] is empty;it can not
be shared
{[
concat [| 1; 2; 3 |] [| 4; 5 |] = [| 1; 2; 3; 4; 5 |];;
concat [||] [| "a"; "b"; "c" |] = [| "a"; "b"; "c" |]
]} *)
val concatMany : 'a t t -> 'a t
(** [concatMany xss]
@return a fresh array as the concatenation of [xss] (an array of arrays)
{[
concatMany [| [| 1; 2; 3 |]; [| 4; 5; 6 |]; [| 7; 8 |] |] = [| 1; 2; 3; 4; 5; 6; 7; 8 |]
]} *)
val slice : 'a t -> offset:int -> len:int -> 'a t
(** [slice xs offset len] creates a new array with the [len] elements of [xs] starting at [offset] for
[offset] can be negative;and is evaluated as [length xs - offset] [slice xs -1 1] means get the last element as a
singleton array
[slice xs (-len) len] will return a copy of the array
if the array does not have enough data;[slice] extracts through the end of sequence.
if [len] is negative;returns the empty array.
{[
slice [| 10; 11; 12; 13; 14; 15; 16 |] ~offset:2 ~len:3 = [| 12; 13; 14 |];;
slice [| 10; 11; 12; 13; 14; 15; 16 |] ~offset:(-4) ~len:3 = [| 13; 14; 15 |];;
slice [| 10; 11; 12; 13; 14; 15; 16 |] ~offset:4 ~len:9 = [| 14; 15; 16 |]
]} *)
val sliceToEnd : 'a t -> int -> 'a t
(** [sliceToEnd xs offset] creates a new array with the elements of [xs] starting at [offset]
[offset] can be negative;and is evaluated as [length xs - offset] [sliceToEnd xs -1] means get the last element as a
singleton array
[sliceToEnd xs 0] will return a copy of the array
{[
sliceToEnd [| 10; 11; 12; 13; 14; 15; 16 |] 2 = [| 12; 13; 14; 15; 16 |];;
sliceToEnd [| 10; 11; 12; 13; 14; 15; 16 |] (-4) = [| 13; 14; 15; 16 |]
]} *)
(* external copy : 'a t -> (_[@mel.as 0]) -> 'a t = "slice"
[@@mel.send] *)
val copy : 'a t -> 'a t
(** [copy a]
@return a copy of [a];that is;a fresh array containing the same elements as [a]. *)
val fill : 'a t -> offset:int -> len:int -> 'a -> unit
(** [fill arr ~offset ~len x]
Modifies [arr] in place, storing [x] in elements number [offset] to [offset + len - 1].
[offset] can be negative;and is evaluated as [length arr - offset]
[fill arr ~offset:(-1) ~len:1] means fill the last element, if the array does not have enough data;[fill] will
ignore it
{[
let arr = makeBy 5 (fun i -> i);;
fill arr ~offset:2 ~len:2 9;;
arr = [| 0; 1; 9; 9; 4 |];;
fill arr ~offset:7 ~len:2 8;;
arr = [| 0; 1; 9; 9; 4 |]
]} *)
val blit : src:'a t -> srcOffset:int -> dst:'a t -> dstOffset:int -> len:int -> unit
(** [blit ~src:v1 ~srcOffset:o1 ~dst:v2 ~dstOffset:o2 ~len]
copies [len] elements from array [v1];starting at element number [o1];to array [v2], starting at element number
[o2].
It works correctly even if [v1] and [v2] are the same array;and the source and destination chunks overlap.
[offset] can be negative;[-1] means [len - 1];if [len + offset] is still negative;it will be set as 0
For each of the examples;presume that [v1 = [|10;11;12;13;14;15;16;17|]] and [v2 = [|20;21;22;23;24;25;26;27|]]. The
result shown is the content of the destination array.
{[
Belt.Array.blit ~src:v1 ~srcOffset:4 ~dst:v2 ~dstOffset:2 ~len:3
|. [| 20; 21; 14; 15; 16; 25; 26; 27 |] Belt.Array.blit ~src:v1 ~srcOffset:4 ~dst:v1 ~dstOffset:2 ~len:3
|. [| 10; 11; 14; 15; 16; 15; 16; 17 |]
]} *)
val blitUnsafe : src:'a t -> srcOffset:int -> dst:'a t -> dstOffset:int -> len:int -> unit
(** {b Unsafe} blit without bounds checking *)
val forEachU : 'a t -> (('a -> unit)[@bs]) -> unit
val forEach : 'a t -> ('a -> unit) -> unit
(** [forEach xs f]
Call [f] on each element of [xs] from the beginning to end. [f] returns [unit];so no new array is created. Use
[forEach] when you are primarily concerned with repetitively creating side effects.
{[
forEach [| "a"; "b"; "c" |] (fun x -> Js.log ("Item: " ^ x));;
(* prints:
Item: a
Item: b
Item: c
*)
let total = ref 0;;
forEach [| 1; 2; 3; 4 |] (fun x -> total := !total + x);;
!total = 1 + 2 + 3 + 4
]} *)
val mapU : 'a t -> (('a -> 'b)[@bs]) -> 'b array
val map : 'a t -> ('a -> 'b) -> 'b array
(** [map xs f ]
@return a new array by calling [f] for each element of [xs] from the beginning to end
{[
map [| 1; 2 |] (fun x -> x + 10) = [| 11; 12 |]
]} *)
val flatMapU : 'a t -> (('a -> 'b t)[@bs]) -> 'b t
val flatMap : 'a t -> ('a -> 'b t) -> 'b t
(** [flatMap xs f] **return** a new array by calling `f` for each element of `xs` from the beginning to end, and then
concatenating the results ``` flatMap [|1;2|] (fun x-> [|x + 10;x + 20|]) = [|11;21;12;22|] ``` *)
val getByU : 'a t -> (('a -> bool)[@bs]) -> 'a option
val getBy : 'a t -> ('a -> bool) -> 'a option
(** [getBy xs p] returns [Some value] for the first value in [xs] that satisifies the predicate function [p]; returns
[None] if no element satisifies the function.
{[
getBy [|1;4;3;2|] (fun x -> x mod 2 = 0) = Some 4
getBy [|15;13;11|] (fun x -> x mod 2 = 0) = None
]} *)
val getIndexByU : 'a t -> (('a -> bool)[@bs]) -> int option
val getIndexBy : 'a t -> ('a -> bool) -> int option
(** [getIndexBy xs p] returns [Some index] for the first value in [xs] that satisifies the predicate function [p];
returns [None] if no element satisifies the function.
{[
getIndexBy [|1;4;3;2|] (fun x -> x mod 2 = 0) = Some 1
getIndexBy [|15;13;11|] (fun x -> x mod 2 = 0) = None
]} *)
val keepU : 'a t -> (('a -> bool)[@bs]) -> 'a t
val keep : 'a t -> ('a -> bool) -> 'a t
(** [keep xs p ]
@return a new array that keeps all elements satisfying [p]
{[
keep [| 1; 2; 3 |] (fun x -> x mod 2 = 0) = [| 2 |]
]} *)
val keepWithIndexU : 'a t -> (('a -> int -> bool)[@bs]) -> 'a t
val keepWithIndex : 'a t -> ('a -> int -> bool) -> 'a t
(** [keepWithIndex xs p ]
@return
a new array that keeps all elements satisfying [p]. The predicate [p] takes two arguments: the element from [xs]
and the index starting from 0.
{[
keepWithIndex [| 1; 2; 3 |] (fun _x i -> i = 1) = [| 2 |]
]} *)
val keepMapU : 'a t -> (('a -> 'b option)[@bs]) -> 'b array
val keepMap : 'a t -> ('a -> 'b option) -> 'b array
(** [keepMap xs p]
@return a new array that keeps all elements that return a non-None when applied to [p]
{[
keepMap [| 1; 2; 3 |] (fun x -> if x mod 2 = 0 then Some x else None) = [| 2 |]
]} *)
val forEachWithIndexU : 'a t -> ((int -> 'a -> unit)[@bs]) -> unit
val forEachWithIndex : 'a t -> (int -> 'a -> unit) -> unit
(** [forEachWithIndex xs f]
The same as {!forEach}; except that [f] is supplied with two arguments: the index starting from 0 and the element
from [xs]
{[
forEachWithIndex [| "a"; "b"; "c" |] (fun i x -> Js.log ("Item " ^ string_of_int i ^ " is " ^ x));;
(* prints:
Item 0 is a
Item 1 is b
Item 2 is c
*)
let total = ref 0;;
forEachWithIndex [| 10; 11; 12; 13 |] (fun i x -> total := !total + x + i);;
!total = 0 + 10 + 1 + 11 + 2 + 12 + 3 + 13
]} *)
val mapWithIndexU : 'a t -> ((int -> 'a -> 'b)[@bs]) -> 'b t
val mapWithIndex : 'a t -> (int -> 'a -> 'b) -> 'b t
(** [mapWithIndex xs f] applies [f] to each element of [xs]. Function [f] takes two arguments: the index starting from 0
and the element from [xs].
{[
mapWithIndex [| 1; 2; 3 |] (fun i x -> i + x) = [| 0 + 1; 1 + 2; 2 + 3 |]
]} *)
val partitionU : 'a t -> (('a -> bool)[@bs]) -> 'a t * 'a t
val partition : 'a t -> ('a -> bool) -> 'a t * 'a t
(** [partition f a] split array into tuple of two arrays based on predicate f; first of tuple where predicate cause
true, second where predicate cause false
{[
partition [| 1; 2; 3; 4; 5 |] (fun x -> x mod 2 = 0) = ([| 2; 4 |], [| 1; 2; 3 |]);;
partition [| 1; 2; 3; 4; 5 |] (fun x -> x mod 2 <> 0) = ([| 1; 2; 3 |], [| 2; 4 |])
]} *)
val reduceU : 'b array -> 'a -> (('a -> 'b -> 'a)[@bs]) -> 'a
val reduce : 'b array -> 'a -> ('a -> 'b -> 'a) -> 'a
(** [reduce xs init f]
Applies [f] to each element of [xs] from beginning to end. Function [f] has two parameters: the item from the list
and an “accumulator”;which starts with a value of [init]. [reduce] returns the final value of the accumulator.
{[
reduce [| 2; 3; 4 |] 1 ( + ) = 10;;
reduce [| "a"; "b"; "c"; "d" |] "" ( ^ ) = "abcd"
]} *)
val reduceReverseU : 'b array -> 'a -> (('a -> 'b -> 'a)[@bs]) -> 'a
val reduceReverse : 'b array -> 'a -> ('a -> 'b -> 'a) -> 'a
(** [reduceReverse xs init f]
Works like {!reduce};except that function [f] is applied to each item of [xs] from the last back to the first.
{[
reduceReverse [| "a"; "b"; "c"; "d" |] "" ( ^ ) = "dcba"
]} *)
val reduceReverse2U : 'a t -> 'b array -> 'c -> (('c -> 'a -> 'b -> 'c)[@bs]) -> 'c
val reduceReverse2 : 'a t -> 'b array -> 'c -> ('c -> 'a -> 'b -> 'c) -> 'c
(** [reduceReverse2 xs ys init f] Reduces two arrays [xs] and [ys];taking items starting at
[min (length xs) (length ys)] down to and including zero.
{[
reduceReverse2 [| 1; 2; 3 |] [| 1; 2 |] 0 (fun acc x y -> acc + x + y) = 6
]} *)
val reduceWithIndexU : 'a t -> 'b -> (('b -> 'a -> int -> 'b)[@bs]) -> 'b
val reduceWithIndex : 'a t -> 'b -> ('b -> 'a -> int -> 'b) -> 'b
(** [reduceWithIndex xs f]
Applies [f] to each element of [xs] from beginning to end. Function [f] has three parameters: the item from the
array and an “accumulator”, which starts with a value of [init] and the index of each element. [reduceWithIndex]
returns the final value of the accumulator.
{[
reduceWithIndex [| 1; 2; 3; 4 |] 0 (fun acc x i -> acc + x + i) = 16
]} *)
val joinWithU : 'a t -> string -> (('a -> string)[@bs]) -> string
val joinWith : 'a t -> string -> ('a -> string) -> string
(** [joinWith xs sep toString]
Concatenates all the elements of [xs] converted to string with [toString], each separated by [sep], the string given
as the second argument, into a single string. If the array has only one element, then that element will be returned
without using the separator. If the array is empty, the empty string will be returned.
{[
joinWith [| 0; 1 |] ", " string_of_int
= "0, 1" joinWith [||] " " string_of_int
= "" joinWith [| 1 |] " " string_of_int = "1"
]} *)
val someU : 'a t -> (('a -> bool)[@bs]) -> bool
val some : 'a t -> ('a -> bool) -> bool
(** [some xs p]
@return
true if at least one of the elements in [xs] satifies [p];where [p] is a {i predicate}: a function taking an
element and returning a [bool].
{[
some [| 2; 3; 4 |] (fun x -> x mod 2 = 1) = true;;
some [| -1; -3; -5 |] (fun x -> x > 0) = false
]} *)
val everyU : 'a t -> (('a -> bool)[@bs]) -> bool
val every : 'a t -> ('a -> bool) -> bool
(** [every xs p]
@return
true if all elements satisfy [p];where [p] is a {i predicate}: a function taking an element and returning a
[bool].
{[
every [| 1; 3; 5 |] (fun x -> x mod 2 = 1) = true;;
every [| 1; -3; 5 |] (fun x -> x > 0) = false
]} *)
val every2U : 'a t -> 'b t -> (('a -> 'b -> bool)[@bs]) -> bool
val every2 : 'a t -> 'b t -> ('a -> 'b -> bool) -> bool
(** [every2 xs ys p] returns true if [p xi yi] is true for all pairs of elements up to the shorter length (i.e.
[min (length xs) (length ys)])
{[
every2 [| 1; 2; 3 |] [| 0; 1 |] ( > ) = true;;
every2 [||] [| 1 |] (fun x y -> x > y) = true;;
every2 [| 2; 3 |] [| 1 |] (fun x y -> x > y) = true;;
every2 [| 0; 1 |] [| 5; 0 |] (fun x y -> x > y) = false
]} *)
val some2U : 'a t -> 'b t -> (('a -> 'b -> bool)[@bs]) -> bool
val some2 : 'a t -> 'b t -> ('a -> 'b -> bool) -> bool
(** [some2 xs ys p] returns true if [p xi yi] is true for any pair of elements up to the shorter length (i.e.
[min (length xs) (length ys)])
{[
some2 [| 0; 2 |] [| 1; 0; 3 |] ( > ) = true;;
some2 [||] [| 1 |] (fun x y -> x > y) = false;;
some2 [| 2; 3 |] [| 1; 4 |] (fun x y -> x > y) = true
]} *)
val cmpU : 'a t -> 'a t -> (('a -> 'a -> int)[@bs]) -> int
val cmp : 'a t -> 'a t -> ('a -> 'a -> int) -> int
(** [cmp xs ys f]
- Compared by length if [length xs <> length ys];returning -1 if[length xs < length ys] or 1 if
[length xs > length ys]
- Otherwise compare one by one [f x y]. [f] returns
- a negative number if [x] is “less than” [y]
- zero if [x] is “equal to” [y]
- a positive number if [x] is “greater than” [y]
- The comparison returns the first non-zero result of [f];or zero if [f] returns zero for all [x] and [y].
{[
cmp [| 1; 3; 5 |] [| 1; 4; 2 |] (fun a b -> compare a b) = -1;;
cmp [| 1; 3; 5 |] [| 1; 2; 3 |] (fun a b -> compare a b) = 1;;
cmp [| 1; 3; 5 |] [| 1; 3; 5 |] (fun a b -> compare a b) = 0
]} *)
val eqU : 'a t -> 'a t -> (('a -> 'a -> bool)[@bs]) -> bool
val eq : 'a t -> 'a t -> ('a -> 'a -> bool) -> bool
(** [eq xs ys]
- return false if length is not the same
- otherwise compare items one by one using [f xi yi];and return true if all results are true;false otherwise
{[
eq [| 1; 2; 3 |] [| -1; -2; -3 |] (fun a b -> abs a = abs b) = true
]} *)
(* external truncateToLengthUnsafe : 'a t -> int -> unit = "length"
[@@mel.set] *)
val truncateToLengthUnsafe : 'a t -> int -> 'a t
(** {b Unsafe} Native-only approximation of the JavaScript [truncateToLengthUnsafe].
On native this returns a fresh truncated copy of [xs]. It does not mutate the input array length, and it cannot grow
the array because OCaml arrays are fixed length.
Raises [Invalid_argument] if [n] is negative or larger than [length xs].
{[
let arr = [| "ant"; "bee"; "cat"; "dog"; "elk" |]
let truncated = truncateToLengthUnsafe arr 3;;
truncated = [| "ant"; "bee"; "cat" |];;
arr = [| "ant"; "bee"; "cat"; "dog"; "elk" |]
]} *)
val initU : int -> ((int -> 'a)[@bs]) -> 'a t
val init : int -> (int -> 'a) -> 'a t
val push : 'a t -> 'a -> [ `Do_not_use_Array_push_in_native ]
[@@alert not_implemented "is not implemented in native under server-reason-react.belt"]
(** Native-only sentinel value for the JavaScript [push] operation. OCaml arrays are fixed length and cannot grow in
place like JavaScript arrays. Use a copy-based helper instead when you need to append on native. *)
================================================
FILE: packages/Belt/src/Belt_Float.ml
================================================
let toInt = Stdlib.int_of_float
let fromInt = Stdlib.float_of_int
let fromString i = try Some (float_of_string i) with _ -> None
let toString value =
let string = Stdlib.string_of_float value in
let length = String.length string in
if length > 0 && string.[length - 1] = '.' then String.sub string 0 (length - 1) else string
let ( + ) = Stdlib.( +. )
let ( - ) = Stdlib.( -. )
let ( * ) = Stdlib.( *. )
let ( / ) = Stdlib.( /. )
================================================
FILE: packages/Belt/src/Belt_Float.mli
================================================
val toInt : float -> int
val fromInt : int -> float
val fromString : string -> float option
val toString : float -> string
val ( + ) : float -> float -> float
val ( - ) : float -> float -> float
val ( * ) : float -> float -> float
val ( / ) : float -> float -> float
================================================
FILE: packages/Belt/src/Belt_HashMap.ml
================================================
module N = Belt_internalBuckets
module C = Belt_internalBucketsType
module A = Belt_Array
type ('a, 'id) eq = ('a, 'id) Belt_Id.eq
type ('a, 'id) hash = ('a, 'id) Belt_Id.hash
type ('a, 'id) id = ('a, 'id) Belt_Id.hashable
type ('a, 'b, 'id) t = (('a, 'id) hash, ('a, 'id) eq, 'a, 'b) N.t
let clear = C.clear
let size = C.size
let forEach = N.forEach
let forEachU = N.forEachU
let reduce = N.reduce
let reduceU = N.reduceU
let logStats = N.logStats
let keepMapInPlaceU = N.keepMapInPlaceU
let keepMapInPlace = N.keepMapInPlace
let toArray = N.toArray
let copy = N.copy
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let getBucketHistogram = N.getBucketHistogram
let isEmpty = C.isEmpty
let rec copyBucketReHash ~hash ~h_buckets ~ndata_tail old_bucket =
match C.toOpt old_bucket with
| None -> ()
| Some cell ->
let nidx = hash (N.key cell) land (A.length h_buckets - 1) in
let v = C.return cell in
(match C.toOpt (A.getUnsafe ndata_tail nidx) with
| None -> A.setUnsafe h_buckets nidx v
| Some tail -> N.nextSet tail v);
A.setUnsafe ndata_tail nidx v;
copyBucketReHash ~hash ~h_buckets ~ndata_tail (N.next cell)
let resize ~hash h =
let odata = C.buckets h in
let osize = A.length odata in
let nsize = osize * 2 in
if nsize >= osize then (
let h_buckets = A.makeUninitialized nsize in
let ndata_tail = A.makeUninitialized nsize in
C.bucketsSet h h_buckets;
for i = 0 to osize - 1 do
copyBucketReHash ~hash ~h_buckets ~ndata_tail (A.getUnsafe odata i)
done;
for i = 0 to nsize - 1 do
match C.toOpt (A.getUnsafe ndata_tail i) with None -> () | Some tail -> N.nextSet tail C.emptyOpt
done)
let rec replaceInBucket ~eq key info cell =
if eq (N.key cell) key then (
N.valueSet cell info;
false)
else match C.toOpt (N.next cell) with None -> true | Some cell -> replaceInBucket ~eq key info cell
let set0 h key value ~eq ~hash =
let h_buckets = C.buckets h in
let buckets_len = A.length h_buckets in
let i = hash key land (buckets_len - 1) in
let l = A.getUnsafe h_buckets i in
(match C.toOpt l with
| None ->
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:C.emptyOpt));
C.sizeSet h (C.size h + 1)
| Some bucket ->
if replaceInBucket ~eq key value bucket then (
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:l));
C.sizeSet h (C.size h + 1)));
if C.size h > buckets_len lsl 1 then resize ~hash h
let set h key value = set0 h key value ~eq:(Belt_Id.getEqInternal (C.eq h)) ~hash:(Belt_Id.getHashInternal (C.hash h))
let rec removeInBucket h h_buckets i key prec bucket ~eq =
match C.toOpt bucket with
| None -> ()
| Some cell ->
let cell_next = N.next cell in
if eq (N.key cell) key then (
N.nextSet prec cell_next;
C.sizeSet h (C.size h - 1))
else removeInBucket ~eq h h_buckets i key cell cell_next
let remove h key =
let h_buckets = C.buckets h in
let i = (Belt_Id.getHashInternal (C.hash h)) key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets i in
match C.toOpt bucket with
| None -> ()
| Some cell ->
let eq = Belt_Id.getEqInternal (C.eq h) in
if eq (N.key cell) key then (
A.setUnsafe h_buckets i (N.next cell);
C.sizeSet h (C.size h - 1))
else removeInBucket ~eq h h_buckets i key cell (N.next cell)
let rec getAux ~eq key buckets =
match C.toOpt buckets with
| None -> None
| Some cell -> if eq key (N.key cell) then Some (N.value cell) else getAux ~eq key (N.next cell)
let get h key =
let h_buckets = C.buckets h in
let nid = (Belt_Id.getHashInternal (C.hash h)) key land (A.length h_buckets - 1) in
match C.toOpt @@ A.getUnsafe h_buckets nid with
| None -> None
| Some cell1 -> (
let eq = Belt_Id.getEqInternal (C.eq h) in
if eq key (N.key cell1) then Some (N.value cell1)
else
match C.toOpt (N.next cell1) with
| None -> None
| Some cell2 -> (
if eq key (N.key cell2) then Some (N.value cell2)
else
match C.toOpt (N.next cell2) with
| None -> None
| Some cell3 -> if eq key (N.key cell3) then Some (N.value cell3) else getAux ~eq key (N.next cell3)))
let rec memInBucket key cell ~eq =
eq (N.key cell) key
|| match C.toOpt (N.next cell) with None -> false | Some nextCell -> memInBucket ~eq key nextCell
let has h key =
let h_buckets = C.buckets h in
let nid = (Belt_Id.getHashInternal (C.hash h)) key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets nid in
match C.toOpt bucket with None -> false | Some bucket -> memInBucket ~eq:(Belt_Id.getEqInternal (C.eq h)) key bucket
let make (type key identity) ~hintSize ~(id : (key, identity) id) =
let module M = (val id) in
C.make ~hash:M.hash ~eq:M.eq ~hintSize
let fromArray (type a identity) arr ~(id : (a, identity) id) =
let module M = (val id) in
let hash, eq = (M.hash, M.eq) in
let len = A.length arr in
let v = C.make ~hash ~eq ~hintSize:len in
let eq, hash = (Belt_Id.getEqInternal eq, Belt_Id.getHashInternal hash) in
for i = 0 to len - 1 do
let key, value = A.getUnsafe arr i in
set0 ~eq ~hash v key value
done;
v
let mergeMany h arr =
let hash, eq = (Belt_Id.getHashInternal (C.hash h), Belt_Id.getEqInternal (C.eq h)) in
let len = A.length arr in
for i = 0 to len - 1 do
let key, value = A.getUnsafe arr i in
set0 h ~eq ~hash key value
done
module Int = Belt_HashMapInt
module String = Belt_HashMapString
================================================
FILE: packages/Belt/src/Belt_HashMap.mli
================================================
(* Copyright (C) 2018 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A {b mutable} Hash map which allows customized hash behavior.
All data are parameterized by not its only type but also a unique identity in the time of initialization, so that
two {i HashMaps of ints} initialized with different {i hash} functions will have different type.
For example:
{[
type t = int
module I0 =
(val Belt.Id.hashableU
~hash:(fun[\@u] (a : t) -> a & 0xff_ff)
~eq:(fun[\@u] a b -> a = b)
)
let s0 : (_, string,_) t = make ~hintSize:40 ~id:(module I0)
module I1 =
(val Belt.Id.hashableU
~hash:(fun[\@u] (a : t) -> a & 0xff)
~eq:(fun[\@u] a b -> a = b)
)
let s1 : (_, string,_) t = make ~hintSize:40 ~id:(module I1)
]}
The invariant must be held: for two elements who are {i equal}, their hashed value should be the same
Here the compiler would infer [s0] and [s1] having different type so that it would not mix.
{[
val s0 : (int, I0.identity) t
val s1 : (int, I1.identity) t
]}
We can add elements to the collection:
{[
let () =
add s1 0 "3";
add s1 1 "3"
]}
Since this is an mutable data strucure, [s1] will contain two pairs. *)
module Int = Belt_HashMapInt
(** Specalized when key type is [int], more efficient than the generic type *)
module String = Belt_HashMapString
(** Specalized when key type is [string], more efficient than the generic type *)
type ('key, 'value, 'id) t
(** The type of hash tables from type ['key] to type ['value]. *)
type ('a, 'id) id = ('a, 'id) Belt_Id.hashable
val make : hintSize:int -> id:('key, 'id) id -> ('key, 'value, 'id) t
(*TODO: allow randomization for security *)
val clear : ('key, 'value, 'id) t -> unit
(** Empty a hash table. *)
val isEmpty : _ t -> bool
val set : ('key, 'value, 'id) t -> 'key -> 'value -> unit
(** [set tbl k v] if [k] does not exist, add the binding [k,v], otherwise, update the old value with the new [v] *)
val copy : ('key, 'value, 'id) t -> ('key, 'value, 'id) t
val get : ('key, 'value, 'id) t -> 'key -> 'value option
val has : ('key, 'value, 'id) t -> 'key -> bool
(** [has tbl x] checks if [x] is bound in [tbl]. *)
val remove : ('key, 'value, 'id) t -> 'key -> unit
val forEachU : ('key, 'value, 'id) t -> (('key -> 'value -> unit)[@u]) -> unit
val forEach : ('key, 'value, 'id) t -> ('key -> 'value -> unit) -> unit
(** [forEach tbl f] applies [f] to all bindings in table [tbl]. [f] receives the key as first argument, and the
associated value as second argument. Each binding is presented exactly once to [f]. *)
val reduceU : ('key, 'value, 'id) t -> 'c -> (('c -> 'key -> 'value -> 'c)[@u]) -> 'c
val reduce : ('key, 'value, 'id) t -> 'c -> ('c -> 'key -> 'value -> 'c) -> 'c
(** [reduce tbl init f] computes [(f kN dN ... (f k1 d1 init)...)], where [k1 ... kN] are the keys of all bindings in
[tbl], and [d1 ... dN] are the associated values. Each binding is presented exactly once to [f].
The order in which the bindings are passed to [f] is unspecified. However, if the table contains several bindings
for the same key, they are passed to [f] in reverse order of introduction, that is, the most recent binding is
passed first. *)
val keepMapInPlaceU : ('key, 'value, 'id) t -> (('key -> 'value -> 'value option)[@u]) -> unit
val keepMapInPlace : ('key, 'value, 'id) t -> ('key -> 'value -> 'value option) -> unit
val size : _ t -> int
(** [size tbl] returns the number of bindings in [tbl]. It takes constant time. *)
val toArray : ('key, 'value, 'id) t -> ('key * 'value) array
val keysToArray : ('key, _, _) t -> 'key array
val valuesToArray : (_, 'value, _) t -> 'value array
val fromArray : ('key * 'value) array -> id:('key, 'id) id -> ('key, 'value, 'id) t
val mergeMany : ('key, 'value, 'id) t -> ('key * 'value) array -> unit
val getBucketHistogram : _ t -> int array
val logStats : _ t -> unit
================================================
FILE: packages/Belt/src/Belt_HashMapInt.ml
================================================
[@@@ocaml.text " Adapted by Authors of BuckleScript 2017 "]
type key = int
type seed = int
let caml_hash_mix_int = Caml_hash.caml_hash_mix_int
let final_mix = Caml_hash.caml_hash_final_mix
let hash (s : key) = Nativeint.to_int (final_mix (caml_hash_mix_int Nativeint.zero (Nativeint.of_int s)))
module N = Belt_internalBuckets
module C = Belt_internalBucketsType
module A = Belt_Array
type 'b t = (unit, unit, key, 'b) N.t
let rec copyBucketReHash ~h_buckets ~ndata_tail old_bucket =
match C.toOpt old_bucket with
| None -> ()
| Some cell ->
let nidx = hash (N.key cell) land (A.length h_buckets - 1) in
let v = C.return cell in
(match C.toOpt (A.getUnsafe ndata_tail nidx) with
| None -> A.setUnsafe h_buckets nidx v
| Some tail -> N.nextSet tail v);
A.setUnsafe ndata_tail nidx v;
copyBucketReHash ~h_buckets ~ndata_tail (N.next cell)
let resize h =
let odata = C.buckets h in
let osize = A.length odata in
let nsize = osize * 2 in
if nsize >= osize then (
let h_buckets = A.makeUninitialized nsize in
let ndata_tail = A.makeUninitialized nsize in
C.bucketsSet h h_buckets;
for i = 0 to osize - 1 do
copyBucketReHash ~h_buckets ~ndata_tail (A.getUnsafe odata i)
done;
for i = 0 to nsize - 1 do
match C.toOpt (A.getUnsafe ndata_tail i) with None -> () | Some tail -> N.nextSet tail C.emptyOpt
done)
let rec replaceInBucket (key : key) info cell =
if N.key cell = key then (
N.valueSet cell info;
false)
else match C.toOpt (N.next cell) with None -> true | Some cell -> replaceInBucket key info cell
let set h (key : key) value =
let h_buckets = C.buckets h in
let buckets_len = A.length h_buckets in
let i = hash key land (buckets_len - 1) in
let l = A.getUnsafe h_buckets i in
(match C.toOpt l with
| None ->
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:C.emptyOpt));
C.sizeSet h (C.size h + 1)
| Some bucket ->
if replaceInBucket key value bucket then (
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:l));
C.sizeSet h (C.size h + 1)));
if C.size h > buckets_len lsl 1 then resize h
let rec removeInBucket h h_buckets i (key : key) prec buckets =
match C.toOpt buckets with
| None -> ()
| Some cell ->
let cell_next = N.next cell in
if N.key cell = key then (
N.nextSet prec cell_next;
C.sizeSet h (C.size h - 1))
else removeInBucket h h_buckets i key cell cell_next
let remove h key =
let h_buckets = C.buckets h in
let i = hash key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets i in
match C.toOpt bucket with
| None -> ()
| Some cell ->
if N.key cell = key then (
A.setUnsafe h_buckets i (N.next cell);
C.sizeSet h (C.size h - 1))
else removeInBucket h h_buckets i key cell (N.next cell)
let rec getAux (key : key) buckets =
match C.toOpt buckets with
| None -> None
| Some cell -> if key = N.key cell then Some (N.value cell) else getAux key (N.next cell)
let get h (key : key) =
let h_buckets = C.buckets h in
let nid = hash key land (A.length h_buckets - 1) in
match C.toOpt @@ A.getUnsafe h_buckets nid with
| None -> None
| Some cell1 -> (
if key = N.key cell1 then Some (N.value cell1)
else
match C.toOpt (N.next cell1) with
| None -> None
| Some cell2 -> (
if key = N.key cell2 then Some (N.value cell2)
else
match C.toOpt (N.next cell2) with
| None -> None
| Some cell3 -> if key = N.key cell3 then Some (N.value cell3) else getAux key (N.next cell3)))
let rec memInBucket (key : key) cell =
N.key cell = key || match C.toOpt (N.next cell) with None -> false | Some nextCell -> memInBucket key nextCell
let has h key =
let h_buckets = C.buckets h in
let nid = hash key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets nid in
match C.toOpt bucket with None -> false | Some bucket -> memInBucket key bucket
let make ~hintSize = C.make ~hintSize ~hash:() ~eq:()
let clear = C.clear
let size = C.size
let forEachU = N.forEachU
let forEach = N.forEach
let reduceU = N.reduceU
let reduce = N.reduce
let logStats = N.logStats
let keepMapInPlaceU = N.keepMapInPlaceU
let keepMapInPlace = N.keepMapInPlace
let toArray = N.toArray
let copy = N.copy
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let getBucketHistogram = N.getBucketHistogram
let isEmpty = C.isEmpty
let fromArray arr =
let len = A.length arr in
let v = make len in
for i = 0 to len - 1 do
let k, value = A.getUnsafe arr i in
set v k value
done;
v
let mergeMany h arr =
let len = A.length arr in
for i = 0 to len - 1 do
let k, v = A.getUnsafe arr i in
set h k v
done
================================================
FILE: packages/Belt/src/Belt_HashMapInt.mli
================================================
type key = int
type 'b t
val make : hintSize:int -> 'b t
val clear : 'b t -> unit
val isEmpty : _ t -> bool
val set : 'a t -> key -> 'a -> unit
(** [setDone tbl k v] if [k] does not exist, add the binding [k,v], otherwise, update the old value with the new [v] *)
val copy : 'a t -> 'a t
val get : 'a t -> key -> 'a option
val has : 'b t -> key -> bool
val remove : 'a t -> key -> unit
val forEachU : 'b t -> ((key -> 'b -> unit)[@u]) -> unit
val forEach : 'b t -> (key -> 'b -> unit) -> unit
val reduceU : 'b t -> 'c -> (('c -> key -> 'b -> 'c)[@u]) -> 'c
val reduce : 'b t -> 'c -> ('c -> key -> 'b -> 'c) -> 'c
val keepMapInPlaceU : 'a t -> ((key -> 'a -> 'a option)[@u]) -> unit
val keepMapInPlace : 'a t -> (key -> 'a -> 'a option) -> unit
val size : _ t -> int
val toArray : 'a t -> (key * 'a) array
val keysToArray : 'a t -> key array
val valuesToArray : 'a t -> 'a array
val fromArray : (key * 'a) array -> 'a t
val mergeMany : 'a t -> (key * 'a) array -> unit
val getBucketHistogram : _ t -> int array
val logStats : _ t -> unit
================================================
FILE: packages/Belt/src/Belt_HashMapString.ml
================================================
[@@@ocaml.text " Adapted by Authors of BuckleScript 2017 "]
type key = string
type seed = int
let caml_hash_mix_string = Caml_hash.caml_hash_mix_string
let final_mix = Caml_hash.caml_hash_final_mix
let hash (s : key) = Nativeint.to_int (final_mix (caml_hash_mix_string Nativeint.zero s))
module N = Belt_internalBuckets
module C = Belt_internalBucketsType
module A = Belt_Array
type 'b t = (unit, unit, key, 'b) N.t
let rec copyBucketReHash ~h_buckets ~ndata_tail old_bucket =
match C.toOpt old_bucket with
| None -> ()
| Some cell ->
let nidx = hash (N.key cell) land (A.length h_buckets - 1) in
let v = C.return cell in
(match C.toOpt (A.getUnsafe ndata_tail nidx) with
| None -> A.setUnsafe h_buckets nidx v
| Some tail -> N.nextSet tail v);
A.setUnsafe ndata_tail nidx v;
copyBucketReHash ~h_buckets ~ndata_tail (N.next cell)
let resize h =
let odata = C.buckets h in
let osize = A.length odata in
let nsize = osize * 2 in
if nsize >= osize then (
let h_buckets = A.makeUninitialized nsize in
let ndata_tail = A.makeUninitialized nsize in
C.bucketsSet h h_buckets;
for i = 0 to osize - 1 do
copyBucketReHash ~h_buckets ~ndata_tail (A.getUnsafe odata i)
done;
for i = 0 to nsize - 1 do
match C.toOpt (A.getUnsafe ndata_tail i) with None -> () | Some tail -> N.nextSet tail C.emptyOpt
done)
let rec replaceInBucket (key : key) info cell =
if N.key cell = key then (
N.valueSet cell info;
false)
else match C.toOpt (N.next cell) with None -> true | Some cell -> replaceInBucket key info cell
let set h (key : key) value =
let h_buckets = C.buckets h in
let buckets_len = A.length h_buckets in
let i = hash key land (buckets_len - 1) in
let l = A.getUnsafe h_buckets i in
(match C.toOpt l with
| None ->
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:C.emptyOpt));
C.sizeSet h (C.size h + 1)
| Some bucket ->
if replaceInBucket key value bucket then (
A.setUnsafe h_buckets i (C.return (N.bucket ~key ~value ~next:l));
C.sizeSet h (C.size h + 1)));
if C.size h > buckets_len lsl 1 then resize h
let rec removeInBucket h h_buckets i (key : key) prec buckets =
match C.toOpt buckets with
| None -> ()
| Some cell ->
let cell_next = N.next cell in
if N.key cell = key then (
N.nextSet prec cell_next;
C.sizeSet h (C.size h - 1))
else removeInBucket h h_buckets i key cell cell_next
let remove h key =
let h_buckets = C.buckets h in
let i = hash key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets i in
match C.toOpt bucket with
| None -> ()
| Some cell ->
if N.key cell = key then (
A.setUnsafe h_buckets i (N.next cell);
C.sizeSet h (C.size h - 1))
else removeInBucket h h_buckets i key cell (N.next cell)
let rec getAux (key : key) buckets =
match C.toOpt buckets with
| None -> None
| Some cell -> if key = N.key cell then Some (N.value cell) else getAux key (N.next cell)
let get h (key : key) =
let h_buckets = C.buckets h in
let nid = hash key land (A.length h_buckets - 1) in
match C.toOpt @@ A.getUnsafe h_buckets nid with
| None -> None
| Some cell1 -> (
if key = N.key cell1 then Some (N.value cell1)
else
match C.toOpt (N.next cell1) with
| None -> None
| Some cell2 -> (
if key = N.key cell2 then Some (N.value cell2)
else
match C.toOpt (N.next cell2) with
| None -> None
| Some cell3 -> if key = N.key cell3 then Some (N.value cell3) else getAux key (N.next cell3)))
let rec memInBucket (key : key) cell =
N.key cell = key || match C.toOpt (N.next cell) with None -> false | Some nextCell -> memInBucket key nextCell
let has h key =
let h_buckets = C.buckets h in
let nid = hash key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets nid in
match C.toOpt bucket with None -> false | Some bucket -> memInBucket key bucket
let make ~hintSize = C.make ~hintSize ~hash:() ~eq:()
let clear = C.clear
let size = C.size
let forEachU = N.forEachU
let forEach = N.forEach
let reduceU = N.reduceU
let reduce = N.reduce
let logStats = N.logStats
let keepMapInPlaceU = N.keepMapInPlaceU
let keepMapInPlace = N.keepMapInPlace
let toArray = N.toArray
let copy = N.copy
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let getBucketHistogram = N.getBucketHistogram
let isEmpty = C.isEmpty
let fromArray arr =
let len = A.length arr in
let v = make len in
for i = 0 to len - 1 do
let k, value = A.getUnsafe arr i in
set v k value
done;
v
let mergeMany h arr =
let len = A.length arr in
for i = 0 to len - 1 do
let k, v = A.getUnsafe arr i in
set h k v
done
================================================
FILE: packages/Belt/src/Belt_HashMapString.mli
================================================
type key = string
type 'b t
val make : hintSize:int -> 'b t
val clear : 'b t -> unit
val isEmpty : _ t -> bool
val set : 'a t -> key -> 'a -> unit
(** [setDone tbl k v] if [k] does not exist, add the binding [k,v], otherwise, update the old value with the new [v] *)
val copy : 'a t -> 'a t
val get : 'a t -> key -> 'a option
val has : 'b t -> key -> bool
val remove : 'a t -> key -> unit
val forEachU : 'b t -> ((key -> 'b -> unit)[@u]) -> unit
val forEach : 'b t -> (key -> 'b -> unit) -> unit
val reduceU : 'b t -> 'c -> (('c -> key -> 'b -> 'c)[@u]) -> 'c
val reduce : 'b t -> 'c -> ('c -> key -> 'b -> 'c) -> 'c
val keepMapInPlaceU : 'a t -> ((key -> 'a -> 'a option)[@u]) -> unit
val keepMapInPlace : 'a t -> (key -> 'a -> 'a option) -> unit
val size : _ t -> int
val toArray : 'a t -> (key * 'a) array
val keysToArray : 'a t -> key array
val valuesToArray : 'a t -> 'a array
val fromArray : (key * 'a) array -> 'a t
val mergeMany : 'a t -> (key * 'a) array -> unit
val getBucketHistogram : _ t -> int array
val logStats : _ t -> unit
================================================
FILE: packages/Belt/src/Belt_HashSet.ml
================================================
module Int = Belt_HashSetInt
module String = Belt_HashSetString
module N = Belt_internalSetBuckets
module C = Belt_internalBucketsType
module A = Belt_Array
type ('a, 'id) eq = ('a, 'id) Belt_Id.eq
type ('a, 'id) hash = ('a, 'id) Belt_Id.hash
type ('a, 'id) id = ('a, 'id) Belt_Id.hashable
type ('a, 'id) t = (('a, 'id) hash, ('a, 'id) eq, 'a) N.t
let rec copyBucket ~hash ~h_buckets ~ndata_tail old_bucket =
match C.toOpt old_bucket with
| None -> ()
| Some cell ->
let nidx = (Belt_Id.getHashInternal hash) (N.key cell) land (A.length h_buckets - 1) in
let v = C.return cell in
(match C.toOpt (A.getUnsafe ndata_tail nidx) with
| None -> A.setUnsafe h_buckets nidx v
| Some tail -> N.nextSet tail v);
A.setUnsafe ndata_tail nidx v;
copyBucket ~hash ~h_buckets ~ndata_tail (N.next cell)
let tryDoubleResize ~hash h =
let odata = C.buckets h in
let osize = A.length odata in
let nsize = osize * 2 in
if nsize >= osize then (
let h_buckets = A.makeUninitialized nsize in
C.bucketsSet h h_buckets;
let rec reinsertBucket bucket =
match C.toOpt bucket with
| None -> ()
| Some cell ->
let next = N.next cell in
let index = (Belt_Id.getHashInternal hash) (N.key cell) land (nsize - 1) in
A.setUnsafe h_buckets index (C.return @@ N.bucket ~key:(N.key cell) ~next:(A.getUnsafe h_buckets index));
reinsertBucket next
in
for i = 0 to osize - 1 do
reinsertBucket (A.getUnsafe odata i)
done)
let rec removeBucket ~eq h h_buckets i key prec cell =
let cell_next = N.next cell in
if (Belt_Id.getEqInternal eq) (N.key cell) key then (
N.nextSet prec cell_next;
C.sizeSet h (C.size h - 1))
else match C.toOpt cell_next with None -> () | Some cell_next -> removeBucket ~eq h h_buckets i key cell cell_next
let remove h key =
let eq = C.eq h in
let h_buckets = C.buckets h in
let i = (Belt_Id.getHashInternal (C.hash h)) key land (A.length h_buckets - 1) in
let l = A.getUnsafe h_buckets i in
match C.toOpt l with
| None -> ()
| Some cell -> (
let next_cell = N.next cell in
if (Belt_Id.getEqInternal eq) (N.key cell) key then (
C.sizeSet h (C.size h - 1);
A.setUnsafe h_buckets i next_cell)
else
match C.toOpt next_cell with None -> () | Some next_cell -> removeBucket ~eq h h_buckets i key cell next_cell)
let rec missingInBucket ~eq key cell =
if (Belt_Id.getEqInternal eq) (N.key cell) key then false
else match C.toOpt (N.next cell) with None -> true | Some next -> missingInBucket ~eq key next
let add0 h key ~hash ~eq =
let h_buckets = C.buckets h in
let buckets_len = A.length h_buckets in
let i = (Belt_Id.getHashInternal hash) key land (buckets_len - 1) in
let l = A.getUnsafe h_buckets i in
(match C.toOpt l with
| None ->
C.sizeSet h (C.size h + 1);
A.setUnsafe h_buckets i (C.return @@ N.bucket ~key ~next:C.emptyOpt)
| Some cell when missingInBucket ~eq key cell ->
C.sizeSet h (C.size h + 1);
A.setUnsafe h_buckets i (C.return @@ N.bucket ~key ~next:l)
| Some _ -> ());
if C.size h > buckets_len lsl 1 then tryDoubleResize ~hash h
let add h key = add0 ~hash:(C.hash h) ~eq:(C.eq h) h key
let rec memInBucket ~eq key cell =
(Belt_Id.getEqInternal eq) (N.key cell) key
|| match C.toOpt (N.next cell) with None -> false | Some nextCell -> memInBucket ~eq key nextCell
let has h key =
let eq, h_buckets = (C.eq h, C.buckets h) in
let nid = (Belt_Id.getHashInternal (C.hash h)) key land (A.length h_buckets - 1) in
let bucket = A.getUnsafe h_buckets nid in
match C.toOpt bucket with None -> false | Some bucket -> memInBucket ~eq key bucket
let make (type value identity) ~hintSize ~(id : (value, identity) id) =
let module M = (val id) in
C.make ~hintSize ~hash:M.hash ~eq:M.eq
let clear = C.clear
let size = C.size
let forEachU = N.forEachU
let forEach = N.forEach
let reduceU = N.reduceU
let reduce = N.reduce
let logStats = N.logStats
let toArray = N.toArray
let copy = N.copy
let getBucketHistogram = N.getBucketHistogram
let isEmpty = C.isEmpty
let fromArray (type a identity) arr ~(id : (a, identity) id) =
let module M = (val id) in
let eq, hash = (M.eq, M.hash) in
let len = A.length arr in
let v = C.make ~hintSize:len ~hash ~eq in
for i = 0 to len - 1 do
add0 ~eq ~hash v (A.getUnsafe arr i)
done;
v
let mergeMany h arr =
let eq, hash = (C.eq h, C.hash h) in
let len = A.length arr in
for i = 0 to len - 1 do
add0 h ~eq ~hash (A.getUnsafe arr i)
done
================================================
FILE: packages/Belt/src/Belt_HashSet.mli
================================================
(* Copyright (C) 2018 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A {b mutable} Hash set which allows customized hash behavior.
All data are parameterized by not its only type but also a unique identity in the time of initialization, so that
two {i HashSets of ints} initialized with different {i hash} functions will have different type.
For example:
{[
type t = int
module I0 =
(val Belt.Id.hashableU
~hash:(fun[\@u] (a : t) -> a & 0xff_ff)
~eq:(fun[\@u] a b -> a = b)
)
let s0 = make ~id:(module I0) ~hintSize:40
module I1 =
(val Belt.Id.hashableU
~hash:(fun[\@u] (a : t) -> a & 0xff)
~eq:(fun[\@u] a b -> a = b)
)
let s1 = make ~id:(module I1) ~hintSize:40
]}
The invariant must be held: for two elements who are {i equal}, their hashed value should be the same
Here the compiler would infer [s0] and [s1] having different type so that it would not mix.
{[
val s0 : (int, I0.identity) t
val s1 : (int, I1.identity) t
]}
We can add elements to the collection:
{[
let () =
add s1 0;
add s1 1
]}
Since this is an mutable data strucure, [s1] will contain two pairs. *)
module Int = Belt_HashSetInt
(** Specalized when key type is [int], more efficient than the generic type *)
module String = Belt_HashSetString
(** Specalized when key type is [string], more efficient than the generic type *)
(* TODO: add a poly module
module Poly = Belt_HashSetPoly
challenge:
- generic equal handles JS data structure
- eq/hash consistent
*)
type ('a, 'id) t
(** The type of hash tables from type ['a] to type ['b]. *)
type ('a, 'id) id = ('a, 'id) Belt_Id.hashable
val make : hintSize:int -> id:('a, 'id) id -> ('a, 'id) t
val clear : ('a, 'id) t -> unit
val isEmpty : _ t -> bool
val add : ('a, 'id) t -> 'a -> unit
val copy : ('a, 'id) t -> ('a, 'id) t
val has : ('a, 'id) t -> 'a -> bool
val remove : ('a, 'id) t -> 'a -> unit
val forEachU : ('a, 'id) t -> (('a -> unit)[@u]) -> unit
val forEach : ('a, 'id) t -> ('a -> unit) -> unit
(** Order unspecified. *)
val reduceU : ('a, 'id) t -> 'c -> (('c -> 'a -> 'c)[@u]) -> 'c
val reduce : ('a, 'id) t -> 'c -> ('c -> 'a -> 'c) -> 'c
(** Order unspecified. *)
val size : ('a, 'id) t -> int
val logStats : _ t -> unit
val toArray : ('a, 'id) t -> 'a array
val fromArray : 'a array -> id:('a, 'id) id -> ('a, 'id) t
val mergeMany : ('a, 'id) t -> 'a array -> unit
val getBucketHistogram : _ t -> int array
================================================
FILE: packages/Belt/src/Belt_HashSetInt.ml
================================================
type key = int
type t = (key, unit) Hashtbl.t
let make ~hintSize = Hashtbl.create hintSize
let clear = Hashtbl.clear
let isEmpty h = Hashtbl.length h = 0
let add h key = Hashtbl.replace h key ()
let copy = Hashtbl.copy
let has = Hashtbl.mem
let remove = Hashtbl.remove
let forEachU h f = Hashtbl.iter (fun key () -> f key) h
let forEach h f = forEachU h (fun key -> f key)
let reduceU h init f =
let acc = ref init in
Hashtbl.iter (fun key () -> acc := f !acc key) h;
!acc
let reduce h init f = reduceU h init (fun acc key -> f acc key)
let size = Hashtbl.length
let logStats h =
let stats = Hashtbl.stats h in
Printf.printf "{\n\tbindings: %d,\n\tbuckets: %d\n\thistogram: %s\n}" stats.num_bindings stats.num_buckets
(Belt_Array.reduceU stats.bucket_histogram "" (fun acc x -> acc ^ string_of_int x))
let toArray h = Hashtbl.fold (fun key () acc -> key :: acc) h [] |> Array.of_list
let fromArray arr =
let h = make ~hintSize:(Belt_Array.length arr) in
Belt_Array.forEachU arr (fun key -> add h key);
h
let mergeMany h arr = Belt_Array.forEachU arr (fun key -> add h key)
let getBucketHistogram h = (Hashtbl.stats h).bucket_histogram
================================================
FILE: packages/Belt/src/Belt_HashSetInt.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.HashSet} specialized with key type to be a primitive type.
It is more efficient in general, the API is the same with {!Belt.HashSet} except its key type is fixed, and identity
is not needed(using the built-in one)
{b See} {!Belt.HashSet} *)
type key = int
type t
val make : hintSize:int -> t
val clear : t -> unit
val isEmpty : t -> bool
val add : t -> key -> unit
val copy : t -> t
val has : t -> key -> bool
val remove : t -> key -> unit
val forEachU : t -> ((key -> unit)[@u]) -> unit
val forEach : t -> (key -> unit) -> unit
val reduceU : t -> 'c -> (('c -> key -> 'c)[@u]) -> 'c
val reduce : t -> 'c -> ('c -> key -> 'c) -> 'c
val size : t -> int
val logStats : t -> unit
val toArray : t -> key array
val fromArray : key array -> t
val mergeMany : t -> key array -> unit
val getBucketHistogram : t -> int array
================================================
FILE: packages/Belt/src/Belt_HashSetString.ml
================================================
type key = string
type t = (key, unit) Hashtbl.t
let make ~hintSize = Hashtbl.create hintSize
let clear = Hashtbl.clear
let isEmpty h = Hashtbl.length h = 0
let add h key = Hashtbl.replace h key ()
let copy = Hashtbl.copy
let has = Hashtbl.mem
let remove = Hashtbl.remove
let forEachU h f = Hashtbl.iter (fun key () -> f key) h
let forEach h f = forEachU h (fun key -> f key)
let reduceU h init f =
let acc = ref init in
Hashtbl.iter (fun key () -> acc := f !acc key) h;
!acc
let reduce h init f = reduceU h init (fun acc key -> f acc key)
let size = Hashtbl.length
let logStats h =
let stats = Hashtbl.stats h in
Printf.printf "{\n\tbindings: %d,\n\tbuckets: %d\n\thistogram: %s\n}" stats.num_bindings stats.num_buckets
(Belt_Array.reduceU stats.bucket_histogram "" (fun acc x -> acc ^ string_of_int x))
let toArray h = Hashtbl.fold (fun key () acc -> key :: acc) h [] |> Array.of_list
let fromArray arr =
let h = make ~hintSize:(Belt_Array.length arr) in
Belt_Array.forEachU arr (fun key -> add h key);
h
let mergeMany h arr = Belt_Array.forEachU arr (fun key -> add h key)
let getBucketHistogram h = (Hashtbl.stats h).bucket_histogram
================================================
FILE: packages/Belt/src/Belt_HashSetString.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.HashSet} specialized with key type to be a primitive type.
It is more efficient in general, the API is the same with {!Belt.HashSet} except its key type is fixed, and identity
is not needed(using the built-in one)
{b See} {!Belt.HashSet} *)
type key = string
type t
val make : hintSize:int -> t
val clear : t -> unit
val isEmpty : t -> bool
val add : t -> key -> unit
val copy : t -> t
val has : t -> key -> bool
val remove : t -> key -> unit
val forEachU : t -> ((key -> unit)[@u]) -> unit
val forEach : t -> (key -> unit) -> unit
val reduceU : t -> 'c -> (('c -> key -> 'c)[@u]) -> 'c
val reduce : t -> 'c -> ('c -> key -> 'c) -> 'c
val size : t -> int
val logStats : t -> unit
val toArray : t -> key array
val fromArray : key array -> t
val mergeMany : t -> key array -> unit
val getBucketHistogram : t -> int array
================================================
FILE: packages/Belt/src/Belt_Id.ml
================================================
type ('a, 'id) hash = 'a -> int
type ('a, 'id) eq = 'a -> 'a -> bool
type ('a, 'id) cmp = 'a -> 'a -> int
let getHashInternal : ('a, 'id) hash -> 'a -> int = Obj.magic
let getEqInternal : ('a, 'id) eq -> 'a -> 'a -> bool = Obj.magic
let getCmpInternal : ('a, 'id) cmp -> 'a -> 'a -> int = Obj.magic
module type Comparable = sig
type identity
type t
val cmp : (t, identity) cmp
end
type ('key, 'id) comparable = (module Comparable with type t = 'key and type identity = 'id)
module MakeComparableU (M : sig
type t
val cmp : t -> t -> int
end) =
struct
type identity
type t = M.t
let cmp = M.cmp
end
module MakeComparable (M : sig
type t
val cmp : t -> t -> int
end) =
struct
type identity
type t = M.t
let cmp =
let cmp = M.cmp in
fun a b -> cmp a b
end
let comparableU (type key) ~cmp =
let module N = MakeComparableU (struct
type t = key
let cmp = cmp
end) in
(module N : Comparable with type t = key)
let comparable (type key) ~cmp =
let module N = MakeComparable (struct
type t = key
let cmp = cmp
end) in
(module N : Comparable with type t = key)
module type Hashable = sig
type identity
type t
val hash : (t, identity) hash
val eq : (t, identity) eq
end
type ('key, 'id) hashable = (module Hashable with type t = 'key and type identity = 'id)
module MakeHashableU (M : sig
type t
val hash : t -> int
val eq : t -> t -> bool
end) =
struct
type identity
type t = M.t
let hash = M.hash
let eq = M.eq
end
module MakeHashable (M : sig
type t
val hash : t -> int
val eq : t -> t -> bool
end) =
struct
type identity
type t = M.t
let hash =
let hash = M.hash in
fun a -> hash a
let eq =
let eq = M.eq in
fun a b -> eq a b
end
let hashableU (type key) ~hash ~eq =
let module N = MakeHashableU (struct
type t = key
let hash = hash
let eq = eq
end) in
(module N : Hashable with type t = key)
let hashable (type key) ~hash ~eq =
let module N = MakeHashable (struct
type t = key
let hash = hash
let eq = eq
end) in
(module N : Hashable with type t = key)
================================================
FILE: packages/Belt/src/Belt_Id.mli
================================================
type ('a, 'id) hash = 'a -> int
type ('a, 'id) eq = 'a -> 'a -> bool
type ('a, 'id) cmp = 'a -> 'a -> int
val getHashInternal : ('a, 'id) hash -> 'a -> int
val getEqInternal : ('a, 'id) eq -> 'a -> 'a -> bool
val getCmpInternal : ('a, 'id) cmp -> 'a -> 'a -> int
module type Comparable = sig
type identity
type t
val cmp : (t, identity) cmp
end
type ('key, 'id) comparable = (module Comparable with type identity = 'id and type t = 'key)
module MakeComparableU : functor
(M : sig
type t
val cmp : t -> t -> int
end)
-> sig
type identity
type t = M.t
val cmp : M.t -> M.t -> int
end
module MakeComparable : functor
(M : sig
type t
val cmp : t -> t -> int
end)
-> sig
type identity
type t = M.t
val cmp : M.t -> M.t -> int
end
val comparableU : cmp:('key -> 'key -> int) -> (module Comparable with type t = 'key)
val comparable : cmp:('key -> 'key -> int) -> (module Comparable with type t = 'key)
module type Hashable = sig
type identity
type t
val hash : (t, identity) hash
val eq : (t, identity) eq
end
type ('key, 'id) hashable = (module Hashable with type identity = 'id and type t = 'key)
module MakeHashableU : functor
(M : sig
type t
val hash : t -> int
val eq : t -> t -> bool
end)
-> sig
type identity
type t = M.t
val hash : M.t -> int
val eq : M.t -> M.t -> bool
end
module MakeHashable : functor
(M : sig
type t
val hash : t -> int
val eq : t -> t -> bool
end)
-> sig
type identity
type t = M.t
val hash : M.t -> int
val eq : M.t -> M.t -> bool
end
val hashableU : hash:('key -> int) -> eq:('key -> 'key -> bool) -> (module Hashable with type t = 'key)
val hashable : hash:('key -> int) -> eq:('key -> 'key -> bool) -> (module Hashable with type t = 'key)
================================================
FILE: packages/Belt/src/Belt_Int.ml
================================================
let toFloat = Stdlib.float_of_int
let fromFloat = Stdlib.int_of_float
let fromString input =
match int_of_string_opt input with
| Some value -> Some value
| None -> ( try Some (int_of_float (float_of_string input)) with _ -> None)
let toString = string_of_int
let ( + ) = Stdlib.( + )
let ( - ) = Stdlib.( - )
let ( * ) = Stdlib.( * )
let ( / ) = Stdlib.( / )
================================================
FILE: packages/Belt/src/Belt_Int.mli
================================================
val toFloat : int -> float
val fromFloat : float -> int
val fromString : string -> int option
val toString : int -> string
val ( + ) : int -> int -> int
val ( - ) : int -> int -> int
val ( * ) : int -> int -> int
val ( / ) : int -> int -> int
================================================
FILE: packages/Belt/src/Belt_List.ml
================================================
type 'a t = 'a list
module A = Belt_Array
external mutableCell : 'a -> 'a t -> 'a t = "belt_makemutablelist"
let unsafeMutateTail a b = Obj.set_field (Obj.repr a) 1 (Obj.repr b)
let unsafeTail a = Obj.obj (Obj.field (Obj.repr a) 1)
let head x = match x with [] -> None | x :: _ -> Some x
let headExn x =
match x with
| [] ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| x :: _ -> x
let tail x = match x with [] -> None | _ :: xs -> Some xs
let tailExn x =
match x with
| [] ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| _ :: t -> t
let add xs x = x :: xs
let rec nthAux x n = match x with h :: t -> if n = 0 then Some h else nthAux t (n - 1) | _ -> None
let rec nthAuxAssert x n =
match x with
| h :: t -> if n = 0 then h else nthAuxAssert t (n - 1)
| _ ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
let get x n = if n < 0 then None else nthAux x n
let getExn x n =
if n < 0 then
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
else nthAuxAssert x n
let rec partitionAux p cell precX precY =
match cell with
| [] -> ()
| h :: t ->
let next = mutableCell h [] in
if p h then (
unsafeMutateTail precX next;
partitionAux p t next precY)
else (
unsafeMutateTail precY next;
partitionAux p t precX next)
let rec splitAux cell precX precY =
match cell with
| [] -> ()
| (a, b) :: t ->
let nextA = mutableCell a [] in
let nextB = mutableCell b [] in
unsafeMutateTail precX nextA;
unsafeMutateTail precY nextB;
splitAux t nextA nextB
let rec copyAuxCont cellX prec =
match cellX with
| [] -> prec
| h :: t ->
let next = mutableCell h [] in
unsafeMutateTail prec next;
copyAuxCont t next
let rec copyAuxWitFilter f cellX prec =
match cellX with
| [] -> ()
| h :: t ->
if f h then (
let next = mutableCell h [] in
unsafeMutateTail prec next;
copyAuxWitFilter f t next)
else copyAuxWitFilter f t prec
let rec copyAuxWitFilterMap f cellX prec =
match cellX with
| [] -> ()
| h :: t -> (
match f h with
| Some h ->
let next = mutableCell h [] in
unsafeMutateTail prec next;
copyAuxWitFilterMap f t next
| None -> copyAuxWitFilterMap f t prec)
let rec removeAssocAuxWithMap cellX x prec f =
match cellX with
| [] -> false
| ((a, _) as h) :: t ->
if f a x then (
unsafeMutateTail prec t;
true)
else
let next = mutableCell h [] in
unsafeMutateTail prec next;
removeAssocAuxWithMap t x next f
let rec setAssocAuxWithMap cellX x k prec eq =
match cellX with
| [] -> false
| ((a, _) as h) :: t ->
if eq a x then (
unsafeMutateTail prec ((x, k) :: t);
true)
else
let next = mutableCell h [] in
unsafeMutateTail prec next;
setAssocAuxWithMap t x k next eq
let rec copyAuxWithMap cellX prec f =
match cellX with
| [] -> ()
| h :: t ->
let next = mutableCell (f h) [] in
unsafeMutateTail prec next;
copyAuxWithMap t next f
let rec zipAux cellX cellY prec =
match (cellX, cellY) with
| h1 :: t1, h2 :: t2 ->
let next = mutableCell (h1, h2) [] in
unsafeMutateTail prec next;
zipAux t1 t2 next
| [], _ | _, [] -> ()
let rec copyAuxWithMap2 f cellX cellY prec =
match (cellX, cellY) with
| h1 :: t1, h2 :: t2 ->
let next = mutableCell (f h1 h2) [] in
unsafeMutateTail prec next;
copyAuxWithMap2 f t1 t2 next
| [], _ | _, [] -> ()
let rec copyAuxWithMapI f i cellX prec =
match cellX with
| h :: t ->
let next = mutableCell (f i h) [] in
unsafeMutateTail prec next;
copyAuxWithMapI f (i + 1) t next
| [] -> ()
let rec takeAux n cell prec =
if n = 0 then true
else
match cell with
| [] -> false
| x :: xs ->
let cell = mutableCell x [] in
unsafeMutateTail prec cell;
takeAux (n - 1) xs cell
let rec splitAtAux n cell prec =
if n = 0 then Some cell
else
match cell with
| [] -> None
| x :: xs ->
let cell = mutableCell x [] in
unsafeMutateTail prec cell;
splitAtAux (n - 1) xs cell
let take lst n =
if n < 0 then None
else if n = 0 then Some []
else
match lst with
| [] -> None
| x :: xs ->
let cell = mutableCell x [] in
let has = takeAux (n - 1) xs cell in
if has then Some cell else None
let rec dropAux l n = if n = 0 then Some l else match l with _ :: tl -> dropAux tl (n - 1) | [] -> None
let drop lst n = if n < 0 then None else dropAux lst n
let splitAt lst n =
if n < 0 then None
else if n = 0 then Some ([], lst)
else
match lst with
| [] -> None
| x :: xs -> (
let cell = mutableCell x [] in
let rest = splitAtAux (n - 1) xs cell in
match rest with Some rest -> Some (cell, rest) | None -> None)
let concat xs ys =
match xs with
| [] -> ys
| h :: t ->
let cell = mutableCell h [] in
unsafeMutateTail (copyAuxCont t cell) ys;
cell
let mapU xs f =
match xs with
| [] -> []
| h :: t ->
let cell = mutableCell (f h) [] in
copyAuxWithMap t cell f;
cell
let map xs f = mapU xs (fun x -> f x)
let zipByU l1 l2 f =
match (l1, l2) with
| a1 :: l1, a2 :: l2 ->
let cell = mutableCell (f a1 a2) [] in
copyAuxWithMap2 f l1 l2 cell;
cell
| [], _ | _, [] -> []
let zipBy l1 l2 f = zipByU l1 l2 (fun x y -> f x y)
let mapWithIndexU xs f =
match xs with
| [] -> []
| h :: t ->
let cell = mutableCell (f 0 h) [] in
copyAuxWithMapI f 1 t cell;
cell
let mapWithIndex xs f = mapWithIndexU xs (fun i x -> f i x)
let makeByU n f =
if n <= 0 then []
else
let headX = mutableCell (f 0) [] in
let cur = ref headX in
let i = ref 1 in
while !i < n do
let v = mutableCell (f !i) [] in
unsafeMutateTail !cur v;
cur := v;
incr i
done;
headX
let makeBy n f = makeByU n (fun x -> f x)
let make n v =
if n <= 0 then []
else
let headX = mutableCell v [] in
let cur = ref headX in
let i = ref 1 in
while !i < n do
let v = mutableCell v [] in
unsafeMutateTail !cur v;
cur := v;
incr i
done;
headX
let rec lengthAux x acc = match x with [] -> acc | _ :: t -> lengthAux t (acc + 1)
let length xs = lengthAux xs 0
let size = length
let rec fillAux arr i x =
match x with
| [] -> ()
| h :: t ->
A.setUnsafe arr i h;
fillAux arr (i + 1) t
let rec fromArrayAux a i res = if i < 0 then res else fromArrayAux a (i - 1) (A.getUnsafe a i :: res)
let fromArray a = fromArrayAux a (A.length a - 1) []
let toArray (x : _ t) =
let len = length x in
let arr = match x with x :: _ -> A.makeUninitializedUnsafe len x | _ -> [||] in
fillAux arr 0 x;
arr
let shuffle xs =
let v = toArray xs in
A.shuffleInPlace v;
fromArray v
let rec fillAuxMap arr i x f =
match x with
| [] -> ()
| h :: t ->
A.setUnsafe arr i (f h);
fillAuxMap arr (i + 1) t f
let rec reverseConcat l1 l2 = match l1 with [] -> l2 | a :: l -> reverseConcat l (a :: l2)
let reverse l = reverseConcat l []
let rec flattenAux prec xs =
match xs with [] -> unsafeMutateTail prec [] | h :: r -> flattenAux (copyAuxCont h prec) r
let rec flatten xs =
match xs with
| [] -> []
| [] :: xs -> flatten xs
| (h :: t) :: r ->
let cell = mutableCell h [] in
flattenAux (copyAuxCont t cell) r;
cell
let concatMany xs =
match xs with
| [||] -> []
| [| x |] -> x
| _ ->
let len = A.length xs in
let v = ref (A.getUnsafe xs (len - 1)) in
for i = len - 2 downto 0 do
v := concat (A.getUnsafe xs i) !v
done;
!v
let rec mapRevAux f accu xs = match xs with [] -> accu | a :: l -> mapRevAux f (f a :: accu) l
let mapReverseU l f = mapRevAux f [] l
let mapReverse l f = mapReverseU l (fun x -> f x)
let rec forEachU xs f =
match xs with
| [] -> ()
| a :: l ->
f a;
forEachU l f
let forEach xs f = forEachU xs (fun x -> f x)
let rec iteri xs i f =
match xs with
| [] -> ()
| a :: l ->
f i a;
iteri l (i + 1) f
let forEachWithIndexU l f = iteri l 0 f
let forEachWithIndex l f = forEachWithIndexU l (fun i x -> f i x)
let rec reduceU l accu f = match l with [] -> accu | a :: l -> reduceU l (f accu a) f
let reduce l accu f = reduceU l accu (fun acc x -> f acc x)
let rec reduceReverseUnsafeU l accu f = match l with [] -> accu | a :: l -> f (reduceReverseUnsafeU l accu f) a
let reduceReverseU (type a b) (l : a list) (acc : b) f =
let len = length l in
if len < 1000 then reduceReverseUnsafeU l acc f else A.reduceReverseU (toArray l) acc f
let reduceReverse l accu f = reduceReverseU l accu (fun a b -> f a b)
let rec mapRevAux2 l1 l2 accu f =
match (l1, l2) with a1 :: l1, a2 :: l2 -> mapRevAux2 l1 l2 (f a1 a2 :: accu) f | _, [] | [], _ -> accu
let mapReverse2U l1 l2 f = mapRevAux2 l1 l2 [] f
let mapReverse2 l1 l2 f = mapReverse2U l1 l2 (fun a b -> f a b)
let rec forEach2U l1 l2 f =
match (l1, l2) with
| a1 :: l1, a2 :: l2 ->
f a1 a2;
forEach2U l1 l2 f
| [], _ | _, [] -> ()
let forEach2 l1 l2 f = forEach2U l1 l2 (fun a b -> f a b)
let rec reduce2U l1 l2 accu f =
match (l1, l2) with a1 :: l1, a2 :: l2 -> reduce2U l1 l2 (f accu a1 a2) f | [], _ | _, [] -> accu
let reduce2 l1 l2 acc f = reduce2U l1 l2 acc (fun a b c -> f a b c)
let rec reduceReverse2UnsafeU l1 l2 accu f =
match (l1, l2) with
| [], [] -> accu
| a1 :: l1, a2 :: l2 -> f (reduceReverse2UnsafeU l1 l2 accu f) a1 a2
| _, [] | [], _ -> accu
let reduceReverse2U (type a b c) (l1 : a list) (l2 : b list) (acc : c) f =
let len = length l1 in
if len < 1000 then reduceReverse2UnsafeU l1 l2 acc f else A.reduceReverse2U (toArray l1) (toArray l2) acc f
let reduceReverse2 l1 l2 acc f = reduceReverse2U l1 l2 acc (fun a b c -> f a b c)
let rec everyU xs p = match xs with [] -> true | a :: l -> p a && everyU l p
let every xs p = everyU xs (fun x -> p x)
let rec someU xs p = match xs with [] -> false | a :: l -> p a || someU l p
let some xs p = someU xs (fun x -> p x)
let rec every2U l1 l2 p = match (l1, l2) with _, [] | [], _ -> true | a1 :: l1, a2 :: l2 -> p a1 a2 && every2U l1 l2 p
let every2 l1 l2 p = every2U l1 l2 (fun a b -> p a b)
let rec cmpByLength l1 l2 =
match (l1, l2) with [], [] -> 0 | _, [] -> 1 | [], _ -> -1 | _ :: l1s, _ :: l2s -> cmpByLength l1s l2s
let rec cmpU l1 l2 p =
match (l1, l2) with
| [], [] -> 0
| _, [] -> 1
| [], _ -> -1
| a1 :: l1, a2 :: l2 ->
let c = p a1 a2 in
if c = 0 then cmpU l1 l2 p else c
let cmp l1 l2 f = cmpU l1 l2 (fun x y -> f x y)
let rec eqU l1 l2 p =
match (l1, l2) with
| [], [] -> true
| _, [] | [], _ -> false
| a1 :: l1, a2 :: l2 -> if p a1 a2 then eqU l1 l2 p else false
let eq l1 l2 f = eqU l1 l2 (fun x y -> f x y)
let rec some2U l1 l2 p = match (l1, l2) with [], _ | _, [] -> false | a1 :: l1, a2 :: l2 -> p a1 a2 || some2U l1 l2 p
let some2 l1 l2 p = some2U l1 l2 (fun a b -> p a b)
let rec hasU xs x eq = match xs with [] -> false | a :: l -> eq a x || hasU l x eq
let has xs x eq = hasU xs x (fun a b -> eq a b)
let rec getAssocU xs x eq = match xs with [] -> None | (a, b) :: l -> if eq a x then Some b else getAssocU l x eq
let getAssoc xs x eq = getAssocU xs x (fun a b -> eq a b)
let rec hasAssocU xs x eq = match xs with [] -> false | (a, b) :: l -> eq a x || hasAssocU l x eq
let hasAssoc xs x eq = hasAssocU xs x (fun a b -> eq a b)
let removeAssocU xs x eq =
match xs with
| [] -> []
| ((a, _) as pair) :: l ->
if eq a x then l
else
let cell = mutableCell pair [] in
let removed = removeAssocAuxWithMap l x cell eq in
if removed then cell else xs
let removeAssoc xs x eq = removeAssocU xs x (fun a b -> eq a b)
let setAssocU xs x k eq =
match xs with
| [] -> [ (x, k) ]
| ((a, _) as pair) :: l ->
if eq a x then (x, k) :: l
else
let cell = mutableCell pair [] in
let replaced = setAssocAuxWithMap l x k cell eq in
if replaced then cell else (x, k) :: xs
let setAssoc xs x k eq = setAssocU xs x k (fun a b -> eq a b)
let sortU xs cmp =
let arr = toArray xs in
Belt_SortArray.stableSortInPlaceByU arr cmp;
fromArray arr
let sort xs cmp = sortU xs (fun x y -> cmp x y)
let rec getByU xs p = match xs with [] -> None | x :: l -> if p x then Some x else getByU l p
let getBy xs p = getByU xs (fun a -> p a)
let rec keepU xs p =
match xs with
| [] -> []
| h :: t ->
if p h then (
let cell = mutableCell h [] in
copyAuxWitFilter p t cell;
cell)
else keepU t p
let keep xs p = keepU xs (fun x -> p x)
let rec copyAuxWithFilterIndex f cellX prec i =
match cellX with
| [] -> ()
| h :: t ->
if f h i then (
let next = mutableCell h [] in
unsafeMutateTail prec next;
copyAuxWithFilterIndex f t next (i + 1))
else copyAuxWithFilterIndex f t prec (i + 1)
let rec copyAuxWitFilterMap f cellX prec =
match cellX with
| [] -> ()
| h :: t -> (
match f h with
| Some h ->
let next = mutableCell h [] in
unsafeMutateTail prec next;
copyAuxWitFilterMap f t next
| None -> copyAuxWitFilterMap f t prec)
let keepWithIndexU xs p =
let rec auxKeepWithIndex xs p i =
match xs with
| [] -> []
| h :: t ->
if p h i then (
let cell = mutableCell h [] in
copyAuxWithFilterIndex p t cell (i + 1);
cell)
else auxKeepWithIndex t p (i + 1)
in
auxKeepWithIndex xs p 0
let keepWithIndex xs p = keepWithIndexU xs (fun x i -> p x i)
let rec keepMapU xs p =
match xs with
| [] -> []
| h :: t -> (
match p h with
| Some h ->
let cell = mutableCell h [] in
copyAuxWitFilterMap p t cell;
cell
| None -> keepMapU t p)
let keepMap xs p = keepMapU xs (fun x -> p x)
let partitionU l p =
match l with
| [] -> ([], [])
| h :: t ->
let nextX = mutableCell h [] in
let nextY = mutableCell h [] in
let b = p h in
partitionAux p t nextX nextY;
if b then (nextX, unsafeTail nextY) else (unsafeTail nextX, nextY)
let partition l p = partitionU l (fun x -> p x)
let rec unzip xs =
match xs with
| [] -> ([], [])
| (x, y) :: l ->
let cellX = mutableCell x [] in
let cellY = mutableCell y [] in
splitAux l cellX cellY;
(cellX, cellY)
let rec zip l1 l2 =
match (l1, l2) with
| _, [] | [], _ -> []
| a1 :: l1, a2 :: l2 ->
let cell = mutableCell (a1, a2) [] in
zipAux l1 l2 cell;
cell
let rec reduceWithIndexAuxU l acc f i =
match l with [] -> acc | x :: xs -> reduceWithIndexAuxU xs (f acc x i [@bs]) f (i + 1)
let reduceWithIndexU l acc f = reduceWithIndexAuxU l acc f 0
let reduceWithIndex l acc f = reduceWithIndexU l acc (fun[@bs] acc x i -> f acc x i)
let filter = keep
let filterWithIndex = keepWithIndexU
================================================
FILE: packages/Belt/src/Belt_List.mli
================================================
(** {!Belt.List}
Utilities for List data type.
This module is compatible with original ocaml stdlib. In general, all functions comes with the original stdlib also
applies to this collection, however, this module provides faster and stack safer utilities *)
type 'a t = 'a list
(** ['a t] is compatible with built-in [list] type *)
val length : 'a t -> int
(** [length xs]
@return the length of the list [xs] *)
val size : 'a t -> int
(** {b See} {!length} *)
val head : 'a t -> 'a option
(** [head xs] returns [None] if [xs] is the empty list, otherwise it returns [Some value] where [value] is the first
element in the list.
{[
head [] = None;;
head [ 1; 2; 3 ] = Some 1
]} *)
val headExn : 'a t -> 'a
(** [headExn xs]
{b See} {!head}
{b raise} an exception if [xs] is empty *)
val tail : 'a t -> 'a t option
(** [tail xs] returns [None] if [xs] is empty; otherwise it returns [Some xs2] where [xs2] is everything except the
first element of [xs];
{[
tail [] = None;;
tail [ 1; 2; 3; 4 ] = Some [ 2; 3; 4 ]
]} *)
val tailExn : 'a t -> 'a t
(** [tailExn xs]
{b See} {!tail}
{b raise} an exception if [xs] is empty *)
val add : 'a t -> 'a -> 'a t
(** [add xs y] adds [y] to the beginning of list [xs]
{[
add [ 1 ] 3 = [ 3; 1 ]
]} *)
val get : 'a t -> int -> 'a option
(** [get xs n]
return the nth element in [xs], or [None] if [n] is larger than the length
{[
get [ 0; 3; 32 ] 2 = Some 32;;
get [ 0; 3; 32 ] 3 = None
]} *)
val getExn : 'a t -> int -> 'a
(** [getExn xs n]
{b See} {!get}
{b raise} an exception if [n] is larger than the length *)
val make : int -> 'a -> 'a t
(** [make n v]
- return a list of length [n] with each element filled with value [v]
- return the empty list if [n] is negative
{[
make 3 1 = [ 1; 1; 1 ]
]} *)
val makeByU : int -> ((int -> 'a)[@bs]) -> 'a t
val makeBy : int -> (int -> 'a) -> 'a t
(** [makeBy n f]
- return a list of length [n] with element [i] initialized with [f i]
- return the empty list if [n] is negative
{[
makeBy 5 (fun i -> i) = [ 0; 1; 2; 3; 4 ];;
makeBy 5 (fun i -> i * i) = [ 0; 1; 4; 9; 16 ]
]} *)
val shuffle : 'a t -> 'a t
(** [shuffle xs]
@return a new list in random order *)
val drop : 'a t -> int -> 'a t option
(** [drop xs n]
return the list obtained by dropping the first [n] elements, or [None] if [xs] has fewer than [n] elements
{[
drop [ 1; 2; 3 ] 2 = Some [ 3 ];;
drop [ 1; 2; 3 ] 3 = Some [];;
drop [ 1; 2; 3 ] 4 = None
]} *)
val take : 'a t -> int -> 'a t option
(** [take xs n]
return a list with the first [n] elements from [xs], or [None] if [xs] has fewer than [n] elements
{[
take [ 1; 2; 3 ] 1 = Some [ 1 ];;
take [ 1; 2; 3 ] 2 = Some [ 1; 2 ];;
take [ 1; 2; 3 ] 4 = None
]} *)
val splitAt : 'a t -> int -> ('a list * 'a list) option
(** [splitAt xs n] split the list [xs] at position [n] return None when the length of [xs] is less than [n]
{[
splitAt [ 0; 1; 2; 3; 4 ] 2 = Some ([ 0; 1 ], [ 2; 3; 4 ])
]} *)
val concat : 'a t -> 'a t -> 'a t
(** [concat xs ys]
@return the list obtained by adding [ys] after [xs]
{[
concat [ 1; 2; 3 ] [ 4; 5 ] = [ 1; 2; 3; 4; 5 ]
]} *)
val concatMany : 'a t array -> 'a t
(** [concatMany a] return the list obtained by concatenating in order all the lists in array [a]
{[
concatMany [| [ 1; 2; 3 ]; []; [ 3 ]; [ 4 ] |] = [ 1; 2; 3; 3; 4 ]
]} *)
val reverseConcat : 'a t -> 'a t -> 'a t
(** [reverseConcat xs ys] is equivalent to [concat (reverse xs) ys]
{[
reverseConcat [ 1; 2 ] [ 3; 4 ] = [ 2; 1; 3; 4 ]
]} *)
val flatten : 'a t t -> 'a t
(** [flatten ls] return the list obtained by concatenating in order all the lists in list [ls]
{[
flatten [ [ 1; 2; 3 ]; []; [ 3 ]; [ 4 ] ] = [ 1; 2; 3; 3; 4 ]
]} *)
val mapU : 'a t -> (('a -> 'b)[@bs]) -> 'b t
val map : 'a t -> ('a -> 'b) -> 'b t
(** [map xs f]
return the list obtained by applying [f] to each element of [xs]
{[
map [ 1; 2 ] (fun x -> x + 1) = [ 3; 4 ]
]} *)
val zip : 'a t -> 'b t -> ('a * 'b) t
(** [zip xs ys]
@return a list of pairs from the two lists with the length of the shorter list
{[
zip [ 1; 2 ] [ 3; 4; 5 ] = [ (1, 3); (2, 4) ]
]} *)
val zipByU : 'a t -> 'b t -> (('a -> 'b -> 'c)[@bs]) -> 'c t
val zipBy : 'a t -> 'b t -> ('a -> 'b -> 'c) -> 'c t
(** [zipBy xs ys f]
{b See} {!zip}
Equivalent to [zip xs ys |> List.map (fun (x,y) -> f x y)]
{[
zipBy [ 1; 2; 3 ] [ 4; 5 ] (fun a b -> (2 * a) + b) = [ 6; 9 ]
]} *)
val mapWithIndexU : 'a t -> ((int -> 'a -> 'b)[@bs]) -> 'b t
val mapWithIndex : 'a t -> (int -> 'a -> 'b) -> 'b t
(** [mapWithIndex xs f] applies [f] to each element of [xs]. Function [f] takes two arguments: the index starting from 0
and the element from [xs].
{[
mapWithIndex [ 1; 2; 3 ] (fun i x -> i + x) = [ 0 + 1; 1 + 2; 2 + 3 ]
]} *)
val fromArray : 'a array -> 'a t
(** [fromArray arr] converts the given array to a list
{[
fromArray [| 1; 2; 3 |] = [ 1; 2; 3 ]
]} *)
val toArray : 'a t -> 'a array
(** [toArray xs] converts the given list to an array
{[
toArray [ 1; 2; 3 ] = [| 1; 2; 3 |]
]} *)
(* type json = Js_json.t *)
(* val toJson : 'a t -> ('a -> json [@bs]) -> json *)
(* val fromJson : json -> (json -> 'a [@bs]) -> 'a t *)
val reverse : 'a t -> 'a t
(** [reverse xs] returns a new list whose elements are those of [xs] in reverse order.
{[
reverse [ 1; 2; 3 ] = [ 3; 2; 1 ]
]} *)
val mapReverseU : 'a t -> (('a -> 'b)[@bs]) -> 'b t
val mapReverse : 'a t -> ('a -> 'b) -> 'b t
(** [mapReverse xs f]
Equivalent to [reverse (map xs f)]
{[
mapReverse [ 3; 4; 5 ] (fun x -> x * x) = [ 25; 16; 9 ]
]} *)
val forEachU : 'a t -> (('a -> unit)[@bs]) -> unit
val forEach : 'a t -> ('a -> unit) -> unit
(** [forEach xs f ] Call [f] on each element of [xs] from the beginning to end. [f] returns [unit], so no new array is
created. Use [foreach] when you are primarily concerned with repetitively creating side effects.
{[
forEach [ "a"; "b"; "c" ] (fun x -> Js.log ("Item: " ^ x));;
(* prints:
Item: a
Item: b
Item: c
*)
let us = ref 0;;
forEach [ 1; 2; 3; 4 ] (fun x -> us := !us + x);;
!us = 1 + 2 + 3 + 4
]} *)
val forEachWithIndexU : 'a t -> ((int -> 'a -> unit)[@bs]) -> unit
val forEachWithIndex : 'a t -> (int -> 'a -> unit) -> unit
(** [forEachWithIndex xs f]
{[
forEach [ "a"; "b"; "c" ] (fun i x -> Js.log ("Item " ^ string_of_int i ^ " is " ^ x));;
(* prints:
Item 0 is a
Item 1 is b
Item 2 is cc
*)
let total = ref 0;;
forEachWithIndex [ 10; 11; 12; 13 ] (fun i x -> total := !total + x + i);;
!total = 0 + 10 + 1 + 11 + 2 + 12 + 3 + 13
]} *)
val reduceU : 'a t -> 'b -> (('b -> 'a -> 'b)[@bs]) -> 'b
val reduce : 'a t -> 'b -> ('b -> 'a -> 'b) -> 'b
(** [reduce xs f]
Applies [f] to each element of [xs] from beginning to end. Function [f] has two parameters: the item from the list
and an “accumulator”, which starts with a value of [init]. [reduce] returns the final value of the accumulator.
{[
reduce [ 1; 2; 3; 4 ] 0 ( + ) = 10;;
reduce [ 1; 2; 3; 4 ] 10 ( - ) = 0;;
reduce [ 1; 2; 3; 4 ] [] add = [ 4; 3; 2; 1 ]
]} *)
val reduceWithIndexU : 'a t -> 'b -> (('b -> 'a -> int -> 'b)[@bs]) -> 'b
val reduceWithIndex : 'a t -> 'b -> ('b -> 'a -> int -> 'b) -> 'b
(** [reduceWithIndex xs f]
Applies [f] to each element of [xs] from beginning to end. Function [f] has three parameters: the item from the list
and an “accumulator”, which starts with a value of [init] and the index of each element. [reduceWithIndex] returns
the final value of the accumulator.
{[
reduceWithIndex [ 1; 2; 3; 4 ] 0 (fun acc x i -> acc + x + i) = 16
]} *)
val reduceReverseU : 'a t -> 'b -> (('b -> 'a -> 'b)[@bs]) -> 'b
val reduceReverse : 'a t -> 'b -> ('b -> 'a -> 'b) -> 'b
(** [reduceReverse xs f]
Works like {!reduce}, except that function [f] is applied to each item of [xs] from the last back to the first.
{[
reduceReverse [ 1; 2; 3; 4 ] 0 ( + ) = 10;;
reduceReverse [ 1; 2; 3; 4 ] 10 ( - ) = 0;;
reduceReverse [ 1; 2; 3; 4 ] [] add = [ 1; 2; 3; 4 ]
]} *)
val mapReverse2U : 'a t -> 'b t -> (('a -> 'b -> 'c)[@bs]) -> 'c t
val mapReverse2 : 'a t -> 'b t -> ('a -> 'b -> 'c) -> 'c t
(** [mapReverse2 xs ys f]
equivalent to [reverse (zipBy xs ys f)]
{[
mapReverse2 [ 1; 2; 3 ] [ 1; 2 ] ( + ) = [ 4; 2 ]
]} *)
val forEach2U : 'a t -> 'b t -> (('a -> 'b -> unit)[@bs]) -> unit
val forEach2 : 'a t -> 'b t -> ('a -> 'b -> unit) -> unit
(** [forEach2 xs ys f] stop with the shorter list *)
val reduce2U : 'b t -> 'c t -> 'a -> (('a -> 'b -> 'c -> 'a)[@bs]) -> 'a
val reduce2 : 'b t -> 'c t -> 'a -> ('a -> 'b -> 'c -> 'a) -> 'a
(** [reduce2 xs ys init f ]
Applies [f] to each element of [xs] and [ys] from beginning to end. Stops with the shorter list. Function [f] has
three parameters: an “accumulator” which starts with a value of [init], an item from [xs], and an item from [ys].
[reduce2] returns the final value of the accumulator.
{[
reduce2 [1;2;3] [4;5] 0 (fun acc x y -> acc + x * x + y) = 0 + (1 * 1 + 4) + (2 * 2 + 5);;
reduce2 [1;2;3] [4;5] [] (fun acc x y -> add acc (x + y) = [2 +5;1 + 4 ];; (*add appends at end *)
]} *)
val reduceReverse2U : 'a t -> 'b t -> 'c -> (('c -> 'a -> 'b -> 'c)[@bs]) -> 'c
val reduceReverse2 : 'a t -> 'b t -> 'c -> ('c -> 'a -> 'b -> 'c) -> 'c
(** [reduceReverse2 xs ys init f ]
Applies [f] to each element of [xs] and [ys] from end to beginning. Stops with the shorter list. Function [f] has
three parameters: an “accumulator” which starts with a value of [init], an item from [xs], and an item from [ys].
[reduce2] returns the final value of the accumulator.
{[
reduceReverse2 [1;2;3] [4;5] 0 (fun acc x y -> acc + x * x + y) = 0 + (1 * 1 + 4) + (2 * 2 + 5);;
reduceReverse2 [1;2;3] [4;5] [] (fun acc x y -> add acc (x + y) = [1 + 4;2 + 5];; (*add appends at end *)
]}*)
val everyU : 'a t -> (('a -> bool)[@bs]) -> bool
val every : 'a t -> ('a -> bool) -> bool
(** [every xs p]
@return
true if all elements satisfy [p], where [p] is a {i predicate}: a function taking an element and returning a
[bool].
{[
every [] (fun x -> x mod 2 = 0) = true;;
every [ 2; 4; 6 ] (fun x -> x mod 2 = 0) = true;;
every [ 1; -3; 5 ] (fun x -> x > 0) = false
]} *)
val someU : 'a t -> (('a -> bool)[@bs]) -> bool
val some : 'a t -> ('a -> bool) -> bool
(** [some xs p]
@return
true if at least one of the elements in [xs] satifies [p], where [p] is a {i predicate}: a function taking an
element and returning a [bool].
{[
some [] (fun x -> x mod 2 = 0) = false;;
some [ 1; 2; 4 ] (fun x -> x mod 2 = 0) = true;;
some [ -1; -3; -5 ] (fun x -> x > 0) = false
]} *)
val every2U : 'a t -> 'b t -> (('a -> 'b -> bool)[@bs]) -> bool
val every2 : 'a t -> 'b t -> ('a -> 'b -> bool) -> bool
(** [every2 xs ys p] returns true if predicate [p xi yi] is true for all pairs of elements up to the shorter length
(i.e. [min (length xs) (length ys)])
{[
every2 [ 1; 2; 3 ] [ 0; 1 ] ( > ) = true;;
every2 [] [ 1 ] (fun x y -> x > y) = true;;
every2 [ 2; 3 ] [ 1 ] (fun x y -> x > y) = true;;
every2 [ 0; 1 ] [ 5; 0 ] (fun x y -> x > y) = false
]} *)
val some2U : 'a t -> 'b t -> (('a -> 'b -> bool)[@bs]) -> bool
val some2 : 'a t -> 'b t -> ('a -> 'b -> bool) -> bool
(** [some2 xs ys p] returns true if [p xi yi] is true for any pair of elements up to the shorter length (i.e.
[min (length xs) (length ys)])
{[
some2 [ 0; 2 ] [ 1; 0; 3 ] ( > ) = true;;
some2 [] [ 1 ] (fun x y -> x > y) = false;;
some2 [ 2; 3 ] [ 1; 4 ] (fun x y -> x > y) = true
]} *)
val cmpByLength : 'a t -> 'a t -> int
(** [cmpByLength l1 l2]
Compare two lists solely by length. Returns -1 if [length l1] is less than [length l2], 0 if [length l1] equals
[length l2], and 1 if [length l1] is greater than [length l2].
{[
cmpByLength [ 1; 2 ] [ 3; 4; 5; 6 ] = -1;;
cmpByLength [ 1; 2; 3 ] [ 4; 5; 6 ] = 0;;
cmpByLength [ 1; 2; 3; 4 ] [ 5; 6 ] = 1
]} *)
val cmpU : 'a t -> 'a t -> (('a -> 'a -> int)[@bs]) -> int
val cmp : 'a t -> 'a t -> ('a -> 'a -> int) -> int
(** Compare elements one by one [f x y]. [f] returns
- a negative number if [x] is “less than” [y]
- zero if [x] is “equal to” [y]
- a positive number if [x] is “greater than” [y] The comparison returns the first non-zero result of [f], or zero if
[f] returns zero for all [x] and [y]. If all items have compared equal, but [xs] is exhausted first, return -1.
([xs] is shorter) If all items have compared equal, but [ys] is exhausted first, return 1 ([xs] is longer)
{[
cmp [ 3 ] [ 3; 7 ] (fun a b -> compare a b)
= -1 cmp [ 5; 3 ] [ 5 ] (fun a b -> compare a b)
= 1 cmp [| 1; 3; 5 |] [| 1; 4; 2 |] (fun a b -> compare a b)
= -1
;;
cmp [| 1; 3; 5 |] [| 1; 2; 3 |] (fun a b -> compare a b) = 1;;
cmp [| 1; 3; 5 |] [| 1; 3; 5 |] (fun a b -> compare a b) = 0
]}
{b Attention}: The total ordering of List is different from Array, for Array, we compare the length first and, only
if the lengths are equal, elements one by one. For lists, we just compare elements one by one *)
val eqU : 'a t -> 'a t -> (('a -> 'a -> bool)[@bs]) -> bool
val eq : 'a t -> 'a t -> ('a -> 'a -> bool) -> bool
(** [eq xs ys eqElem] check equality of [xs] and [ys] using [eqElem] for equality on elements, where [eqElem] is a
function that returns true if items [x] and [y] meet some criterion for equality, false otherwise. [eq] false if
length of [xs] and [ys] are not the same.
{[
eq [ 1; 2; 3 ] [ 1; 2 ] ( = ) = false;;
eq [ 1; 2 ] [ 1; 2 ] ( = ) = true;;
eq [ 1; 2; 3 ] [ -1; -2; -3 ] (fun a b -> abs a = abs b) = true
]} *)
val hasU : 'a t -> 'b -> (('a -> 'b -> bool)[@bs]) -> bool
val has : 'a t -> 'b -> ('a -> 'b -> bool) -> bool
(** [has xs eqFcn] returns true if the list contains at least one element for which [eqFcn x] returns true
{[
has [ 1; 2; 3 ] 2 ( = ) = true;;
has [ 1; 2; 3 ] 4 ( = ) = false;;
has [ -1; -2; -3 ] 2 (fun a b -> abs a = abs b) = true
]} *)
val getByU : 'a t -> (('a -> bool)[@bs]) -> 'a option
val getBy : 'a t -> ('a -> bool) -> 'a option
(** [getBy xs p] returns [Some value] for the first value in [xs] that satisifies the predicate function [p]; returns
[None] if no element satisifies the function.
{[
getBy [1;4;3;2] (fun x -> x mod 2 = 0) = Some 4
getBy [15;13;11] (fun x -> x mod 2 = 0) = None
]} *)
val keepU : 'a t -> (('a -> bool)[@bs]) -> 'a t
val keep : 'a t -> ('a -> bool) -> 'a t
(** [keep xs p] returns a list of all elements in [xs] which satisfy the predicate function [p]
{[
keep [ 1; 2; 3; 4 ] (fun x -> x mod 2 = 0) = [ 2; 4 ]
]} *)
val keepWithIndexU : 'a t -> (('a -> int -> bool)[@bs]) -> 'a t
val keepWithIndex : 'a t -> ('a -> int -> bool) -> 'a t
(** [keepWithIndex xs p] returns a list of all elements in [xs] which satisfy the predicate function [p]
{[
keepWithIndex [ 1; 2; 3; 4 ] (fun _x i -> i mod 2 = 0) = [ 1; 3 ]
]} *)
val keepMapU : 'a t -> (('a -> 'b option)[@bs]) -> 'b t
val keepMap : 'a t -> ('a -> 'b option) -> 'b t
(** [keepMap xs f] applies [f] to each element of [xs]. If [f xi] returns [Some value], then [value] is kept in the
resulting list; if [f xi] returns [None], the element is not retained in the result.
{[
keepMap [ 1; 2; 3; 4 ] (fun x -> if x mod 2 = 0 then Some (-x) else None) = [ -2; -4 ]
]} *)
val partitionU : 'a t -> (('a -> bool)[@bs]) -> 'a t * 'a t
val partition : 'a t -> ('a -> bool) -> 'a t * 'a t
(** [partition xs p] creates a pair of lists; the first list consists of all elements of [xs] that satisfy the predicate
function [p]; the second list consists of all elements of [xs] that do not satisfy [p]
{[
partition [ 1; 2; 3; 4 ] (fun x -> x mod 2 = 0) = ([ 2; 4 ], [ 1; 3 ])
]} *)
val unzip : ('a * 'b) t -> 'a t * 'b t
(** [unzip xs] takes a list of pairs and creates a pair of lists. The first list contains all the first items of the
pairs; the second list contains all the second items.
{[
unzip [ (1, 2); (3, 4) ] = ([ 1; 3 ], [ 2; 4 ]);;
unzip [ (1, 2); (3, 4); (5, 6); (7, 8) ] = ([ 1; 3; 5; 7 ], [ 2; 4; 6; 8 ])
]} *)
val getAssocU : ('a * 'c) t -> 'b -> (('a -> 'b -> bool)[@bs]) -> 'c option
val getAssoc : ('a * 'c) t -> 'b -> ('a -> 'b -> bool) -> 'c option
(** [getAssoc xs k eq]
return the second element of a pair in [xs] where the first element equals [x] as per the predicate function [eq],
or [None] if not found
{[
getAssoc [ 1, "a"; 2, "b"; 3, "c"] 2 (=) = Some "b"
getAssoc [9, "morning"; 15, "afternoon"; 22, "night"] 3 (fun a b -> a mod 12 = b mod 12) = Some "afternoon"
]} *)
val hasAssocU : ('a * 'c) t -> 'b -> (('a -> 'b -> bool)[@bs]) -> bool
val hasAssoc : ('a * 'c) t -> 'b -> ('a -> 'b -> bool) -> bool
(** [hasAssoc xs k eq] return true if there is a pair in [xs] where the first element equals [k] as per the predicate
funtion [eq]
{[
hasAssoc [ (1, "a"); (2, "b"); (3, "c") ] 1 ( = ) = true;;
hasAssoc [ (9, "morning"); (15, "afternoon"); (22, "night") ] 3 (fun a b -> a mod 12 = b mod 12) = true
]} *)
val removeAssocU : ('a * 'c) t -> 'b -> (('a -> 'b -> bool)[@bs]) -> ('a * 'c) t
val removeAssoc : ('a * 'c) t -> 'b -> ('a -> 'b -> bool) -> ('a * 'c) t
(** [removeAssoc xs k eq] Return a list after removing the first pair whose first value is [k] per the equality
predicate [eq]; if not found, return a new list identical to [xs].
{[
removeAssoc [ (1, "a"); (2, "b"); (3, "c") ] 1 ( = )
= [ (2, "b"); (3, "c") ] removeAssoc [ (1, "a"); (2, "b"); (3, "c") ] 99 ( = )
= [ (1, "a"); (2, "b"); (3, "c") ]
]} *)
val setAssocU : ('a * 'c) t -> 'a -> 'c -> (('a -> 'a -> bool)[@bs]) -> ('a * 'c) t
val setAssoc : ('a * 'c) t -> 'a -> 'c -> ('a -> 'a -> bool) -> ('a * 'c) t
(** [setAssoc xs k v eq] if [k] exists in [xs] by satisfying the [eq] predicate, return a new list with the key and
value replaced by the new [k] and [v]; otherwise, return a new list with the pair [k, v] added to the head of [xs].
{[
setAssoc [ (1, "a"); (2, "b"); (3, "c") ] 2 "x" ( = ) = [ (1, "a"); (2, "x"); (3, "c") ];;
setAssoc [ (1, "a"); (3, "c") ] 2 "b" ( = )
= [ (2, "b"); (1, "a"); (3, "c") ]
setAssoc
[ (9, "morning"); (3, "morning?!"); (22, "night") ]
15 "afternoon"
(fun a b -> a mod 12 = b mod 12)
= [ (9, "morning"); (15, "afternoon"); (22, "night") ]
]}
Note carefully the last example! Since [15 mod 12] equals [3 mod 12], {i both} the key and value are replaced in the
list. *)
val sortU : 'a t -> (('a -> 'a -> int)[@bs]) -> 'a t
val sort : 'a t -> ('a -> 'a -> int) -> 'a t
(** [sort xs] Returns a sorted list.
{[
sort [ 5; 4; 9; 3; 7 ] (fun a b -> a - b) = [ 3; 4; 5; 7; 9 ]
]} *)
================================================
FILE: packages/Belt/src/Belt_Map.ml
================================================
module Int = Belt_MapInt
(** specalized when key type is [int], more efficient than the generic type *)
module String = Belt_MapString
(** specalized when key type is [string], more efficient than the generic type *)
module Dict = Belt_MapDict
(** seprate function from data, a more verboe but slightly more efficient *)
type ('key, 'id) id = ('key, 'id) Belt_Id.comparable
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
type ('k, 'v, 'id) t = { cmp : ('k, 'id) cmp; data : ('k, 'v, 'id) Dict.t }
module S = struct
include (
struct
let t : cmp:('k, 'id) cmp -> data:('k, 'v, 'id) Dict.t -> ('k, 'v, 'id) t = fun ~cmp ~data -> { cmp; data }
let cmp : ('k, 'v, 'id) t -> ('k, 'id) cmp = fun o -> o.cmp
let data : ('k, 'v, 'id) t -> ('k, 'v, 'id) Dict.t = fun o -> o.data
end :
sig
val t : cmp:('k, 'id) cmp -> data:('k, 'v, 'id) Dict.t -> ('k, 'v, 'id) t
val cmp : ('k, 'v, 'id) t -> ('k, 'id) cmp
val data : ('k, 'v, 'id) t -> ('k, 'v, 'id) Dict.t
end)
end
let fromArray (type k idx) data ~(id : (k, idx) id) =
let module M = (val id) in
let cmp = M.cmp in
S.t ~cmp ~data:(Dict.fromArray ~cmp data)
let remove m x =
let cmp, odata =
let open S in
(cmp m, data m)
in
let newData = Dict.remove odata x ~cmp in
if newData == odata then m else S.t ~cmp ~data:newData
let removeMany m x =
let cmp, odata = (S.cmp m, S.data m) in
let newData = Dict.removeMany odata x ~cmp in
S.t ~cmp ~data:newData
let set m key d =
let cmp = S.cmp m in
S.t ~cmp ~data:(Dict.set ~cmp (S.data m) key d)
let mergeMany m e =
let cmp = S.cmp m in
S.t ~cmp ~data:(Dict.mergeMany ~cmp (S.data m) e)
let updateU m key f =
let cmp = S.cmp m in
S.t ~cmp ~data:(Dict.updateU ~cmp (S.data m) key f)
let update m key f = updateU m key (fun a -> f a)
let split m x =
let cmp = S.cmp m in
let (l, r), b = Dict.split ~cmp (S.data m) x in
((S.t ~cmp ~data:l, S.t ~cmp ~data:r), b)
let mergeU s1 s2 f =
let cmp = S.cmp s1 in
S.t ~cmp ~data:(Dict.mergeU ~cmp (S.data s1) (S.data s2) f)
let merge s1 s2 f = mergeU s1 s2 (fun a b c -> f a b c)
let make (type key idx) ~(id : (key, idx) id) =
let module M = (val id) in
S.t ~cmp:M.cmp ~data:Dict.empty
let isEmpty map = Dict.isEmpty (S.data map)
let forEachU m f = Dict.forEachU (S.data m) f
let forEach m f = forEachU m (fun a b -> f a b)
let reduceU m acc f = Dict.reduceU (S.data m) acc f
let reduce m acc f = reduceU m acc (fun a b c -> f a b c)
let everyU m f = Dict.everyU (S.data m) f
let every m f = everyU m (fun a b -> f a b)
let someU m f = Dict.someU (S.data m) f
let some m f = someU m (fun a b -> f a b)
let keepU m f = S.t ~cmp:(S.cmp m) ~data:(Dict.keepU (S.data m) f)
let keep m f = keepU m (fun a b -> f a b)
let partitionU m p =
let cmp = S.cmp m in
let l, r = Dict.partitionU (S.data m) p in
(S.t ~cmp ~data:l, S.t ~cmp ~data:r)
let partition m p = partitionU m (fun a b -> p a b)
let mapU m f = S.t ~cmp:(S.cmp m) ~data:(Dict.mapU (S.data m) f)
let map m f = mapU m (fun a -> f a)
let mapWithKeyU m f = S.t ~cmp:(S.cmp m) ~data:(Dict.mapWithKeyU (S.data m) f)
let mapWithKey m f = mapWithKeyU m (fun a b -> f a b)
let size map = Dict.size (S.data map)
let toList map = Dict.toList (S.data map)
let toArray m = Dict.toArray (S.data m)
let keysToArray m = Dict.keysToArray (S.data m)
let valuesToArray m = Dict.valuesToArray (S.data m)
let minKey m = Dict.minKey (S.data m)
let minKeyUndefined m = Dict.minKeyUndefined (S.data m)
let maxKey m = Dict.maxKey (S.data m)
let maxKeyUndefined m = Dict.maxKeyUndefined (S.data m)
let minimum m = Dict.minimum (S.data m)
let minUndefined m = Dict.minUndefined (S.data m)
let maximum m = Dict.maximum (S.data m)
let maxUndefined m = Dict.maxUndefined (S.data m)
let get map x = Dict.get ~cmp:(S.cmp map) (S.data map) x
let getUndefined map x = Dict.getUndefined ~cmp:(S.cmp map) (S.data map) x
let getWithDefault map x def = Dict.getWithDefault ~cmp:(S.cmp map) (S.data map) x def
let getExn map x = Dict.getExn ~cmp:(S.cmp map) (S.data map) x
let has map x = Dict.has ~cmp:(S.cmp map) (S.data map) x
let checkInvariantInternal m = Dict.checkInvariantInternal (S.data m)
let eqU m1 m2 veq = Dict.eqU ~kcmp:(S.cmp m1) ~veq (S.data m1) (S.data m2)
let eq m1 m2 veq = eqU m1 m2 (fun a b -> veq a b)
let cmpU m1 m2 vcmp = Dict.cmpU ~kcmp:(S.cmp m1) ~vcmp (S.data m1) (S.data m2)
let cmp m1 m2 vcmp = cmpU m1 m2 (fun a b -> vcmp a b)
let getData = S.data
let getId (type key identity) (m : (key, _, identity) t) : (key, identity) id =
let module T = struct
type nonrec identity = identity
type nonrec t = key
let cmp = S.cmp m
end in
(module T)
let packIdData (type key idx) ~(id : (key, idx) id) ~data =
let module M = (val id) in
S.t ~cmp:M.cmp ~data
let findFirstByU m f = Dict.findFirstByU m.data f
let findFirstBy m f = findFirstByU m (fun a b -> f a b)
================================================
FILE: packages/Belt/src/Belt_Map.mli
================================================
(***********************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the GNU Library General Public License, with *)
(* the special exception on linking described in file ../LICENSE. *)
(* *)
(* Adapted by authors of ReScript without using functors *)
(***********************************************************************)
(** A {i immutable} sorted map module which allows customize {i compare} behavior.
The implementation uses balanced binary trees, and therefore searching and insertion take time logarithmic in the
size of the map.
For more info on this module's usage of identity, `make` and others, please see the top level documentation of Belt,
{b A special encoding for collection safety}.
Example usage:
{[
module PairComparator = Belt.Id.MakeComparable (struct
type t = int * int
let cmp (a0, a1) (b0, b1) = match Pervasives.compare a0 b0 with 0 -> Pervasives.compare a1 b1 | c -> c
end)
let myMap = Belt.Map.make ~id:(module PairComparator)
let myMap2 = Belt.Map.set myMap (1, 2) "myValue"
]}
The API documentation below will assume a predeclared comparator module for integers, IntCmp *)
module Int = Belt_MapInt
(** Specalized when key type is [int], more efficient than the generic type, its compare behavior is fixed using the
built-in comparison *)
module String = Belt_MapString
(** specalized when key type is [string], more efficient than the generic type, its compare behavior is fixed using the
built-in comparison *)
module Dict = Belt_MapDict
(** This module seprate identity from data, it is a bit more verboe but slightly more efficient due to the fact that
there is no need to pack identity and data back after each operation
{b Advanced usage only} *)
type ('key, 'value, 'identity) t
(** [('key, 'identity) t]
['key] is the field type
['value] is the element type
['identity] the identity of the collection *)
type ('key, 'id) id = ('key, 'id) Belt_Id.comparable
(** The identity needed for making an empty map*)
(*
How we retain soundness:
The only way to create a value of type [_ t] from scratch
is through [empty] which requires [_ Belt_Id.t]
The only way to create [_ Belt_Id.t] is using [Belt_Id.Make] which
will create a fresh type [id] per module
Generic operations over tree without [cmp] are still exported
(for efficient reasons) so that [data] does not need be boxed and unboxed.
The soundness is guaranteed in two aspects:
When create a value of [_ t] it needs both [_ Belt_Id.t] and [_ t0].
[_ Belt_Id.t] is an abstract type. Note [add0] requires [_ Belt_Id.cmp] which
is also an abstract type which can only come from [_ Belt_Id.t]
When destructing a value of [_ t], the ['id] parameter is threaded.
*)
(* should not export [Belt_Id.compare].
should only export [Belt_Id.t] or [Belt_Id.cmp] instead *)
val make : id:('k, 'id) id -> ('k, 'v, 'id) t
(** [make ~id] creates a new map by taking in the comparator
{[
let m = Belt.Map.make ~id:(module IntCmp)
]} *)
val isEmpty : _ t -> bool
(** [isEmpty m] checks whether a map m is empty
{[
isEmpty (fromArray [| (1, "1") |] ~id:(module IntCmp)) = false
]} *)
val has : ('k, 'v, 'id) t -> 'k -> bool
(** [has m k] checks whether m has the key k
{[
has (fromArray [| (1, "1") |] ~id:(module IntCmp)) 1 = true
]} *)
val cmpU : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> (('v -> 'v -> int)[@u]) -> int
val cmp : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> ('v -> 'v -> int) -> int
(** [cmp m0 m1 vcmp]
Total ordering of map given total ordering of value function.
It will compare size first and each element following the order one by one. *)
val eqU : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> (('v -> 'v -> bool)[@u]) -> bool
val eq : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> ('v -> 'v -> bool) -> bool
(** [eq m1 m2 veq] tests whether the maps [m1] and [m2] are equal, that is, contain equal keys and associate them with
equal data. [veq] is the equality predicate used to compare the data associated with the keys. *)
val findFirstByU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> ('k * 'v) option
val findFirstBy : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> ('k * 'v) option
(** [findFirstBy m p] uses funcion [f] to find the first key value pair to match predicate [p].
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |];;
findFirstBy s0 (fun k v -> k = 4) = option (4, "4")
]} *)
val forEachU : ('k, 'v, 'id) t -> (('k -> 'v -> unit)[@u]) -> unit
val forEach : ('k, 'v, 'id) t -> ('k -> 'v -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the 'k as first argument, and the associated
value as second argument. The bindings are passed to [f] in increasing order with respect to the ordering over the
type of the keys.
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |]
let acc = ref [];;
forEach s0 (fun k v -> acc := (k, v) :: !acc);;
!acc = [ (4, "4"); (3, "3"); (2, "2"); (1, "1") ]
]} *)
val reduceU : ('k, 'v, 'id) t -> 'acc -> (('acc -> 'k -> 'v -> 'acc)[@u]) -> 'acc
val reduce : ('k, 'v, 'id) t -> 'acc -> ('acc -> 'k -> 'v -> 'acc) -> 'acc
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data.
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |];;
reduce s0 [] (fun acc k v -> (k, v) acc) = [ (4, "4"); (3, "3"); (2, "2"); (1, "1") ]
]} *)
val everyU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> bool
val every : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. Order unspecified *)
val someU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> bool
val some : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. Order unspecified *)
val size : ('k, 'v, 'id) t -> int
(** [size s]
{[
size (fromArray [ (2, "2"); (2, "1"); (3, "3") ] ~id:(module IntCmp)) = 2
]} *)
val toArray : ('k, 'v, 'id) t -> ('k * 'v) array
(** [toArray s]
{[
toArray (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) = [ (1, "1"); (2, "2"); (3, "3") ]
]} *)
val toList : ('k, 'v, 'id) t -> ('k * 'v) list
(** In increasing order
{b See} {!toArray} *)
val fromArray : ('k * 'v) array -> id:('k, 'id) id -> ('k, 'v, 'id) t
(** [fromArray kvs ~id]
{[
toArray (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) = [ (1, "1"); (2, "2"); (3, "3") ]
]} *)
val keysToArray : ('k, 'v, 'id) t -> 'k array
(** [keysToArray s]
{[
keysToArray (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) = [| 1; 2; 3 |]
]} *)
val valuesToArray : ('k, 'v, 'id) t -> 'v array
(** [valuesToArray s]
{[
valuesToArray (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) = [| "1"; "2"; "3" |]
]} *)
val minKey : ('k, _, _) t -> 'k option
(** [minKey s]
@return the minimum key, None if not exist *)
val minKeyUndefined : ('k, _, _) t -> 'k Js.undefined
(** {b See} {!minKey}*)
val maxKey : ('k, _, _) t -> 'k option
(** [maxKey s]
@return the maximum key, None if not exist *)
val maxKeyUndefined : ('k, _, _) t -> 'k Js.undefined
(** {b See} {!maxKey} *)
val minimum : ('k, 'v, _) t -> ('k * 'v) option
(** [minimum s]
@return the minimum key value pair, None if not exist *)
val minUndefined : ('k, 'v, _) t -> ('k * 'v) Js.undefined
(** {b See} {!minimum} *)
val maximum : ('k, 'v, _) t -> ('k * 'v) option
(** [maximum s]
@return the maximum key value pair, None if not exist *)
val maxUndefined : ('k, 'v, _) t -> ('k * 'v) Js.undefined
(** {b See} {!maximum} *)
val get : ('k, 'v, 'id) t -> 'k -> 'v option
(** [get s k]
{[
get (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) 2 = Some "2";;
get (fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)) 2 = None
]} *)
val getUndefined : ('k, 'v, 'id) t -> 'k -> 'v Js.undefined
(** {b See} {!get}
@return [undefined] when not found *)
val getWithDefault : ('k, 'v, 'id) t -> 'k -> 'v -> 'v
(** [getWithDefault s k default]
{b See} {!get}
@return [default] when [k] is not found *)
val getExn : ('k, 'v, 'id) t -> 'k -> 'v
(** [getExn s k]
{b See} {!getExn}
{b raise} when [k] not exist *)
(****************************************************************************)
val remove : ('k, 'v, 'id) t -> 'k -> ('k, 'v, 'id) t
(** [remove m x] when [x] is not in [m], [m] is returned reference unchanged.
{[
let s0 = fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)
let s1 = remove s0 1
let s2 = remove s1 1;;
s1 == s2;;
keysToArray s1 = [| 2; 3 |]
]} *)
val removeMany : ('k, 'v, 'id) t -> 'k array -> ('k, 'v, 'id) t
(** [removeMany s xs]
Removing each of [xs] to [s], note unlike {!remove}, the reference of return value might be changed even if none in
[xs] exists [s] *)
val set : ('k, 'v, 'id) t -> 'k -> 'v -> ('k, 'v, 'id) t
(** [set m x y ] returns a map containing the same bindings as [m], with a new binding of [x] to [y]. If [x] was already
bound in [m], its previous binding disappears.
{[
let s0 = fromArray [ (2, "2"); (1, "1"); (3, "3") ] ~id:(module IntCmp)
let s1 = set s0 2 "3";;
valuesToArray s1 = [ "1"; "3"; "3" ]
]} *)
val updateU : ('k, 'v, 'id) t -> 'k -> (('v option -> 'v option)[@u]) -> ('k, 'v, 'id) t
val update : ('k, 'v, 'id) t -> 'k -> ('v option -> 'v option) -> ('k, 'v, 'id) t
(** [update m x f] returns a map containing the same bindings as [m], except for the binding of [x]. Depending on the
value of [y] where [y] is [f (get x m)], the binding of [x] is added, removed or updated. If [y] is [None], the
binding is removed if it exists; otherwise, if [y] is [Some z] then [x] is associated to [z] in the resulting map.
*)
val mergeMany : ('k, 'v, 'id) t -> ('k * 'v) array -> ('k, 'v, 'id) t
(** [mergeMany s xs]
Add each of [xs] to [s], note unlike {!set}, the reference of return value might be changed even if all values in
[xs] exist [s] *)
val mergeU :
('k, 'v, 'id) t -> ('k, 'v2, 'id) t -> (('k -> 'v option -> 'v2 option -> 'v3 option)[@u]) -> ('k, 'v3, 'id) t
val merge : ('k, 'v, 'id) t -> ('k, 'v2, 'id) t -> ('k -> 'v option -> 'v2 option -> 'v3 option) -> ('k, 'v3, 'id) t
(** [merge m1 m2 f] computes a map whose keys is a subset of keys of [m1] and of [m2]. The presence of each such
binding, and the corresponding value, is determined with the function [f]. *)
val keepU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> ('k, 'v, 'id) t
val keep : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> ('k, 'v, 'id) t
(** [keep m p] returns the map with all the bindings in [m] that satisfy predicate [p]. *)
val partitionU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> ('k, 'v, 'id) t * ('k, 'v, 'id) t
val partition : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> ('k, 'v, 'id) t * ('k, 'v, 'id) t
(** [partition m p] returns a pair of maps [(m1, m2)], where [m1] contains all the bindings of [s] that satisfy the
predicate [p], and [m2] is the map with all the bindings of [s] that do not satisfy [p]. *)
val split : ('k, 'v, 'id) t -> 'k -> (('k, 'v, 'id) t * ('k, 'v, 'id) t) * 'v option
(** [split x m] returns a tuple [(l r), data], where [l] is the map with all the bindings of [m] whose 'k is strictly
less than [x]; [r] is the map with all the bindings of [m] whose 'k is strictly greater than [x]; [data] is [None]
if [m] contains no binding for [x], or [Some v] if [m] binds [v] to [x]. *)
val mapU : ('k, 'v, 'id) t -> (('v -> 'v2)[@u]) -> ('k, 'v2, 'id) t
val map : ('k, 'v, 'id) t -> ('v -> 'v2) -> ('k, 'v2, 'id) t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : ('k, 'v, 'id) t -> (('k -> 'v -> 'v2)[@u]) -> ('k, 'v2, 'id) t
val mapWithKey : ('k, 'v, 'id) t -> ('k -> 'v -> 'v2) -> ('k, 'v2, 'id) t
(** [mapWithKey m f]
The same as {!map} except that [f] is supplied with one more argument: the key *)
val getData : ('k, 'v, 'id) t -> ('k, 'v, 'id) Belt_MapDict.t
(** [getData s0]
{b Advanced usage only}
@return
the raw data (detached from comparator), but its type is still manifested, so that user can pass identity directly
without boxing *)
val getId : ('k, 'v, 'id) t -> ('k, 'id) id
(** [getId s0]
{b Advanced usage only}
@return the identity of [s0] *)
val packIdData : id:('k, 'id) id -> data:('k, 'v, 'id) Belt_MapDict.t -> ('k, 'v, 'id) t
(** [packIdData ~id ~data]
{b Advanced usage only}
@return the packed collection *)
(**/**)
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(**/**)
================================================
FILE: packages/Belt/src/Belt_MapDict.ml
================================================
module N = Belt_internalAVLtree
module A = Belt_Array
type ('key, 'a, 'id) t = ('key, 'a) N.t
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
let empty = N.empty
let fromArray = N.fromArray
let isEmpty = N.isEmpty
let cmp = N.cmp
let cmpU = N.cmpU
let eq = N.eq
let eqU = N.eqU
let has = N.has
let forEach = N.forEach
let forEachU = N.forEachU
let reduce = N.reduce
let reduceU = N.reduceU
let every = N.every
let everyU = N.everyU
let some = N.some
let someU = N.someU
let size = N.size
let toList = N.toList
let toArray = N.toArray
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let minimum = N.minimum
let maximum = N.maximum
let minKey = N.minKey
let maxKey = N.maxKey
let minKeyUndefined = N.minKeyUndefined
let maxKeyUndefined = N.maxKeyUndefined
let minUndefined = N.minUndefined
let maxUndefined = N.maxUndefined
let get = N.get
let getUndefined = N.getUndefined
let getWithDefault = N.getWithDefault
let getExn = N.getExn
let mapWithKey = N.mapWithKey
let mapWithKeyU = N.mapWithKeyU
let mapU = N.mapU
let map = N.map
let keep = N.keepShared
let keepU = N.keepSharedU
let partitionU = N.partitionSharedU
let partition = N.partitionShared
let checkInvariantInternal = N.checkInvariantInternal
let rec set (t : _ t) newK newD ~cmp =
match N.toOpt t with
| None -> N.singleton newK newD
| Some n ->
let k = N.key n in
let c = (Belt_Id.getCmpInternal cmp) newK k in
if c = 0 then N.return (N.updateValue n newD)
else
let l, r, v = (N.left n, N.right n, N.value n) in
if c < 0 then N.bal (set ~cmp l newK newD) k v r else N.bal l k v (set ~cmp r newK newD)
let rec updateU (t : _ t) newK f ~cmp : _ t =
match N.toOpt t with
| None -> ( match f None with None -> t | Some newD -> N.singleton newK newD)
| Some n ->
let k = N.key n in
let c = (Belt_Id.getCmpInternal cmp) newK k in
if c = 0 then
match f (Some (N.value n)) with
| None -> (
let l, r = (N.left n, N.right n) in
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r)
| Some newD -> N.return (N.updateValue n newD)
else
let l, r, v = (N.left n, N.right n, N.value n) in
if c < 0 then
let ll = updateU ~cmp l newK f in
if l == ll then t else N.bal ll k v r
else
let rr = updateU ~cmp r newK f in
if r == rr then t else N.bal l k v rr
let update t newK f ~cmp = updateU t newK (fun a -> f a) ~cmp
let rec remove t x ~cmp =
match N.toOpt t with
| None -> t
| Some n ->
let l, v, d, r =
let open N in
(left n, key n, value n, right n)
in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r
else if c < 0 then
let ll = remove l x ~cmp in
if ll == l then t else N.bal ll v d r
else
let rr = remove r x ~cmp in
if rr == r then t else N.bal l v d rr
let mergeMany h arr ~cmp =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key, value = A.getUnsafe arr i in
v := set !v ~cmp key value
done;
!v
let rec splitAuxPivot n x pres ~cmp =
let l, v, d, r =
let open N in
(left n, key n, value n, right n)
in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then (
pres := Some d;
(l, r))
else if c < 0 then
match N.toOpt l with
| None ->
let open N in
(empty, return n)
| Some l ->
let ll, rl = splitAuxPivot ~cmp l x pres in
(ll, N.join rl v d r)
else
match N.toOpt r with
| None ->
let open N in
(return n, empty)
| Some r ->
let lr, rr = splitAuxPivot ~cmp r x pres in
(N.join l v d lr, rr)
let split n x ~cmp =
match N.toOpt n with
| None ->
( (let open N in
(empty, empty)),
None )
| Some n ->
let pres = ref None in
let v = splitAuxPivot ~cmp n x pres in
(v, !pres)
let rec mergeU s1 s2 f ~cmp =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, None -> N.empty
| Some _, None -> N.keepMapU s1 (fun k v -> f k (Some v) None)
| None, Some _ -> N.keepMapU s2 (fun k v -> f k None (Some v))
| Some s1n, Some s2n ->
if N.height s1n >= N.height s2n then
let l1, v1, d1, r1 =
let open N in
(left s1n, key s1n, value s1n, right s1n)
in
let d2 = ref None in
let l2, r2 = splitAuxPivot ~cmp s2n v1 d2 in
let d2 = !d2 in
let newLeft = mergeU ~cmp l1 l2 f in
let newD = f v1 (Some d1) d2 in
let newRight = mergeU ~cmp r1 r2 f in
N.concatOrJoin newLeft v1 newD newRight
else
let l2, v2, d2, r2 =
let open N in
(left s2n, key s2n, value s2n, right s2n)
in
let d1 = ref None in
let l1, r1 = splitAuxPivot ~cmp s1n v2 d1 in
let d1 = !d1 in
let newLeft = mergeU ~cmp l1 l2 f in
let newD = f v2 d1 (Some d2) in
let newRight = mergeU ~cmp r1 r2 f in
N.concatOrJoin newLeft v2 newD newRight
let merge s1 s2 f ~cmp = mergeU s1 s2 (fun a b c -> f a b c) ~cmp
let removeMany t keys ~cmp =
let len = A.length keys in
let rec loop current i = if i < len then loop (remove current (A.getUnsafe keys i) ~cmp) (i + 1) else current in
loop t 0
let findFirstByU = N.findFirstByU
let findFirstBy = N.findFirstBy
================================================
FILE: packages/Belt/src/Belt_MapDict.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
type ('key, 'value, 'id) t
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
val empty : ('k, 'v, 'id) t
val isEmpty : ('k, 'v, 'id) t -> bool
val has : ('k, 'a, 'id) t -> 'k -> cmp:('k, 'id) cmp -> bool
val cmpU : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> kcmp:('k, 'id) cmp -> vcmp:(('v -> 'v -> int)[@u]) -> int
val cmp : ('k, 'v, 'id) t -> ('k, 'v, 'id) t -> kcmp:('k, 'id) cmp -> vcmp:('v -> 'v -> int) -> int
val eqU : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> kcmp:('k, 'id) cmp -> veq:(('a -> 'a -> bool)[@u]) -> bool
val eq : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> kcmp:('k, 'id) cmp -> veq:('a -> 'a -> bool) -> bool
(** [eq m1 m2 cmp] tests whether the maps [m1] and [m2] are equal, that is, contain equal keys and associate them with
equal data. [cmp] is the equality predicate used to compare the data associated with the keys. *)
val findFirstByU : ('k, 'v, 'id) t -> (('k -> 'v -> bool)[@u]) -> ('k * 'v) option
val findFirstBy : ('k, 'v, 'id) t -> ('k -> 'v -> bool) -> ('k * 'v) option
(** [findFirstBy m p] uses funcion [f] to find the first key value pair to match predicate [p].
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |];;
findFirstBy s0 (fun k v -> k = 4) = option (4, "4")
]} *)
val forEachU : ('k, 'a, 'id) t -> (('k -> 'a -> unit)[@u]) -> unit
val forEach : ('k, 'a, 'id) t -> ('k -> 'a -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the key as first argument, and the associated
value as second argument. The bindings are passed to [f] in increasing order with respect to the ordering over the
type of the keys. *)
val reduceU : ('k, 'a, 'id) t -> 'b -> (('b -> 'k -> 'a -> 'b)[@u]) -> 'b
val reduce : ('k, 'a, 'id) t -> 'b -> ('b -> 'k -> 'a -> 'b) -> 'b
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> bool
val every : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. Order unspecified *)
val someU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> bool
val some : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. Order unspecified *)
val size : ('k, 'a, 'id) t -> int
val toList : ('k, 'a, 'id) t -> ('k * 'a) list
(** In increasing order. *)
val toArray : ('k, 'a, 'id) t -> ('k * 'a) array
val fromArray : ('k * 'a) array -> cmp:('k, 'id) cmp -> ('k, 'a, 'id) t
val keysToArray : ('k, 'a, 'id) t -> 'k array
val valuesToArray : ('k, 'a, 'id) t -> 'a array
val minKey : ('k, _, _) t -> 'k option
val minKeyUndefined : ('k, _, _) t -> 'k Js.undefined
val maxKey : ('k, _, _) t -> 'k option
val maxKeyUndefined : ('k, _, _) t -> 'k Js.undefined
val minimum : ('k, 'a, _) t -> ('k * 'a) option
val minUndefined : ('k, 'a, _) t -> ('k * 'a) Js.undefined
val maximum : ('k, 'a, _) t -> ('k * 'a) option
val maxUndefined : ('k, 'a, _) t -> ('k * 'a) Js.undefined
val get : ('k, 'a, 'id) t -> 'k -> cmp:('k, 'id) cmp -> 'a option
val getUndefined : ('k, 'a, 'id) t -> 'k -> cmp:('k, 'id) cmp -> 'a Js.undefined
val getWithDefault : ('k, 'a, 'id) t -> 'k -> 'a -> cmp:('k, 'id) cmp -> 'a
val getExn : ('k, 'a, 'id) t -> 'k -> cmp:('k, 'id) cmp -> 'a
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
val remove : ('a, 'b, 'id) t -> 'a -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
(** [remove m x] returns a map containing the same bindings as [m], except for [x] which is unbound in the returned map.
*)
val removeMany : ('a, 'b, 'id) t -> 'a array -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
val set : ('a, 'b, 'id) t -> 'a -> 'b -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
(** [set m x y] returns a map containing the same bindings as [m], plus a binding of [x] to [y]. If [x] was already
bound in [m], its previous binding disappears. *)
val updateU : ('a, 'b, 'id) t -> 'a -> (('b option -> 'b option)[@u]) -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
val update : ('a, 'b, 'id) t -> 'a -> ('b option -> 'b option) -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
val mergeU :
('a, 'b, 'id) t ->
('a, 'c, 'id) t ->
(('a -> 'b option -> 'c option -> 'd option)[@u]) ->
cmp:('a, 'id) cmp ->
('a, 'd, 'id) t
val merge :
('a, 'b, 'id) t ->
('a, 'c, 'id) t ->
('a -> 'b option -> 'c option -> 'd option) ->
cmp:('a, 'id) cmp ->
('a, 'd, 'id) t
(** [merge m1 m2 f] computes a map whose keys is a subset of keys of [m1] and of [m2]. The presence of each such
binding, and the corresponding value, is determined with the function [f]. *)
val mergeMany : ('a, 'b, 'id) t -> ('a * 'b) array -> cmp:('a, 'id) cmp -> ('a, 'b, 'id) t
val keepU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> ('k, 'a, 'id) t
val keep : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> ('k, 'a, 'id) t
(** [keep m p] returns the map with all the bindings in [m] that satisfy predicate [p]. *)
val partitionU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> ('k, 'a, 'id) t * ('k, 'a, 'id) t
val partition : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> ('k, 'a, 'id) t * ('k, 'a, 'id) t
(** [partition m p] returns a pair of maps [(m1, m2)], where [m1] contains all the bindings of [s] that satisfy the
predicate [p], and [m2] is the map with all the bindings of [s] that do not satisfy [p]. *)
val split : ('a, 'b, 'id) t -> 'a -> cmp:('a, 'id) cmp -> (('a, 'b, 'id) t * ('a, 'b, 'id) t) * 'b option
(** [split x m] returns a triple [(l, data, r)], where [l] is the map with all the bindings of [m] whose key is strictly
less than [x]; [r] is the map with all the bindings of [m] whose key is strictly greater than [x]; [data] is [None]
if [m] contains no binding for [x], or [Some v] if [m] binds [v] to [x]. *)
val mapU : ('k, 'a, 'id) t -> (('a -> 'b)[@u]) -> ('k, 'b, 'id) t
val map : ('k, 'a, 'id) t -> ('a -> 'b) -> ('k, 'b, 'id) t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : ('k, 'a, 'id) t -> (('k -> 'a -> 'b)[@u]) -> ('k, 'b, 'id) t
val mapWithKey : ('k, 'a, 'id) t -> ('k -> 'a -> 'b) -> ('k, 'b, 'id) t
================================================
FILE: packages/Belt/src/Belt_MapInt.ml
================================================
type key = int
module I = Belt_internalMapInt
module N = Belt_internalAVLtree
module A = Belt_Array
type 'a t = (key, 'a) N.t
let empty = N.empty
let isEmpty = N.isEmpty
let singleton = N.singleton
let minKey = N.minKey
let minKeyUndefined = N.minKeyUndefined
let maxKey = N.maxKey
let maxKeyUndefined = N.maxKeyUndefined
let minimum = N.minimum
let minUndefined = N.minUndefined
let maximum = N.maximum
let maxUndefined = N.maxUndefined
let forEachU = N.forEachU
let forEach = N.forEach
let mapU = N.mapU
let map = N.map
let mapWithKeyU = N.mapWithKeyU
let mapWithKey = N.mapWithKey
let reduceU = N.reduceU
let reduce = N.reduce
let everyU = N.everyU
let every = N.every
let someU = N.someU
let some = N.some
let keepU = N.keepSharedU
let keep = N.keepShared
let partitionU = N.partitionSharedU
let partition = N.partitionShared
let size = N.size
let toList = N.toList
let toArray = N.toArray
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let checkInvariantInternal = N.checkInvariantInternal
let rec set t (newK : key) (newD : _) =
match N.toOpt t with
| None -> N.singleton newK newD
| Some n ->
let k = N.key n in
if newK = k then N.return (N.updateValue n newD)
else
let v = N.value n in
if newK < k then N.bal (set (N.left n) newK newD) k v (N.right n)
else N.bal (N.left n) k v (set (N.right n) newK newD)
let rec updateU t (x : key) f =
match N.toOpt t with
| None -> ( match f None with None -> t | Some data -> N.singleton x data)
| Some n ->
let k = N.key n in
if x = k then
match f (Some (N.value n)) with
| None -> (
let l, r = (N.left n, N.right n) in
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r)
| Some data -> N.return (N.updateValue n data)
else
let l, r, v = (N.left n, N.right n, N.value n) in
if x < k then
let ll = updateU l x f in
if l == ll then t else N.bal ll k v r
else
let rr = updateU r x f in
if r == rr then t else N.bal l k v rr
let update t x f = updateU t x (fun a -> f a)
let rec removeAux n (x : key) =
let l, v, r =
let open N in
(left n, key n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r
else if x < v then
match N.toOpt l with
| None -> N.return n
| Some left ->
let ll = removeAux left x in
if ll == l then N.return n
else
let open N in
bal ll v (value n) r
else
match N.toOpt r with
| None -> N.return n
| Some right ->
let rr = removeAux right x in
N.bal l v (N.value n) rr
let remove n x = match N.toOpt n with None -> N.empty | Some n -> removeAux n x
let rec removeMany0 t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = removeAux t ele in
match N.toOpt u with None -> u | Some t -> removeMany0 t xs (i + 1) len
else N.return t
let removeMany t keys =
let len = A.length keys in
match N.toOpt t with None -> N.empty | Some t -> removeMany0 t keys 0 len
let mergeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key, value = A.getUnsafe arr i in
v := set !v key value
done;
!v
let mergeArray = mergeMany
let has = I.has
let cmpU = I.cmpU
let cmp = I.cmp
let eqU = I.eqU
let eq = I.eq
let findFirstByU = N.findFirstByU
let findFirstBy t f = findFirstByU t (fun[@u] a b -> f a b)
let get = I.get
let getUndefined = I.getUndefined
let getWithDefault = I.getWithDefault
let getExn = I.getExn
let split = I.split
let mergeU = I.mergeU
let merge = I.merge
let fromArray = I.fromArray
================================================
FILE: packages/Belt/src/Belt_MapInt.mli
================================================
type key = int
type 'value t
(** The type of maps from type [key] to type ['value]. *)
val empty : 'v t
val isEmpty : 'v t -> bool
val has : 'v t -> key -> bool
val cmpU : 'v t -> 'v t -> (('v -> 'v -> int)[@u]) -> int
val cmp : 'v t -> 'v t -> ('v -> 'v -> int) -> int
val eqU : 'v t -> 'v t -> (('v -> 'v -> bool)[@u]) -> bool
val eq : 'v t -> 'v t -> ('v -> 'v -> bool) -> bool
(** [eq m1 m2] tests whether the maps [m1] and [m2] are equal, that is, contain equal keys and associate them with equal
data. *)
val findFirstByU : 'v t -> ((key -> 'v -> bool)[@u]) -> (key * 'v) option
val findFirstBy : 'v t -> (key -> 'v -> bool) -> (key * 'v) option
(** [findFirstBy m p] uses funcion [f] to find the first key value pair to match predicate [p].
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |];;
findFirstBy s0 (fun k v -> k = 4) = option (4, "4")
]} *)
val forEachU : 'v t -> ((key -> 'v -> unit)[@u]) -> unit
val forEach : 'v t -> (key -> 'v -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the key as first argument, and the associated
value as second argument. The bindings are passed to [f] in increasing order with respect to the ordering over the
type of the keys. *)
val reduceU : 'v t -> 'v2 -> (('v2 -> key -> 'v -> 'v2)[@u]) -> 'v2
val reduce : 'v t -> 'v2 -> ('v2 -> key -> 'v -> 'v2) -> 'v2
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : 'v t -> ((key -> 'v -> bool)[@u]) -> bool
val every : 'v t -> (key -> 'v -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. Order unspecified *)
val someU : 'v t -> ((key -> 'v -> bool)[@u]) -> bool
val some : 'v t -> (key -> 'v -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. Order unspecified *)
val size : 'v t -> int
val toList : 'v t -> (key * 'v) list
(** In increasing order. *)
val toArray : 'v t -> (key * 'v) array
val fromArray : (key * 'v) array -> 'v t
val keysToArray : 'v t -> key array
val valuesToArray : 'v t -> 'v array
val minKey : _ t -> key option
val minKeyUndefined : _ t -> key Js.undefined
val maxKey : _ t -> key option
val maxKeyUndefined : _ t -> key Js.undefined
val minimum : 'v t -> (key * 'v) option
val minUndefined : 'v t -> (key * 'v) Js.undefined
val maximum : 'v t -> (key * 'v) option
val maxUndefined : 'v t -> (key * 'v) Js.undefined
val get : 'v t -> key -> 'v option
val getUndefined : 'v t -> key -> 'v Js.undefined
val getWithDefault : 'v t -> key -> 'v -> 'v
val getExn : 'v t -> key -> 'v
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
val remove : 'v t -> key -> 'v t
(** [remove m x] returns a map containing the same bindings as [m], except for [x] which is unbound in the returned map.
*)
val removeMany : 'v t -> key array -> 'v t
val set : 'v t -> key -> 'v -> 'v t
(** [set m x y] returns a map containing the same bindings as [m], plus a binding of [x] to [y]. If [x] was already
bound in [m], its previous binding disappears. *)
val updateU : 'v t -> key -> (('v option -> 'v option)[@u]) -> 'v t
val update : 'v t -> key -> ('v option -> 'v option) -> 'v t
val mergeU : 'v t -> 'v2 t -> ((key -> 'v option -> 'v2 option -> 'c option)[@u]) -> 'c t
val merge : 'v t -> 'v2 t -> (key -> 'v option -> 'v2 option -> 'c option) -> 'c t
(** [merge m1 m2 f] computes a map whose keys is a subset of keys of [m1] and of [m2]. The presence of each such
binding, and the corresponding value, is determined with the function [f]. *)
val mergeMany : 'v t -> (key * 'v) array -> 'v t
val keepU : 'v t -> ((key -> 'v -> bool)[@u]) -> 'v t
val keep : 'v t -> (key -> 'v -> bool) -> 'v t
(** [keep m p] returns the map with all the bindings in [m] that satisfy predicate [p]. *)
val partitionU : 'v t -> ((key -> 'v -> bool)[@u]) -> 'v t * 'v t
val partition : 'v t -> (key -> 'v -> bool) -> 'v t * 'v t
(** [partition m p] returns a pair of maps [(m1, m2)], where [m1] contains all the bindings of [s] that satisfy the
predicate [p], and [m2] is the map with all the bindings of [s] that do not satisfy [p]. *)
val split : key -> 'v t -> 'v t * 'v option * 'v t
(** [split x m] returns a triple [(l, data, r)], where [l] is the map with all the bindings of [m] whose key is strictly
less than [x]; [r] is the map with all the bindings of [m] whose key is strictly greater than [x]; [data] is [None]
if [m] contains no binding for [x], or [Some v] if [m] binds [v] to [x]. *)
val mapU : 'v t -> (('v -> 'v2)[@u]) -> 'v2 t
val map : 'v t -> ('v -> 'v2) -> 'v2 t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : 'v t -> ((key -> 'v -> 'v2)[@u]) -> 'v2 t
val mapWithKey : 'v t -> (key -> 'v -> 'v2) -> 'v2 t
================================================
FILE: packages/Belt/src/Belt_MapString.ml
================================================
type key = string
module I = Belt_internalMapString
module N = Belt_internalAVLtree
module A = Belt_Array
type 'a t = (key, 'a) N.t
let empty = N.empty
let isEmpty = N.isEmpty
let singleton = N.singleton
let minKey = N.minKey
let minKeyUndefined = N.minKeyUndefined
let maxKey = N.maxKey
let maxKeyUndefined = N.maxKeyUndefined
let minimum = N.minimum
let minUndefined = N.minUndefined
let maximum = N.maximum
let maxUndefined = N.maxUndefined
let forEachU = N.forEachU
let forEach = N.forEach
let mapU = N.mapU
let map = N.map
let mapWithKeyU = N.mapWithKeyU
let mapWithKey = N.mapWithKey
let reduceU = N.reduceU
let reduce = N.reduce
let everyU = N.everyU
let every = N.every
let someU = N.someU
let some = N.some
let keepU = N.keepSharedU
let keep = N.keepShared
let partitionU = N.partitionSharedU
let partition = N.partitionShared
let size = N.size
let toList = N.toList
let toArray = N.toArray
let keysToArray = N.keysToArray
let valuesToArray = N.valuesToArray
let checkInvariantInternal = N.checkInvariantInternal
let rec set t (newK : key) (newD : _) =
match N.toOpt t with
| None -> N.singleton newK newD
| Some n ->
let k = N.key n in
if newK = k then N.return (N.updateValue n newD)
else
let v = N.value n in
if newK < k then N.bal (set (N.left n) newK newD) k v (N.right n)
else N.bal (N.left n) k v (set (N.right n) newK newD)
let rec updateU t (x : key) f =
match N.toOpt t with
| None -> ( match f None with None -> t | Some data -> N.singleton x data)
| Some n ->
let k = N.key n in
if x = k then
match f (Some (N.value n)) with
| None -> (
let l, r = (N.left n, N.right n) in
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r)
| Some data -> N.return (N.updateValue n data)
else
let l, r, v = (N.left n, N.right n, N.value n) in
if x < k then
let ll = updateU l x f in
if l == ll then t else N.bal ll k v r
else
let rr = updateU r x f in
if r == rr then t else N.bal l k v rr
let update t x f = updateU t x (fun a -> f a)
let rec removeAux n (x : key) =
let l, v, r =
let open N in
(left n, key n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r
else if x < v then
match N.toOpt l with
| None -> N.return n
| Some left ->
let ll = removeAux left x in
if ll == l then N.return n
else
let open N in
bal ll v (value n) r
else
match N.toOpt r with
| None -> N.return n
| Some right ->
let rr = removeAux right x in
N.bal l v (N.value n) rr
let remove n x = match N.toOpt n with None -> N.empty | Some n -> removeAux n x
let rec removeMany0 t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = removeAux t ele in
match N.toOpt u with None -> u | Some t -> removeMany0 t xs (i + 1) len
else N.return t
let removeMany t keys =
let len = A.length keys in
match N.toOpt t with None -> N.empty | Some t -> removeMany0 t keys 0 len
let mergeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key, value = A.getUnsafe arr i in
v := set !v key value
done;
!v
let mergeArray = mergeMany
let has = I.has
let cmpU = I.cmpU
let cmp = I.cmp
let eqU = I.eqU
let eq = I.eq
let findFirstByU = N.findFirstByU
let findFirstBy t f = findFirstByU t (fun[@u] a b -> f a b)
let get = I.get
let getUndefined = I.getUndefined
let getWithDefault = I.getWithDefault
let getExn = I.getExn
let split = I.split
let mergeU = I.mergeU
let merge = I.merge
let fromArray = I.fromArray
================================================
FILE: packages/Belt/src/Belt_MapString.mli
================================================
type key = string
type 'value t
(** The type of maps from type [key] to type ['value]. *)
val empty : 'v t
val isEmpty : 'v t -> bool
val has : 'v t -> key -> bool
val cmpU : 'v t -> 'v t -> (('v -> 'v -> int)[@u]) -> int
val cmp : 'v t -> 'v t -> ('v -> 'v -> int) -> int
val eqU : 'v t -> 'v t -> (('v -> 'v -> bool)[@u]) -> bool
val eq : 'v t -> 'v t -> ('v -> 'v -> bool) -> bool
(** [eq m1 m2] tests whether the maps [m1] and [m2] are equal, that is, contain equal keys and associate them with equal
data. *)
val findFirstByU : 'v t -> ((key -> 'v -> bool)[@u]) -> (key * 'v) option
val findFirstBy : 'v t -> (key -> 'v -> bool) -> (key * 'v) option
(** [findFirstBy m p] uses funcion [f] to find the first key value pair to match predicate [p].
{[
let s0 = fromArray ~id:(module IntCmp) [| (4, "4"); (1, "1"); (2, "2," 3 "") |];;
findFirstBy s0 (fun k v -> k = 4) = option (4, "4")
]} *)
val forEachU : 'v t -> ((key -> 'v -> unit)[@u]) -> unit
val forEach : 'v t -> (key -> 'v -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the key as first argument, and the associated
value as second argument. The bindings are passed to [f] in increasing order with respect to the ordering over the
type of the keys. *)
val reduceU : 'v t -> 'v2 -> (('v2 -> key -> 'v -> 'v2)[@u]) -> 'v2
val reduce : 'v t -> 'v2 -> ('v2 -> key -> 'v -> 'v2) -> 'v2
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : 'v t -> ((key -> 'v -> bool)[@u]) -> bool
val every : 'v t -> (key -> 'v -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. Order unspecified *)
val someU : 'v t -> ((key -> 'v -> bool)[@u]) -> bool
val some : 'v t -> (key -> 'v -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. Order unspecified *)
val size : 'v t -> int
val toList : 'v t -> (key * 'v) list
(** In increasing order. *)
val toArray : 'v t -> (key * 'v) array
val fromArray : (key * 'v) array -> 'v t
val keysToArray : 'v t -> key array
val valuesToArray : 'v t -> 'v array
val minKey : _ t -> key option
val minKeyUndefined : _ t -> key Js.undefined
val maxKey : _ t -> key option
val maxKeyUndefined : _ t -> key Js.undefined
val minimum : 'v t -> (key * 'v) option
val minUndefined : 'v t -> (key * 'v) Js.undefined
val maximum : 'v t -> (key * 'v) option
val maxUndefined : 'v t -> (key * 'v) Js.undefined
val get : 'v t -> key -> 'v option
val getUndefined : 'v t -> key -> 'v Js.undefined
val getWithDefault : 'v t -> key -> 'v -> 'v
val getExn : 'v t -> key -> 'v
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
val remove : 'v t -> key -> 'v t
(** [remove m x] returns a map containing the same bindings as [m], except for [x] which is unbound in the returned map.
*)
val removeMany : 'v t -> key array -> 'v t
val set : 'v t -> key -> 'v -> 'v t
(** [set m x y] returns a map containing the same bindings as [m], plus a binding of [x] to [y]. If [x] was already
bound in [m], its previous binding disappears. *)
val updateU : 'v t -> key -> (('v option -> 'v option)[@u]) -> 'v t
val update : 'v t -> key -> ('v option -> 'v option) -> 'v t
val mergeU : 'v t -> 'v2 t -> ((key -> 'v option -> 'v2 option -> 'c option)[@u]) -> 'c t
val merge : 'v t -> 'v2 t -> (key -> 'v option -> 'v2 option -> 'c option) -> 'c t
(** [merge m1 m2 f] computes a map whose keys is a subset of keys of [m1] and of [m2]. The presence of each such
binding, and the corresponding value, is determined with the function [f]. *)
val mergeMany : 'v t -> (key * 'v) array -> 'v t
val keepU : 'v t -> ((key -> 'v -> bool)[@u]) -> 'v t
val keep : 'v t -> (key -> 'v -> bool) -> 'v t
(** [keep m p] returns the map with all the bindings in [m] that satisfy predicate [p]. *)
val partitionU : 'v t -> ((key -> 'v -> bool)[@u]) -> 'v t * 'v t
val partition : 'v t -> (key -> 'v -> bool) -> 'v t * 'v t
(** [partition m p] returns a pair of maps [(m1, m2)], where [m1] contains all the bindings of [s] that satisfy the
predicate [p], and [m2] is the map with all the bindings of [s] that do not satisfy [p]. *)
val split : key -> 'v t -> 'v t * 'v option * 'v t
(** [split x m] returns a triple [(l, data, r)], where [l] is the map with all the bindings of [m] whose key is strictly
less than [x]; [r] is the map with all the bindings of [m] whose key is strictly greater than [x]; [data] is [None]
if [m] contains no binding for [x], or [Some v] if [m] binds [v] to [x]. *)
val mapU : 'v t -> (('v -> 'v2)[@u]) -> 'v2 t
val map : 'v t -> ('v -> 'v2) -> 'v2 t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : 'v t -> ((key -> 'v -> 'v2)[@u]) -> 'v2 t
val mapWithKey : 'v t -> (key -> 'v -> 'v2) -> 'v2 t
================================================
FILE: packages/Belt/src/Belt_MutableMap.ml
================================================
module Int = Belt_MutableMapInt
module String = Belt_MutableMapString
module N = Belt_internalAVLtree
module A = Belt_Array
type ('key, 'id) id = ('key, 'id) Belt_Id.comparable
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
module S = struct
include (
struct
type ('k, 'v, 'id) t = { cmp : ('k, 'id) cmp; mutable data : ('k, 'v) N.t }
let t : cmp:('k, 'id) cmp -> data:('k, 'v) N.t -> ('k, 'v, 'id) t = fun ~cmp ~data -> { cmp; data }
let cmp : ('k, 'v, 'id) t -> ('k, 'id) cmp = fun o -> o.cmp
let dataSet : ('k, 'v, 'id) t -> ('k, 'v) N.t -> unit = fun o v -> o.data <- v
let data : ('k, 'v, 'id) t -> ('k, 'v) N.t = fun o -> o.data
end :
sig
type ('k, 'v, 'id) t
val t : cmp:('k, 'id) cmp -> data:('k, 'v) N.t -> ('k, 'v, 'id) t
val cmp : ('k, 'v, 'id) t -> ('k, 'id) cmp
val dataSet : ('k, 'v, 'id) t -> ('k, 'v) N.t -> unit
val data : ('k, 'v, 'id) t -> ('k, 'v) N.t
end)
end
type ('k, 'v, 'id) t = ('k, 'v, 'id) S.t
let rec removeMutateAux nt x ~cmp =
let k = N.key nt in
let c = (Belt_Id.getCmpInternal cmp) x k in
if c = 0 then
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt)
| None, Some _ -> r
| (Some _ | None), None -> l
else if c < 0 then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeMutateAux ~cmp l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeMutateAux ~cmp r x);
N.return (N.balMutate nt)
let remove d k =
let oldRoot = S.data d in
match N.toOpt oldRoot with
| None -> ()
| Some oldRoot2 ->
let newRoot = removeMutateAux ~cmp:(S.cmp d) oldRoot2 k in
if newRoot != oldRoot then S.dataSet d newRoot
let rec removeArrayMutateAux t xs i len ~cmp =
if i < len then
let ele = A.getUnsafe xs i in
let u = removeMutateAux t ele ~cmp in
match N.toOpt u with None -> N.empty | Some t -> removeArrayMutateAux t xs (i + 1) len ~cmp
else N.return t
let removeMany d xs =
let oldRoot = S.data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
let newRoot = removeArrayMutateAux nt xs 0 len ~cmp:(S.cmp d) in
if newRoot != oldRoot then S.dataSet d newRoot
let rec updateDone t x f ~cmp =
match N.toOpt t with
| None -> ( match f None with Some data -> N.singleton x data | None -> t)
| Some nt ->
let k = N.key nt in
let c = (Belt_Id.getCmpInternal cmp) x k in
if c = 0 then (
match f (Some (N.value nt)) with
| None -> (
let l, r = (N.left nt, N.right nt) in
match (N.toOpt l, N.toOpt r) with
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt)
| None, Some _ -> r
| (Some _ | None), None -> l)
| Some data ->
N.valueSet nt data;
N.return nt)
else
let l, r =
let open N in
(left nt, right nt)
in
if c < 0 then
let ll = updateDone l x f ~cmp in
N.leftSet nt ll
else N.rightSet nt (updateDone r x f ~cmp);
N.return (N.balMutate nt)
let updateU t x f =
let oldRoot = S.data t in
let newRoot = updateDone oldRoot x f ~cmp:(S.cmp t) in
if newRoot != oldRoot then S.dataSet t newRoot
let update t x f = updateU t x (fun a -> f a)
let make (type key identity) ~(id : (key, identity) id) =
let module M = (val id) in
S.t ~cmp:M.cmp ~data:N.empty
let clear m = S.dataSet m N.empty
let isEmpty d = N.isEmpty (S.data d)
let minKey m = N.minKey (S.data m)
let minKeyUndefined m = N.minKeyUndefined (S.data m)
let maxKey m = N.maxKey (S.data m)
let maxKeyUndefined m = N.maxKeyUndefined (S.data m)
let minimum m = N.minimum (S.data m)
let minUndefined m = N.minUndefined (S.data m)
let maximum m = N.maximum (S.data m)
let maxUndefined m = N.maxUndefined (S.data m)
let forEachU d f = N.forEachU (S.data d) f
let forEach d f = forEachU d (fun a b -> f a b)
let reduceU d acc cb = N.reduceU (S.data d) acc cb
let reduce d acc cb = reduceU d acc (fun a b c -> cb a b c)
let everyU d p = N.everyU (S.data d) p
let every d p = everyU d (fun a b -> p a b)
let someU d p = N.someU (S.data d) p
let some d p = someU d (fun a b -> p a b)
let size d = N.size (S.data d)
let toList d = N.toList (S.data d)
let toArray d = N.toArray (S.data d)
let keysToArray d = N.keysToArray (S.data d)
let valuesToArray d = N.valuesToArray (S.data d)
let fromSortedArrayUnsafe (type key identity) ~(id : (key, identity) id) xs : _ t =
let module M = (val id) in
S.t ~data:(N.fromSortedArrayUnsafe xs) ~cmp:M.cmp
let checkInvariantInternal d = N.checkInvariantInternal (S.data d)
let cmpU m1 m2 cmp = N.cmpU ~kcmp:(S.cmp m1) ~vcmp:cmp (S.data m1) (S.data m2)
let cmp m1 m2 cmp = cmpU m1 m2 (fun a b -> cmp a b)
let eqU m1 m2 cmp = N.eqU ~kcmp:(S.cmp m1) ~veq:cmp (S.data m1) (S.data m2)
let eq m1 m2 cmp = eqU m1 m2 (fun a b -> cmp a b)
let mapU m f = S.t ~cmp:(S.cmp m) ~data:(N.mapU (S.data m) f)
let map m f = mapU m (fun a -> f a)
let mapWithKeyU m f = S.t ~cmp:(S.cmp m) ~data:(N.mapWithKeyU (S.data m) f)
let mapWithKey m f = mapWithKeyU m (fun a b -> f a b)
let get m x = N.get ~cmp:(S.cmp m) (S.data m) x
let getUndefined m x = N.getUndefined ~cmp:(S.cmp m) (S.data m) x
let getWithDefault m x def = N.getWithDefault ~cmp:(S.cmp m) (S.data m) x def
let getExn m x = N.getExn ~cmp:(S.cmp m) (S.data m) x
let has m x = N.has ~cmp:(S.cmp m) (S.data m) x
let fromArray (type k identity) data ~(id : (k, identity) id) =
let module M = (val id) in
let cmp = M.cmp in
S.t ~cmp ~data:(N.fromArray ~cmp data)
let set m e v =
let oldRoot = S.data m in
let newRoot = N.updateMutate ~cmp:(S.cmp m) oldRoot e v in
if newRoot != oldRoot then S.dataSet m newRoot
let mergeManyAux t xs ~cmp =
let v = ref t in
for i = 0 to A.length xs - 1 do
let key, value = A.getUnsafe xs i in
v := N.updateMutate !v key value ~cmp
done;
!v
let mergeMany d xs =
let oldRoot = S.data d in
let newRoot = mergeManyAux oldRoot xs ~cmp:(S.cmp d) in
if newRoot != oldRoot then S.dataSet d newRoot
================================================
FILE: packages/Belt/src/Belt_MutableMap.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
module Int = Belt_MutableMapInt
module String = Belt_MutableMapString
(** A {b mutable} sorted map module which allows customize {i compare} behavior.
Same as Belt.Map, but mutable. *)
type ('k, 'v, 'id) t
type ('key, 'id) id = ('key, 'id) Belt_Id.comparable
val make : id:('k, 'id) id -> ('k, 'a, 'id) t
val clear : _ t -> unit
val isEmpty : _ t -> bool
val has : ('k, _, _) t -> 'k -> bool
val cmpU : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> (('a -> 'a -> int)[@u]) -> int
val cmp : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> ('a -> 'a -> int) -> int
(** [cmp m1 m2 cmp] First compare by size, if size is the same, compare by key, value pair *)
val eqU : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> (('a -> 'a -> bool)[@u]) -> bool
val eq : ('k, 'a, 'id) t -> ('k, 'a, 'id) t -> ('a -> 'a -> bool) -> bool
(** [eq m1 m2 eqf] tests whether the maps [m1] and [m2] are equal, that is, contain equal keys and associate them with
equal data. [eqf] is the equality predicate used to compare the data associated with the keys. *)
val forEachU : ('k, 'a, 'id) t -> (('k -> 'a -> unit)[@u]) -> unit
val forEach : ('k, 'a, 'id) t -> ('k -> 'a -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the 'k as first argument, and the associated
value as second argument. The bindings are passed to [f] in increasing order with respect to the ordering over the
type of the keys. *)
val reduceU : ('k, 'a, 'id) t -> 'b -> (('b -> 'k -> 'a -> 'b)[@u]) -> 'b
val reduce : ('k, 'a, 'id) t -> 'b -> ('b -> 'k -> 'a -> 'b) -> 'b
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> bool
val every : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. *)
val someU : ('k, 'a, 'id) t -> (('k -> 'a -> bool)[@u]) -> bool
val some : ('k, 'a, 'id) t -> ('k -> 'a -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. *)
val size : ('k, 'a, 'id) t -> int
val toList : ('k, 'a, 'id) t -> ('k * 'a) list
(** In increasing order*)
val toArray : ('k, 'a, 'id) t -> ('k * 'a) array
(** In increasing order*)
val fromArray : ('k * 'a) array -> id:('k, 'id) id -> ('k, 'a, 'id) t
val keysToArray : ('k, _, _) t -> 'k array
val valuesToArray : (_, 'a, _) t -> 'a array
val minKey : ('k, _, _) t -> 'k option
val minKeyUndefined : ('k, _, _) t -> 'k Js.undefined
val maxKey : ('k, _, _) t -> 'k option
val maxKeyUndefined : ('k, _, _) t -> 'k Js.undefined
val minimum : ('k, 'a, _) t -> ('k * 'a) option
val minUndefined : ('k, 'a, _) t -> ('k * 'a) Js.undefined
val maximum : ('k, 'a, _) t -> ('k * 'a) option
val maxUndefined : ('k, 'a, _) t -> ('k * 'a) Js.undefined
val get : ('k, 'a, 'id) t -> 'k -> 'a option
val getUndefined : ('k, 'a, 'id) t -> 'k -> 'a Js.undefined
val getWithDefault : ('k, 'a, 'id) t -> 'k -> 'a -> 'a
val getExn : ('k, 'a, 'id) t -> 'k -> 'a
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(****************************************************************************)
(*TODO: add functional [merge, partition, keep, split]*)
val remove : ('k, 'a, 'id) t -> 'k -> unit
(** [remove m x] do the in-place modification, *)
val removeMany : ('k, 'a, 'id) t -> 'k array -> unit
val set : ('k, 'a, 'id) t -> 'k -> 'a -> unit
(** [set m x y ] do the in-place modification *)
val updateU : ('k, 'a, 'id) t -> 'k -> (('a option -> 'a option)[@u]) -> unit
val update : ('k, 'a, 'id) t -> 'k -> ('a option -> 'a option) -> unit
val mergeMany : ('k, 'a, 'id) t -> ('k * 'a) array -> unit
val mapU : ('k, 'a, 'id) t -> (('a -> 'b)[@u]) -> ('k, 'b, 'id) t
val map : ('k, 'a, 'id) t -> ('a -> 'b) -> ('k, 'b, 'id) t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : ('k, 'a, 'id) t -> (('k -> 'a -> 'b)[@u]) -> ('k, 'b, 'id) t
val mapWithKey : ('k, 'a, 'id) t -> ('k -> 'a -> 'b) -> ('k, 'b, 'id) t
================================================
FILE: packages/Belt/src/Belt_MutableMapInt.ml
================================================
module I = Belt_internalMapInt
type key = int
module N = Belt_internalAVLtree
module A = Belt_Array
include (
struct
type 'a t = { mutable data : 'a I.t }
let t : data:'a I.t -> 'a t = fun ~data -> { data }
let dataSet : 'a t -> 'a I.t -> unit = fun o v -> o.data <- v
let data : 'a t -> 'a I.t = fun o -> o.data
end :
sig
type 'a t
val t : data:'a I.t -> 'a t
val dataSet : 'a t -> 'a I.t -> unit
val data : 'a t -> 'a I.t
end)
let make () = t ~data:N.empty
let isEmpty m = N.isEmpty (data m)
let clear m = dataSet m N.empty
let singleton k v = t ~data:(N.singleton k v)
let minKeyUndefined m = N.minKeyUndefined (data m)
let minKey m = N.minKey (data m)
let maxKeyUndefined m = N.maxKeyUndefined (data m)
let maxKey m = N.maxKey (data m)
let minimum m = N.minimum (data m)
let minUndefined m = N.minUndefined (data m)
let maximum m = N.maximum (data m)
let maxUndefined m = N.maxUndefined (data m)
let set (m : _ t) k v =
let old_data = data m in
let v = I.addMutate old_data k v in
if v != old_data then dataSet m v
let forEachU d f = N.forEachU (data d) f
let forEach d f = forEachU d (fun a b -> f a b)
let mapU d f = t ~data:(N.mapU (data d) f)
let map d f = mapU d (fun a -> f a)
let mapWithKeyU d f = t ~data:(N.mapWithKeyU (data d) f)
let mapWithKey d f = mapWithKeyU d (fun a b -> f a b)
let reduceU d acc f = N.reduceU (data d) acc f
let reduce d acc f = reduceU d acc (fun a b c -> f a b c)
let everyU d f = N.everyU (data d) f
let every d f = everyU d (fun a b -> f a b)
let someU d f = N.someU (data d) f
let some d f = someU d (fun a b -> f a b)
let size d = N.size (data d)
let toList d = N.toList (data d)
let toArray d = N.toArray (data d)
let keysToArray d = N.keysToArray (data d)
let valuesToArray d = N.valuesToArray (data d)
let checkInvariantInternal d = N.checkInvariantInternal (data d)
let has d v = I.has (data d) v
let rec removeMutateAux nt (x : key) =
let k = N.key nt in
if x = k then (
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeMutateAux l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeMutateAux r x);
N.return (N.balMutate nt)
let remove d v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some root ->
let newRoot = removeMutateAux root v in
if newRoot != oldRoot then dataSet d newRoot
let rec updateDone t (x : key) f =
match N.toOpt t with
| None -> ( match f None with Some data -> N.singleton x data | None -> t)
| Some nt ->
let k = N.key nt in
if k = x then (
match f (Some (N.value nt)) with
| None -> (
let l, r = (N.left nt, N.right nt) in
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
| Some data ->
N.valueSet nt data;
N.return nt)
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then
let ll = updateDone l x f in
N.leftSet nt ll
else N.rightSet nt (updateDone r x f);
N.return (N.balMutate nt)
let updateU t x f =
let oldRoot = data t in
let newRoot = updateDone oldRoot x f in
if newRoot != oldRoot then dataSet t newRoot
let update t x f = updateU t x (fun a -> f a)
let rec removeArrayMutateAux t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = removeMutateAux t ele in
match N.toOpt u with None -> N.empty | Some t -> removeArrayMutateAux t xs (i + 1) len
else N.return t
let removeMany (type key id) (d : _ t) xs =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
let newRoot = removeArrayMutateAux nt xs 0 len in
if newRoot != oldRoot then dataSet d newRoot
let fromArray xs = t ~data:(I.fromArray xs)
let cmpU d0 d1 f = I.cmpU (data d0) (data d1) f
let cmp d0 d1 f = cmpU d0 d1 (fun a b -> f a b)
let eqU d0 d1 f = I.eqU (data d0) (data d1) f
let eq d0 d1 f = eqU d0 d1 (fun a b -> f a b)
let get d x = I.get (data d) x
let getUndefined d x = I.getUndefined (data d) x
let getWithDefault d x def = I.getWithDefault (data d) x def
let getExn d x = I.getExn (data d) x
================================================
FILE: packages/Belt/src/Belt_MutableMapInt.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
type key = int
type 'a t
val make : unit -> 'a t
val clear : 'a t -> unit
val isEmpty : 'a t -> bool
val has : 'a t -> key -> bool
val cmpU : 'a t -> 'a t -> (('a -> 'a -> int)[@u]) -> int
val cmp : 'a t -> 'a t -> ('a -> 'a -> int) -> int
(** [cmp m1 m2 cmp] First compare by size, if size is the same, compare by key, value pair *)
val eqU : 'a t -> 'a t -> (('a -> 'a -> bool)[@u]) -> bool
val eq : 'a t -> 'a t -> ('a -> 'a -> bool) -> bool
(** [eq m1 m2 cmp] *)
val forEachU : 'a t -> ((key -> 'a -> unit)[@u]) -> unit
val forEach : 'a t -> (key -> 'a -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the key as first argument, and the associated
value as second argument. The application order of [f] is in increasing order. *)
val reduceU : 'a t -> 'b -> (('b -> key -> 'a -> 'b)[@u]) -> 'b
val reduce : 'a t -> 'b -> ('b -> key -> 'a -> 'b) -> 'b
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : 'a t -> ((key -> 'a -> bool)[@u]) -> bool
val every : 'a t -> (key -> 'a -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. The application order of [p] is
unspecified. *)
val someU : 'a t -> ((key -> 'a -> bool)[@u]) -> bool
val some : 'a t -> (key -> 'a -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. The application order of [p] is
unspecified. *)
val size : 'a t -> int
val toList : 'a t -> (key * 'a) list
(** In increasing order *)
val toArray : 'a t -> (key * 'a) array
(** In increasing order *)
val fromArray : (key * 'a) array -> 'a t
val keysToArray : 'a t -> key array
val valuesToArray : 'a t -> 'a array
val minKey : _ t -> key option
val minKeyUndefined : _ t -> key Js.undefined
val maxKey : _ t -> key option
val maxKeyUndefined : _ t -> key Js.undefined
val minimum : 'a t -> (key * 'a) option
val minUndefined : 'a t -> (key * 'a) Js.undefined
val maximum : 'a t -> (key * 'a) option
val maxUndefined : 'a t -> (key * 'a) Js.undefined
val get : 'a t -> key -> 'a option
val getUndefined : 'a t -> key -> 'a Js.undefined
val getWithDefault : 'a t -> key -> 'a -> 'a
val getExn : 'a t -> key -> 'a
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(****************************************************************************)
(*TODO: add functional [merge, partition, keep, split]*)
val remove : 'a t -> key -> unit
(** [remove m x] do the in-place modification *)
val removeMany : 'a t -> key array -> unit
val set : 'a t -> key -> 'a -> unit
(** [set m x y] do the in-place modification, return [m] for chaining. If [x] was already bound in [m], its previous
binding disappears. *)
val updateU : 'a t -> key -> (('a option -> 'a option)[@u]) -> unit
val update : 'a t -> key -> ('a option -> 'a option) -> unit
val mapU : 'a t -> (('a -> 'b)[@u]) -> 'b t
val map : 'a t -> ('a -> 'b) -> 'b t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : 'a t -> ((key -> 'a -> 'b)[@u]) -> 'b t
val mapWithKey : 'a t -> (key -> 'a -> 'b) -> 'b t
================================================
FILE: packages/Belt/src/Belt_MutableMapString.ml
================================================
module I = Belt_internalMapString
type key = string
module N = Belt_internalAVLtree
module A = Belt_Array
include (
struct
type 'a t = { mutable data : 'a I.t }
let t : data:'a I.t -> 'a t = fun ~data -> { data }
let dataSet : 'a t -> 'a I.t -> unit = fun o v -> o.data <- v
let data : 'a t -> 'a I.t = fun o -> o.data
end :
sig
type 'a t
val t : data:'a I.t -> 'a t
val dataSet : 'a t -> 'a I.t -> unit
val data : 'a t -> 'a I.t
end)
let make () = t ~data:N.empty
let isEmpty m = N.isEmpty (data m)
let clear m = dataSet m N.empty
let singleton k v = t ~data:(N.singleton k v)
let minKeyUndefined m = N.minKeyUndefined (data m)
let minKey m = N.minKey (data m)
let maxKeyUndefined m = N.maxKeyUndefined (data m)
let maxKey m = N.maxKey (data m)
let minimum m = N.minimum (data m)
let minUndefined m = N.minUndefined (data m)
let maximum m = N.maximum (data m)
let maxUndefined m = N.maxUndefined (data m)
let set (m : _ t) k v =
let old_data = data m in
let v = I.addMutate old_data k v in
if v != old_data then dataSet m v
let forEachU d f = N.forEachU (data d) f
let forEach d f = forEachU d (fun a b -> f a b)
let mapU d f = t ~data:(N.mapU (data d) f)
let map d f = mapU d (fun a -> f a)
let mapWithKeyU d f = t ~data:(N.mapWithKeyU (data d) f)
let mapWithKey d f = mapWithKeyU d (fun a b -> f a b)
let reduceU d acc f = N.reduceU (data d) acc f
let reduce d acc f = reduceU d acc (fun a b c -> f a b c)
let everyU d f = N.everyU (data d) f
let every d f = everyU d (fun a b -> f a b)
let someU d f = N.someU (data d) f
let some d f = someU d (fun a b -> f a b)
let size d = N.size (data d)
let toList d = N.toList (data d)
let toArray d = N.toArray (data d)
let keysToArray d = N.keysToArray (data d)
let valuesToArray d = N.valuesToArray (data d)
let checkInvariantInternal d = N.checkInvariantInternal (data d)
let has d v = I.has (data d) v
let rec removeMutateAux nt (x : key) =
let k = N.key nt in
if x = k then (
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeMutateAux l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeMutateAux r x);
N.return (N.balMutate nt)
let remove d v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some root ->
let newRoot = removeMutateAux root v in
if newRoot != oldRoot then dataSet d newRoot
let rec updateDone t (x : key) f =
match N.toOpt t with
| None -> ( match f None with Some data -> N.singleton x data | None -> t)
| Some nt ->
let k = N.key nt in
if k = x then (
match f (Some (N.value nt)) with
| None -> (
let l, r = (N.left nt, N.right nt) in
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
| Some data ->
N.valueSet nt data;
N.return nt)
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then
let ll = updateDone l x f in
N.leftSet nt ll
else N.rightSet nt (updateDone r x f);
N.return (N.balMutate nt)
let updateU t x f =
let oldRoot = data t in
let newRoot = updateDone oldRoot x f in
if newRoot != oldRoot then dataSet t newRoot
let update t x f = updateU t x (fun a -> f a)
let rec removeArrayMutateAux t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = removeMutateAux t ele in
match N.toOpt u with None -> N.empty | Some t -> removeArrayMutateAux t xs (i + 1) len
else N.return t
let removeMany (type key id) (d : _ t) xs =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
let newRoot = removeArrayMutateAux nt xs 0 len in
if newRoot != oldRoot then dataSet d newRoot
let fromArray xs = t ~data:(I.fromArray xs)
let cmpU d0 d1 f = I.cmpU (data d0) (data d1) f
let cmp d0 d1 f = cmpU d0 d1 (fun a b -> f a b)
let eqU d0 d1 f = I.eqU (data d0) (data d1) f
let eq d0 d1 f = eqU d0 d1 (fun a b -> f a b)
let get d x = I.get (data d) x
let getUndefined d x = I.getUndefined (data d) x
let getWithDefault d x def = I.getWithDefault (data d) x def
let getExn d x = I.getExn (data d) x
================================================
FILE: packages/Belt/src/Belt_MutableMapString.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
type key = string
type 'a t
val make : unit -> 'a t
val clear : 'a t -> unit
val isEmpty : 'a t -> bool
val has : 'a t -> key -> bool
val cmpU : 'a t -> 'a t -> (('a -> 'a -> int)[@u]) -> int
val cmp : 'a t -> 'a t -> ('a -> 'a -> int) -> int
(** [cmp m1 m2 cmp] First compare by size, if size is the same, compare by key, value pair *)
val eqU : 'a t -> 'a t -> (('a -> 'a -> bool)[@u]) -> bool
val eq : 'a t -> 'a t -> ('a -> 'a -> bool) -> bool
(** [eq m1 m2 cmp] *)
val forEachU : 'a t -> ((key -> 'a -> unit)[@u]) -> unit
val forEach : 'a t -> (key -> 'a -> unit) -> unit
(** [forEach m f] applies [f] to all bindings in map [m]. [f] receives the key as first argument, and the associated
value as second argument. The application order of [f] is in increasing order. *)
val reduceU : 'a t -> 'b -> (('b -> key -> 'a -> 'b)[@u]) -> 'b
val reduce : 'a t -> 'b -> ('b -> key -> 'a -> 'b) -> 'b
(** [reduce m a f] computes [(f kN dN ... (f k1 d1 a)...)], where [k1 ... kN] are the keys of all bindings in [m] (in
increasing order), and [d1 ... dN] are the associated data. *)
val everyU : 'a t -> ((key -> 'a -> bool)[@u]) -> bool
val every : 'a t -> (key -> 'a -> bool) -> bool
(** [every m p] checks if all the bindings of the map satisfy the predicate [p]. The application order of [p] is
unspecified. *)
val someU : 'a t -> ((key -> 'a -> bool)[@u]) -> bool
val some : 'a t -> (key -> 'a -> bool) -> bool
(** [some m p] checks if at least one binding of the map satisfy the predicate [p]. The application order of [p] is
unspecified. *)
val size : 'a t -> int
val toList : 'a t -> (key * 'a) list
(** In increasing order *)
val toArray : 'a t -> (key * 'a) array
(** In increasing order *)
val fromArray : (key * 'a) array -> 'a t
val keysToArray : 'a t -> key array
val valuesToArray : 'a t -> 'a array
val minKey : _ t -> key option
val minKeyUndefined : _ t -> key Js.undefined
val maxKey : _ t -> key option
val maxKeyUndefined : _ t -> key Js.undefined
val minimum : 'a t -> (key * 'a) option
val minUndefined : 'a t -> (key * 'a) Js.undefined
val maximum : 'a t -> (key * 'a) option
val maxUndefined : 'a t -> (key * 'a) Js.undefined
val get : 'a t -> key -> 'a option
val getUndefined : 'a t -> key -> 'a Js.undefined
val getWithDefault : 'a t -> key -> 'a -> 'a
val getExn : 'a t -> key -> 'a
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(****************************************************************************)
(*TODO: add functional [merge, partition, keep, split]*)
val remove : 'a t -> key -> unit
(** [remove m x] do the in-place modification *)
val removeMany : 'a t -> key array -> unit
val set : 'a t -> key -> 'a -> unit
(** [set m x y] do the in-place modification, return [m] for chaining. If [x] was already bound in [m], its previous
binding disappears. *)
val updateU : 'a t -> key -> (('a option -> 'a option)[@u]) -> unit
val update : 'a t -> key -> ('a option -> 'a option) -> unit
val mapU : 'a t -> (('a -> 'b)[@u]) -> 'b t
val map : 'a t -> ('a -> 'b) -> 'b t
(** [map m f] returns a map with same domain as [m], where the associated value [a] of all bindings of [m] has been
replaced by the result of the application of [f] to [a]. The bindings are passed to [f] in increasing order with
respect to the ordering over the type of the keys. *)
val mapWithKeyU : 'a t -> ((key -> 'a -> 'b)[@u]) -> 'b t
val mapWithKey : 'a t -> (key -> 'a -> 'b) -> 'b t
================================================
FILE: packages/Belt/src/Belt_MutableQueue.ml
================================================
module A = Belt_Array
include (
struct
type 'a node = { content : 'a; mutable next : 'a cell }
and 'a cell = 'a node Js.null
and 'a t = { mutable length : int; mutable first : 'a cell; mutable last : 'a cell }
let node : content:'a -> next:'a cell -> 'a node = fun ~content ~next -> { content; next }
let content : 'a node -> 'a = fun o -> o.content
let nextSet : 'a node -> 'a cell -> unit = fun o v -> o.next <- v
let next : 'a node -> 'a cell = fun o -> o.next
let t : length:int -> first:'a cell -> last:'a cell -> 'a t = fun ~length ~first ~last -> { length; first; last }
let lengthSet : 'a t -> int -> unit = fun o v -> o.length <- v
let length : 'a t -> int = fun o -> o.length
let firstSet : 'a t -> 'a cell -> unit = fun o v -> o.first <- v
let first : 'a t -> 'a cell = fun o -> o.first
let lastSet : 'a t -> 'a cell -> unit = fun o v -> o.last <- v
let last : 'a t -> 'a cell = fun o -> o.last
end :
sig
type 'a node
and 'a cell = 'a node Js.null
and 'a t
val node : content:'a -> next:'a cell -> 'a node
val content : 'a node -> 'a
val nextSet : 'a node -> 'a cell -> unit
val next : 'a node -> 'a cell
val t : length:int -> first:'a cell -> last:'a cell -> 'a t
val lengthSet : 'a t -> int -> unit
val length : 'a t -> int
val firstSet : 'a t -> 'a cell -> unit
val first : 'a t -> 'a cell
val lastSet : 'a t -> 'a cell -> unit
val last : 'a t -> 'a cell
end)
let null = Js.null
let return = Js.Null.return
let make () = t ~length:0 ~first:null ~last:null
let clear q =
lengthSet q 0;
firstSet q null;
lastSet q null
let add q x =
let cell = return @@ node ~content:x ~next:null in
match Js.nullToOption (last q) with
| None ->
lengthSet q 1;
firstSet q cell;
lastSet q cell
| Some last ->
lengthSet q (length q + 1);
nextSet last cell;
lastSet q cell
let peek q = match Js.nullToOption (first q) with None -> None | Some v -> Some (content v)
let peekUndefined q =
match Js.nullToOption (first q) with None -> Js.undefined | Some v -> Js.Undefined.return (content v)
let peekExn q =
match Js.nullToOption (first q) with
| None ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| Some v -> content v
let pop q =
match Js.nullToOption (first q) with
| None -> None
| Some x ->
let next = next x in
if next = Js.null then (
clear q;
Some (content x))
else (
lengthSet q (length q - 1);
firstSet q next;
Some (content x))
let popExn q =
match Js.nullToOption (first q) with
| None ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| Some x ->
let next = next x in
if next = Js.null then (
clear q;
content x)
else (
lengthSet q (length q - 1);
firstSet q next;
content x)
let popUndefined q =
match Js.nullToOption (first q) with
| None -> Js.undefined
| Some x ->
let next = next x in
if next = Js.null then (
clear q;
Js.Undefined.return (content x))
else (
lengthSet q (length q - 1);
firstSet q next;
Js.Undefined.return (content x))
let rec copyAux qRes prev cell =
match Js.nullToOption cell with
| None ->
lastSet qRes prev;
qRes
| Some x ->
let content = content x in
let res = return @@ node ~content ~next:null in
(match Js.nullToOption prev with None -> firstSet qRes res | Some p -> nextSet p res);
copyAux qRes res (next x)
let copy q = copyAux (t ~length:(length q) ~first:null ~last:null) null (first q)
let rec copyMapAux qRes prev cell f =
match Js.nullToOption cell with
| None ->
lastSet qRes prev;
qRes
| Some x ->
let content = f (content x) in
let res = return @@ node ~content ~next:null in
(match Js.nullToOption prev with None -> firstSet qRes res | Some p -> nextSet p res);
copyMapAux qRes res (next x) f
let mapU q f = copyMapAux (t ~length:(length q) ~first:null ~last:null) null (first q) f
let map q f = mapU q (fun a -> f a)
let isEmpty q = length q = 0
let size q = length q
let rec iterAux cell f =
match Js.nullToOption cell with
| None -> ()
| Some x ->
f (content x);
iterAux (next x) f
let forEachU q f = iterAux (first q) f
let forEach q f = forEachU q (fun a -> f a)
let rec foldAux f accu cell =
match Js.nullToOption cell with
| None -> accu
| Some x ->
let accu = f accu (content x) in
foldAux f accu (next x)
let reduceU q accu f = foldAux f accu (first q)
let reduce q accu f = reduceU q accu (fun a b -> f a b)
let transfer q1 q2 =
if length q1 > 0 then
match Js.nullToOption (last q2) with
| None ->
lengthSet q2 (length q1);
firstSet q2 (first q1);
lastSet q2 (last q1);
clear q1
| Some l ->
lengthSet q2 (length q2 + length q1);
nextSet l (first q1);
lastSet q2 (last q1);
clear q1
let rec fillAux i arr cell =
match Js.nullToOption cell with
| None -> ()
| Some x ->
A.setUnsafe arr i (content x);
fillAux (i + 1) arr (next x)
let toArray x =
let v =
match Js.Null.toOption (first x) with None -> [||] | Some y -> A.makeUninitializedUnsafe (length x) (content y)
in
fillAux 0 v (first x);
v
let fromArray arr =
let q = make () in
for i = 0 to A.length arr - 1 do
add q (A.getUnsafe arr i)
done;
q
================================================
FILE: packages/Belt/src/Belt_MutableQueue.mli
================================================
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
(* Adapted significantly by ReScript Authors *)
(** First-in first-out queues.
This module implements queues (FIFOs), with in-place modification. *)
type 'a t
(** The type of queues containing elements of type ['a]. *)
val make : unit -> 'a t
(** @return a new queue, initially empty. *)
val clear : 'a t -> unit
(** Discard all elements from the queue. *)
val isEmpty : 'a t -> bool
(** @return [true] if the given queue is empty, [false] otherwise. *)
val fromArray : 'a array -> 'a t
(** [fromArray a] is equivalent to [Array.forEach a (add q a)] *)
val add : 'a t -> 'a -> unit
(** [add q x] adds the element [x] at the end of the queue [q]. *)
val peek : 'a t -> 'a option
(** [peekOpt q] returns the first element in queue [q], without removing it from the queue. *)
val peekUndefined : 'a t -> 'a Js.undefined
(** [peekUndefined q] returns [undefined] if not found *)
val peekExn : 'a t -> 'a
(** [peekExn q]
{b raise} an exception if [q] is empty *)
val pop : 'a t -> 'a option
(** [pop q] removes and returns the first element in queue [q].*)
val popUndefined : 'a t -> 'a Js.undefined
(** [popUndefined q] removes and returns the first element in queue [q]. it will return undefined if it is already empty
*)
val popExn : 'a t -> 'a
(** [popExn q]
{b raise} an exception if [q] is empty *)
val copy : 'a t -> 'a t
(** [copy q]
@return a fresh queue *)
val size : 'a t -> int
(** @return the number of elements in a queue. *)
val mapU : 'a t -> (('a -> 'b)[@u]) -> 'b t
val map : 'a t -> ('a -> 'b) -> 'b t
val forEachU : 'a t -> (('a -> unit)[@u]) -> unit
val forEach : 'a t -> ('a -> unit) -> unit
(** [forEach q f] applies [f] in turn to all elements of [q], from the least recently entered to the most recently
entered. The queue itself is unchanged. *)
val reduceU : 'a t -> 'b -> (('b -> 'a -> 'b)[@u]) -> 'b
val reduce : 'a t -> 'b -> ('b -> 'a -> 'b) -> 'b
(** [reduce q accu f] is equivalent to [List.reduce l accu f], where [l] is the list of [q]'s elements. The queue
remains unchanged. *)
val transfer : 'a t -> 'a t -> unit
(** [transfer q1 q2] adds all of [q1]'s elements at the end of the queue [q2], then clears [q1]. It is equivalent to the
sequence [forEach (fun x -> add x q2) q1; clear q1], but runs in constant time. *)
val toArray : 'a t -> 'a array
(** First added will be in the beginning of the array *)
================================================
FILE: packages/Belt/src/Belt_MutableSet.ml
================================================
module Int = Belt_MutableSetInt
module String = Belt_MutableSetString
module N = Belt_internalAVLset
module A = Belt_Array
module Sort = Belt_SortArray
type ('k, 'id) id = ('k, 'id) Belt_Id.comparable
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
module S = struct
include (
struct
type ('value, 'id) t = { cmp : ('value, 'id) cmp; mutable data : 'value N.t }
let t : cmp:('value, 'id) cmp -> data:'value N.t -> ('value, 'id) t = fun ~cmp ~data -> { cmp; data }
let cmp : ('value, 'id) t -> ('value, 'id) cmp = fun o -> o.cmp
let dataSet : ('value, 'id) t -> 'value N.t -> unit = fun o v -> o.data <- v
let data : ('value, 'id) t -> 'value N.t = fun o -> o.data
end :
sig
type ('value, 'id) t
val t : cmp:('value, 'id) cmp -> data:'value N.t -> ('value, 'id) t
val cmp : ('value, 'id) t -> ('value, 'id) cmp
val dataSet : ('value, 'id) t -> 'value N.t -> unit
val data : ('value, 'id) t -> 'value N.t
end)
end
type ('k, 'id) t = ('k, 'id) S.t
let rec remove0 nt x ~cmp =
let k = N.value nt in
let c = cmp x k in
if c = 0 then (
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if c < 0 then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (remove0 ~cmp l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (remove0 ~cmp r x);
N.return (N.balMutate nt)
let remove d v =
let oldRoot = S.data d in
match N.toOpt oldRoot with
| None -> ()
| Some oldRoot2 ->
let newRoot = remove0 ~cmp:(Belt_Id.getCmpInternal (S.cmp d)) oldRoot2 v in
if newRoot != oldRoot then S.dataSet d newRoot
let rec removeMany0 t xs i len ~cmp =
if i < len then
let ele = A.getUnsafe xs i in
let u = remove0 t ele ~cmp in
match N.toOpt u with None -> N.empty | Some t -> removeMany0 t xs (i + 1) len ~cmp
else N.return t
let removeMany d xs =
let oldRoot = S.data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
S.dataSet d (removeMany0 nt xs 0 len ~cmp:(Belt_Id.getCmpInternal (S.cmp d)))
let rec removeCheck0 nt x removed ~cmp =
let k = N.value nt in
let c = (Belt_Id.getCmpInternal cmp) x k in
if c = 0 then (
let () = removed := true in
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if c < 0 then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeCheck0 ~cmp l x removed);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeCheck0 ~cmp r x removed);
N.return (N.balMutate nt)
let removeCheck d v =
let oldRoot = S.data d in
match N.toOpt oldRoot with
| None -> false
| Some oldRoot2 ->
let removed = ref false in
let newRoot = removeCheck0 ~cmp:(S.cmp d) oldRoot2 v removed in
if newRoot != oldRoot then S.dataSet d newRoot;
!removed
let rec addCheck0 t x added ~cmp =
match N.toOpt t with
| None ->
added := true;
N.singleton x
| Some nt ->
let k = N.value nt in
let c = cmp x k in
if c = 0 then t
else
let l, r =
let open N in
(left nt, right nt)
in
if c < 0 then
let ll = addCheck0 ~cmp l x added in
N.leftSet nt ll
else N.rightSet nt (addCheck0 ~cmp r x added);
N.return (N.balMutate nt)
let addCheck m e =
let oldRoot = S.data m in
let added = ref false in
let newRoot = addCheck0 ~cmp:(Belt_Id.getCmpInternal (S.cmp m)) oldRoot e added in
if newRoot != oldRoot then S.dataSet m newRoot;
!added
let add m e =
let oldRoot = S.data m in
let newRoot = N.addMutate ~cmp:(S.cmp m) oldRoot e in
if newRoot != oldRoot then S.dataSet m newRoot
let addArrayMutate t xs ~cmp =
let v = ref t in
for i = 0 to A.length xs - 1 do
v := N.addMutate !v (A.getUnsafe xs i) ~cmp
done;
!v
let mergeMany d xs = S.dataSet d (addArrayMutate (S.data d) xs ~cmp:(S.cmp d))
let make (type value identity) ~(id : (value, identity) id) =
let module M = (val id) in
S.t ~cmp:M.cmp ~data:N.empty
let isEmpty d = N.isEmpty (S.data d)
let minimum d = N.minimum (S.data d)
let minUndefined d = N.minUndefined (S.data d)
let maximum d = N.maximum (S.data d)
let maxUndefined d = N.maxUndefined (S.data d)
let forEachU d f = N.forEachU (S.data d) f
let forEach d f = forEachU d (fun a -> f a)
let reduceU d acc cb = N.reduceU (S.data d) acc cb
let reduce d acc cb = reduceU d acc (fun a b -> cb a b)
let everyU d p = N.everyU (S.data d) p
let every d p = everyU d (fun a -> p a)
let someU d p = N.someU (S.data d) p
let some d p = someU d (fun a -> p a)
let size d = N.size (S.data d)
let toList d = N.toList (S.data d)
let toArray d = N.toArray (S.data d)
let fromSortedArrayUnsafe (type value identity) xs ~(id : (value, identity) id) : _ t =
let module M = (val id) in
S.t ~data:(N.fromSortedArrayUnsafe xs) ~cmp:M.cmp
let checkInvariantInternal d = N.checkInvariantInternal (S.data d)
let fromArray (type value identity) data ~(id : (value, identity) id) =
let module M = (val id) in
let cmp = M.cmp in
S.t ~cmp ~data:(N.fromArray ~cmp data)
let cmp d0 d1 = N.cmp ~cmp:(S.cmp d0) (S.data d0) (S.data d1)
let eq d0 d1 = N.eq ~cmp:(S.cmp d0) (S.data d0) (S.data d1)
let get d x = N.get ~cmp:(S.cmp d) (S.data d) x
let getUndefined d x = N.getUndefined ~cmp:(S.cmp d) (S.data d) x
let getExn d x = N.getExn ~cmp:(S.cmp d) (S.data d) x
let split d key =
let arr = N.toArray (S.data d) in
let cmp = S.cmp d in
let i = Sort.binarySearchByU arr key (Belt_Id.getCmpInternal cmp) in
let len = A.length arr in
if i < 0 then
let next = -i - 1 in
( (S.t ~data:(N.fromSortedArrayAux arr 0 next) ~cmp, S.t ~data:(N.fromSortedArrayAux arr next (len - next)) ~cmp),
false )
else
( (S.t ~data:(N.fromSortedArrayAux arr 0 i) ~cmp, S.t ~data:(N.fromSortedArrayAux arr (i + 1) (len - i - 1)) ~cmp),
true )
let keepU d p = S.t ~data:(N.keepCopyU (S.data d) p) ~cmp:(S.cmp d)
let keep d p = keepU d (fun a -> p a)
let partitionU d p =
let cmp = S.cmp d in
let a, b = N.partitionCopyU (S.data d) p in
(S.t ~data:a ~cmp, S.t ~data:b ~cmp)
let partition d p = partitionU d (fun a -> p a)
let subset a b = N.subset ~cmp:(S.cmp a) (S.data a) (S.data b)
let intersect a b : _ t =
let cmp = S.cmp a in
match (N.toOpt (S.data a), N.toOpt (S.data b)) with
| None, _ -> S.t ~cmp ~data:N.empty
| _, None -> S.t ~cmp ~data:N.empty
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
let p = Belt_Id.getCmpInternal cmp in
if
p (A.getUnsafe tmp (sizea - 1)) (A.getUnsafe tmp sizea) < 0
|| p (A.getUnsafe tmp (totalSize - 1)) (A.getUnsafe tmp 0) < 0
then S.t ~cmp ~data:N.empty
else
let tmp2 = A.makeUninitializedUnsafe (min sizea sizeb) (N.value dataa0) in
let k = Sort.intersectU tmp 0 sizea tmp sizea sizeb tmp2 0 p in
S.t ~data:(N.fromSortedArrayAux tmp2 0 k) ~cmp
let diff a b : _ t =
let cmp = S.cmp a in
let dataa = S.data a in
match (N.toOpt dataa, N.toOpt (S.data b)) with
| None, _ -> S.t ~cmp ~data:N.empty
| _, None -> S.t ~data:(N.copy dataa) ~cmp
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
let p = Belt_Id.getCmpInternal cmp in
if
p (A.getUnsafe tmp (sizea - 1)) (A.getUnsafe tmp sizea) < 0
|| p (A.getUnsafe tmp (totalSize - 1)) (A.getUnsafe tmp 0) < 0
then S.t ~data:(N.copy dataa) ~cmp
else
let tmp2 = A.makeUninitializedUnsafe sizea (N.value dataa0) in
let k = Sort.diffU tmp 0 sizea tmp sizea sizeb tmp2 0 p in
S.t ~data:(N.fromSortedArrayAux tmp2 0 k) ~cmp
let union a b =
let cmp = S.cmp a in
let dataa, datab = (S.data a, S.data b) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> S.t ~data:(N.copy datab) ~cmp
| _, None -> S.t ~data:(N.copy dataa) ~cmp
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
let p = Belt_Id.getCmpInternal cmp in
if p (A.getUnsafe tmp (sizea - 1)) (A.getUnsafe tmp sizea) < 0 then
S.t ~data:(N.fromSortedArrayAux tmp 0 totalSize) ~cmp
else
let tmp2 = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
let k = Sort.unionU tmp 0 sizea tmp sizea sizeb tmp2 0 p in
S.t ~data:(N.fromSortedArrayAux tmp2 0 k) ~cmp
let has d x = N.has ~cmp:(S.cmp d) (S.data d) x
let copy d = S.t ~data:(N.copy (S.data d)) ~cmp:(S.cmp d)
================================================
FILE: packages/Belt/src/Belt_MutableSet.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A {i mutable} sorted set module which allows customize {i compare} behavior.
Same as Belt.Set, but mutable. *)
module Int = Belt_MutableSetInt
(** Specalized when key type is [int], more efficient than the generic type *)
module String = Belt_MutableSetString
(** Specalized when key type is [string], more efficient than the generic type *)
type ('k, 'id) t
type ('k, 'id) id = ('k, 'id) Belt_Id.comparable
val make : id:('value, 'id) id -> ('value, 'id) t
val fromArray : 'k array -> id:('k, 'id) id -> ('k, 'id) t
val fromSortedArrayUnsafe : 'value array -> id:('value, 'id) id -> ('value, 'id) t
val copy : ('k, 'id) t -> ('k, 'id) t
val isEmpty : _ t -> bool
val has : ('value, _) t -> 'value -> bool
val add : ('value, 'id) t -> 'value -> unit
val addCheck : ('value, 'id) t -> 'value -> bool
val mergeMany : ('value, 'id) t -> 'value array -> unit
val remove : ('value, 'id) t -> 'value -> unit
val removeCheck : ('value, 'id) t -> 'value -> bool
(* [b = removeCheck s e] [b] is true means one element removed *)
val removeMany : ('value, 'id) t -> 'value array -> unit
val union : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
val intersect : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
val diff : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
val subset : ('value, 'id) t -> ('value, 'id) t -> bool
val cmp : ('value, 'id) t -> ('value, 'id) t -> int
val eq : ('value, 'id) t -> ('value, 'id) t -> bool
val forEachU : ('value, 'id) t -> (('value -> unit)[@u]) -> unit
val forEach : ('value, 'id) t -> ('value -> unit) -> unit
(** [forEach m f] applies [f] in turn to all elements of [m]. In increasing order *)
val reduceU : ('value, 'id) t -> 'a -> (('a -> 'value -> 'a)[@u]) -> 'a
val reduce : ('value, 'id) t -> 'a -> ('a -> 'value -> 'a) -> 'a
(** In increasing order. *)
val everyU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val every : ('value, 'id) t -> ('value -> bool) -> bool
(** [every s p] checks if all elements of the set satisfy the predicate [p]. Order unspecified *)
val someU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val some : ('value, 'id) t -> ('value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. *)
val keepU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t
val keep : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t
(** [keep s p] returns the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t * ('value, 'id) t
val partition : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t * ('value, 'id) t
(** [partition p s] returns a pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that satisfy the
predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : ('value, 'id) t -> int
val toList : ('value, 'id) t -> 'value list
(** In increasing order*)
val toArray : ('value, 'id) t -> 'value array
(** In increasing order*)
val minimum : ('value, 'id) t -> 'value option
val minUndefined : ('value, 'id) t -> 'value Js.undefined
val maximum : ('value, 'id) t -> 'value option
val maxUndefined : ('value, 'id) t -> 'value Js.undefined
val get : ('value, 'id) t -> 'value -> 'value option
val getUndefined : ('value, 'id) t -> 'value -> 'value Js.undefined
val getExn : ('value, 'id) t -> 'value -> 'value
val split : ('value, 'id) t -> 'value -> (('value, 'id) t * ('value, 'id) t) * bool
(** [split s x] returns a triple [((l, r), present)], where [l] is the set of elements of [s] that are strictly less
than [x]; [r] is the set of elements of [s] that are strictly greater than [x]; [present] is [false] if [s] contains
no element equal to [x], or [true] if [s] contains an element equal to [x]. [l,r] are freshly made, no sharing with
[s] *)
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(*
[add0] was not exposed for various reasons:
1. such api is dangerious
[ cmp: ('value,'id) Belt_Cmp.cmp ->
('value, 'id) t0 -> 'value ->
('value, 'id) t0]
2. It is not really significantly more *)
================================================
FILE: packages/Belt/src/Belt_MutableSetInt.ml
================================================
[@@@ocaml.text
" This module is {!Belt.MutableSet} specialized with key type to be a primitive type.\n\
\ It is more efficient in general, the API is the same with {!Belt.MutableSet} except its key type is fixed,\n\
\ and identity is not needed(using the built-in one) \n"]
module I = Belt_internalSetInt
module S = Belt_SortArrayInt
module N = Belt_internalAVLset
module A = Belt_Array
type value = I.value [@@ocaml.doc " The type of the set elements. "]
include (
struct
type t = { mutable data : I.t }
let t : data:I.t -> t = fun ~data -> { data }
let dataSet : t -> I.t -> unit = fun o v -> o.data <- v
let data : t -> I.t = fun o -> o.data
end :
sig
type t
val t : data:I.t -> t
val dataSet : t -> I.t -> unit
val data : t -> I.t
end)
let rec remove0 nt (x : value) =
let k = N.value nt in
if x = k then (
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (remove0 l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (remove0 r x);
N.return (N.balMutate nt)
let remove d v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some oldRoot2 ->
let newRoot = remove0 oldRoot2 v in
if newRoot != oldRoot then dataSet d newRoot
let rec removeMany0 t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = remove0 t ele in
match N.toOpt u with None -> N.empty | Some t -> removeMany0 t xs (i + 1) len
else N.return t
let removeMany (d : t) xs =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
dataSet d (removeMany0 nt xs 0 len)
let rec removeCheck0 nt (x : value) removed =
let k = N.value nt in
if x = k then (
let () = removed := true in
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeCheck0 l x removed);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeCheck0 r x removed);
N.return (N.balMutate nt)
let removeCheck (d : t) v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> false
| Some oldRoot2 ->
let removed = ref false in
let newRoot = removeCheck0 oldRoot2 v removed in
if newRoot != oldRoot then dataSet d newRoot;
!removed
let rec addCheck0 t (x : value) added =
match N.toOpt t with
| None ->
added := true;
N.singleton x
| Some nt ->
let k = N.value nt in
if x = k then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then
let ll = addCheck0 l x added in
N.leftSet nt ll
else N.rightSet nt (addCheck0 r x added);
N.return (N.balMutate nt)
let addCheck (m : t) e =
let oldRoot = data m in
let added = ref false in
let newRoot = addCheck0 oldRoot e added in
if newRoot != oldRoot then dataSet m newRoot;
!added
let add d k =
let oldRoot = data d in
let v = I.addMutate oldRoot k in
if v != oldRoot then dataSet d v
let addArrayMutate t xs =
let v = ref t in
for i = 0 to A.length xs - 1 do
v := I.addMutate !v (A.getUnsafe xs i)
done;
!v
let mergeMany d arr = dataSet d (addArrayMutate (data d) arr)
let make () = t ~data:N.empty
let isEmpty d = N.isEmpty (data d)
let minimum d = N.minimum (data d)
let minUndefined d = N.minUndefined (data d)
let maximum d = N.maximum (data d)
let maxUndefined d = N.maxUndefined (data d)
let forEachU d f = N.forEachU (data d) f
let forEach d f = forEachU d (fun a -> f a)
let reduceU d acc cb = N.reduceU (data d) acc cb
let reduce d acc cb = reduceU d acc (fun a b -> cb a b)
let everyU d p = N.everyU (data d) p
let every d p = everyU d (fun a -> p a)
let someU d p = N.someU (data d) p
let some d p = someU d (fun a -> p a)
let size d = N.size (data d)
let toList d = N.toList (data d)
let toArray d = N.toArray (data d)
let fromSortedArrayUnsafe xs = t ~data:(N.fromSortedArrayUnsafe xs)
let checkInvariantInternal d = N.checkInvariantInternal (data d)
let fromArray xs = t ~data:(I.fromArray xs)
let cmp d0 d1 = I.cmp (data d0) (data d1)
let eq d0 d1 = I.eq (data d0) (data d1)
let get d x = I.get (data d) x
let getUndefined d x = I.getUndefined (data d) x
let getExn d x = I.getExn (data d) x
let split d key =
let arr = N.toArray (data d) in
let i = S.binarySearch arr key in
let len = A.length arr in
if i < 0 then
let next = -i - 1 in
((t ~data:(N.fromSortedArrayAux arr 0 next), t ~data:(N.fromSortedArrayAux arr next (len - next))), false)
else ((t ~data:(N.fromSortedArrayAux arr 0 i), t ~data:(N.fromSortedArrayAux arr (i + 1) (len - i - 1))), true)
let keepU d p = t ~data:(N.keepCopyU (data d) p)
let keep d p = keepU d (fun a -> p a)
let partitionU d p =
let a, b = N.partitionCopyU (data d) p in
(t ~data:a, t ~data:b)
let partition d p = partitionU d (fun a -> p a)
let subset a b = I.subset (data a) (data b)
let intersect dataa datab =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> make ()
| _, None -> make ()
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea || A.getUnsafe tmp (totalSize - 1) < A.getUnsafe tmp 0 then
make ()
else
let tmp2 = A.makeUninitializedUnsafe (min sizea sizeb) (N.value dataa0) in
let k = S.intersect tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let diff dataa datab : t =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> make ()
| _, None -> t ~data:(N.copy dataa)
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea || A.getUnsafe tmp (totalSize - 1) < A.getUnsafe tmp 0 then
t ~data:(N.copy dataa)
else
let tmp2 = A.makeUninitializedUnsafe sizea (N.value dataa0) in
let k = S.diff tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let union (dataa : t) (datab : t) : t =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> t ~data:(N.copy datab)
| _, None -> t ~data:(N.copy dataa)
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea then t ~data:(N.fromSortedArrayAux tmp 0 totalSize)
else
let tmp2 = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
let k = S.union tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let has d x = I.has (data d) x
let copy d = t ~data:(N.copy (data d))
================================================
FILE: packages/Belt/src/Belt_MutableSetInt.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.MutableSet} specialized with key type to be a primitive type.
It is more efficient in general, the API is the same with {!Belt.MutableSet} except its key type is fixed, and
identity is not needed(using the built-in one)
{b See} {!Belt.MutableSet} *)
type value = int
(** The type of the set elements. *)
type t
(** The type of sets. *)
val make : unit -> t
val fromArray : value array -> t
val fromSortedArrayUnsafe : value array -> t
val copy : t -> t
val isEmpty : t -> bool
val has : t -> value -> bool
val add : t -> value -> unit
val addCheck : t -> value -> bool
val mergeMany : t -> value array -> unit
val remove : t -> value -> unit
val removeCheck : t -> value -> bool
val removeMany : t -> value array -> unit
val union : t -> t -> t
val intersect : t -> t -> t
val diff : t -> t -> t
val subset : t -> t -> bool
val cmp : t -> t -> int
val eq : t -> t -> bool
val forEachU : t -> ((value -> unit)[@u]) -> unit
val forEach : t -> (value -> unit) -> unit
(** In increasing order*)
val reduceU : t -> 'a -> (('a -> value -> 'a)[@u]) -> 'a
val reduce : t -> 'a -> ('a -> value -> 'a) -> 'a
(** Iterate in increasing order. *)
val everyU : t -> ((value -> bool)[@u]) -> bool
val every : t -> (value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : t -> ((value -> bool)[@u]) -> bool
val some : t -> (value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. Oder unspecified. *)
val keepU : t -> ((value -> bool)[@u]) -> t
val keep : t -> (value -> bool) -> t
(** [keep s p] returns a fresh copy of the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : t -> ((value -> bool)[@u]) -> t * t
val partition : t -> (value -> bool) -> t * t
(** [partition s p] returns a fresh copy pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that
satisfy the predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : t -> int
val toList : t -> value list
(** In increasing order with respect *)
val toArray : t -> value array
(** In increasing order with respect *)
val minimum : t -> value option
val minUndefined : t -> value Js.undefined
val maximum : t -> value option
val maxUndefined : t -> value Js.undefined
val get : t -> value -> value option
val getUndefined : t -> value -> value Js.undefined
val getExn : t -> value -> value
val split : t -> value -> (t * t) * bool
(** [split s key] return a fresh copy of each *)
val checkInvariantInternal : t -> unit
(** {b raise} when invariant is not held *)
================================================
FILE: packages/Belt/src/Belt_MutableSetString.ml
================================================
[@@@ocaml.text
" This module is {!Belt.MutableSet} specialized with key type to be a primitive type.\n\
\ It is more efficient in general, the API is the same with {!Belt.MutableSet} except its key type is fixed,\n\
\ and identity is not needed(using the built-in one) \n"]
module I = Belt_internalSetString
module S = Belt_SortArrayString
module N = Belt_internalAVLset
module A = Belt_Array
type value = I.value [@@ocaml.doc " The type of the set elements. "]
include (
struct
type t = { mutable data : I.t }
let t : data:I.t -> t = fun ~data -> { data }
let dataSet : t -> I.t -> unit = fun o v -> o.data <- v
let data : t -> I.t = fun o -> o.data
end :
sig
type t
val t : data:I.t -> t
val dataSet : t -> I.t -> unit
val data : t -> I.t
end)
let rec remove0 nt (x : value) =
let k = N.value nt in
if x = k then (
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (remove0 l x);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (remove0 r x);
N.return (N.balMutate nt)
let remove d v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some oldRoot2 ->
let newRoot = remove0 oldRoot2 v in
if newRoot != oldRoot then dataSet d newRoot
let rec removeMany0 t xs i len =
if i < len then
let ele = A.getUnsafe xs i in
let u = remove0 t ele in
match N.toOpt u with None -> N.empty | Some t -> removeMany0 t xs (i + 1) len
else N.return t
let removeMany (d : t) xs =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> ()
| Some nt ->
let len = A.length xs in
dataSet d (removeMany0 nt xs 0 len)
let rec removeCheck0 nt (x : value) removed =
let k = N.value nt in
if x = k then (
let () = removed := true in
let l, r =
let open N in
(left nt, right nt)
in
match
let open N in
(toOpt l, toOpt r)
with
| None, _ -> r
| _, None -> l
| Some _, Some nr ->
N.rightSet nt (N.removeMinAuxWithRootMutate nt nr);
N.return (N.balMutate nt))
else if x < k then (
match N.toOpt (N.left nt) with
| None -> N.return nt
| Some l ->
N.leftSet nt (removeCheck0 l x removed);
N.return (N.balMutate nt))
else
match N.toOpt (N.right nt) with
| None -> N.return nt
| Some r ->
N.rightSet nt (removeCheck0 r x removed);
N.return (N.balMutate nt)
let removeCheck (d : t) v =
let oldRoot = data d in
match N.toOpt oldRoot with
| None -> false
| Some oldRoot2 ->
let removed = ref false in
let newRoot = removeCheck0 oldRoot2 v removed in
if newRoot != oldRoot then dataSet d newRoot;
!removed
let rec addCheck0 t (x : value) added =
match N.toOpt t with
| None ->
added := true;
N.singleton x
| Some nt ->
let k = N.value nt in
if x = k then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then
let ll = addCheck0 l x added in
N.leftSet nt ll
else N.rightSet nt (addCheck0 r x added);
N.return (N.balMutate nt)
let addCheck (m : t) e =
let oldRoot = data m in
let added = ref false in
let newRoot = addCheck0 oldRoot e added in
if newRoot != oldRoot then dataSet m newRoot;
!added
let add d k =
let oldRoot = data d in
let v = I.addMutate oldRoot k in
if v != oldRoot then dataSet d v
let addArrayMutate t xs =
let v = ref t in
for i = 0 to A.length xs - 1 do
v := I.addMutate !v (A.getUnsafe xs i)
done;
!v
let mergeMany d arr = dataSet d (addArrayMutate (data d) arr)
let make () = t ~data:N.empty
let isEmpty d = N.isEmpty (data d)
let minimum d = N.minimum (data d)
let minUndefined d = N.minUndefined (data d)
let maximum d = N.maximum (data d)
let maxUndefined d = N.maxUndefined (data d)
let forEachU d f = N.forEachU (data d) f
let forEach d f = forEachU d (fun a -> f a)
let reduceU d acc cb = N.reduceU (data d) acc cb
let reduce d acc cb = reduceU d acc (fun a b -> cb a b)
let everyU d p = N.everyU (data d) p
let every d p = everyU d (fun a -> p a)
let someU d p = N.someU (data d) p
let some d p = someU d (fun a -> p a)
let size d = N.size (data d)
let toList d = N.toList (data d)
let toArray d = N.toArray (data d)
let fromSortedArrayUnsafe xs = t ~data:(N.fromSortedArrayUnsafe xs)
let checkInvariantInternal d = N.checkInvariantInternal (data d)
let fromArray xs = t ~data:(I.fromArray xs)
let cmp d0 d1 = I.cmp (data d0) (data d1)
let eq d0 d1 = I.eq (data d0) (data d1)
let get d x = I.get (data d) x
let getUndefined d x = I.getUndefined (data d) x
let getExn d x = I.getExn (data d) x
let split d key =
let arr = N.toArray (data d) in
let i = S.binarySearch arr key in
let len = A.length arr in
if i < 0 then
let next = -i - 1 in
((t ~data:(N.fromSortedArrayAux arr 0 next), t ~data:(N.fromSortedArrayAux arr next (len - next))), false)
else ((t ~data:(N.fromSortedArrayAux arr 0 i), t ~data:(N.fromSortedArrayAux arr (i + 1) (len - i - 1))), true)
let keepU d p = t ~data:(N.keepCopyU (data d) p)
let keep d p = keepU d (fun a -> p a)
let partitionU d p =
let a, b = N.partitionCopyU (data d) p in
(t ~data:a, t ~data:b)
let partition d p = partitionU d (fun a -> p a)
let subset a b = I.subset (data a) (data b)
let intersect dataa datab =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> make ()
| _, None -> make ()
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea || A.getUnsafe tmp (totalSize - 1) < A.getUnsafe tmp 0 then
make ()
else
let tmp2 = A.makeUninitializedUnsafe (min sizea sizeb) (N.value dataa0) in
let k = S.intersect tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let diff dataa datab : t =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> make ()
| _, None -> t ~data:(N.copy dataa)
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea || A.getUnsafe tmp (totalSize - 1) < A.getUnsafe tmp 0 then
t ~data:(N.copy dataa)
else
let tmp2 = A.makeUninitializedUnsafe sizea (N.value dataa0) in
let k = S.diff tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let union (dataa : t) (datab : t) : t =
let dataa, datab = (data dataa, data datab) in
match (N.toOpt dataa, N.toOpt datab) with
| None, _ -> t ~data:(N.copy datab)
| _, None -> t ~data:(N.copy dataa)
| Some dataa0, Some datab0 ->
let sizea, sizeb = (N.lengthNode dataa0, N.lengthNode datab0) in
let totalSize = sizea + sizeb in
let tmp = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
ignore @@ N.fillArray dataa0 0 tmp;
ignore @@ N.fillArray datab0 sizea tmp;
if A.getUnsafe tmp (sizea - 1) < A.getUnsafe tmp sizea then t ~data:(N.fromSortedArrayAux tmp 0 totalSize)
else
let tmp2 = A.makeUninitializedUnsafe totalSize (N.value dataa0) in
let k = S.union tmp 0 sizea tmp sizea sizeb tmp2 0 in
t ~data:(N.fromSortedArrayAux tmp2 0 k)
let has d x = I.has (data d) x
let copy d = t ~data:(N.copy (data d))
================================================
FILE: packages/Belt/src/Belt_MutableSetString.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.MutableSet} specialized with key type to be a primitive type.
It is more efficient in general, the API is the same with {!Belt.MutableSet} except its key type is fixed, and
identity is not needed(using the built-in one)
{b See} {!Belt.MutableSet} *)
type value = string
(** The type of the set elements. *)
type t
(** The type of sets. *)
val make : unit -> t
val fromArray : value array -> t
val fromSortedArrayUnsafe : value array -> t
val copy : t -> t
val isEmpty : t -> bool
val has : t -> value -> bool
val add : t -> value -> unit
val addCheck : t -> value -> bool
val mergeMany : t -> value array -> unit
val remove : t -> value -> unit
val removeCheck : t -> value -> bool
val removeMany : t -> value array -> unit
val union : t -> t -> t
val intersect : t -> t -> t
val diff : t -> t -> t
val subset : t -> t -> bool
val cmp : t -> t -> int
val eq : t -> t -> bool
val forEachU : t -> ((value -> unit)[@u]) -> unit
val forEach : t -> (value -> unit) -> unit
(** In increasing order*)
val reduceU : t -> 'a -> (('a -> value -> 'a)[@u]) -> 'a
val reduce : t -> 'a -> ('a -> value -> 'a) -> 'a
(** Iterate in increasing order. *)
val everyU : t -> ((value -> bool)[@u]) -> bool
val every : t -> (value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : t -> ((value -> bool)[@u]) -> bool
val some : t -> (value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. Oder unspecified. *)
val keepU : t -> ((value -> bool)[@u]) -> t
val keep : t -> (value -> bool) -> t
(** [keep s p] returns a fresh copy of the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : t -> ((value -> bool)[@u]) -> t * t
val partition : t -> (value -> bool) -> t * t
(** [partition s p] returns a fresh copy pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that
satisfy the predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : t -> int
val toList : t -> value list
(** In increasing order with respect *)
val toArray : t -> value array
(** In increasing order with respect *)
val minimum : t -> value option
val minUndefined : t -> value Js.undefined
val maximum : t -> value option
val maxUndefined : t -> value Js.undefined
val get : t -> value -> value option
val getUndefined : t -> value -> value Js.undefined
val getExn : t -> value -> value
val split : t -> value -> (t * t) * bool
(** [split s key] return a fresh copy of each *)
val checkInvariantInternal : t -> unit
(** {b raise} when invariant is not held *)
================================================
FILE: packages/Belt/src/Belt_MutableStack.ml
================================================
include (
struct
type 'a t = { mutable root : 'a opt_cell }
and 'a opt_cell = 'a cell Js.null
and 'a cell = { head : 'a; tail : 'a opt_cell }
let t : root:'a opt_cell -> 'a t = fun ~root -> { root }
let rootSet : 'a t -> 'a opt_cell -> unit = fun o v -> o.root <- v
let root : 'a t -> 'a opt_cell = fun o -> o.root
let cell : head:'a -> tail:'a opt_cell -> 'a cell = fun ~head ~tail -> { head; tail }
let head : 'a cell -> 'a = fun o -> o.head
let tail : 'a cell -> 'a opt_cell = fun o -> o.tail
end :
sig
type 'a t
and 'a opt_cell = 'a cell Js.null
and 'a cell
val t : root:'a opt_cell -> 'a t
val rootSet : 'a t -> 'a opt_cell -> unit
val root : 'a t -> 'a opt_cell
val cell : head:'a -> tail:'a opt_cell -> 'a cell
val head : 'a cell -> 'a
val tail : 'a cell -> 'a opt_cell
end)
let make () = t ~root:Js.null
let clear s = rootSet s Js.null
let copy (s : _ t) : _ t = t ~root:(root s)
let push s x = rootSet s (Js.Null.return @@ cell ~head:x ~tail:(root s))
let topUndefined (s : 'a t) =
match Js.nullToOption (root s) with None -> Js.undefined | Some x -> Js.Undefined.return (head x)
let top s = match Js.nullToOption (root s) with None -> None | Some x -> Some (head x)
let isEmpty s = root s = Js.null
let popUndefined s =
match Js.nullToOption (root s) with
| None -> Js.undefined
| Some x ->
rootSet s (tail x);
Js.Undefined.return (head x)
let pop s =
match Js.nullToOption (root s) with
| None -> None
| Some x ->
rootSet s (tail x);
Some (head x)
let rec lengthAux (x : _ cell) acc =
match Js.nullToOption (tail x) with None -> acc + 1 | Some x -> lengthAux x (acc + 1)
let size s = match Js.nullToOption (root s) with None -> 0 | Some x -> lengthAux x 0
let rec iterAux (s : _ opt_cell) f =
match Js.nullToOption s with
| None -> ()
| Some x ->
f (head x);
iterAux (tail x) f
let forEachU s f = iterAux (root s) f
let forEach s f = forEachU s (fun x -> f x)
let dynamicPopIterU s f =
let cursor = ref (root s) in
while !cursor != Js.null do
let v = Js.Null.getUnsafe !cursor in
rootSet s (tail v);
f (head v);
cursor := root s
done
let dynamicPopIter s f = dynamicPopIterU s (fun x -> f x)
================================================
FILE: packages/Belt/src/Belt_MutableStack.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** First in last out stack.
This module implements stacks, with in-place modification. *)
type 'a t
val make : unit -> 'a t
(** @return a new stack, initially empty. *)
val clear : 'a t -> unit
(** Discard all elements from the stack. *)
val copy : 'a t -> 'a t
(** [copy x] O(1) operation, return a new stack *)
val push : 'a t -> 'a -> unit
val popUndefined : 'a t -> 'a Js.undefined
val pop : 'a t -> 'a option
val topUndefined : 'a t -> 'a Js.undefined
val top : 'a t -> 'a option
val isEmpty : 'a t -> bool
val size : 'a t -> int
val forEachU : 'a t -> (('a -> unit)[@u]) -> unit
val forEach : 'a t -> ('a -> unit) -> unit
val dynamicPopIterU : 'a t -> (('a -> unit)[@u]) -> unit
val dynamicPopIter : 'a t -> ('a -> unit) -> unit
(** [dynamicPopIter s f ] apply [f] to each element of [s]. The item is poped before applying [f], [s] will be empty
after this opeartion. This function is useful for worklist algorithm *)
================================================
FILE: packages/Belt/src/Belt_Option.ml
================================================
let getExn = function
| Some x -> x
| None ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
let mapWithDefaultU opt default f = match opt with Some x -> f x | None -> default
let mapWithDefault opt default f = mapWithDefaultU opt default (fun x -> f x)
let mapU opt f = match opt with Some x -> Some (f x) | None -> None
let map opt f = mapU opt (fun x -> f x)
let flatMapU opt f = match opt with Some x -> f x | None -> None
let flatMap opt f = flatMapU opt (fun x -> f x)
let getWithDefault opt default = match opt with Some x -> x | None -> default
let orElse opt other = match opt with Some _ -> opt | None -> other
let isSome = function Some _ -> true | None -> false
let isNone = function Some _ -> false | None -> true
let eqU a b f = match (a, b) with Some a, Some b -> f a b | None, Some _ | Some _, None -> false | None, None -> true
let eq a b f = eqU a b (fun x y -> f x y)
let cmpU a b f = match (a, b) with Some a, Some b -> f a b | None, Some _ -> -1 | Some _, None -> 1 | None, None -> 0
let cmp a b f = cmpU a b (fun x y -> f x y)
let keepU opt f = match opt with Some x when f x -> opt | Some _ | None -> None
let keep opt f = keepU opt (fun x -> f x)
let forEachU opt f = match opt with Some x -> f x | None -> ()
let forEach opt f = forEachU opt (fun x -> f x)
external getUnsafe : 'a option -> 'a = "%identity"
================================================
FILE: packages/Belt/src/Belt_Option.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** {!Belt.Option}
Utilities for option data type *)
val keepU : 'a option -> (('a -> bool)[@u]) -> 'a option
(** Uncurried version of [keep] *)
val keep : 'a option -> ('a -> bool) -> 'a option
(** [keep optionValue p]
If [optionValue] is [Some value] and [p value = true], it returns [Some value]; otherwise returns [None]
{[
keep (Some 10) (fun x -> x > 5);;
(* returns [Some 10] *)
keep (Some 4) (fun x -> x > 5);;
(* returns [None] *)
keep None (fun x -> x > 5) (* returns [None] *)
]} *)
val forEachU : 'a option -> (('a -> unit)[@u]) -> unit
(** Uncurried version of [forEach] *)
val forEach : 'a option -> ('a -> unit) -> unit
(** [forEach optionValue f]
If [optionValue] is [Some value], it calls [f value]; otherwise returns [()]
{[
forEach (Some "thing") (fun x -> Js.log x);;
(* logs "thing" *)
forEach None (fun x -> Js.log x) (* returns () *)
]} *)
val getExn : 'a option -> 'a
(** [getExn optionalValue] Returns [value] if [optionalValue] is [Some value], otherwise raises [getExn]
{[
getExn (Some 3) = 3;;
getExn None (* Raises getExn error *)
]} *)
external getUnsafe : 'a option -> 'a = "%identity"
(** [getUnsafe x] returns x This is an unsafe operation, it assumes x is neither not None or (Some (None .. )) *)
val mapWithDefaultU : 'a option -> 'b -> (('a -> 'b)[@u]) -> 'b
(** Uncurried version of [mapWithDefault] *)
val mapWithDefault : 'a option -> 'b -> ('a -> 'b) -> 'b
(** [mapWithDefault optionValue default f]
If [optionValue] is [Some value], returns [f value]; otherwise returns [default]
{[
mapWithDefault (Some 3) 0 (fun x -> x + 5) = 8;;
mapWithDefault None 0 (fun x -> x + 5) = 0
]} *)
val mapU : 'a option -> (('a -> 'b)[@u]) -> 'b option
(** Uncurried version of [map] *)
val map : 'a option -> ('a -> 'b) -> 'b option
(** [map optionValue f]
If [optionValue] is [Some value], returns [Some (f value)]; otherwise returns [None]
{[
map (Some 3) (fun x -> x * x) = Some 9;;
map None (fun x -> x * x) = None
]} *)
val flatMapU : 'a option -> (('a -> 'b option)[@u]) -> 'b option
(** Uncurried version of [flatMap] *)
val flatMap : 'a option -> ('a -> 'b option) -> 'b option
(** [flatMap optionValue f]
If [optionValue] is [Some value], returns [f value]; otherwise returns [None] The function [f] must have a return
type of ['a option]
{[
let f (x : float) = if x >= 0.0 then Some (sqrt x) else None;;
flatMap (Some 4.0) f = Some 2.0;;
flatMap (Some (-4.0)) f = None;;
flatMap None f = None
]} *)
val getWithDefault : 'a option -> 'a -> 'a
(** [getWithDefault optionalValue default]
If [optionalValue] is [Some value], returns [value], otherwise [default]
{[
getWithDefault (Some 1812) 1066 = 1812;;
getWithDefault None 1066 = 1066
]} *)
val orElse : 'a option -> 'a option -> 'a option
(** [orElse optionalValue otherOptional]
If [optionalValue] is [Some value], returns [Some value], otherwise [otherOptional]
{[
orElse (Some 1812) (Some 1066) = Some 1812;;
orElse None (Some 1066) = Some 1066;;
orElse None None = None
]} *)
val isSome : 'a option -> bool
(** Returns [true] if the argument is [Some value], [false] otherwise *)
val isNone : 'a option -> bool
(** Returns [true] if the argument is [None], [false] otherwise *)
val eqU : 'a option -> 'b option -> (('a -> 'b -> bool)[@u]) -> bool
(** Uncurried version of [eq] *)
val eq : 'a option -> 'b option -> ('a -> 'b -> bool) -> bool
(** [eq optValue1 optvalue2 predicate]
Evaluates two optional values for equality with respect to a predicate function.
If both [optValue1] and [optValue2] are [None], returns [true].
If one of the arguments is [Some value] and the other is [None], returns [false]
If arguments are [Some value1] and [Some value2], returns the result of [predicate value1 value2]; the [predicate]
function must return a [bool]
{[
let clockEqual = fun a b -> a mod 12 = b mod 12;;
eq (Some 3) (Some 15) clockEqual = true;;
eq (Some 3) None clockEqual = false;;
eq None (Some 3) clockEqual = false;;
eq None None clockEqual = true
]} *)
val cmpU : 'a option -> 'b option -> (('a -> 'b -> int)[@u]) -> int
(** Uncurried version of [cmp] *)
val cmp : 'a option -> 'b option -> ('a -> 'b -> int) -> int
(** [cmp optValue1 optvalue2 comparisonFcn]
Compares two optional values with respect to a comparison function
If both [optValue1] and [optValue2] are [None], returns 0.
If the first argument is [Some value1] and the second is [None], returns 1 (something is greater than nothing)
If the first argument is [None] and the second is [Some value2], returns -1 (nothing is less than something)
If the arguments are [Some value1] and [Some value2], returns the result of [comparisonFcn value1 value2];
[comparisonFcn] takes two arguments and returns -1 if the first argument is less than the second, 0 if the arguments
are equal, and 1 if the first argument is greater than the second.
{[
let clockCompare = fun a b -> compare (a mod 12) (b mod 12);;
cmp (Some 3) (Some 15) clockCompare = 0;;
cmp (Some 3) (Some 14) clockCompare = 1;;
cmp (Some 2) (Some 15) clockCompare = -1;;
cmp None (Some 15) clockCompare = -1;;
cmp (Some 14) None clockCompare = 1;;
cmp None None clockCompare = 0
]} *)
================================================
FILE: packages/Belt/src/Belt_Range.ml
================================================
let forEachU s f action =
for i = s to f do
(action i : unit)
done
let forEach s f action = forEachU s f (fun a -> action a)
let rec everyU s f p = if s > f then true else p s && everyU (s + 1) f p
let every s f p = everyU s f (fun a -> p a)
let rec everyByAux s f ~step p = if s > f then true else p s && everyByAux (s + step) f ~step p
let everyByU s f ~step p = if step > 0 then everyByAux s f ~step p else true
let everyBy s f ~step p = everyByU s f ~step (fun a -> p a)
let rec someU s f p = if s > f then false else p s || someU (s + 1) f p
let some s f p = someU s f (fun a -> p a)
let rec someByAux s f ~step p = if s > f then false else p s || someByAux (s + step) f ~step p
let someByU s f ~step p = if step > 0 then someByAux s f ~step p else false
let someBy s f ~step p = someByU s f ~step (fun a -> p a)
================================================
FILE: packages/Belt/src/Belt_Range.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A small module to provide a inclusive range operations [[start, finsish]], it use a for-loop internally instead of
creating an array *)
val forEachU : int -> int -> ((int -> unit)[@u]) -> unit
val forEach : int -> int -> (int -> unit) -> unit
(** [forEach start finish action]
equivalent to [Belt.Array.(forEach (range start finish) action)] *)
val everyU : int -> int -> ((int -> bool)[@u]) -> bool
val every : int -> int -> (int -> bool) -> bool
(** [every start finish p]
equivalent to [Belt.Array.(every (range start finish) p )] *)
val everyByU : int -> int -> step:int -> ((int -> bool)[@u]) -> bool
val everyBy : int -> int -> step:int -> (int -> bool) -> bool
(** [everyBy start finish ~step p]
{b See} {!Belt.Array.rangeBy}
equivalent to [Belt.Array.(every (rangeBy start finish ~step) p)] *)
val someU : int -> int -> ((int -> bool)[@u]) -> bool
val some : int -> int -> (int -> bool) -> bool
(** [some start finish p]
equivalent to [Belt.Array.(some (range start finish) p)] *)
val someByU : int -> int -> step:int -> ((int -> bool)[@u]) -> bool
val someBy : int -> int -> step:int -> (int -> bool) -> bool
(** [someBy start finish ~step p]
{b See} {!Belt.Array.rangeBy}
equivalent to [Belt.Array.(some (rangeBy start finish ~step) p)] *)
================================================
FILE: packages/Belt/src/Belt_Result.ml
================================================
type ('a, 'b) t = ('a, 'b) result = Ok of 'a | Error of 'b
let getExn = function
| Ok x -> x
| Error _ ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
let mapWithDefaultU opt default f = match opt with Ok x -> f x | Error _ -> default
let mapWithDefault opt default f = mapWithDefaultU opt default (fun x -> f x)
let mapU opt f = match opt with Ok x -> Ok (f x) | Error y -> Error y
let map opt f = mapU opt (fun x -> f x)
let flatMapU opt f = match opt with Ok x -> f x | Error y -> Error y
let flatMap opt f = flatMapU opt (fun x -> f x)
let getWithDefault opt default = match opt with Ok x -> x | Error _ -> default
let isOk = function Ok _ -> true | Error _ -> false
let isError = function Ok _ -> false | Error _ -> true
let eqU a b f =
match (a, b) with Ok a, Ok b -> f a b | Error _, Ok _ | Ok _, Error _ -> false | Error _, Error _ -> true
let eq a b f = eqU a b (fun x y -> f x y)
let cmpU a b f =
match (a, b) with Ok a, Ok b -> f a b | Error _, Ok _ -> -1 | Ok _, Error _ -> 1 | Error _, Error _ -> 0
let cmp a b f = cmpU a b (fun x y -> f x y)
================================================
FILE: packages/Belt/src/Belt_Result.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** {!Belt.Result}
Utilities for result data type. *)
(** [Belt.Result] is a data type with two variants: [Ok] and [Error]. Each of these variants can contain data, and those
two pieces of data need not have the same data type. [Belt.Result] is useful when you need to not only determine
whether some data is valid or not (use [Belt.Option] for that), but also keep information about the invalid data.
In the examples, we presume the existence of two variables:
{[
let good = Ok 42
let bad = Error "Invalid data"
]} *)
type ('a, 'b) t = ('a, 'b) Stdlib.result = Ok of 'a | Error of 'b
val getExn : ('a, 'b) t -> 'a
(** [getExn res]
when [res] is [Ok n], returns [n] when [res] is [Error m], {b raise} an exception
{[
getExn good = 42;;
getExn bad (* raises exception *)
]} *)
val mapWithDefaultU : ('a, 'c) t -> 'b -> (('a -> 'b)[@u]) -> 'b
val mapWithDefault : ('a, 'c) t -> 'b -> ('a -> 'b) -> 'b
(** [mapWithDefault res default f]
When [res] is [Ok n], returns [f n], otherwise [default].
{[
mapWithDefault good 0 (fun x -> x / 2) = 21 mapWithDefault bad 0 (fun x -> x / 2) = 0
]} *)
val mapU : ('a, 'c) t -> (('a -> 'b)[@u]) -> ('b, 'c) t
val map : ('a, 'c) t -> ('a -> 'b) -> ('b, 'c) t
(** [map res f]
When [res] is [Ok n], returns [Ok (f n)]. Otherwise returns [res] unchanged. Function [f] takes a value of the same
type as [n] and returns an ordinary value.
{[
let f x = sqrt (float_of_int x)
map (Ok 64) f = Ok 8.0
map (Error "Invalid data") f = Error "Invalid data"
]} *)
val flatMapU : ('a, 'c) t -> (('a -> ('b, 'c) t)[@u]) -> ('b, 'c) t
val flatMap : ('a, 'c) t -> ('a -> ('b, 'c) t) -> ('b, 'c) t
(** [flatMap res f]
When [res] is [Ok n], returns [f n]. Otherwise, returns [res] unchanged. Function [f] takes a value of the same type
as [n] and returns a [Belt.Result].
{[
let recip x =
if x != 0.0
then
Ok (1.0 /. x)
else
Error "Divide by zero"
flatMap (Ok 2.0) recip = Ok 0.5
flatMap (Ok 0.0) recip = Error "Divide by zero"
flatMap (Error "Already bad") recip = Error "Already bad"
]} *)
val getWithDefault : ('a, 'b) t -> 'a -> 'a
(** [getWithDefault res defaultValue]
if [res] is [Ok n], returns [n], otherwise [default]
{[
getWithDefault (Ok 42) 0 = 42 getWithDefault (Error "Invalid Data") = 0
]} *)
val isOk : ('a, 'b) t -> bool
(** [isOk res]
Returns [true] if [res] is of the form [Ok n], [false] if it is the [Error e] variant. *)
val isError : ('a, 'b) t -> bool
(** [isError res]
Returns [true] if [res] is of the form [Error e], [false] if it is the [Ok n] variant. *)
val eqU : ('a, 'c) t -> ('b, 'd) t -> (('a -> 'b -> bool)[@u]) -> bool
val eq : ('a, 'c) t -> ('b, 'd) t -> ('a -> 'b -> bool) -> bool
(** [eq res1 res2 f]
Determine if two [Belt.Result] variables are equal with respect to an equality function. If [res1] and [res2] are of
the form [Ok n] and [Ok m], return the result of [f n m]. If one of [res1] and [res2] are of the form [Error e],
return false If both [res1] and [res2] are of the form [Error e], return true
{[
let good1 = Ok 42
let good2 = Ok 32
let bad1 = Error "invalid"
let bad2 = Error "really invalid"
let mod10equal a b =
a mod 10 == b mod 10
eq good1 good2 mod10equal = true
eq good1 bad1 mod10equal = false
eq bad2 good2 mod10equal = false
eq bad1 bad2 mod10equal = true
]} *)
val cmpU : ('a, 'c) t -> ('b, 'd) t -> (('a -> 'b -> int)[@u]) -> int
val cmp : ('a, 'c) t -> ('b, 'd) t -> ('a -> 'b -> int) -> int
(** [cmp res1 res2 f]
Compare two [Belt.Result] variables with respect to a comparison function. The comparison function returns -1 if the
first variable is "less than" the second, 0 if the two variables are equal, and 1 if the first is "greater than" the
second.
If [res1] and [res2] are of the form [Ok n] and [Ok m], return the result of [f n m]. If [res1] is of the form
[Error e] and [res2] of the form [Ok n], return -1 (nothing is less than something) If [res1] is of the form [Ok n]
and [res2] of the form [Error e], return 1 (something is greater than nothing) If both [res1] and [res2] are of the
form [Error e], return 0 (equal)
{[
let good1 = Ok 59
let good2 = Ok 37
let bad1 = Error "invalid"
let bad2 = Error "really invalid"
let mod10cmp a b =
Pervasives.compare (a mod 10) (b mod 10) cmp (Ok 39) (Ok 57) mod10cmp
= 1 cmp (Ok 57) (Ok 39) mod10cmp
= -1 cmp (Ok 39) (Error "y") mod10cmp
= 1 cmp (Error "x") (Ok 57) mod10cmp
= -1 cmp (Error "x") (Error "y") mod10cmp
= 0
]} *)
================================================
FILE: packages/Belt/src/Belt_Set.ml
================================================
(* Copyright (C) 2017 Hongbo Zhang, Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
module Int = Belt_SetInt
module String = Belt_SetString
module Dict = Belt_SetDict
type ('value, 'id) id = ('value, 'id) Belt_Id.comparable
type ('value, 'id) cmp = ('value, 'id) Belt_Id.cmp
type ('value, 'id) t = { cmp : ('value, 'id) cmp; data : ('value, 'id) Dict.t }
let fromArray (type value identity) data ~(id : (value, identity) id) =
let module M = (val id) in
let cmp = M.cmp in
{ cmp; data = Dict.fromArray ~cmp data }
let remove m e =
let { cmp; data } = m in
let newData = Dict.remove ~cmp data e in
if newData == data then m else { cmp; data = newData }
let add m e =
let { cmp; data } = m in
let newData = Dict.add ~cmp data e in
if newData == data then m else { cmp; data = newData }
let mergeMany ({ cmp; _ } as m) e = { cmp; data = Dict.mergeMany ~cmp m.data e }
let removeMany ({ cmp; _ } as m) e = { cmp; data = Dict.removeMany ~cmp m.data e }
let union ({ cmp; _ } as m) n = { data = Dict.union ~cmp m.data n.data; cmp }
let intersect m n =
let cmp = m.cmp in
{ data = Dict.intersect ~cmp m.data n.data; cmp }
let diff m n =
let cmp = m.cmp in
{ cmp; data = Dict.diff ~cmp m.data n.data }
let subset m n =
let cmp = m.cmp in
Dict.subset ~cmp m.data n.data
let split m e =
let cmp = m.cmp in
let (l, r), b = Dict.split ~cmp m.data e in
(({ cmp; data = l }, { cmp; data = r }), b)
let make (type value identity) ~(id : (value, identity) id) =
let module M = (val id) in
{ cmp = M.cmp; data = Dict.empty }
let isEmpty m = Dict.isEmpty m.data
let cmp m n =
let cmp = m.cmp in
Dict.cmp ~cmp m.data n.data
let eq m n = Dict.eq ~cmp:m.cmp m.data n.data
let forEachU m f = Dict.forEachU m.data f
let forEach m f = forEachU m (fun[@u] a -> f a)
let reduceU m acc f = Dict.reduceU m.data acc f
let reduce m acc f = reduceU m acc (fun[@u] a b -> f a b)
let everyU m f = Dict.everyU m.data f
let every m f = everyU m (fun[@u] a -> f a)
let someU m f = Dict.someU m.data f
let some m f = someU m (fun[@u] a -> f a)
let keepU m f = { cmp = m.cmp; data = Dict.keepU m.data f }
let keep m f = keepU m (fun[@u] a -> f a)
let partitionU m f =
let l, r = Dict.partitionU m.data f in
let cmp = m.cmp in
({ data = l; cmp }, { data = r; cmp })
let partition m f = partitionU m (fun[@u] a -> f a)
let size m = Dict.size m.data
let toList m = Dict.toList m.data
let toArray m = Dict.toArray m.data
let minimum m = Dict.minimum m.data
let minUndefined m = Dict.minUndefined m.data
let maximum m = Dict.maximum m.data
let maxUndefined m = Dict.maxUndefined m.data
let get m e = Dict.get ~cmp:m.cmp m.data e
let getUndefined m e = Dict.getUndefined ~cmp:m.cmp m.data e
let getExn m e = Dict.getExn ~cmp:m.cmp m.data e
let has m e = Dict.has ~cmp:m.cmp m.data e
let fromSortedArrayUnsafe (type value identity) xs ~(id : (value, identity) id) =
let module M = (val id) in
{ cmp = M.cmp; data = Dict.fromSortedArrayUnsafe xs }
let getData m = m.data
let getId (type value identity) (m : (value, identity) t) : (value, identity) id =
let module T = struct
type nonrec identity = identity
type nonrec t = value
let cmp = m.cmp
end in
(module T)
let packIdData (type value identity) ~(id : (value, identity) id) ~data =
let module M = (val id) in
{ cmp = M.cmp; data }
let checkInvariantInternal d = Dict.checkInvariantInternal d.data
================================================
FILE: packages/Belt/src/Belt_Set.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A {i immutable} sorted set module which allows customize {i compare} behavior.
The implementation uses balanced binary trees, and therefore searching and insertion take time logarithmic in the
size of the map.
For more info on this module's usage of identity, `make` and others, please see the top level documentation of Belt,
{b A special encoding for collection safety}.
Example usage:
{[
module PairComparator = Belt.Id.MakeComparable (struct
type t = int * int
let cmp (a0, a1) (b0, b1) = match Pervasives.compare a0 b0 with 0 -> Pervasives.compare a1 b1 | c -> c
end)
let mySet = Belt.Set.make ~id:(module PairComparator)
let mySet2 = Belt.Set.add mySet (1, 2)
]}
The API documentation below will assume a predeclared comparator module for integers, IntCmp *)
module Int = Belt_SetInt
(** Specalized when value type is [int], more efficient than the generic type, its compare behavior is fixed using the
built-in comparison *)
module String = Belt_SetString
(** Specalized when value type is [string], more efficient than the generic type, its compare behavior is fixed using
the built-in comparison *)
module Dict = Belt_SetDict
(** This module seprate identity from data, it is a bit more verboe but slightly more efficient due to the fact that
there is no need to pack identity and data back after each operation *)
type ('value, 'identity) t
(** [('value, 'identity) t]
['value] is the element type
['identity] the identity of the collection *)
type ('value, 'id) id = ('value, 'id) Belt_Id.comparable
(** The identity needed for making a set from scratch *)
val make : id:('value, 'id) id -> ('value, 'id) t
(** [make ~id] creates a new set by taking in the comparator
{[
let s = make ~id:(module IntCmp)
]} *)
val fromArray : 'value array -> id:('value, 'id) id -> ('value, 'id) t
(** [fromArray xs ~id]
{[
toArray (fromArray [ 1; 3; 2; 4 ] (module IntCmp)) = [ 1; 2; 3; 4 ]
]} *)
val fromSortedArrayUnsafe : 'value array -> id:('value, 'id) id -> ('value, 'id) t
(** [fromSortedArrayUnsafe xs ~id]
The same as {!fromArray} except it is after assuming the input array [x] is already sorted
{b Unsafe} *)
val isEmpty : _ t -> bool
(** {[
isEmpty (fromArray [||] ~id:(module IntCmp)) = true;;
isEmpty (fromArray [| 1 |] ~id:(module IntCmp)) = true
]} *)
val has : ('value, 'id) t -> 'value -> bool
(** {[
let v = fromArray [| 1; 4; 2; 5 |] ~id:(module IntCmp);;
has v 3 = false;;
has v 1 = true
]} *)
val add : ('value, 'id) t -> 'value -> ('value, 'id) t
(** [add s x] If [x] was already in [s], [s] is returned unchanged.
{[
let s0 = make ~id:(module IntCmp)
let s1 = add s0 1
let s2 = add s1 2
let s3 = add s2 2;;
toArray s0 = [||];;
toArray s1 = [| 1 |];;
toArray s2 = [| 1; 2 |];;
toArray s3 = [| 1; 2 |];;
s2 == s3
]} *)
val mergeMany : ('value, 'id) t -> 'value array -> ('value, 'id) t
(** [mergeMany s xs]
Adding each of [xs] to [s], note unlike {!add}, the reference of return value might be changed even if all values in
[xs] exist [s] *)
val remove : ('value, 'id) t -> 'value -> ('value, 'id) t
(** [remove m x] If [x] was not in [m], [m] is returned reference unchanged.
{[
let s0 = fromArray ~id:(module IntCmp) [| 2; 3; 1; 4; 5 |]
let s1 = remove s0 1
let s2 = remove s1 3
let s3 = remove s2 3;;
toArray s1 = [| 2; 3; 4; 5 |];;
toArray s2 = [| 2; 4; 5 |];;
s2 == s3
]} *)
val removeMany : ('value, 'id) t -> 'value array -> ('value, 'id) t
(** [removeMany s xs]
Removing each of [xs] to [s], note unlike {!remove}, the reference of return value might be changed even if none in
[xs] exists [s] *)
val union : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
(** [union s0 s1]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
let s1 = fromArray ~id:(module IntCmp) [|5;2;3;1;5;4;|];;
toArray (union s0 s1) = [|1;2;3;4;5;6|]
]} *)
val intersect : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
(** [intersect s0 s1]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
let s1 = fromArray ~id:(module IntCmp) [|5;2;3;1;5;4;|];;
toArray (intersect s0 s1) = [|2;3;5|]
]} *)
val diff : ('value, 'id) t -> ('value, 'id) t -> ('value, 'id) t
(** [diff s0 s1]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
let s1 = fromArray ~id:(module IntCmp) [|5;2;3;1;5;4;|];;
toArray (diff s0 s1) = [|6|];;
toArray (diff s1 s0) = [|1;4|];;
]} *)
val subset : ('value, 'id) t -> ('value, 'id) t -> bool
(** [subset s0 s1]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
let s1 = fromArray ~id:(module IntCmp) [|5;2;3;1;5;4;|];;
let s2 = intersect s0 s1;;
subset s2 s0 = true;;
subset s2 s1 = true;;
subset s1 s0 = false;;
]} *)
val cmp : ('value, 'id) t -> ('value, 'id) t -> int
(** Total ordering between sets. Can be used as the ordering function for doing sets of sets. It compare [size] first
and then iterate over each element following the order of elements *)
val eq : ('value, 'id) t -> ('value, 'id) t -> bool
(** [eq s0 s1]
@return true if [toArray s0 = toArray s1] *)
val forEachU : ('value, 'id) t -> (('value -> unit)[@u]) -> unit
val forEach : ('value, 'id) t -> ('value -> unit) -> unit
(** [forEach s f] applies [f] in turn to all elements of [s]. In increasing order
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
let acc = ref [] ;;
forEach s0 (fun x -> acc := x !acc);;
!acc = [6;5;3;2];;
]} *)
val reduceU : ('value, 'id) t -> 'a -> (('a -> 'value -> 'a)[@u]) -> 'a
val reduce : ('value, 'id) t -> 'a -> ('a -> 'value -> 'a) -> 'a
(** In increasing order.
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
reduce s0 [] Belt.List.add = [6;5;3;2];;
]} *)
val everyU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val every : ('value, 'id) t -> ('value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val some : ('value, 'id) t -> ('value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. *)
val keepU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t
val keep : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t
(** [keep m p] returns the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t * ('value, 'id) t
val partition : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t * ('value, 'id) t
(** [partition m p] returns a pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that satisfy the
predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : ('value, 'id) t -> int
(** [size s]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
size s0 = 4;;
]} *)
val toArray : ('value, 'id) t -> 'value array
(** [toArray s0]
{[
let s0 = fromArray ~id:(module IntCmp) [|5;2;3;5;6|]];;
toArray s0 = [|2;3;5;6|];;
]}*)
val toList : ('value, 'id) t -> 'value list
(** In increasing order
{b See} {!toArray} *)
val minimum : ('value, 'id) t -> 'value option
(** [minimum s0]
@return the minimum element of the collection, [None] if it is empty *)
val minUndefined : ('value, 'id) t -> 'value Js.undefined
(** [minUndefined s0]
@return the minimum element of the collection, [undefined] if it is empty *)
val maximum : ('value, 'id) t -> 'value option
(** [maximum s0]
@return the maximum element of the collection, [None] if it is empty *)
val maxUndefined : ('value, 'id) t -> 'value Js.undefined
(** [maxUndefined s0]
@return the maximum element of the collection, [undefined] if it is empty *)
val get : ('value, 'id) t -> 'value -> 'value option
(** [get s0 k]
@return
the reference of the value [k'] which is equivalent to [k] using the comparator specifiecd by this collection,
[None] if it does not exist *)
val getUndefined : ('value, 'id) t -> 'value -> 'value Js.undefined
(** {b See} {!get} *)
val getExn : ('value, 'id) t -> 'value -> 'value
(** {b See} {!get}
{b raise} if not exist *)
val split : ('value, 'id) t -> 'value -> (('value, 'id) t * ('value, 'id) t) * bool
(** [split set ele]
@return a tuple [((smaller, larger), present)], [present] is true when [ele] exist in [set] *)
(**/**)
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
(**/**)
(****************************************************************************)
(** Below are operations only when better performance needed, it is still safe API but more verbose. More API will be
exposed by needs *)
val getData : ('value, 'id) t -> ('value, 'id) Belt_SetDict.t
(** [getData s0]
{b Advanced usage only}
@return
the raw data (detached from comparator), but its type is still manifested, so that user can pass identity directly
without boxing *)
val getId : ('value, 'id) t -> ('value, 'id) id
(** [getId s0]
{b Advanced usage only}
@return the identity of [s0] *)
val packIdData : id:('value, 'id) id -> data:('value, 'id) Belt_SetDict.t -> ('value, 'id) t
(** [packIdData ~id ~data]
{b Advanced usage only}
@return the packed collection *)
================================================
FILE: packages/Belt/src/Belt_SetDict.ml
================================================
(* Copyright (C) 2017 Hongbo Zhang, Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
module N = Belt_internalAVLset
module A = Belt_Array
type ('k, 'id) t = 'k N.t
type ('key, 'id) cmp = ('key, 'id) Belt_Id.cmp
(* here we relies on reference transparence
address equality means everything equal across time
no need to call [bal] again
*)
let rec add (t : _ t) x ~cmp : _ t =
match t with
| None -> N.singleton x
| Some nt ->
let k = nt.value in
let c = ((Belt_Id.getCmpInternal cmp) x k [@u]) in
if c = 0 then t
else
let { N.left = l; right = r; _ } = nt in
if c < 0 then
let ll = add ~cmp l x in
if ll == l then t else N.bal ll k r
else
let rr = add ~cmp r x in
if rr == r then t else N.bal l k rr
let rec remove (t : _ t) x ~cmp : _ t =
match t with
| None -> t
| Some n ->
let { N.left = l; value = v; right = r; _ } = n in
let c = ((Belt_Id.getCmpInternal cmp) x v [@u]) in
if c = 0 then
match (l, r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let v = ref rn.value in
let r = N.removeMinAuxWithRef rn v in
N.bal l v.contents r
else if c < 0 then
let ll = remove ~cmp l x in
if ll == l then t else N.bal ll v r
else
let rr = remove ~cmp r x in
if rr == r then t else N.bal l v rr
let mergeMany h arr ~cmp =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v.contents <- add v.contents ~cmp key
done;
v.contents
let removeMany h arr ~cmp =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v.contents <- remove v.contents ~cmp key
done;
v.contents
let rec splitAuxNoPivot ~cmp (n : _ N.node) x : _ * _ =
let { N.left = l; value = v; right = r; _ } = n in
let c = ((Belt_Id.getCmpInternal cmp) x v [@u]) in
if c = 0 then (l, r)
else if c < 0 then
match l with
| None -> (None, Some n)
| Some l ->
let ll, rl = splitAuxNoPivot ~cmp l x in
(ll, N.joinShared rl v r)
else
match r with
| None -> (Some n, None)
| Some r ->
let lr, rr = splitAuxNoPivot ~cmp r x in
(N.joinShared l v lr, rr)
let rec splitAuxPivot ~cmp (n : _ N.node) x pres : _ * _ =
let { N.left = l; value = v; right = r; _ } = n in
let c = ((Belt_Id.getCmpInternal cmp) x v [@u]) in
if c = 0 then (
pres.contents <- true;
(l, r))
else if c < 0 then
match l with
| None -> (None, Some n)
| Some l ->
let ll, rl = splitAuxPivot ~cmp l x pres in
(ll, N.joinShared rl v r)
else
match r with
| None -> (Some n, None)
| Some r ->
let lr, rr = splitAuxPivot ~cmp r x pres in
(N.joinShared l v lr, rr)
let split (t : _ t) x ~cmp =
match t with
| None -> ((None, None), false)
| Some n ->
let pres = ref false in
let v = splitAuxPivot ~cmp n x pres in
(v, pres.contents)
(* [union s1 s2]
Use the pivot to split the smaller collection
*)
let rec union (s1 : _ t) (s2 : _ t) ~cmp : _ t =
match (s1, s2) with
| None, _ -> s2
| _, None -> s1
| Some n1, Some n2 ->
let h1, h2 = (n1.height, n2.height) in
if h1 >= h2 then
if h2 = 1 then add ~cmp s1 n2.value
else
let { N.left = l1; value = v1; right = r1; _ } = n1 in
let l2, r2 = splitAuxNoPivot ~cmp n2 v1 in
N.joinShared (union ~cmp l1 l2) v1 (union ~cmp r1 r2)
else if h1 = 1 then add s2 ~cmp n1.value
else
let { N.left = l2; value = v2; right = r2; _ } = n2 in
let l1, r1 = splitAuxNoPivot ~cmp n1 v2 in
N.joinShared (union ~cmp l1 l2) v2 (union ~cmp r1 r2)
let rec intersect (s1 : _ t) (s2 : _ t) ~cmp =
match (s1, s2) with
| None, _ | _, None -> None
| Some n1, Some n2 ->
let { N.left = l1; value = v1; right = r1; _ } = n1 in
let pres = ref false in
let l2, r2 = splitAuxPivot ~cmp n2 v1 pres in
let ll = intersect ~cmp l1 l2 in
let rr = intersect ~cmp r1 r2 in
if pres.contents then N.joinShared ll v1 rr else N.concatShared ll rr
let rec diff s1 s2 ~cmp =
match (s1, s2) with
| None, _ | _, None -> s1
| Some n1, Some n2 ->
let { N.left = l1; value = v1; right = r1; _ } = n1 in
let pres = ref false in
let l2, r2 = splitAuxPivot ~cmp n2 v1 pres in
let ll = diff ~cmp l1 l2 in
let rr = diff ~cmp r1 r2 in
if pres.contents then N.concatShared ll rr else N.joinShared ll v1 rr
let empty = None
let fromArray = N.fromArray
let isEmpty = N.isEmpty
let cmp = N.cmp
let eq = N.eq
let has = N.has
let forEachU = N.forEachU
let forEach = N.forEach
let reduceU = N.reduceU
let reduce = N.reduce
let everyU = N.everyU
let every = N.every
let someU = N.someU
let some = N.some
let size = N.size
let toList = N.toList
let toArray = N.toArray
let minimum = N.minimum
let maximum = N.maximum
let maxUndefined = N.maxUndefined
let minUndefined = N.minUndefined
let get = N.get
let getExn = N.getExn
let getUndefined = N.getUndefined
let fromSortedArrayUnsafe = N.fromSortedArrayUnsafe
let subset = N.subset
let keep = N.keepShared
let keepU = N.keepSharedU
let partitionU = N.partitionSharedU
let partition = N.partitionShared
let checkInvariantInternal = N.checkInvariantInternal
================================================
FILE: packages/Belt/src/Belt_SetDict.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
type ('value, 'identity) t
type ('value, 'id) cmp = ('value, 'id) Belt_Id.cmp
val empty : ('value, 'id) t
val fromArray : 'value array -> cmp:('value, 'id) cmp -> ('value, 'id) t
val fromSortedArrayUnsafe : 'value array -> ('value, 'id) t
val isEmpty : _ t -> bool
val has : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> bool
val add : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> ('value, 'id) t
(** [add s x] If [x] was already in [s], [s] is returned unchanged. *)
val mergeMany : ('value, 'id) t -> 'value array -> cmp:('value, 'id) cmp -> ('value, 'id) t
val remove : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> ('value, 'id) t
(** [remove m x] If [x] was not in [m], [m] is returned reference unchanged. *)
val removeMany : ('value, 'id) t -> 'value array -> cmp:('value, 'id) cmp -> ('value, 'id) t
val union : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> ('value, 'id) t
val intersect : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> ('value, 'id) t
val diff : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> ('value, 'id) t
val subset : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> bool
(** [subset s1 s2] tests whether the set [s1] is a subset of the set [s2]. *)
val cmp : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> int
(** Total ordering between sets. Can be used as the ordering function for doing sets of sets. *)
val eq : ('value, 'id) t -> ('value, 'id) t -> cmp:('value, 'id) cmp -> bool
(** [eq s1 s2] tests whether the sets [s1] and [s2] are equal, that is, contain equal elements. *)
val forEachU : ('value, 'id) t -> (('value -> unit)[@u]) -> unit
val forEach : ('value, 'id) t -> ('value -> unit) -> unit
(** [forEach s f] applies [f] in turn to all elements of [s]. In increasing order *)
val reduceU : ('value, 'id) t -> 'a -> (('a -> 'value -> 'a)[@u]) -> 'a
val reduce : ('value, 'id) t -> 'a -> ('a -> 'value -> 'a) -> 'a
(** Iterate in increasing order. *)
val everyU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val every : ('value, 'id) t -> ('value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : ('value, 'id) t -> (('value -> bool)[@u]) -> bool
val some : ('value, 'id) t -> ('value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. Oder unspecified. *)
val keepU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t
val keep : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t
(** [keep p s] returns the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : ('value, 'id) t -> (('value -> bool)[@u]) -> ('value, 'id) t * ('value, 'id) t
val partition : ('value, 'id) t -> ('value -> bool) -> ('value, 'id) t * ('value, 'id) t
(** [partition p s] returns a pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that satisfy the
predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : ('value, 'id) t -> int
val toList : ('value, 'id) t -> 'value list
(** In increasing order *)
val toArray : ('value, 'id) t -> 'value array
val minimum : ('value, 'id) t -> 'value option
val minUndefined : ('value, 'id) t -> 'value Js.undefined
val maximum : ('value, 'id) t -> 'value option
val maxUndefined : ('value, 'id) t -> 'value Js.undefined
val get : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> 'value option
val getUndefined : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> 'value Js.undefined
val getExn : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> 'value
val split : ('value, 'id) t -> 'value -> cmp:('value, 'id) cmp -> (('value, 'id) t * ('value, 'id) t) * bool
(** [split x s] returns a triple [(l, present, r)], where [l] is the set of elements of [s] that are strictly less than
[x]; [r] is the set of elements of [s] that are strictly greater than [x]; [present] is [false] if [s] contains no
element equal to [x], or [true] if [s] contains an element equal to [x]. *)
val checkInvariantInternal : _ t -> unit
(** {b raise} when invariant is not held *)
================================================
FILE: packages/Belt/src/Belt_SetInt.ml
================================================
module I = Belt_internalSetInt
module N = Belt_internalAVLset
module A = Belt_Array
type value = I.value
type t = I.t
let empty = N.empty
let isEmpty = N.isEmpty
let minimum = N.minimum
let minUndefined = N.minUndefined
let maximum = N.maximum
let maxUndefined = N.maxUndefined
let forEach = N.forEach
let forEachU = N.forEachU
let reduce = N.reduce
let reduceU = N.reduceU
let every = N.every
let everyU = N.everyU
let some = N.some
let someU = N.someU
let keep = N.keepShared
let keepU = N.keepSharedU
let partition = N.partitionShared
let partitionU = N.partitionSharedU
let size = N.size
let toList = N.toList
let toArray = N.toArray
let fromSortedArrayUnsafe = N.fromSortedArrayUnsafe
let checkInvariantInternal = N.checkInvariantInternal
let rec add (t : t) (x : value) : t =
match N.toOpt t with
| None -> N.singleton x
| Some nt ->
let v = N.value nt in
if x = v then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < v then
let ll = add l x in
if ll == l then t else N.bal ll v r
else
let rr = add r x in
if rr == r then t else N.bal l v rr
let mergeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v := add !v key
done;
!v
let rec remove (t : t) (x : value) : t =
match N.toOpt t with
| None -> t
| Some n ->
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let v = ref (N.value rn) in
let r = N.removeMinAuxWithRef rn v in
N.bal l !v r
else if x < v then
let ll = remove l x in
if ll == l then t else N.bal ll v r
else
let rr = remove r x in
if rr == r then t else N.bal l v rr
let removeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v := remove !v key
done;
!v
let fromArray = I.fromArray
let cmp = I.cmp
let eq = I.eq
let get = I.get
let getUndefined = I.getUndefined
let getExn = I.getExn
let subset = I.subset
let has = I.has
let rec splitAuxNoPivot (n : _ N.node) (x : value) : t * t =
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then (l, r)
else if x < v then
match N.toOpt l with
| None -> (N.empty, N.return n)
| Some l ->
let ll, rl = splitAuxNoPivot l x in
(ll, N.joinShared rl v r)
else
match N.toOpt r with
| None -> (N.return n, N.empty)
| Some r ->
let lr, rr = splitAuxNoPivot r x in
(N.joinShared l v lr, rr)
let rec splitAuxPivot (n : _ N.node) (x : value) pres : t * t =
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then (
pres := true;
(l, r))
else if x < v then
match N.toOpt l with
| None -> (N.empty, N.return n)
| Some l ->
let ll, rl = splitAuxPivot l x pres in
(ll, N.joinShared rl v r)
else
match N.toOpt r with
| None -> (N.return n, N.empty)
| Some r ->
let lr, rr = splitAuxPivot r x pres in
(N.joinShared l v lr, rr)
let split (t : t) (x : value) =
match N.toOpt t with
| None -> ((N.empty, N.empty), false)
| Some n ->
let pres = ref false in
let v = splitAuxPivot n x pres in
(v, !pres)
let rec union (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ -> s2
| _, None -> s1
| Some n1, Some n2 ->
let h1, h2 =
let open N in
(height n1, height n2)
in
if h1 >= h2 then
if h2 = 1 then add s1 (N.value n2)
else
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let l2, r2 = splitAuxNoPivot n2 v1 in
N.joinShared (union l1 l2) v1 (union r1 r2)
else if h1 = 1 then add s2 (N.value n1)
else
let l2, v2, r2 =
let open N in
(left n2, value n2, right n2)
in
let l1, r1 = splitAuxNoPivot n1 v2 in
N.joinShared (union l1 l2) v2 (union r1 r2)
let rec intersect (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ | _, None -> N.empty
| Some n1, Some n2 ->
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let pres = ref false in
let l2, r2 = splitAuxPivot n2 v1 pres in
let ll = intersect l1 l2 in
let rr = intersect r1 r2 in
if !pres then N.joinShared ll v1 rr else N.concatShared ll rr
let rec diff (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ | _, None -> s1
| Some n1, Some n2 ->
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let pres = ref false in
let l2, r2 = splitAuxPivot n2 v1 pres in
let ll = diff l1 l2 in
let rr = diff r1 r2 in
if !pres then N.concatShared ll rr else N.joinShared ll v1 rr
================================================
FILE: packages/Belt/src/Belt_SetInt.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.Set} specialized with value type to be a primitive type. It is more efficient in general, the
API is the same with {!Belt.Set} except its value type is fixed, and identity is not needed(using the built-in one)
{b See} {!Belt.Set} *)
type value = int
(** The type of the set elements. *)
type t
(** The type of sets. *)
val empty : t
val fromArray : value array -> t
val fromSortedArrayUnsafe : value array -> t
val isEmpty : t -> bool
val has : t -> value -> bool
val add : t -> value -> t
(** [add s x] If [x] was already in [s], [s] is returned unchanged. *)
val mergeMany : t -> value array -> t
val remove : t -> value -> t
(** [remove m x] If [x] was not in [m], [m] is returned reference unchanged. *)
val removeMany : t -> value array -> t
val union : t -> t -> t
val intersect : t -> t -> t
val diff : t -> t -> t
val subset : t -> t -> bool
(** [subset s1 s2] tests whether the set [s1] is a subset of the set [s2]. *)
val cmp : t -> t -> int
(** Total ordering between sets. Can be used as the ordering function for doing sets of sets. *)
val eq : t -> t -> bool
(** [eq s1 s2] tests whether the sets [s1] and [s2] are equal, that is, contain equal elements. *)
val forEachU : t -> ((value -> unit)[@u]) -> unit
val forEach : t -> (value -> unit) -> unit
(** [forEach s f] applies [f] in turn to all elements of [s]. In increasing order *)
val reduceU : t -> 'a -> (('a -> value -> 'a)[@u]) -> 'a
val reduce : t -> 'a -> ('a -> value -> 'a) -> 'a
(** Iterate in increasing order. *)
val everyU : t -> ((value -> bool)[@u]) -> bool
val every : t -> (value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : t -> ((value -> bool)[@u]) -> bool
val some : t -> (value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. Oder unspecified. *)
val keepU : t -> ((value -> bool)[@u]) -> t
val keep : t -> (value -> bool) -> t
(** [keep p s] returns the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : t -> ((value -> bool)[@u]) -> t * t
val partition : t -> (value -> bool) -> t * t
(** [partition p s] returns a pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that satisfy the
predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : t -> int
val toList : t -> value list
(** In increasing order *)
val toArray : t -> value array
val minimum : t -> value option
val minUndefined : t -> value Js.undefined
val maximum : t -> value option
val maxUndefined : t -> value Js.undefined
val get : t -> value -> value option
val getUndefined : t -> value -> value Js.undefined
val getExn : t -> value -> value
val split : t -> value -> (t * t) * bool
(** [split x s] returns a triple [(l, present, r)], where [l] is the set of elements of [s] that are strictly less than
[x]; [r] is the set of elements of [s] that are strictly greater than [x]; [present] is [false] if [s] contains no
element equal to [x], or [true] if [s] contains an element equal to [x]. *)
val checkInvariantInternal : t -> unit
(** {b raise} when invariant is not held *)
================================================
FILE: packages/Belt/src/Belt_SetString.ml
================================================
module I = Belt_internalSetString
module N = Belt_internalAVLset
module A = Belt_Array
type value = I.value
type t = I.t
let empty = N.empty
let isEmpty = N.isEmpty
let minimum = N.minimum
let minUndefined = N.minUndefined
let maximum = N.maximum
let maxUndefined = N.maxUndefined
let forEach = N.forEach
let forEachU = N.forEachU
let reduce = N.reduce
let reduceU = N.reduceU
let every = N.every
let everyU = N.everyU
let some = N.some
let someU = N.someU
let keep = N.keepShared
let keepU = N.keepSharedU
let partition = N.partitionShared
let partitionU = N.partitionSharedU
let size = N.size
let toList = N.toList
let toArray = N.toArray
let fromSortedArrayUnsafe = N.fromSortedArrayUnsafe
let checkInvariantInternal = N.checkInvariantInternal
let rec add (t : t) (x : value) : t =
match N.toOpt t with
| None -> N.singleton x
| Some nt ->
let v = N.value nt in
if x = v then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < v then
let ll = add l x in
if ll == l then t else N.bal ll v r
else
let rr = add r x in
if rr == r then t else N.bal l v rr
let mergeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v := add !v key
done;
!v
let rec remove (t : t) (x : value) : t =
match N.toOpt t with
| None -> t
| Some n ->
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let v = ref (N.value rn) in
let r = N.removeMinAuxWithRef rn v in
N.bal l !v r
else if x < v then
let ll = remove l x in
if ll == l then t else N.bal ll v r
else
let rr = remove r x in
if rr == r then t else N.bal l v rr
let removeMany h arr =
let len = A.length arr in
let v = ref h in
for i = 0 to len - 1 do
let key = A.getUnsafe arr i in
v := remove !v key
done;
!v
let fromArray = I.fromArray
let cmp = I.cmp
let eq = I.eq
let get = I.get
let getUndefined = I.getUndefined
let getExn = I.getExn
let subset = I.subset
let has = I.has
let rec splitAuxNoPivot (n : _ N.node) (x : value) : t * t =
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then (l, r)
else if x < v then
match N.toOpt l with
| None -> (N.empty, N.return n)
| Some l ->
let ll, rl = splitAuxNoPivot l x in
(ll, N.joinShared rl v r)
else
match N.toOpt r with
| None -> (N.return n, N.empty)
| Some r ->
let lr, rr = splitAuxNoPivot r x in
(N.joinShared l v lr, rr)
let rec splitAuxPivot (n : _ N.node) (x : value) pres : t * t =
let l, v, r =
let open N in
(left n, value n, right n)
in
if x = v then (
pres := true;
(l, r))
else if x < v then
match N.toOpt l with
| None -> (N.empty, N.return n)
| Some l ->
let ll, rl = splitAuxPivot l x pres in
(ll, N.joinShared rl v r)
else
match N.toOpt r with
| None -> (N.return n, N.empty)
| Some r ->
let lr, rr = splitAuxPivot r x pres in
(N.joinShared l v lr, rr)
let split (t : t) (x : value) =
match N.toOpt t with
| None -> ((N.empty, N.empty), false)
| Some n ->
let pres = ref false in
let v = splitAuxPivot n x pres in
(v, !pres)
let rec union (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ -> s2
| _, None -> s1
| Some n1, Some n2 ->
let h1, h2 =
let open N in
(height n1, height n2)
in
if h1 >= h2 then
if h2 = 1 then add s1 (N.value n2)
else
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let l2, r2 = splitAuxNoPivot n2 v1 in
N.joinShared (union l1 l2) v1 (union r1 r2)
else if h1 = 1 then add s2 (N.value n1)
else
let l2, v2, r2 =
let open N in
(left n2, value n2, right n2)
in
let l1, r1 = splitAuxNoPivot n1 v2 in
N.joinShared (union l1 l2) v2 (union r1 r2)
let rec intersect (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ | _, None -> N.empty
| Some n1, Some n2 ->
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let pres = ref false in
let l2, r2 = splitAuxPivot n2 v1 pres in
let ll = intersect l1 l2 in
let rr = intersect r1 r2 in
if !pres then N.joinShared ll v1 rr else N.concatShared ll rr
let rec diff (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ | _, None -> s1
| Some n1, Some n2 ->
let l1, v1, r1 =
let open N in
(left n1, value n1, right n1)
in
let pres = ref false in
let l2, r2 = splitAuxPivot n2 v1 pres in
let ll = diff l1 l2 in
let rr = diff r1 r2 in
if !pres then N.concatShared ll rr else N.joinShared ll v1 rr
================================================
FILE: packages/Belt/src/Belt_SetString.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This module is {!Belt.Set} specialized with value type to be a primitive type. It is more efficient in general, the
API is the same with {!Belt.Set} except its value type is fixed, and identity is not needed(using the built-in one)
{b See} {!Belt.Set} *)
type value = string
(** The type of the set elements. *)
type t
(** The type of sets. *)
val empty : t
val fromArray : value array -> t
val fromSortedArrayUnsafe : value array -> t
val isEmpty : t -> bool
val has : t -> value -> bool
val add : t -> value -> t
(** [add s x] If [x] was already in [s], [s] is returned unchanged. *)
val mergeMany : t -> value array -> t
val remove : t -> value -> t
(** [remove m x] If [x] was not in [m], [m] is returned reference unchanged. *)
val removeMany : t -> value array -> t
val union : t -> t -> t
val intersect : t -> t -> t
val diff : t -> t -> t
val subset : t -> t -> bool
(** [subset s1 s2] tests whether the set [s1] is a subset of the set [s2]. *)
val cmp : t -> t -> int
(** Total ordering between sets. Can be used as the ordering function for doing sets of sets. *)
val eq : t -> t -> bool
(** [eq s1 s2] tests whether the sets [s1] and [s2] are equal, that is, contain equal elements. *)
val forEachU : t -> ((value -> unit)[@u]) -> unit
val forEach : t -> (value -> unit) -> unit
(** [forEach s f] applies [f] in turn to all elements of [s]. In increasing order *)
val reduceU : t -> 'a -> (('a -> value -> 'a)[@u]) -> 'a
val reduce : t -> 'a -> ('a -> value -> 'a) -> 'a
(** Iterate in increasing order. *)
val everyU : t -> ((value -> bool)[@u]) -> bool
val every : t -> (value -> bool) -> bool
(** [every p s] checks if all elements of the set satisfy the predicate [p]. Order unspecified. *)
val someU : t -> ((value -> bool)[@u]) -> bool
val some : t -> (value -> bool) -> bool
(** [some p s] checks if at least one element of the set satisfies the predicate [p]. Oder unspecified. *)
val keepU : t -> ((value -> bool)[@u]) -> t
val keep : t -> (value -> bool) -> t
(** [keep p s] returns the set of all elements in [s] that satisfy predicate [p]. *)
val partitionU : t -> ((value -> bool)[@u]) -> t * t
val partition : t -> (value -> bool) -> t * t
(** [partition p s] returns a pair of sets [(s1, s2)], where [s1] is the set of all the elements of [s] that satisfy the
predicate [p], and [s2] is the set of all the elements of [s] that do not satisfy [p]. *)
val size : t -> int
val toList : t -> value list
(** In increasing order *)
val toArray : t -> value array
val minimum : t -> value option
val minUndefined : t -> value Js.undefined
val maximum : t -> value option
val maxUndefined : t -> value Js.undefined
val get : t -> value -> value option
val getUndefined : t -> value -> value Js.undefined
val getExn : t -> value -> value
val split : t -> value -> (t * t) * bool
(** [split x s] returns a triple [(l, present, r)], where [l] is the set of elements of [s] that are strictly less than
[x]; [r] is the set of elements of [s] that are strictly greater than [x]; [present] is [false] if [s] contains no
element equal to [x], or [true] if [s] contains an element equal to [x]. *)
val checkInvariantInternal : t -> unit
(** {b raise} when invariant is not held *)
================================================
FILE: packages/Belt/src/Belt_SortArray.ml
================================================
module Int = Belt_SortArrayInt
module String = Belt_SortArrayString
module A = Belt_Array
let rec sortedLengthAuxMore xs prec acc len lt =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if lt v prec then sortedLengthAuxMore xs v (acc + 1) len lt else acc
let rec sortedLengthAuxLess xs prec acc len lt =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if lt prec v then sortedLengthAuxLess xs v (acc + 1) len lt else acc
let strictlySortedLengthU xs lt =
let len = A.length xs in
match len with
| 0 | 1 -> len
| _ ->
let x0, x1 = (A.getUnsafe xs 0, A.getUnsafe xs 1) in
if lt x0 x1 then sortedLengthAuxLess xs x1 2 len lt
else if lt x1 x0 then -sortedLengthAuxMore xs x1 2 len lt
else 1
let strictlySortedLength xs lt = strictlySortedLengthU xs (fun x y -> lt x y)
let rec isSortedAux a i cmp last_bound =
if i = last_bound then true
else if cmp (A.getUnsafe a i) (A.getUnsafe a (i + 1)) <= 0 then isSortedAux a (i + 1) cmp last_bound
else false
let isSortedU a cmp =
let len = A.length a in
if len = 0 then true else isSortedAux a 0 cmp (len - 1)
let isSorted a cmp = isSortedU a (fun x y -> cmp x y)
let cutoff = 5
let merge src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
let src1r = src1ofs + src1len and src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if cmp s1 s2 <= 0 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 (d + 1) else A.blitUnsafe src2 i2 dst (d + 1) (src2r - i2))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) (d + 1) else A.blitUnsafe src i1 dst (d + 1) (src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let unionU src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
let c = cmp s1 s2 in
if c < 0 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let d = d + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d
else (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2))
else if c = 0 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2)
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
let d = d + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let union src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
unionU src src1ofs src1len src2 src2ofs src2len dst dstofs (fun x y -> cmp x y)
let intersectU src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
let c = cmp s1 s2 in
if c < 0 then
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d
else if c = 0 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d else d)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d else d
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let intersect src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
intersectU src src1ofs src1len src2 src2ofs src2len dst dstofs (fun x y -> cmp x y)
let diffU src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
let c = cmp s1 s2 in
if c < 0 then (
A.setUnsafe dst d s1;
let d = d + 1 in
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d)
else if c = 0 then
let i1 = i1 + 1 in
let i2 = i2 + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let diff src src1ofs src1len src2 src2ofs src2len dst dstofs cmp =
diffU src src1ofs src1len src2 src2ofs src2len dst dstofs (fun x y -> cmp x y)
let insertionSort src srcofs dst dstofs len cmp =
for i = 0 to len - 1 do
let e = A.getUnsafe src (srcofs + i) in
let j = ref (dstofs + i - 1) in
while !j >= dstofs && cmp (A.getUnsafe dst !j) e > 0 do
A.setUnsafe dst (!j + 1) (A.getUnsafe dst !j);
decr j
done;
A.setUnsafe dst (!j + 1) e
done
let rec sortTo src srcofs dst dstofs len cmp =
if len <= cutoff then insertionSort src srcofs dst dstofs len cmp
else
let l1 = len / 2 in
let l2 = len - l1 in
sortTo src (srcofs + l1) dst (dstofs + l1) l2 cmp;
sortTo src srcofs src (srcofs + l2) l1 cmp;
merge src (srcofs + l2) l1 dst (dstofs + l1) l2 dst dstofs cmp
let stableSortInPlaceByU a cmp =
let l = A.length a in
if l <= cutoff then insertionSort a 0 a 0 l cmp
else
let l1 = l / 2 in
let l2 = l - l1 in
let t = Belt_Array.makeUninitializedUnsafe l2 (Belt_Array.getUnsafe a 0) in
sortTo a l1 t 0 l2 cmp;
sortTo a 0 a l2 l1 cmp;
merge a l2 l1 t 0 l2 a 0 cmp
let stableSortInPlaceBy a cmp = stableSortInPlaceByU a (fun x y -> cmp x y)
let stableSortByU a cmp =
let b = A.copy a in
stableSortInPlaceByU b cmp;
b
let stableSortBy a cmp = stableSortByU a (fun x y -> cmp x y)
let rec binarySearchAux arr lo hi key cmp =
let mid = (lo + hi) / 2 in
let midVal = A.getUnsafe arr mid in
let c = cmp key midVal in
if c = 0 then mid
else if c < 0 then
if hi = mid then if cmp (A.getUnsafe arr lo) key = 0 then lo else -(hi + 1) else binarySearchAux arr lo mid key cmp
else if lo = mid then if cmp (A.getUnsafe arr hi) key = 0 then hi else -(hi + 1)
else binarySearchAux arr mid hi key cmp
let binarySearchByU sorted key cmp : int =
let len = A.length sorted in
if len = 0 then -1
else
let lo = A.getUnsafe sorted 0 in
let c = cmp key lo in
if c < 0 then -1
else
let hi = A.getUnsafe sorted (len - 1) in
let c2 = cmp key hi in
if c2 > 0 then -(len + 1) else binarySearchAux sorted 0 (len - 1) key cmp
let binarySearchBy sorted key cmp = binarySearchByU sorted key (fun x y -> cmp x y)
================================================
FILE: packages/Belt/src/Belt_SortArray.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** A module for Array sort relevant utiliites *)
module Int = Belt_SortArrayInt
(** Specalized when key type is [int], more efficient than the generic type *)
module String = Belt_SortArrayString
(** Specalized when key type is [string], more efficient than the generic type *)
val strictlySortedLengthU : 'a array -> (('a -> 'a -> bool)[@u]) -> int
val strictlySortedLength : 'a array -> ('a -> 'a -> bool) -> int
(** [strictlySortedLenght xs cmp] return [+n] means increasing order [-n] means negative order
{[
strictlySortedLength [| 1; 2; 3; 4; 3 |] (fun x y -> x < y) = 4;;
strictlySortedLength [||] (fun x y -> x < y) = 0;;
strictlySortedLength [| 1 |] (fun x y -> x < y) = 1;;
strictlySortedLength [| 4; 3; 2; 1 |] (fun x y -> x < y) = -4
]} *)
val isSortedU : 'a array -> (('a -> 'a -> int)[@u]) -> bool
val isSorted : 'a array -> ('a -> 'a -> int) -> bool
(** [isSorted arr cmp]
@return
true if array is increasingly sorted (equal is okay )
{[
isSorted [| 1; 1; 2; 3; 4 |] (fun x y -> compare x y) = true
]} *)
val stableSortInPlaceByU : 'a array -> (('a -> 'a -> int)[@u]) -> unit
val stableSortInPlaceBy : 'a array -> ('a -> 'a -> int) -> unit
(** [stableSortBy xs cmp]
Sort xs in place using comparator [cmp], the stable means if the elements are equal, their order will be preserved
*)
val stableSortByU : 'a array -> (('a -> 'a -> int)[@u]) -> 'a array
val stableSortBy : 'a array -> ('a -> 'a -> int) -> 'a array
(** [stableSort xs cmp]
@return a fresh array
The same as {!stableSortInPlaceBy} except that [xs] is not modified *)
val binarySearchByU : 'a array -> 'a -> (('a -> 'a -> int)[@u]) -> int
val binarySearchBy : 'a array -> 'a -> ('a -> 'a -> int) -> int
(** If value is not found and value is less than one or more elements in array, the negative number returned is the
bitwise complement of the index of the first element that is larger than value.
If value is not found and value is greater than all elements in array, the negative number returned is the bitwise
complement of (the index of the last element plus 1)
for example, if [key] is smaller than all elements return [-1] since [lnot (-1) = 0] if [key] is larger than all
elements return [- (len + 1)] since [lnot (-(len+1)) = len]
{[
binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 33 = 4;;
lnot (binarySearchBy [| 1; 3; 5; 7 |] 4) = 2
]} *)
(**/**)
val unionU : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> (('a -> 'a -> int)[@u]) -> int
val union : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> ('a -> 'a -> int) -> int
(** [union src src1ofs src1len src2 src2ofs src2len dst dstofs cmp] assume [src] and [src2] is strictly sorted. for
equivalent elements, it is picked from [src] also assume that [dst] is large enough to store all elements *)
val intersectU : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> (('a -> 'a -> int)[@u]) -> int
val intersect : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> ('a -> 'a -> int) -> int
(** [union src src1ofs src1len src2 src2ofs src2len dst dstofs cmp] return the [offset] in the output array *)
val diffU : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> (('a -> 'a -> int)[@u]) -> int
val diff : 'a array -> int -> int -> 'a array -> int -> int -> 'a array -> int -> ('a -> 'a -> int) -> int
(**/**)
================================================
FILE: packages/Belt/src/Belt_SortArrayInt.ml
================================================
type element = int
module A = Belt_Array
let rec sortedLengthAuxMore (xs : element array) prec acc len =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if prec > v then sortedLengthAuxMore xs v (acc + 1) len else acc
let rec sortedLengthAuxLess (xs : element array) prec acc len =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if prec < v then sortedLengthAuxLess xs v (acc + 1) len else acc
let strictlySortedLength (xs : element array) =
let len = A.length xs in
match len with
| 0 | 1 -> len
| _ ->
let x0, x1 = (A.getUnsafe xs 0, A.getUnsafe xs 1) in
if x0 < x1 then sortedLengthAuxLess xs x1 2 len else if x0 > x1 then -sortedLengthAuxMore xs x1 2 len else 1
let rec isSortedAux (a : element array) i last_bound =
if i = last_bound then true
else if A.getUnsafe a i <= A.getUnsafe a (i + 1) then isSortedAux a (i + 1) last_bound
else false
let isSorted a =
let len = A.length a in
if len = 0 then true else isSortedAux a 0 (len - 1)
let cutoff = 5
let merge (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len and src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 <= s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 (d + 1) else A.blitUnsafe src2 i2 dst (d + 1) (src2r - i2))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) (d + 1) else A.blitUnsafe src i1 dst (d + 1) (src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let union (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let d = d + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d
else (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2))
else if s1 = s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2)
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
let d = d + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let intersect (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d
else if s1 = s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d else d)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d else d
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let diff (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then (
A.setUnsafe dst d s1;
let d = d + 1 in
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d)
else if s1 = s2 then
let i1 = i1 + 1 in
let i2 = i2 + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let insertionSort (src : element array) srcofs dst dstofs len =
for i = 0 to len - 1 do
let e = A.getUnsafe src (srcofs + i) in
let j = ref (dstofs + i - 1) in
while !j >= dstofs && A.getUnsafe dst !j > e do
A.setUnsafe dst (!j + 1) (A.getUnsafe dst !j);
decr j
done;
A.setUnsafe dst (!j + 1) e
done
let rec sortTo (src : element array) srcofs dst dstofs len =
if len <= cutoff then insertionSort src srcofs dst dstofs len
else
let l1 = len / 2 in
let l2 = len - l1 in
sortTo src (srcofs + l1) dst (dstofs + l1) l2;
sortTo src srcofs src (srcofs + l2) l1;
merge src (srcofs + l2) l1 dst (dstofs + l1) l2 dst dstofs
let stableSortInPlace (a : element array) =
let l = A.length a in
if l <= cutoff then insertionSort a 0 a 0 l
else
let l1 = l / 2 in
let l2 = l - l1 in
let t = Belt_Array.makeUninitializedUnsafe l2 (Belt_Array.getUnsafe a 0) in
sortTo a l1 t 0 l2;
sortTo a 0 a l2 l1;
merge a l2 l1 t 0 l2 a 0
let stableSort a =
let b = A.copy a in
stableSortInPlace b;
b
let rec binarySearchAux (arr : element array) lo hi key =
let mid = (lo + hi) / 2 in
let midVal = A.getUnsafe arr mid in
if key = midVal then mid
else if key < midVal then
if hi = mid then if A.getUnsafe arr lo = key then lo else -(hi + 1) else binarySearchAux arr lo mid key
else if lo = mid then if A.getUnsafe arr hi = key then hi else -(hi + 1)
else binarySearchAux arr mid hi key
let binarySearch (sorted : element array) key : int =
let len = A.length sorted in
if len = 0 then -1
else
let lo = A.getUnsafe sorted 0 in
if key < lo then -1
else
let hi = A.getUnsafe sorted (len - 1) in
if key > hi then -(len + 1) else binarySearchAux sorted 0 (len - 1) key
================================================
FILE: packages/Belt/src/Belt_SortArrayInt.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This is a specialized module for {!Belt.SortArray}, the docs in that module also applies here, except the comparator
is fixed and inlined *)
type element = int
val strictlySortedLength : element array -> int
(** The same as {!Belt.SortArray.strictlySortedLength } except the comparator is fixed
@return [+n] means increasing order [-n] means negative order *)
val isSorted : element array -> bool
(** [sorted xs] return true if [xs] is in non strict increasing order *)
val stableSortInPlace : element array -> unit
(** The same as {!Belt.SortArray.stableSortInPlaceBy} except the comparator is fixed *)
val stableSort : element array -> element array
(** The same as {!Belt.SortArray.stableSortBy} except the comparator is fixed *)
val binarySearch : element array -> element -> int
(** If value is not found and value is less than one or more elements in array, the negative number returned is the
bitwise complement of the index of the first element that is larger than value.
If value is not found and value is greater than all elements in array, the negative number returned is the bitwise
complement of (the index of the last element plus 1)
for example, if [key] is smaller than all elements return [-1] since [lnot (-1) = 0] if [key] is larger than all
elements return [- (len + 1)] since [lnot (-(len+1)) = len] *)
(**/**)
val union : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
(** [union src src1ofs src1len src2 src2ofs src2len dst dstofs cmp] assume [src] and [src2] is strictly sorted. for
equivalent elements, it is picked from [src] also assume that [dst] is large enough to store all elements *)
val intersect : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
val diff : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
(**/**)
================================================
FILE: packages/Belt/src/Belt_SortArrayString.ml
================================================
type element = string
module A = Belt_Array
let rec sortedLengthAuxMore (xs : element array) prec acc len =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if prec > v then sortedLengthAuxMore xs v (acc + 1) len else acc
let rec sortedLengthAuxLess (xs : element array) prec acc len =
if acc >= len then acc
else
let v = A.getUnsafe xs acc in
if prec < v then sortedLengthAuxLess xs v (acc + 1) len else acc
let strictlySortedLength (xs : element array) =
let len = A.length xs in
match len with
| 0 | 1 -> len
| _ ->
let x0, x1 = (A.getUnsafe xs 0, A.getUnsafe xs 1) in
if x0 < x1 then sortedLengthAuxLess xs x1 2 len else if x0 > x1 then -sortedLengthAuxMore xs x1 2 len else 1
let rec isSortedAux (a : element array) i last_bound =
if i = last_bound then true
else if A.getUnsafe a i <= A.getUnsafe a (i + 1) then isSortedAux a (i + 1) last_bound
else false
let isSorted a =
let len = A.length a in
if len = 0 then true else isSortedAux a 0 (len - 1)
let cutoff = 5
let merge (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len and src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 <= s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 (d + 1) else A.blitUnsafe src2 i2 dst (d + 1) (src2r - i2))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) (d + 1) else A.blitUnsafe src i1 dst (d + 1) (src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let union (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let d = d + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d
else (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2))
else if s1 = s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then (
A.blitUnsafe src2 i2 dst d (src2r - i2);
d + src2r - i2)
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
else (
A.setUnsafe dst d s2;
let i2 = i2 + 1 in
let d = d + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1))
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let intersect (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d
else if s1 = s2 then (
A.setUnsafe dst d s1;
let i1 = i1 + 1 in
let i2 = i2 + 1 in
let d = d + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d else d)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d else d
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let diff (src : element array) src1ofs src1len src2 src2ofs src2len dst dstofs =
let src1r = src1ofs + src1len in
let src2r = src2ofs + src2len in
let rec loop i1 s1 i2 s2 d =
if s1 < s2 then (
A.setUnsafe dst d s1;
let d = d + 1 in
let i1 = i1 + 1 in
if i1 < src1r then loop i1 (A.getUnsafe src i1) i2 s2 d else d)
else if s1 = s2 then
let i1 = i1 + 1 in
let i2 = i2 + 1 in
if i1 < src1r && i2 < src2r then loop i1 (A.getUnsafe src i1) i2 (A.getUnsafe src2 i2) d
else if i1 = src1r then d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
else
let i2 = i2 + 1 in
if i2 < src2r then loop i1 s1 i2 (A.getUnsafe src2 i2) d
else (
A.blitUnsafe src i1 dst d (src1r - i1);
d + src1r - i1)
in
loop src1ofs (A.getUnsafe src src1ofs) src2ofs (A.getUnsafe src2 src2ofs) dstofs
let insertionSort (src : element array) srcofs dst dstofs len =
for i = 0 to len - 1 do
let e = A.getUnsafe src (srcofs + i) in
let j = ref (dstofs + i - 1) in
while !j >= dstofs && A.getUnsafe dst !j > e do
A.setUnsafe dst (!j + 1) (A.getUnsafe dst !j);
decr j
done;
A.setUnsafe dst (!j + 1) e
done
let rec sortTo (src : element array) srcofs dst dstofs len =
if len <= cutoff then insertionSort src srcofs dst dstofs len
else
let l1 = len / 2 in
let l2 = len - l1 in
sortTo src (srcofs + l1) dst (dstofs + l1) l2;
sortTo src srcofs src (srcofs + l2) l1;
merge src (srcofs + l2) l1 dst (dstofs + l1) l2 dst dstofs
let stableSortInPlace (a : element array) =
let l = A.length a in
if l <= cutoff then insertionSort a 0 a 0 l
else
let l1 = l / 2 in
let l2 = l - l1 in
let t = Belt_Array.makeUninitializedUnsafe l2 (Belt_Array.getUnsafe a 0) in
sortTo a l1 t 0 l2;
sortTo a 0 a l2 l1;
merge a l2 l1 t 0 l2 a 0
let stableSort a =
let b = A.copy a in
stableSortInPlace b;
b
let rec binarySearchAux (arr : element array) lo hi key =
let mid = (lo + hi) / 2 in
let midVal = A.getUnsafe arr mid in
if key = midVal then mid
else if key < midVal then
if hi = mid then if A.getUnsafe arr lo = key then lo else -(hi + 1) else binarySearchAux arr lo mid key
else if lo = mid then if A.getUnsafe arr hi = key then hi else -(hi + 1)
else binarySearchAux arr mid hi key
let binarySearch (sorted : element array) key : int =
let len = A.length sorted in
if len = 0 then -1
else
let lo = A.getUnsafe sorted 0 in
if key < lo then -1
else
let hi = A.getUnsafe sorted (len - 1) in
if key > hi then -(len + 1) else binarySearchAux sorted 0 (len - 1) key
================================================
FILE: packages/Belt/src/Belt_SortArrayString.mli
================================================
(* Copyright (C) 2017 Authors of ReScript
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition to the permissions granted to you by the LGPL, you may combine
* or link a "work that uses the Library" with a publicly distributed version
* of this file to produce a combined library or application, then distribute
* that combined work under the terms of your choosing, with no requirement
* to comply with the obligations normally placed on you by section 4 of the
* LGPL version 3 (or the corresponding section of a later version of the LGPL
* should you choose to use a later version).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
(** This is a specialized module for {!Belt.SortArray}, the docs in that module also applies here, except the comparator
is fixed and inlined *)
type element = string
val strictlySortedLength : element array -> int
(** The same as {!Belt.SortArray.strictlySortedLength } except the comparator is fixed
@return [+n] means increasing order [-n] means negative order *)
val isSorted : element array -> bool
(** [sorted xs] return true if [xs] is in non strict increasing order *)
val stableSortInPlace : element array -> unit
(** The same as {!Belt.SortArray.stableSortInPlaceBy} except the comparator is fixed *)
val stableSort : element array -> element array
(** The same as {!Belt.SortArray.stableSortBy} except the comparator is fixed *)
val binarySearch : element array -> element -> int
(** If value is not found and value is less than one or more elements in array, the negative number returned is the
bitwise complement of the index of the first element that is larger than value.
If value is not found and value is greater than all elements in array, the negative number returned is the bitwise
complement of (the index of the last element plus 1)
for example, if [key] is smaller than all elements return [-1] since [lnot (-1) = 0] if [key] is larger than all
elements return [- (len + 1)] since [lnot (-(len+1)) = len] *)
(**/**)
val union : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
(** [union src src1ofs src1len src2 src2ofs src2len dst dstofs cmp] assume [src] and [src2] is strictly sorted. for
equivalent elements, it is picked from [src] also assume that [dst] is large enough to store all elements *)
val intersect : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
val diff : element array -> int -> int -> element array -> int -> int -> element array -> int -> int
(**/**)
================================================
FILE: packages/Belt/src/Belt_internalAVLset.ml
================================================
type 'value node = {
mutable value : 'value; [@mel.as "v"]
mutable height : int; [@mel.as "h"]
mutable left : 'value t; [@mel.as "l"]
mutable right : 'value t; [@mel.as "r"]
}
and 'value t = 'value node option
let node : value:'value -> height:int -> left:'value t -> right:'value t -> 'value node =
fun ~value ~height ~left ~right -> { value; height; left; right }
let valueSet : 'value node -> 'value -> unit = fun o v -> o.value <- v
let value : 'value node -> 'value = fun o -> o.value
let heightSet : 'value node -> int -> unit = fun o v -> o.height <- v
let height : 'value node -> int = fun o -> o.height
let leftSet : 'value node -> 'value t -> unit = fun o v -> o.left <- v
let left : 'value node -> 'value t = fun o -> o.left
let rightSet : 'value node -> 'value t -> unit = fun o v -> o.right <- v
let right : 'value node -> 'value t = fun o -> o.right
module A = Belt_Array
module S = Belt_SortArray
let toOpt = Js.toOption
let return : 'a -> 'a Js.null = Js.Null.return
let empty = Js.empty
let unsafeCoerce : 'a Js.null -> 'a = Js.Null.getUnsafe
type ('a, 'b) cmp = ('a, 'b) Belt_Id.cmp
let treeHeight (n : _ t) = match toOpt n with None -> 0 | Some n -> height n
let rec copy n =
match toOpt n with
| None -> n
| Some n ->
let l, r = (left n, right n) in
return @@ node ~left:(copy l) ~right:(copy r) ~value:(value n) ~height:(height n)
let create (l : _ t) v (r : _ t) =
let hl = match toOpt l with None -> 0 | Some n -> height n in
let hr = match toOpt r with None -> 0 | Some n -> height n in
return @@ node ~left:l ~value:v ~right:r ~height:(if hl >= hr then hl + 1 else hr + 1)
let singleton x = return @@ node ~left:empty ~value:x ~right:empty ~height:1
let heightGe l r =
match (toOpt l, toOpt r) with _, None -> true | Some hl, Some hr -> height hl >= height hr | None, Some _ -> false
let bal l v r =
let hl = match toOpt l with None -> 0 | Some n -> height n in
let hr = match toOpt r with None -> 0 | Some n -> height n in
if hl > hr + 2 then
let ll, lv, lr =
let __ocaml_internal_obj = unsafeCoerce l in
(left __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
if heightGe ll lr then create ll lv (create lr v r)
else
let lrl, lrv, lrr =
let __ocaml_internal_obj = unsafeCoerce lr in
(left __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
create (create ll lv lrl) lrv (create lrr v r)
else if hr > hl + 2 then
let rl, rv, rr =
let __ocaml_internal_obj = unsafeCoerce r in
(left __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
if heightGe rr rl then create (create l v rl) rv rr
else
let rll, rlv, rlr =
let __ocaml_internal_obj = unsafeCoerce rl in
(left __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
create (create l v rll) rlv (create rlr rv rr)
else return @@ node ~left:l ~value:v ~right:r ~height:(if hl >= hr then hl + 1 else hr + 1)
let rec min0Aux n = match toOpt (left n) with None -> value n | Some n -> min0Aux n
let minimum n = match toOpt n with None -> None | Some n -> Some (min0Aux n)
let minUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (min0Aux n)
let rec max0Aux n = match toOpt (right n) with None -> value n | Some n -> max0Aux n
let maximum n = match toOpt n with None -> None | Some n -> Some (max0Aux n)
let maxUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (max0Aux n)
let rec removeMinAuxWithRef n v =
let ln, rn, kn = (left n, right n, value n) in
match toOpt ln with
| None ->
v := kn;
rn
| Some ln -> bal (removeMinAuxWithRef ln v) kn rn
let isEmpty n = match toOpt n with Some _ -> false | None -> true
let rec stackAllLeft v s = match toOpt v with None -> s | Some x -> stackAllLeft (left x) (x :: s)
let rec forEachU n f =
match toOpt n with
| None -> ()
| Some n ->
forEachU (left n) f;
f (value n);
forEachU (right n) f
let forEach n f = forEachU n (fun a -> f a)
let rec reduceU s accu f =
match toOpt s with
| None -> accu
| Some n ->
let l, k, r = (left n, value n, right n) in
reduceU r (f (reduceU l accu f) k) f
let reduce s accu f = reduceU s accu (fun a b -> f a b)
let rec everyU n p =
match toOpt n with None -> true | Some n -> p (value n) && everyU (left n) p && everyU (right n) p
let every n p = everyU n (fun a -> p a)
let rec someU n p = match toOpt n with None -> false | Some n -> p (value n) || someU (left n) p || someU (right n) p
let some n p = someU n (fun a -> p a)
let rec addMinElement n v =
match toOpt n with None -> singleton v | Some n -> bal (addMinElement (left n) v) (value n) (right n)
let rec addMaxElement n v =
match toOpt n with None -> singleton v | Some n -> bal (left n) (value n) (addMaxElement (right n) v)
let rec joinShared ln v rn =
match (toOpt ln, toOpt rn) with
| None, _ -> addMinElement rn v
| _, None -> addMaxElement ln v
| Some l, Some r ->
let lh = height l in
let rh = height r in
if lh > rh + 2 then bal (left l) (value l) (joinShared (right l) v rn)
else if rh > lh + 2 then bal (joinShared ln v (left r)) (value r) (right r)
else create ln v rn
let concatShared t1 t2 =
match (toOpt t1, toOpt t2) with
| None, _ -> t2
| _, None -> t1
| _, Some t2n ->
let v = ref (value t2n) in
let t2r = removeMinAuxWithRef t2n v in
joinShared t1 !v t2r
let rec partitionSharedU n p =
match toOpt n with
| None -> (empty, empty)
| Some n ->
let value = value n in
let lt, lf = partitionSharedU (left n) p in
let pv = p value in
let rt, rf = partitionSharedU (right n) p in
if pv then (joinShared lt value rt, concatShared lf rf) else (concatShared lt rt, joinShared lf value rf)
let partitionShared n p = partitionSharedU n (fun a -> p a)
let rec lengthNode n =
let l, r = (left n, right n) in
let sizeL = match toOpt l with None -> 0 | Some l -> lengthNode l in
let sizeR = match toOpt r with None -> 0 | Some r -> lengthNode r in
1 + sizeL + sizeR
let size n = match toOpt n with None -> 0 | Some n -> lengthNode n
let rec toListAux n accu =
match toOpt n with None -> accu | Some n -> toListAux (left n) (value n :: toListAux (right n) accu)
let toList s = toListAux s []
let rec checkInvariantInternal (v : _ t) =
match toOpt v with
| None -> ()
| Some n ->
let l, r = (left n, right n) in
let diff = treeHeight l - treeHeight r in
if Stdlib.not (diff <= 2 && diff >= -2) then
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
else (
checkInvariantInternal l;
checkInvariantInternal r)
let rec fillArray n i arr =
let l, v, r = (left n, value n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArray l i arr in
A.setUnsafe arr next v;
let rnext = next + 1 in
match toOpt r with None -> rnext | Some r -> fillArray r rnext arr
include (
struct
type cursor = { mutable forward : int; mutable backward : int }
let cursor : forward:int -> backward:int -> cursor = fun ~forward ~backward -> { forward; backward }
let forwardSet : cursor -> int -> unit = fun o v -> o.forward <- v
let forward : cursor -> int = fun o -> o.forward
let backwardSet : cursor -> int -> unit = fun o v -> o.backward <- v
let backward : cursor -> int = fun o -> o.backward
end :
sig
type cursor
val cursor : forward:int -> backward:int -> cursor
val forwardSet : cursor -> int -> unit
val forward : cursor -> int
val backwardSet : cursor -> int -> unit
val backward : cursor -> int
end)
let rec fillArrayWithPartition n cursor arr p =
let l, v, r = (left n, value n, right n) in
(match toOpt l with None -> () | Some l -> fillArrayWithPartition l cursor arr p);
(if p v then (
let c = forward cursor in
A.setUnsafe arr c v;
forwardSet cursor (c + 1))
else
let c = backward cursor in
A.setUnsafe arr c v;
backwardSet cursor (c - 1));
match toOpt r with None -> () | Some r -> fillArrayWithPartition r cursor arr p
let rec fillArrayWithFilter n i arr p =
let l, v, r = (left n, value n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArrayWithFilter l i arr p in
let rnext =
if p v then (
A.setUnsafe arr next v;
next + 1)
else next
in
match toOpt r with None -> rnext | Some r -> fillArrayWithFilter r rnext arr p
let toArray n =
match toOpt n with
| None -> [||]
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (value n) in
ignore (fillArray n 0 v : int);
v
let rec fromSortedArrayRevAux arr off len =
match len with
| 0 -> empty
| 1 -> singleton (A.getUnsafe arr off)
| 2 ->
let x0, x1 =
let open A in
(getUnsafe arr off, getUnsafe arr (off - 1))
in
return @@ node ~left:(singleton x0) ~value:x1 ~height:2 ~right:empty
| 3 ->
let x0, x1, x2 =
let open A in
(getUnsafe arr off, getUnsafe arr (off - 1), getUnsafe arr (off - 2))
in
return @@ node ~left:(singleton x0) ~right:(singleton x2) ~value:x1 ~height:2
| _ ->
let nl = len / 2 in
let left = fromSortedArrayRevAux arr off nl in
let mid = A.getUnsafe arr (off - nl) in
let right = fromSortedArrayRevAux arr (off - nl - 1) (len - nl - 1) in
create left mid right
let rec fromSortedArrayAux arr off len =
match len with
| 0 -> empty
| 1 -> singleton (A.getUnsafe arr off)
| 2 ->
let x0, x1 =
let open A in
(getUnsafe arr off, getUnsafe arr (off + 1))
in
return @@ node ~left:(singleton x0) ~value:x1 ~height:2 ~right:empty
| 3 ->
let x0, x1, x2 =
let open A in
(getUnsafe arr off, getUnsafe arr (off + 1), getUnsafe arr (off + 2))
in
return @@ node ~left:(singleton x0) ~right:(singleton x2) ~value:x1 ~height:2
| _ ->
let nl = len / 2 in
let left = fromSortedArrayAux arr off nl in
let mid = A.getUnsafe arr (off + nl) in
let right = fromSortedArrayAux arr (off + nl + 1) (len - nl - 1) in
create left mid right
let fromSortedArrayUnsafe arr = fromSortedArrayAux arr 0 (A.length arr)
let rec keepSharedU n p =
match toOpt n with
| None -> empty
| Some n ->
let l, v, r = (left n, value n, right n) in
let newL = keepSharedU l p in
let pv = p v in
let newR = keepSharedU r p in
if pv then if l == newL && r == newR then return n else joinShared newL v newR else concatShared newL newR
let keepShared n p = keepSharedU n (fun a -> p a)
let keepCopyU n p : _ t =
match toOpt n with
| None -> empty
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (value n) in
let last = fillArrayWithFilter n 0 v p in
fromSortedArrayAux v 0 last
let keepCopy n p = keepCopyU n (fun x -> p x)
let partitionCopyU n p =
match toOpt n with
| None -> (empty, empty)
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (value n) in
let backward = size - 1 in
let cursor = cursor ~forward:0 ~backward in
fillArrayWithPartition n cursor v p;
let forwardLen = forward cursor in
(fromSortedArrayAux v 0 forwardLen, fromSortedArrayRevAux v backward (size - forwardLen))
let partitionCopy n p = partitionCopyU n (fun a -> p a)
let rec has (t : _ t) x ~cmp =
match toOpt t with
| None -> false
| Some n ->
let v = value n in
let c = (Belt_Id.getCmpInternal cmp) x v in
c = 0 || has ~cmp (if c < 0 then left n else right n) x
let rec compareAux e1 e2 ~cmp =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let c = (Belt_Id.getCmpInternal cmp) (value h1) (value h2) in
if c = 0 then compareAux ~cmp (stackAllLeft (right h1) t1) (stackAllLeft (right h2) t2) else c
| _, _ -> 0
let cmp s1 s2 ~cmp =
let len1, len2 = (size s1, size s2) in
if len1 = len2 then compareAux ~cmp (stackAllLeft s1 []) (stackAllLeft s2 []) else if len1 < len2 then -1 else 1
let eq s1 s2 ~cmp:c = cmp ~cmp:c s1 s2 = 0
let rec subset (s1 : _ t) (s2 : _ t) ~cmp =
match (toOpt s1, toOpt s2) with
| None, _ -> true
| _, None -> false
| Some t1, Some t2 ->
let l1, v1, r1 = (left t1, value t1, right t1) in
let l2, v2, r2 = (left t2, value t2, right t2) in
let c = (Belt_Id.getCmpInternal cmp) v1 v2 in
if c = 0 then subset ~cmp l1 l2 && subset ~cmp r1 r2
else if c < 0 then subset ~cmp (create l1 v1 empty) l2 && subset ~cmp r1 s2
else subset ~cmp (create empty v1 r1) r2 && subset ~cmp l1 s2
let rec get (n : _ t) x ~cmp =
match toOpt n with
| None -> None
| Some t ->
let v = value t in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then Some v else get ~cmp (if c < 0 then left t else right t) x
let rec getUndefined (n : _ t) x ~cmp =
match toOpt n with
| None -> Js.Undefined.empty
| Some t ->
let v = value t in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then Js.Undefined.return v else getUndefined ~cmp (if c < 0 then left t else right t) x
let rec getExn (n : _ t) x ~cmp =
match toOpt n with
| None ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| Some t ->
let v = value t in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then v else getExn ~cmp (if c < 0 then left t else right t) x
let rotateWithLeftChild k2 =
let k1 = unsafeCoerce (left k2) in
leftSet k2 (right k1);
rightSet k1 (return k2);
let hlk2, hrk2 = (treeHeight (left k2), treeHeight (right k2)) in
heightSet k2 (Stdlib.max hlk2 hrk2 + 1);
let hlk1, hk2 = (treeHeight (left k1), height k2) in
heightSet k1 (Stdlib.max hlk1 hk2 + 1);
k1
let rotateWithRightChild k1 =
let k2 = unsafeCoerce (right k1) in
rightSet k1 (left k2);
leftSet k2 (return k1);
let hlk1, hrk1 = (treeHeight (left k1), treeHeight (right k1)) in
heightSet k1 (Stdlib.max hlk1 hrk1 + 1);
let hrk2, hk1 = (treeHeight (right k2), height k1) in
heightSet k2 (Stdlib.max hrk2 hk1 + 1);
k2
let doubleWithLeftChild k3 =
let v = return (rotateWithRightChild (unsafeCoerce (left k3))) in
leftSet k3 v;
rotateWithLeftChild k3
[@@ocaml.doc " "]
let doubleWithRightChild k2 =
let v = return (rotateWithLeftChild (unsafeCoerce (right k2))) in
rightSet k2 v;
rotateWithRightChild k2
let heightUpdateMutate t =
let hlt, hrt = (treeHeight (left t), treeHeight (right t)) in
heightSet t (Stdlib.max hlt hrt + 1);
t
let balMutate nt =
let l, r = (left nt, right nt) in
let hl, hr = (treeHeight l, treeHeight r) in
if hl > 2 + hr then
let ll, lr =
let __ocaml_internal_obj = unsafeCoerce l in
(left __ocaml_internal_obj, right __ocaml_internal_obj)
in
if heightGe ll lr then heightUpdateMutate (rotateWithLeftChild nt) else heightUpdateMutate (doubleWithLeftChild nt)
else if hr > 2 + hl then
let rl, rr =
let __ocaml_internal_obj = unsafeCoerce r in
(left __ocaml_internal_obj, right __ocaml_internal_obj)
in
if heightGe rr rl then heightUpdateMutate (rotateWithRightChild nt)
else heightUpdateMutate (doubleWithRightChild nt)
else (
heightSet nt (max hl hr + 1);
nt)
let rec addMutate ~cmp (t : _ t) x =
match toOpt t with
| None -> singleton x
| Some nt ->
let k = value nt in
let c = (Belt_Id.getCmpInternal cmp) x k in
if c = 0 then t
else
let l, r = (left nt, right nt) in
if c < 0 then
let ll = addMutate ~cmp l x in
leftSet nt ll
else rightSet nt (addMutate ~cmp r x);
return (balMutate nt)
let fromArray (xs : _ array) ~cmp =
let len = A.length xs in
if len = 0 then empty
else
let next = ref (S.strictlySortedLengthU xs (fun x y -> (Belt_Id.getCmpInternal cmp) x y < 0)) in
let result =
ref
(if !next >= 0 then fromSortedArrayAux xs 0 !next
else (
next := - !next;
fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
result := addMutate ~cmp !result (A.getUnsafe xs i)
done;
!result
let rec removeMinAuxWithRootMutate nt n =
let rn, ln = (right n, left n) in
match toOpt ln with
| None ->
valueSet nt (value n);
rn
| Some ln ->
leftSet n (removeMinAuxWithRootMutate nt ln);
return (balMutate n)
================================================
FILE: packages/Belt/src/Belt_internalAVLset.mli
================================================
type 'value node = { mutable value : 'value; mutable height : int; mutable left : 'value t; mutable right : 'value t }
and 'value t = 'value node option
val node : value:'value -> height:int -> left:'value t -> right:'value t -> 'value node
val valueSet : 'value node -> 'value -> unit
val value : 'value node -> 'value
val heightSet : 'value node -> int -> unit
val height : 'value node -> int
val leftSet : 'value node -> 'value t -> unit
val left : 'value node -> 'value t
val rightSet : 'value node -> 'value t -> unit
val right : 'value node -> 'value t
module A = Belt_Array
module S = Belt_SortArray
val toOpt : 'a option -> 'a option
val return : 'a -> 'a option
val empty : 'a option
val unsafeCoerce : 'a option -> 'a
type ('a, 'b) cmp = ('a, 'b) Belt_Id.cmp
val treeHeight : 'a t -> int
val copy : 'a node option -> 'a node option
val create : 'a t -> 'a -> 'a t -> 'a node option
val singleton : 'a -> 'a node option
val heightGe : 'a node option -> 'b node option -> bool
val bal : 'a node option -> 'a -> 'a node option -> 'a node option
val min0Aux : 'a node -> 'a
val minimum : 'a node option -> 'a option
val minUndefined : 'a node option -> 'a option
val max0Aux : 'a node -> 'a
val maximum : 'a node option -> 'a option
val maxUndefined : 'a node option -> 'a option
val removeMinAuxWithRef : 'a node -> 'a ref -> 'a t
val isEmpty : 'a option -> bool
val stackAllLeft : 'a node option -> 'a node list -> 'a node list
val forEachU : 'a node option -> ('a -> unit) -> unit
val forEach : 'a node option -> ('a -> unit) -> unit
val reduceU : 'a node option -> 'b -> ('b -> 'a -> 'b) -> 'b
val reduce : 'a node option -> 'b -> ('b -> 'a -> 'b) -> 'b
val everyU : 'a node option -> ('a -> bool) -> bool
val every : 'a node option -> ('a -> bool) -> bool
val someU : 'a node option -> ('a -> bool) -> bool
val some : 'a node option -> ('a -> bool) -> bool
val addMinElement : 'a node option -> 'a -> 'a node option
val addMaxElement : 'a node option -> 'a -> 'a node option
val joinShared : 'a node option -> 'a -> 'a node option -> 'a node option
val concatShared : 'a node option -> 'a node option -> 'a node option
val partitionSharedU : 'a node option -> ('a -> bool) -> 'a node option * 'a node option
val partitionShared : 'a node option -> ('a -> bool) -> 'a node option * 'a node option
val lengthNode : 'a node -> int
val size : 'a node option -> int
val toListAux : 'a node option -> 'a list -> 'a list
val toList : 'a node option -> 'a list
val checkInvariantInternal : 'a t -> unit
val fillArray : 'a node -> int -> 'a A.t -> int
type cursor
val cursor : forward:int -> backward:int -> cursor
val forwardSet : cursor -> int -> unit
val forward : cursor -> int
val backwardSet : cursor -> int -> unit
val backward : cursor -> int
val fillArrayWithPartition : 'a node -> cursor -> 'a A.t -> ('a -> bool) -> unit
val fillArrayWithFilter : 'a node -> int -> 'a A.t -> ('a -> bool) -> int
val toArray : 'a node option -> 'a array
val fromSortedArrayRevAux : 'a A.t -> int -> int -> 'a node option
val fromSortedArrayAux : 'a A.t -> int -> int -> 'a node option
val fromSortedArrayUnsafe : 'a A.t -> 'a node option
val keepSharedU : 'a node option -> ('a -> bool) -> 'a t
val keepShared : 'a node option -> ('a -> bool) -> 'a t
val keepCopyU : 'a node option -> ('a -> bool) -> 'a t
val keepCopy : 'a node option -> ('a -> bool) -> 'a t
val partitionCopyU : 'a node option -> ('a -> bool) -> 'a node option * 'a node option
val partitionCopy : 'a node option -> ('a -> bool) -> 'a node option * 'a node option
val has : 'a t -> 'a -> cmp:('a -> 'a -> int) -> bool
val compareAux : 'a node list -> 'a node list -> cmp:('a -> 'a -> int) -> int
val cmp : 'a node option -> 'a node option -> cmp:('a -> 'a -> int) -> int
val eq : 'a node option -> 'a node option -> cmp:('a -> 'a -> int) -> bool
val subset : 'a t -> 'a t -> cmp:('a -> 'a -> int) -> bool
val get : 'a t -> 'a -> cmp:('a -> 'a -> int) -> 'a option
val getUndefined : 'a t -> 'a -> cmp:('a -> 'a -> int) -> 'a option
val getExn : 'a t -> 'a -> cmp:('a -> 'a -> int) -> 'a
val rotateWithLeftChild : 'a node -> 'a node
val rotateWithRightChild : 'a node -> 'a node
val doubleWithLeftChild : 'a node -> 'a node
val doubleWithRightChild : 'a node -> 'a node
val heightUpdateMutate : 'a node -> 'a node
val balMutate : 'a node -> 'a node
val addMutate : cmp:('a -> 'a -> int) -> 'a t -> 'a -> 'a node option
val fromArray : 'a array -> cmp:('a -> 'a -> int) -> 'a node option
val removeMinAuxWithRootMutate : 'a node -> 'a node -> 'a t
================================================
FILE: packages/Belt/src/Belt_internalAVLtree.ml
================================================
[@@@ocaml.text " Almost rewritten by authors of BuckleScript "]
type ('k, 'v) node = {
mutable key : 'k;
mutable value : 'v;
mutable height : int;
mutable left : ('k, 'v) t;
mutable right : ('k, 'v) t;
}
and ('key, 'a) t = ('key, 'a) node Js.null
let node : key:'k -> value:'v -> height:int -> left:('k, 'v) t -> right:('k, 'v) t -> ('k, 'v) node =
fun ~key ~value ~height ~left ~right -> { key; value; height; left; right }
let keySet : ('k, 'v) node -> 'k -> unit = fun o v -> o.key <- v
let key : ('k, 'v) node -> 'k = fun o -> o.key
let valueSet : ('k, 'v) node -> 'v -> unit = fun o v -> o.value <- v
let value : ('k, 'v) node -> 'v = fun o -> o.value
let heightSet : ('k, 'v) node -> int -> unit = fun o v -> o.height <- v
let height : ('k, 'v) node -> int = fun o -> o.height
let leftSet : ('k, 'v) node -> ('k, 'v) t -> unit = fun o v -> o.left <- v
let left : ('k, 'v) node -> ('k, 'v) t = fun o -> o.left
let rightSet : ('k, 'v) node -> ('k, 'v) t -> unit = fun o v -> o.right <- v
let right : ('k, 'v) node -> ('k, 'v) t = fun o -> o.right
type ('k, 'id) cmp = ('k, 'id) Belt_Id.cmp
module A = Belt_Array
module S = Belt_SortArray
let toOpt : 'a Js.null -> 'a option = Js.toOption
let return a = Js.Null.return a
let empty : 'a Js.null = Js.empty
let unsafeCoerce a = Js.Null.getUnsafe a
let treeHeight (n : _ t) = match toOpt n with None -> 0 | Some n -> height n
let rec copy n =
match toOpt n with
| None -> n
| Some n ->
let l, r = (left n, right n) in
return @@ node ~left:(copy l) ~right:(copy r) ~value:(value n) ~key:(key n) ~height:(height n)
let create l x d r =
let hl, hr = (treeHeight l, treeHeight r) in
return @@ node ~left:l ~key:x ~value:d ~right:r ~height:(if hl >= hr then hl + 1 else hr + 1)
let singleton x d = return @@ node ~left:empty ~key:x ~value:d ~right:empty ~height:1
let heightGe l r =
match (toOpt l, toOpt r) with _, None -> true | Some hl, Some hr -> height hl >= height hr | None, Some _ -> false
let updateValue n newValue =
if value n == newValue then n
else node ~left:(left n) ~right:(right n) ~key:(key n) ~value:newValue ~height:(height n)
let bal l x d r =
let hl = match toOpt l with None -> 0 | Some n -> height n in
let hr = match toOpt r with None -> 0 | Some n -> height n in
if hl > hr + 2 then
let ll, lv, ld, lr =
let __ocaml_internal_obj = unsafeCoerce l in
(left __ocaml_internal_obj, key __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
if treeHeight ll >= treeHeight lr then create ll lv ld (create lr x d r)
else
let lrl, lrv, lrd, lrr =
let __ocaml_internal_obj = unsafeCoerce lr in
(left __ocaml_internal_obj, key __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
create (create ll lv ld lrl) lrv lrd (create lrr x d r)
else if hr > hl + 2 then
let rl, rv, rd, rr =
let __ocaml_internal_obj = unsafeCoerce r in
(left __ocaml_internal_obj, key __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
if treeHeight rr >= treeHeight rl then create (create l x d rl) rv rd rr
else
let rll, rlv, rld, rlr =
let __ocaml_internal_obj = unsafeCoerce rl in
(left __ocaml_internal_obj, key __ocaml_internal_obj, value __ocaml_internal_obj, right __ocaml_internal_obj)
in
create (create l x d rll) rlv rld (create rlr rv rd rr)
else return @@ node ~left:l ~key:x ~value:d ~right:r ~height:(if hl >= hr then hl + 1 else hr + 1)
let rec minKey0Aux n = match toOpt (left n) with None -> key n | Some n -> minKey0Aux n
let minKey n = match toOpt n with None -> None | Some n -> Some (minKey0Aux n)
let minKeyUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (minKey0Aux n)
let rec maxKey0Aux n = match toOpt (right n) with None -> key n | Some n -> maxKey0Aux n
let maxKey n = match toOpt n with None -> None | Some n -> Some (maxKey0Aux n)
let maxKeyUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (maxKey0Aux n)
let rec minKV0Aux n = match toOpt (left n) with None -> (key n, value n) | Some n -> minKV0Aux n
let minimum n = match toOpt n with None -> None | Some n -> Some (minKV0Aux n)
let minUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (minKV0Aux n)
let rec maxKV0Aux n = match toOpt (right n) with None -> (key n, value n) | Some n -> maxKV0Aux n
let maximum n = match toOpt n with None -> None | Some n -> Some (maxKV0Aux n)
let maxUndefined n = match toOpt n with None -> Js.undefined | Some n -> Js.Undefined.return (maxKV0Aux n)
let rec removeMinAuxWithRef n kr vr =
let ln, rn, kn, vn = (left n, right n, key n, value n) in
match toOpt ln with
| None ->
kr := kn;
vr := vn;
rn
| Some ln -> bal (removeMinAuxWithRef ln kr vr) kn vn rn
let isEmpty x = match toOpt x with None -> true | Some _ -> false
let rec stackAllLeft v s = match toOpt v with None -> s | Some x -> stackAllLeft (left x) (x :: s)
let rec forEachU n f =
match toOpt n with
| None -> ()
| Some n ->
forEachU (left n) f;
f (key n) (value n);
forEachU (right n) f
let forEach n f = forEachU n (fun a b -> f a b)
let rec mapU n f =
match toOpt n with
| None -> empty
| Some n ->
let newLeft = mapU (left n) f in
let newD = f (value n) in
let newRight = mapU (right n) f in
return @@ node ~left:newLeft ~key:(key n) ~value:newD ~right:newRight ~height:(height n)
let map n f = mapU n (fun a -> f a)
let rec mapWithKeyU n f =
match toOpt n with
| None -> empty
| Some n ->
let key = key n in
let newLeft = mapWithKeyU (left n) f in
let newD = f key (value n) in
let newRight = mapWithKeyU (right n) f in
return @@ node ~left:newLeft ~key ~value:newD ~right:newRight ~height:(height n)
let mapWithKey n f = mapWithKeyU n (fun a b -> f a b)
let rec reduceU m accu f =
match toOpt m with
| None -> accu
| Some n ->
let l, v, d, r = (left n, key n, value n, right n) in
reduceU r (f (reduceU l accu f) v d) f
let reduce m accu f = reduceU m accu (fun a b c -> f a b c)
let rec everyU n p =
match toOpt n with None -> true | Some n -> p (key n) (value n) && everyU (left n) p && everyU (right n) p
let every n p = everyU n (fun a b -> p a b)
let rec someU n p =
match toOpt n with None -> false | Some n -> p (key n) (value n) || someU (left n) p || someU (right n) p
let some n p = someU n (fun a b -> p a b)
let rec addMinElement n k v =
match toOpt n with None -> singleton k v | Some n -> bal (addMinElement (left n) k v) (key n) (value n) (right n)
let rec addMaxElement n k v =
match toOpt n with None -> singleton k v | Some n -> bal (left n) (key n) (value n) (addMaxElement (right n) k v)
let rec join ln v d rn =
match (toOpt ln, toOpt rn) with
| None, _ -> addMinElement rn v d
| _, None -> addMaxElement ln v d
| Some l, Some r ->
let ll, lv, ld, lr, lh = (left l, key l, value l, right l, height l) in
let rl, rv, rd, rr, rh = (left r, key r, value r, right r, height r) in
if lh > rh + 2 then bal ll lv ld (join lr v d rn)
else if rh > lh + 2 then bal (join ln v d rl) rv rd rr
else create ln v d rn
let concat t1 t2 =
match (toOpt t1, toOpt t2) with
| None, _ -> t2
| _, None -> t1
| _, Some t2n ->
let kr, vr = (ref (key t2n), ref (value t2n)) in
let t2r = removeMinAuxWithRef t2n kr vr in
join t1 !kr !vr t2r
let concatOrJoin t1 v d t2 = match d with Some d -> join t1 v d t2 | None -> concat t1 t2
let rec keepSharedU n p =
match toOpt n with
| None -> empty
| Some n ->
let v, d = (key n, value n) in
let newLeft = keepSharedU (left n) p in
let pvd = p v d in
let newRight = keepSharedU (right n) p in
if pvd then join newLeft v d newRight else concat newLeft newRight
let keepShared n p = keepSharedU n (fun a b -> p a b)
let rec keepMapU n p =
match toOpt n with
| None -> empty
| Some n -> (
let v, d = (key n, value n) in
let newLeft = keepMapU (left n) p in
let pvd = p v d in
let newRight = keepMapU (right n) p in
match pvd with None -> concat newLeft newRight | Some d -> join newLeft v d newRight)
let keepMap n p = keepMapU n (fun a b -> p a b)
let rec partitionSharedU n p =
match toOpt n with
| None -> (empty, empty)
| Some n ->
let key, value = (key n, value n) in
let lt, lf = partitionSharedU (left n) p in
let pvd = p key value in
let rt, rf = partitionSharedU (right n) p in
if pvd then (join lt key value rt, concat lf rf) else (concat lt rt, join lf key value rf)
let partitionShared n p = partitionSharedU n (fun a b -> p a b)
let rec lengthNode n =
let l, r = (left n, right n) in
let sizeL = match toOpt l with None -> 0 | Some l -> lengthNode l in
let sizeR = match toOpt r with None -> 0 | Some r -> lengthNode r in
1 + sizeL + sizeR
let size n = match toOpt n with None -> 0 | Some n -> lengthNode n
let rec toListAux n accu =
match toOpt n with
| None -> accu
| Some n ->
let l, r, k, v = (left n, right n, key n, value n) in
toListAux l ((k, v) :: toListAux r accu)
let toList s = toListAux s []
let rec checkInvariantInternal (v : _ t) =
match toOpt v with
| None -> ()
| Some n ->
let l, r = (left n, right n) in
let diff = treeHeight l - treeHeight r in
if Stdlib.not (diff <= 2 && diff >= -2) then
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
else (
checkInvariantInternal l;
checkInvariantInternal r)
let rec fillArrayKey n i arr =
let l, v, r = (left n, key n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArrayKey l i arr in
A.setUnsafe arr next v;
let rnext = next + 1 in
match toOpt r with None -> rnext | Some r -> fillArrayKey r rnext arr
let rec fillArrayValue n i arr =
let l, r = (left n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArrayValue l i arr in
A.setUnsafe arr next (value n);
let rnext = next + 1 in
match toOpt r with None -> rnext | Some r -> fillArrayValue r rnext arr
let rec fillArray n i arr =
let l, v, r = (left n, key n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArray l i arr in
A.setUnsafe arr next (v, value n);
let rnext = next + 1 in
match toOpt r with None -> rnext | Some r -> fillArray r rnext arr
include (
struct
type cursor = { mutable forward : int; mutable backward : int }
let cursor : forward:int -> backward:int -> cursor = fun ~forward ~backward -> { forward; backward }
let forwardSet : cursor -> int -> unit = fun o v -> o.forward <- v
let forward : cursor -> int = fun o -> o.forward
let backwardSet : cursor -> int -> unit = fun o v -> o.backward <- v
let backward : cursor -> int = fun o -> o.backward
end :
sig
type cursor
val cursor : forward:int -> backward:int -> cursor
val forwardSet : cursor -> int -> unit
val forward : cursor -> int
val backwardSet : cursor -> int -> unit
val backward : cursor -> int
end)
let rec fillArrayWithPartition n cursor arr p =
let l, v, r = (left n, key n, right n) in
(match toOpt l with None -> () | Some l -> fillArrayWithPartition l cursor arr p);
(if p v then (
let c = forward cursor in
A.setUnsafe arr c (v, value n);
forwardSet cursor (c + 1))
else
let c = backward cursor in
A.setUnsafe arr c (v, value n);
backwardSet cursor (c - 1));
match toOpt r with None -> () | Some r -> fillArrayWithPartition r cursor arr p
let rec fillArrayWithFilter n i arr p =
let l, v, r = (left n, key n, right n) in
let next = match toOpt l with None -> i | Some l -> fillArrayWithFilter l i arr p in
let rnext =
if p v then (
A.setUnsafe arr next (v, value n);
next + 1)
else next
in
match toOpt r with None -> rnext | Some r -> fillArrayWithFilter r rnext arr p
let toArray n =
match toOpt n with
| None -> [||]
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (key n, value n) in
ignore (fillArray n 0 v : int);
v
let keysToArray n =
match toOpt n with
| None -> [||]
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (key n) in
ignore (fillArrayKey n 0 v : int);
v
let valuesToArray n =
match toOpt n with
| None -> [||]
| Some n ->
let size = lengthNode n in
let v = A.makeUninitializedUnsafe size (value n) in
ignore (fillArrayValue n 0 v : int);
v
let rec fromSortedArrayRevAux arr off len =
match len with
| 0 -> empty
| 1 ->
let k, v = A.getUnsafe arr off in
singleton k v
| 2 ->
let (x0, y0), (x1, y1) =
let open A in
(getUnsafe arr off, getUnsafe arr (off - 1))
in
return @@ node ~left:(singleton x0 y0) ~key:x1 ~value:y1 ~height:2 ~right:empty
| 3 ->
let (x0, y0), (x1, y1), (x2, y2) =
let open A in
(getUnsafe arr off, getUnsafe arr (off - 1), getUnsafe arr (off - 2))
in
return @@ node ~left:(singleton x0 y0) ~right:(singleton x2 y2) ~key:x1 ~value:y1 ~height:2
| _ ->
let nl = len / 2 in
let left = fromSortedArrayRevAux arr off nl in
let midK, midV = A.getUnsafe arr (off - nl) in
let right = fromSortedArrayRevAux arr (off - nl - 1) (len - nl - 1) in
create left midK midV right
let rec fromSortedArrayAux arr off len =
match len with
| 0 -> empty
| 1 ->
let k, v = A.getUnsafe arr off in
singleton k v
| 2 ->
let (x0, y0), (x1, y1) =
let open A in
(getUnsafe arr off, getUnsafe arr (off + 1))
in
return @@ node ~left:(singleton x0 y0) ~key:x1 ~value:y1 ~height:2 ~right:empty
| 3 ->
let (x0, y0), (x1, y1), (x2, y2) =
let open A in
(getUnsafe arr off, getUnsafe arr (off + 1), getUnsafe arr (off + 2))
in
return @@ node ~left:(singleton x0 y0) ~right:(singleton x2 y2) ~key:x1 ~value:y1 ~height:2
| _ ->
let nl = len / 2 in
let left = fromSortedArrayAux arr off nl in
let midK, midV = A.getUnsafe arr (off + nl) in
let right = fromSortedArrayAux arr (off + nl + 1) (len - nl - 1) in
create left midK midV right
let fromSortedArrayUnsafe arr = fromSortedArrayAux arr 0 (A.length arr)
let rec compareAux e1 e2 ~kcmp ~vcmp =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let c = (Belt_Id.getCmpInternal kcmp) (key h1) (key h2) in
if c = 0 then
let cx = vcmp (value h1) (value h2) in
if cx = 0 then compareAux ~kcmp ~vcmp (stackAllLeft (right h1) t1) (stackAllLeft (right h2) t2) else cx
else c
| _, _ -> 0
let rec eqAux e1 e2 ~kcmp ~veq =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
if (Belt_Id.getCmpInternal kcmp) (key h1) (key h2) = 0 && veq (value h1) (value h2) then
eqAux ~kcmp ~veq (stackAllLeft (right h1) t1) (stackAllLeft (right h2) t2)
else false
| _, _ -> true
let cmpU s1 s2 ~kcmp ~vcmp =
let len1, len2 = (size s1, size s2) in
if len1 = len2 then compareAux (stackAllLeft s1 []) (stackAllLeft s2 []) ~kcmp ~vcmp
else if len1 < len2 then -1
else 1
let cmp s1 s2 ~kcmp ~vcmp = cmpU s1 s2 ~kcmp ~vcmp:(fun a b -> vcmp a b)
let eqU s1 s2 ~kcmp ~veq =
let len1, len2 = (size s1, size s2) in
if len1 = len2 then eqAux (stackAllLeft s1 []) (stackAllLeft s2 []) ~kcmp ~veq else false
let eq s1 s2 ~kcmp ~veq = eqU s1 s2 ~kcmp ~veq:(fun a b -> veq a b)
let rec get n x ~cmp =
match toOpt n with
| None -> None
| Some n ->
let v = key n in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then Some (value n) else get ~cmp (if c < 0 then left n else right n) x
let rec getUndefined n x ~cmp =
match toOpt n with
| None -> Js.undefined
| Some n ->
let v = key n in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then Js.Undefined.return (value n) else getUndefined ~cmp (if c < 0 then left n else right n) x
let rec getExn n x ~cmp =
match toOpt n with
| None ->
let error = Printf.sprintf "File %s, line %d" __FILE__ __LINE__ in
Js.Exn.raiseError error
| Some n ->
let v = key n in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then value n else getExn ~cmp (if c < 0 then left n else right n) x
let rec getWithDefault n x def ~cmp =
match toOpt n with
| None -> def
| Some n ->
let v = key n in
let c = (Belt_Id.getCmpInternal cmp) x v in
if c = 0 then value n else getWithDefault ~cmp (if c < 0 then left n else right n) x def
let rec has n x ~cmp =
match toOpt n with
| None -> false
| Some n ->
let v = key n in
let c = (Belt_Id.getCmpInternal cmp) x v in
c = 0 || has ~cmp (if c < 0 then left n else right n) x
let rotateWithLeftChild k2 =
let k1 = unsafeCoerce (left k2) in
leftSet k2 (right k1);
rightSet k1 (return k2);
let hlk2, hrk2 = (treeHeight (left k2), treeHeight (right k2)) in
heightSet k2 (Stdlib.max hlk2 hrk2 + 1);
let hlk1, hk2 = (treeHeight (left k1), height k2) in
heightSet k1 (Stdlib.max hlk1 hk2 + 1);
k1
let rotateWithRightChild k1 =
let k2 = unsafeCoerce (right k1) in
rightSet k1 (left k2);
leftSet k2 (return k1);
let hlk1, hrk1 = (treeHeight (left k1), treeHeight (right k1)) in
heightSet k1 (Stdlib.max hlk1 hrk1 + 1);
let hrk2, hk1 = (treeHeight (right k2), height k1) in
heightSet k2 (Stdlib.max hrk2 hk1 + 1);
k2
let doubleWithLeftChild k3 =
let v = rotateWithRightChild (unsafeCoerce (left k3)) in
leftSet k3 (return v);
rotateWithLeftChild k3
let doubleWithRightChild k2 =
let v = rotateWithLeftChild (unsafeCoerce (right k2)) in
rightSet k2 (return v);
rotateWithRightChild k2
let heightUpdateMutate t =
let hlt, hrt = (treeHeight (left t), treeHeight (right t)) in
heightSet t (Stdlib.max hlt hrt + 1);
t
let balMutate nt =
let l, r = (left nt, right nt) in
let hl, hr = (treeHeight l, treeHeight r) in
if hl > 2 + hr then
let l = unsafeCoerce l in
let ll, lr = (left l, right l) in
if heightGe ll lr then heightUpdateMutate (rotateWithLeftChild nt) else heightUpdateMutate (doubleWithLeftChild nt)
else if hr > 2 + hl then
let r = unsafeCoerce r in
let rl, rr = (left r, right r) in
if heightGe rr rl then heightUpdateMutate (rotateWithRightChild nt)
else heightUpdateMutate (doubleWithRightChild nt)
else (
heightSet nt (max hl hr + 1);
nt)
let rec updateMutate (t : _ t) x data ~cmp =
match toOpt t with
| None -> singleton x data
| Some nt ->
let k = key nt in
let c = (Belt_Id.getCmpInternal cmp) x k in
if c = 0 then (
valueSet nt data;
return nt)
else
let l, r = (left nt, right nt) in
if c < 0 then
let ll = updateMutate ~cmp l x data in
leftSet nt ll
else rightSet nt (updateMutate ~cmp r x data);
return (balMutate nt)
let fromArray (xs : _ array) ~cmp =
let len = A.length xs in
if len = 0 then empty
else
let next = ref (S.strictlySortedLengthU xs (fun (x0, _) (y0, _) -> (Belt_Id.getCmpInternal cmp) x0 y0 < 0)) in
let result =
ref
(if !next >= 0 then fromSortedArrayAux xs 0 !next
else (
next := - !next;
fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
let k, v = A.getUnsafe xs i in
result := updateMutate ~cmp !result k v
done;
!result
let rec removeMinAuxWithRootMutate nt n =
let rn, ln = (right n, left n) in
match toOpt ln with
| None ->
keySet nt (key n);
valueSet nt (value n);
rn
| Some ln ->
leftSet n (removeMinAuxWithRootMutate nt ln);
return (balMutate n)
let rec findFirstByU n p =
match n with
| None -> None
| Some n ->
let left = findFirstByU n.left p in
if left <> None then left
else
let { key = v; value = d } = n in
let pvd = (p v d [@bs]) in
if pvd then Some (v, d)
else
let right = findFirstByU n.right p in
if right <> None then right else None
let findFirstBy n p = findFirstByU n (fun[@bs] a b -> p a b)
================================================
FILE: packages/Belt/src/Belt_internalAVLtree.mli
================================================
type ('k, 'v) node = {
mutable key : 'k;
mutable value : 'v;
mutable height : int;
mutable left : ('k, 'v) t;
mutable right : ('k, 'v) t;
}
and ('key, 'a) t = ('key, 'a) node option
val node : key:'k -> value:'v -> height:int -> left:('k, 'v) t -> right:('k, 'v) t -> ('k, 'v) node
val keySet : ('k, 'v) node -> 'k -> unit
val key : ('k, 'v) node -> 'k
val valueSet : ('k, 'v) node -> 'v -> unit
val value : ('k, 'v) node -> 'v
val heightSet : ('k, 'v) node -> int -> unit
val height : ('k, 'v) node -> int
val leftSet : ('k, 'v) node -> ('k, 'v) t -> unit
val left : ('k, 'v) node -> ('k, 'v) t
val rightSet : ('k, 'v) node -> ('k, 'v) t -> unit
val right : ('k, 'v) node -> ('k, 'v) t
type ('k, 'id) cmp = ('k, 'id) Belt_Id.cmp
module A = Belt_Array
module S = Belt_SortArray
val toOpt : 'a option -> 'a option
val return : 'a -> 'a option
val empty : 'a option
val unsafeCoerce : 'a option -> 'a
val treeHeight : ('a, 'b) t -> int
val copy : ('a, 'b) node option -> ('a, 'b) node option
val create : ('a, 'b) t -> 'a -> 'b -> ('a, 'b) t -> ('a, 'b) node option
val singleton : 'a -> 'b -> ('a, 'b) node option
val heightGe : ('a, 'b) node option -> ('c, 'd) node option -> bool
val updateValue : ('a, 'b) node -> 'b -> ('a, 'b) node
val bal : ('a, 'b) node option -> 'a -> 'b -> ('a, 'b) node option -> ('a, 'b) node option
val minKey0Aux : ('a, 'b) node -> 'a
val minKey : ('a, 'b) node option -> 'a option
val minKeyUndefined : ('a, 'b) node option -> 'a option
val maxKey0Aux : ('a, 'b) node -> 'a
val maxKey : ('a, 'b) node option -> 'a option
val maxKeyUndefined : ('a, 'b) node option -> 'a option
val minKV0Aux : ('a, 'b) node -> 'a * 'b
val minimum : ('a, 'b) node option -> ('a * 'b) option
val minUndefined : ('a, 'b) node option -> ('a * 'b) option
val maxKV0Aux : ('a, 'b) node -> 'a * 'b
val maximum : ('a, 'b) node option -> ('a * 'b) option
val maxUndefined : ('a, 'b) node option -> ('a * 'b) option
val removeMinAuxWithRef : ('a, 'b) node -> 'a ref -> 'b ref -> ('a, 'b) t
val isEmpty : 'a option -> bool
val stackAllLeft : ('a, 'b) node option -> ('a, 'b) node list -> ('a, 'b) node list
val forEachU : ('a, 'b) node option -> ('a -> 'b -> unit) -> unit
val forEach : ('a, 'b) node option -> ('a -> 'b -> unit) -> unit
val mapU : ('a, 'b) node option -> ('b -> 'c) -> ('a, 'c) node option
val map : ('a, 'b) node option -> ('b -> 'c) -> ('a, 'c) node option
val mapWithKeyU : ('a, 'b) node option -> ('a -> 'b -> 'c) -> ('a, 'c) node option
val mapWithKey : ('a, 'b) node option -> ('a -> 'b -> 'c) -> ('a, 'c) node option
val reduceU : ('a, 'b) node option -> 'c -> ('c -> 'a -> 'b -> 'c) -> 'c
val reduce : ('a, 'b) node option -> 'c -> ('c -> 'a -> 'b -> 'c) -> 'c
val everyU : ('a, 'b) node option -> ('a -> 'b -> bool) -> bool
val every : ('a, 'b) node option -> ('a -> 'b -> bool) -> bool
val someU : ('a, 'b) node option -> ('a -> 'b -> bool) -> bool
val some : ('a, 'b) node option -> ('a -> 'b -> bool) -> bool
val addMinElement : ('a, 'b) node option -> 'a -> 'b -> ('a, 'b) node option
val addMaxElement : ('a, 'b) node option -> 'a -> 'b -> ('a, 'b) node option
val join : ('a, 'b) node option -> 'a -> 'b -> ('a, 'b) node option -> ('a, 'b) node option
val concat : ('a, 'b) node option -> ('a, 'b) node option -> ('a, 'b) node option
val concatOrJoin : ('a, 'b) node option -> 'a -> 'b option -> ('a, 'b) node option -> ('a, 'b) node option
val keepSharedU : ('a, 'b) node option -> ('a -> 'b -> bool) -> ('a, 'b) node option
val keepShared : ('a, 'b) node option -> ('a -> 'b -> bool) -> ('a, 'b) node option
val keepMapU : ('a, 'b) node option -> ('a -> 'b -> 'c option) -> ('a, 'c) node option
val keepMap : ('a, 'b) node option -> ('a -> 'b -> 'c option) -> ('a, 'c) node option
val partitionSharedU : ('a, 'b) node option -> ('a -> 'b -> bool) -> ('a, 'b) node option * ('a, 'b) node option
val partitionShared : ('a, 'b) node option -> ('a -> 'b -> bool) -> ('a, 'b) node option * ('a, 'b) node option
val lengthNode : ('a, 'b) node -> int
val size : ('a, 'b) node option -> int
val toListAux : ('a, 'b) node option -> ('a * 'b) list -> ('a * 'b) list
val toList : ('a, 'b) node option -> ('a * 'b) list
val checkInvariantInternal : ('a, 'b) t -> unit
val fillArrayKey : ('a, 'b) node -> int -> 'a A.t -> int
val fillArrayValue : ('a, 'b) node -> int -> 'b A.t -> int
val fillArray : ('weak55, 'a) node -> int -> ('weak55 * 'a) A.t -> int
type cursor
val cursor : forward:int -> backward:int -> cursor
val forwardSet : cursor -> int -> unit
val forward : cursor -> int
val backwardSet : cursor -> int -> unit
val backward : cursor -> int
val fillArrayWithPartition : ('a, 'b) node -> cursor -> ('a * 'b) A.t -> ('a -> bool) -> unit
val fillArrayWithFilter : ('a, 'b) node -> int -> ('a * 'b) A.t -> ('a -> bool) -> int
val toArray : ('a, 'b) node option -> ('a * 'b) array
val keysToArray : ('a, 'b) node option -> 'a array
val valuesToArray : ('a, 'b) node option -> 'b array
val fromSortedArrayRevAux : ('a * 'b) A.t -> int -> int -> ('a, 'b) node option
val fromSortedArrayAux : ('a * 'b) A.t -> int -> int -> ('a, 'b) node option
val fromSortedArrayUnsafe : ('a * 'b) A.t -> ('a, 'b) node option
val compareAux : ('a, 'b) node list -> ('a, 'c) node list -> kcmp:('a -> 'a -> int) -> vcmp:('b -> 'c -> int) -> int
val eqAux : ('a, 'b) node list -> ('a, 'c) node list -> kcmp:('a -> 'a -> int) -> veq:('b -> 'c -> bool) -> bool
val cmpU : ('a, 'b) node option -> ('a, 'c) node option -> kcmp:('a -> 'a -> int) -> vcmp:('b -> 'c -> int) -> int
val cmp : ('a, 'b) node option -> ('a, 'c) node option -> kcmp:('a -> 'a -> int) -> vcmp:('b -> 'c -> int) -> int
val eqU : ('a, 'b) node option -> ('a, 'c) node option -> kcmp:('a -> 'a -> int) -> veq:('b -> 'c -> bool) -> bool
val eq : ('a, 'b) node option -> ('a, 'c) node option -> kcmp:('a -> 'a -> int) -> veq:('b -> 'c -> bool) -> bool
val get : ('a, 'b) node option -> 'a -> cmp:('a -> 'a -> int) -> 'b option
val getUndefined : ('a, 'b) node option -> 'a -> cmp:('a -> 'a -> int) -> 'b option
val getExn : ('a, 'b) node option -> 'a -> cmp:('a -> 'a -> int) -> 'b
val getWithDefault : ('a, 'weak59) node option -> 'a -> 'weak59 -> cmp:('a -> 'a -> int) -> 'weak59
val has : ('a, 'b) node option -> 'a -> cmp:('a -> 'a -> int) -> bool
val rotateWithLeftChild : ('a, 'b) node -> ('a, 'b) node
val rotateWithRightChild : ('a, 'b) node -> ('a, 'b) node
val doubleWithLeftChild : ('a, 'b) node -> ('a, 'b) node
val doubleWithRightChild : ('a, 'b) node -> ('a, 'b) node
val heightUpdateMutate : ('a, 'b) node -> ('a, 'b) node
val balMutate : ('a, 'b) node -> ('a, 'b) node
val updateMutate : ('a, 'b) t -> 'a -> 'b -> cmp:('a -> 'a -> int) -> ('a, 'b) node option
val fromArray : ('a * 'b) array -> cmp:('a -> 'a -> int) -> ('a, 'b) node option
val removeMinAuxWithRootMutate : ('a, 'b) node -> ('a, 'b) node -> ('a, 'b) t
val findFirstByU : ('a, 'b) t -> (('a -> 'b -> bool)[@bs]) -> ('a * 'b) option
val findFirstBy : ('a, 'b) t -> ('a -> 'b -> bool) -> ('a * 'b) option
================================================
FILE: packages/Belt/src/Belt_internalBuckets.ml
================================================
[@@@ocaml.text " Adapted by Authors of BuckleScript 2017 "]
module C = Belt_internalBucketsType
type ('a, 'b) bucket = { mutable key : 'a; mutable value : 'b; mutable next : ('a, 'b) bucket C.opt }
and ('hash, 'eq, 'a, 'b) t = ('hash, 'eq, ('a, 'b) bucket) C.container
let bucket : key:'a -> value:'b -> next:('a, 'b) bucket C.opt -> ('a, 'b) bucket =
fun ~key ~value ~next -> { key; value; next }
let keySet : ('a, 'b) bucket -> 'a -> unit = fun o v -> o.key <- v
let key : ('a, 'b) bucket -> 'a = fun o -> o.key
let valueSet : ('a, 'b) bucket -> 'b -> unit = fun o v -> o.value <- v
let value : ('a, 'b) bucket -> 'b = fun o -> o.value
let nextSet : ('a, 'b) bucket -> ('a, 'b) bucket C.opt -> unit = fun o v -> o.next <- v
let next : ('a, 'b) bucket -> ('a, 'b) bucket C.opt = fun o -> o.next
module A = Belt_Array
let rec copy (x : _ t) : _ t =
C.container ~hash:(C.hash x) ~eq:(C.eq x) ~size:(C.size x) ~buckets:(Stdlib.Array.map copyBucket (C.buckets x))
and copyBucket c =
match C.toOpt c with
| None -> c
| Some c ->
let head = bucket ~key:(key c) ~value:(value c) ~next:C.emptyOpt in
copyAuxCont (next c) head;
C.return head
and copyAuxCont c prec =
match C.toOpt c with
| None -> ()
| Some nc ->
let ncopy = bucket ~key:(key nc) ~value:(value nc) ~next:C.emptyOpt in
nextSet prec (C.return ncopy);
copyAuxCont (next nc) ncopy
let rec bucketLength accu buckets =
match C.toOpt buckets with None -> accu | Some cell -> bucketLength (accu + 1) (next cell)
let rec do_bucket_iter ~f buckets =
match C.toOpt buckets with
| None -> ()
| Some cell ->
f (key cell) (value cell);
do_bucket_iter ~f (next cell)
let forEachU h f =
let d = C.buckets h in
for i = 0 to A.length d - 1 do
do_bucket_iter f (A.getUnsafe d i)
done
let forEach h f = forEachU h (fun a b -> f a b)
let rec do_bucket_fold ~f b accu =
match C.toOpt b with None -> accu | Some cell -> do_bucket_fold ~f (next cell) (f accu (key cell) (value cell))
let reduceU h init f =
let d = C.buckets h in
let accu = ref init in
for i = 0 to A.length d - 1 do
accu := do_bucket_fold ~f (A.getUnsafe d i) !accu
done;
!accu
let reduce h init f = reduceU h init (fun a b c -> f a b c)
let getMaxBucketLength h =
A.reduceU (C.buckets h) 0 (fun m b ->
let len = bucketLength 0 b in
Stdlib.max m len)
let getBucketHistogram h =
let mbl = getMaxBucketLength h in
let histo = A.makeByU (mbl + 1) (fun _ -> 0) in
A.forEachU (C.buckets h) (fun b ->
let l = bucketLength 0 b in
A.setUnsafe histo l (A.getUnsafe histo l + 1));
histo
let logStats h =
let histogram = getBucketHistogram h in
Printf.printf "{\n\tbindings: %d,\n\tbuckets: %d\n\thistogram: %s\n}" (C.size h)
(A.length (C.buckets h))
(A.reduceU histogram "" (fun acc x -> acc ^ string_of_int x))
let rec filterMapInplaceBucket f h i prec cell =
let n = next cell in
match f (key cell) (value cell) with
| None -> (
C.sizeSet h (C.size h - 1);
match C.toOpt n with
| Some nextCell -> filterMapInplaceBucket f h i prec nextCell
| None -> ( match C.toOpt prec with None -> A.setUnsafe (C.buckets h) i prec | Some cell -> nextSet cell n))
| Some data -> (
let bucket = C.return cell in
(match C.toOpt prec with None -> A.setUnsafe (C.buckets h) i bucket | Some c -> nextSet c bucket);
valueSet cell data;
match C.toOpt n with None -> nextSet cell n | Some nextCell -> filterMapInplaceBucket f h i bucket nextCell)
[@@ocaml.doc " iterate the Buckets, in place remove the elements "]
let keepMapInPlaceU h f =
let h_buckets = C.buckets h in
for i = 0 to A.length h_buckets - 1 do
let v = A.getUnsafe h_buckets i in
match C.toOpt v with None -> () | Some v -> filterMapInplaceBucket f h i C.emptyOpt v
done
let keepMapInPlace h f = keepMapInPlaceU h (fun a b -> f a b)
let rec fillArray i arr cell =
A.setUnsafe arr i (key cell, value cell);
match C.toOpt (next cell) with None -> i + 1 | Some v -> fillArray (i + 1) arr v
let toArray h =
let d = C.buckets h in
let current = ref 0 in
let arr = ref None in
for i = 0 to A.length d - 1 do
let cell = A.getUnsafe d i in
match C.toOpt cell with
| None -> ()
| Some cell ->
let arr =
match !arr with
| None ->
let a = A.makeUninitializedUnsafe (C.size h) (key cell, value cell) in
arr := Some a;
a
| Some arr -> arr
in
current := fillArray !current arr cell
done;
match !arr with None -> [||] | Some arr -> arr
let rec fillArrayMap i arr cell f =
A.setUnsafe arr i (f cell);
match C.toOpt (next cell) with None -> i + 1 | Some v -> fillArrayMap (i + 1) arr v f
let linear h f =
let d = C.buckets h in
let current = ref 0 in
let arr = ref None in
for i = 0 to A.length d - 1 do
let cell = A.getUnsafe d i in
match C.toOpt cell with
| None -> ()
| Some cell ->
let arr =
match !arr with
| None ->
let a = A.makeUninitializedUnsafe (C.size h) (f cell) in
arr := Some a;
a
| Some arr -> arr
in
current := fillArrayMap !current arr cell f
done;
match !arr with None -> [||] | Some arr -> arr
let keysToArray h = linear h (fun x -> key x)
let valuesToArray h = linear h (fun x -> value x)
let toArray h = linear h (fun x -> (key x, value x))
================================================
FILE: packages/Belt/src/Belt_internalBuckets.mli
================================================
module C = Belt_internalBucketsType
type ('a, 'b) bucket = { mutable key : 'a; mutable value : 'b; mutable next : ('a, 'b) bucket option }
and ('hash, 'eq, 'a, 'b) t = ('hash, 'eq, ('a, 'b) bucket) C.container
val bucket : key:'a -> value:'b -> next:('a, 'b) bucket C.opt -> ('a, 'b) bucket
val keySet : ('a, 'b) bucket -> 'a -> unit
val key : ('a, 'b) bucket -> 'a
val valueSet : ('a, 'b) bucket -> 'b -> unit
val value : ('a, 'b) bucket -> 'b
val nextSet : ('a, 'b) bucket -> ('a, 'b) bucket C.opt -> unit
val next : ('a, 'b) bucket -> ('a, 'b) bucket C.opt
module A = Belt_Array
val copy : ('a, 'b, 'c, 'd) t -> ('a, 'b, 'c, 'd) t
val copyBucket : ('a, 'b) bucket C.opt -> ('a, 'b) bucket C.opt
val copyAuxCont : ('a, 'b) bucket C.opt -> ('a, 'b) bucket -> unit
val bucketLength : int -> ('a, 'b) bucket C.opt -> int
val do_bucket_iter : f:('a -> 'b -> unit) -> ('a, 'b) bucket C.opt -> unit
val forEachU : ('a, 'b, ('c, 'd) bucket) C.container -> ('c -> 'd -> unit) -> unit
val forEach : ('a, 'b, ('c, 'd) bucket) C.container -> ('c -> 'd -> unit) -> unit
val do_bucket_fold : f:('a -> 'b -> 'c -> 'a) -> ('b, 'c) bucket C.opt -> 'a -> 'a
val reduceU : ('a, 'b, ('c, 'd) bucket) C.container -> 'e -> ('e -> 'c -> 'd -> 'e) -> 'e
val reduce : ('a, 'b, ('c, 'd) bucket) C.container -> 'e -> ('e -> 'c -> 'd -> 'e) -> 'e
val getMaxBucketLength : ('a, 'b, ('c, 'd) bucket) C.container -> int
val getBucketHistogram : ('a, 'b, ('c, 'd) bucket) C.container -> int A.t
val logStats : ('a, 'b, ('c, 'd) bucket) C.container -> unit
val filterMapInplaceBucket :
('a -> 'b -> 'b option) ->
('c, 'd, ('a, 'b) bucket) C.container ->
int ->
('a, 'b) bucket C.opt ->
('a, 'b) bucket ->
unit
val keepMapInPlaceU : ('a, 'b, ('c, 'd) bucket) C.container -> ('c -> 'd -> 'd option) -> unit
val keepMapInPlace : ('a, 'b, ('c, 'd) bucket) C.container -> ('c -> 'd -> 'd option) -> unit
val fillArray : int -> ('a * 'b) A.t -> ('a, 'b) bucket -> int
val toArray : ('a, 'b, ('c, 'd) bucket) C.container -> ('c * 'd) A.t
val fillArrayMap : int -> 'a A.t -> ('b, 'c) bucket -> (('b, 'c) bucket -> 'a) -> int
val linear : ('a, 'b, ('c, 'd) bucket) C.container -> (('c, 'd) bucket -> 'e) -> 'e A.t
val keysToArray : ('a, 'b, ('c, 'd) bucket) C.container -> 'c A.t
val valuesToArray : ('a, 'b, ('c, 'd) bucket) C.container -> 'd A.t
val toArray : ('a, 'b, ('c, 'd) bucket) C.container -> ('c * 'd) A.t
================================================
FILE: packages/Belt/src/Belt_internalBucketsType.ml
================================================
type 'a opt = 'a Js.undefined
include (
struct
type ('hash, 'eq, 'c) container = { mutable size : int; mutable buckets : 'c opt array; hash : 'hash; eq : 'eq }
let container : size:int -> buckets:'c opt array -> hash:'hash -> eq:'eq -> ('hash, 'eq, 'c) container =
fun ~size ~buckets ~hash ~eq -> { size; buckets; hash; eq }
let sizeSet : ('hash, 'eq, 'c) container -> int -> unit = fun o v -> o.size <- v
let size : ('hash, 'eq, 'c) container -> int = fun o -> o.size
let bucketsSet : ('hash, 'eq, 'c) container -> 'c opt array -> unit = fun o v -> o.buckets <- v
let buckets : ('hash, 'eq, 'c) container -> 'c opt array = fun o -> o.buckets
let hash : ('hash, 'eq, 'c) container -> 'hash = fun o -> o.hash
let eq : ('hash, 'eq, 'c) container -> 'eq = fun o -> o.eq
end :
sig
type ('hash, 'eq, 'c) container
val container : size:int -> buckets:'c opt array -> hash:'hash -> eq:'eq -> ('hash, 'eq, 'c) container
val sizeSet : ('hash, 'eq, 'c) container -> int -> unit
val size : ('hash, 'eq, 'c) container -> int
val bucketsSet : ('hash, 'eq, 'c) container -> 'c opt array -> unit
val buckets : ('hash, 'eq, 'c) container -> 'c opt array
val hash : ('hash, 'eq, 'c) container -> 'hash
val eq : ('hash, 'eq, 'c) container -> 'eq
end)
module A = Belt_Array
let toOpt = Js.undefinedToOption
let return = Js.Undefined.return
let emptyOpt = Js.undefined
let rec power_2_above x n = if x >= n then x else if x * 2 < x then x else power_2_above (x * 2) n
let make ~hash ~eq ~hintSize =
let s = power_2_above 16 hintSize in
container ~size:0 ~buckets:(A.makeUninitialized s) ~hash ~eq
let clear h =
sizeSet h 0;
let h_buckets = buckets h in
let len = A.length h_buckets in
for i = 0 to len - 1 do
A.setUnsafe h_buckets i emptyOpt
done
let isEmpty h = size h = 0
================================================
FILE: packages/Belt/src/Belt_internalBucketsType.mli
================================================
type 'a opt = 'a option
type ('hash, 'eq, 'c) container
val container : size:int -> buckets:'c opt array -> hash:'hash -> eq:'eq -> ('hash, 'eq, 'c) container
val sizeSet : ('hash, 'eq, 'c) container -> int -> unit
val size : ('hash, 'eq, 'c) container -> int
val bucketsSet : ('hash, 'eq, 'c) container -> 'c opt array -> unit
val buckets : ('hash, 'eq, 'c) container -> 'c opt array
val hash : ('hash, 'eq, 'c) container -> 'hash
val eq : ('hash, 'eq, 'c) container -> 'eq
module A = Belt_Array
val toOpt : 'a option -> 'a option
val return : 'a -> 'a option
val emptyOpt : 'a option
val power_2_above : int -> int -> int
val make : hash:'a -> eq:'b -> hintSize:int -> ('a, 'b, 'c) container
val clear : ('a, 'b, 'c) container -> unit
val isEmpty : ('a, 'b, 'c) container -> bool
================================================
FILE: packages/Belt/src/Belt_internalMapInt.ml
================================================
type key = int
module N = Belt_internalAVLtree
module A = Belt_Array
module S = Belt_SortArray
type 'a t = (key, 'a) N.t
let rec add t (x : key) (data : _) =
match N.toOpt t with
| None -> N.singleton x data
| Some n ->
let k = N.key n in
if x = k then N.return (N.updateValue n data)
else
let v = N.value n in
if x < k then N.bal (add (N.left n) x data) k v (N.right n) else N.bal (N.left n) k v (add (N.right n) x data)
let rec get n (x : key) =
match N.toOpt n with
| None -> None
| Some n ->
let v = N.key n in
if x = v then Some (N.value n) else get (if x < v then N.left n else N.right n) x
let rec getUndefined n (x : key) =
match N.toOpt n with
| None -> Js.undefined
| Some n ->
let v = N.key n in
if x = v then Js.Undefined.return (N.value n) else getUndefined (if x < v then N.left n else N.right n) x
let rec getExn n (x : key) =
match N.toOpt n with
| None -> Js.Exn.raiseError "File \"../others/internal_map.cppo.ml\", line 51, characters 14-20"
| Some n ->
let v = N.key n in
if x = v then N.value n else getExn (if x < v then N.left n else N.right n) x
let rec getWithDefault n (x : key) def =
match N.toOpt n with
| None -> def
| Some n ->
let v = N.key n in
if x = v then N.value n else getWithDefault (if x < v then N.left n else N.right n) x def
let rec has n (x : key) =
match N.toOpt n with
| None -> false
| Some n ->
let v = N.key n in
x = v || has (if x < v then N.left n else N.right n) x
let rec remove n (x : key) =
match N.toOpt n with
| None -> n
| Some n ->
let l, v, r =
let open N in
(left n, key n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r
else if x < v then
let open N in
bal (remove l x) v (value n) r
else
let open N in
bal l v (value n) (remove r x)
let rec splitAux (x : key) (n : _ N.node) : _ t * _ option * _ t =
let l, v, d, r =
let open N in
(left n, key n, value n, right n)
in
if x = v then (l, Some d, r)
else if x < v then
match N.toOpt l with
| None ->
let open N in
(empty, None, return n)
| Some l ->
let ll, pres, rl = splitAux x l in
(ll, pres, N.join rl v d r)
else
match N.toOpt r with
| None ->
let open N in
(return n, None, empty)
| Some r ->
let lr, pres, rr = splitAux x r in
(N.join l v d lr, pres, rr)
let rec split (x : key) n =
match N.toOpt n with
| None ->
let open N in
(empty, None, empty)
| Some n -> splitAux x n
let rec mergeU s1 s2 f =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, None -> N.empty
| Some n, _
when let open N in
height n >= match N.toOpt s2 with None -> 0 | Some n -> N.height n ->
let l1, v1, d1, r1 =
let open N in
(left n, key n, value n, right n)
in
let l2, d2, r2 = split v1 s2 in
N.concatOrJoin (mergeU l1 l2 f) v1 (f v1 (Some d1) d2) (mergeU r1 r2 f)
| _, Some n ->
let l2, v2, d2, r2 =
let open N in
(left n, key n, value n, right n)
in
let l1, d1, r1 = split v2 s1 in
N.concatOrJoin (mergeU l1 l2 f) v2 (f v2 d1 (Some d2)) (mergeU r1 r2 f)
| _ -> assert false
let merge s1 s2 f = mergeU s1 s2 (fun a b c -> f a b c)
let rec compareAux e1 e2 vcmp =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let c = Stdlib.compare (N.key h1 : key) (N.key h2) in
if c = 0 then
let cx = vcmp (N.value h1) (N.value h2) in
if cx = 0 then compareAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2) vcmp else cx
else c
| _, _ -> 0
let cmpU s1 s2 cmp =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then compareAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) cmp else if len1 < len2 then -1 else 1
let cmp s1 s2 f = cmpU s1 s2 (fun a b -> f a b)
let rec eqAux e1 e2 eq =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
if (N.key h1 : key) = N.key h2 && eq (N.value h1) (N.value h2) then
eqAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2) eq
else false
| _, _ -> true
let eqU s1 s2 eq =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then eqAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) eq else false
let eq s1 s2 f = eqU s1 s2 (fun a b -> f a b)
let rec addMutate (t : _ t) x data : _ t =
match N.toOpt t with
| None -> N.singleton x data
| Some nt ->
let k = N.key nt in
if x = k then (
N.keySet nt x;
N.valueSet nt data;
N.return nt)
else
let l, r = (N.left nt, N.right nt) in
if x < k then
let ll = addMutate l x data in
N.leftSet nt ll
else N.rightSet nt (addMutate r x data);
N.return (N.balMutate nt)
let fromArray (xs : (key * _) array) =
let len = A.length xs in
if len = 0 then N.empty
else
let next = ref (S.strictlySortedLengthU xs (fun (x0, _) (y0, _) -> x0 < y0)) in
let result =
ref
(if !next >= 0 then N.fromSortedArrayAux xs 0 !next
else (
next := - !next;
N.fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
let k, v = A.getUnsafe xs i in
result := addMutate !result k v
done;
!result
================================================
FILE: packages/Belt/src/Belt_internalMapString.ml
================================================
type key = string
module N = Belt_internalAVLtree
module A = Belt_Array
module S = Belt_SortArray
type 'a t = (key, 'a) N.t
let rec add t (x : key) (data : _) =
match N.toOpt t with
| None -> N.singleton x data
| Some n ->
let k = N.key n in
if x = k then N.return (N.updateValue n data)
else
let v = N.value n in
if x < k then N.bal (add (N.left n) x data) k v (N.right n) else N.bal (N.left n) k v (add (N.right n) x data)
let rec get n (x : key) =
match N.toOpt n with
| None -> None
| Some n ->
let v = N.key n in
if x = v then Some (N.value n) else get (if x < v then N.left n else N.right n) x
let rec getUndefined n (x : key) =
match N.toOpt n with
| None -> Js.undefined
| Some n ->
let v = N.key n in
if x = v then Js.Undefined.return (N.value n) else getUndefined (if x < v then N.left n else N.right n) x
let rec getExn n (x : key) =
match N.toOpt n with
| None -> Js.Exn.raiseError "File \"../others/internal_map.cppo.ml\", line 51, characters 14-20"
| Some n ->
let v = N.key n in
if x = v then N.value n else getExn (if x < v then N.left n else N.right n) x
let rec getWithDefault n (x : key) def =
match N.toOpt n with
| None -> def
| Some n ->
let v = N.key n in
if x = v then N.value n else getWithDefault (if x < v then N.left n else N.right n) x def
let rec has n (x : key) =
match N.toOpt n with
| None -> false
| Some n ->
let v = N.key n in
x = v || has (if x < v then N.left n else N.right n) x
let rec remove n (x : key) =
match N.toOpt n with
| None -> n
| Some n ->
let l, v, r =
let open N in
(left n, key n, right n)
in
if x = v then
match (N.toOpt l, N.toOpt r) with
| None, _ -> r
| _, None -> l
| _, Some rn ->
let kr, vr = (ref (N.key rn), ref (N.value rn)) in
let r = N.removeMinAuxWithRef rn kr vr in
N.bal l !kr !vr r
else if x < v then
let open N in
bal (remove l x) v (value n) r
else
let open N in
bal l v (value n) (remove r x)
let rec splitAux (x : key) (n : _ N.node) : _ t * _ option * _ t =
let l, v, d, r =
let open N in
(left n, key n, value n, right n)
in
if x = v then (l, Some d, r)
else if x < v then
match N.toOpt l with
| None ->
let open N in
(empty, None, return n)
| Some l ->
let ll, pres, rl = splitAux x l in
(ll, pres, N.join rl v d r)
else
match N.toOpt r with
| None ->
let open N in
(return n, None, empty)
| Some r ->
let lr, pres, rr = splitAux x r in
(N.join l v d lr, pres, rr)
let rec split (x : key) n =
match N.toOpt n with
| None ->
let open N in
(empty, None, empty)
| Some n -> splitAux x n
let rec mergeU s1 s2 f =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, None -> N.empty
| Some n, _
when let open N in
height n >= match N.toOpt s2 with None -> 0 | Some n -> N.height n ->
let l1, v1, d1, r1 =
let open N in
(left n, key n, value n, right n)
in
let l2, d2, r2 = split v1 s2 in
N.concatOrJoin (mergeU l1 l2 f) v1 (f v1 (Some d1) d2) (mergeU r1 r2 f)
| _, Some n ->
let l2, v2, d2, r2 =
let open N in
(left n, key n, value n, right n)
in
let l1, d1, r1 = split v2 s1 in
N.concatOrJoin (mergeU l1 l2 f) v2 (f v2 d1 (Some d2)) (mergeU r1 r2 f)
| _ -> assert false
let merge s1 s2 f = mergeU s1 s2 (fun a b c -> f a b c)
let rec compareAux e1 e2 vcmp =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let c = Stdlib.compare (N.key h1 : key) (N.key h2) in
if c = 0 then
let cx = vcmp (N.value h1) (N.value h2) in
if cx = 0 then compareAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2) vcmp else cx
else c
| _, _ -> 0
let cmpU s1 s2 cmp =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then compareAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) cmp else if len1 < len2 then -1 else 1
let cmp s1 s2 f = cmpU s1 s2 (fun a b -> f a b)
let rec eqAux e1 e2 eq =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
if (N.key h1 : key) = N.key h2 && eq (N.value h1) (N.value h2) then
eqAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2) eq
else false
| _, _ -> true
let eqU s1 s2 eq =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then eqAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) eq else false
let eq s1 s2 f = eqU s1 s2 (fun a b -> f a b)
let rec addMutate (t : _ t) x data : _ t =
match N.toOpt t with
| None -> N.singleton x data
| Some nt ->
let k = N.key nt in
if x = k then (
N.keySet nt x;
N.valueSet nt data;
N.return nt)
else
let l, r = (N.left nt, N.right nt) in
if x < k then
let ll = addMutate l x data in
N.leftSet nt ll
else N.rightSet nt (addMutate r x data);
N.return (N.balMutate nt)
let fromArray (xs : (key * _) array) =
let len = A.length xs in
if len = 0 then N.empty
else
let next = ref (S.strictlySortedLengthU xs (fun (x0, _) (y0, _) -> x0 < y0)) in
let result =
ref
(if !next >= 0 then N.fromSortedArrayAux xs 0 !next
else (
next := - !next;
N.fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
let k, v = A.getUnsafe xs i in
result := addMutate !result k v
done;
!result
================================================
FILE: packages/Belt/src/Belt_internalSetBuckets.ml
================================================
module C = Belt_internalBucketsType
include (
struct
type 'a bucket = { mutable key : 'a; mutable next : 'a bucket C.opt }
and ('hash, 'eq, 'a) t = ('hash, 'eq, 'a bucket) C.container
let bucket : key:'a -> next:'a bucket C.opt -> 'a bucket = fun ~key ~next -> { key; next }
let keySet : 'a bucket -> 'a -> unit = fun o v -> o.key <- v
let key : 'a bucket -> 'a = fun o -> o.key
let nextSet : 'a bucket -> 'a bucket C.opt -> unit = fun o v -> o.next <- v
let next : 'a bucket -> 'a bucket C.opt = fun o -> o.next
end :
sig
type 'a bucket
and ('hash, 'eq, 'a) t = ('hash, 'eq, 'a bucket) C.container
val bucket : key:'a -> next:'a bucket C.opt -> 'a bucket
val keySet : 'a bucket -> 'a -> unit
val key : 'a bucket -> 'a
val nextSet : 'a bucket -> 'a bucket C.opt -> unit
val next : 'a bucket -> 'a bucket C.opt
end)
module A = Belt_Array
let rec copy (x : _ t) : _ t =
C.container ~hash:(C.hash x) ~eq:(C.eq x) ~size:(C.size x) ~buckets:(Stdlib.Array.map copyBucket (C.buckets x))
and copyBucket c =
match C.toOpt c with
| None -> c
| Some c ->
let head = bucket ~key:(key c) ~next:C.emptyOpt in
copyAuxCont (next c) head;
C.return head
and copyAuxCont c prec =
match C.toOpt c with
| None -> ()
| Some nc ->
let ncopy = bucket ~key:(key nc) ~next:C.emptyOpt in
nextSet prec (C.return ncopy);
copyAuxCont (next nc) ncopy
let rec bucketLength accu buckets =
match C.toOpt buckets with None -> accu | Some cell -> bucketLength (accu + 1) (next cell)
let rec doBucketIter ~f buckets =
match C.toOpt buckets with
| None -> ()
| Some cell ->
f (key cell);
doBucketIter ~f (next cell)
let forEachU h f =
let d = C.buckets h in
for i = 0 to A.length d - 1 do
doBucketIter f (A.getUnsafe d i)
done
let forEach h f = forEachU h (fun a -> f a)
let rec fillArray i arr cell =
A.setUnsafe arr i (key cell);
match C.toOpt (next cell) with None -> i + 1 | Some v -> fillArray (i + 1) arr v
let toArray h =
let d = C.buckets h in
let current = ref 0 in
let arr = ref None in
for i = 0 to A.length d - 1 do
let cell = A.getUnsafe d i in
match C.toOpt cell with
| None -> ()
| Some cell ->
let arr =
match !arr with
| None ->
let a = A.makeUninitializedUnsafe (C.size h) (key cell) in
arr := Some a;
a
| Some arr -> arr
in
current := fillArray !current arr cell
done;
match !arr with None -> [||] | Some arr -> arr
let rec doBucketFold ~f b accu =
match C.toOpt b with None -> accu | Some cell -> doBucketFold ~f (next cell) (f accu (key cell))
let reduceU h init f =
let d = C.buckets h in
let accu = ref init in
for i = 0 to A.length d - 1 do
accu := doBucketFold ~f (A.getUnsafe d i) !accu
done;
!accu
let reduce h init f = reduceU h init (fun a b -> f a b)
let getMaxBucketLength h =
A.reduceU (C.buckets h) 0 (fun m b ->
let len = bucketLength 0 b in
Stdlib.max m len)
let getBucketHistogram h =
let mbl = getMaxBucketLength h in
let histo = A.makeByU (mbl + 1) (fun _ -> 0) in
A.forEachU (C.buckets h) (fun b ->
let l = bucketLength 0 b in
A.setUnsafe histo l (A.getUnsafe histo l + 1));
histo
let logStats h =
let histogram = getBucketHistogram h in
Printf.printf "{\n\tbindings: %d,\n\tbuckets: %d\n\thistogram: %s\n}" (C.size h)
(A.length (C.buckets h))
(A.reduceU histogram "" (fun acc x -> acc ^ string_of_int x))
================================================
FILE: packages/Belt/src/Belt_internalSetBuckets.mli
================================================
module C = Belt_internalBucketsType
type 'a bucket
and ('hash, 'eq, 'a) t = ('hash, 'eq, 'a bucket) C.container
val bucket : key:'a -> next:'a bucket C.opt -> 'a bucket
val keySet : 'a bucket -> 'a -> unit
val key : 'a bucket -> 'a
val nextSet : 'a bucket -> 'a bucket C.opt -> unit
val next : 'a bucket -> 'a bucket C.opt
module A = Belt_Array
val copy : ('a, 'b, 'c) t -> ('a, 'b, 'c) t
val copyBucket : 'a bucket C.opt -> 'a bucket C.opt
val copyAuxCont : 'a bucket C.opt -> 'a bucket -> unit
val bucketLength : int -> 'a bucket C.opt -> int
val doBucketIter : f:('a -> unit) -> 'a bucket C.opt -> unit
val forEachU : ('a, 'b, 'c bucket) C.container -> ('c -> unit) -> unit
val forEach : ('a, 'b, 'c bucket) C.container -> ('c -> unit) -> unit
val fillArray : int -> 'a A.t -> 'a bucket -> int
val toArray : ('a, 'b, 'c bucket) C.container -> 'c A.t
val doBucketFold : f:('a -> 'b -> 'a) -> 'b bucket C.opt -> 'a -> 'a
val reduceU : ('a, 'b, 'c bucket) C.container -> 'd -> ('d -> 'c -> 'd) -> 'd
val reduce : ('a, 'b, 'c bucket) C.container -> 'd -> ('d -> 'c -> 'd) -> 'd
val getMaxBucketLength : ('a, 'b, 'c bucket) C.container -> int
val getBucketHistogram : ('a, 'b, 'c bucket) C.container -> int A.t
val logStats : ('a, 'b, 'c bucket) C.container -> unit
================================================
FILE: packages/Belt/src/Belt_internalSetInt.ml
================================================
type value = int
module S = Belt_SortArrayInt
module N = Belt_internalAVLset
module A = Belt_Array
type t = value N.t
let rec has (t : t) (x : value) =
match N.toOpt t with
| None -> false
| Some n ->
let v = N.value n in
x = v || has (if x < v then N.left n else N.right n) x
let rec compareAux e1 e2 =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let (k1 : value), k2 = (N.value h1, N.value h2) in
if k1 = k2 then compareAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2)
else if k1 < k2 then -1
else 1
| _, _ -> 0
let cmp s1 s2 =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then compareAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) else if len1 < len2 then -1 else 1
let eq (s1 : t) s2 = cmp s1 s2 = 0
let rec subset (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ -> true
| _, None -> false
| Some t1, Some t2 ->
let l1, v1, r1 =
let open N in
(left t1, value t1, right t1)
in
let l2, v2, r2 =
let open N in
(left t2, value t2, right t2)
in
if v1 = v2 then subset l1 l2 && subset r1 r2
else if v1 < v2 then
subset
(let open N in
create l1 v1 empty)
l2
&& subset r1 s2
else
subset
(let open N in
create empty v1 r1)
r2
&& subset l1 s2
let rec get (n : t) (x : value) =
match N.toOpt n with
| None -> None
| Some t ->
let v = N.value t in
if x = v then Some v else get (if x < v then N.left t else N.right t) x
let rec getUndefined (n : t) (x : value) =
match N.toOpt n with
| None -> Js.undefined
| Some t ->
let v = N.value t in
if x = v then Js.Undefined.return v else getUndefined (if x < v then N.left t else N.right t) x
let rec getExn (n : t) (x : value) =
match N.toOpt n with
| None -> Js.Exn.raiseError "File \"../others/internal_set.cppo.ml\", line 90, characters 14-20"
| Some t ->
let v = N.value t in
if x = v then v else getExn (if x < v then N.left t else N.right t) x
let rec addMutate t (x : value) =
match N.toOpt t with
| None -> N.singleton x
| Some nt ->
let k = N.value nt in
if x = k then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then N.leftSet nt (addMutate l x) else N.rightSet nt (addMutate r x);
N.return (N.balMutate nt)
let fromArray (xs : value array) =
let len = A.length xs in
if len = 0 then N.empty
else
let next = ref (S.strictlySortedLength xs) in
let result =
ref
(if !next >= 0 then N.fromSortedArrayAux xs 0 !next
else (
next := - !next;
N.fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
result := addMutate !result (A.getUnsafe xs i)
done;
!result
================================================
FILE: packages/Belt/src/Belt_internalSetString.ml
================================================
type value = string
module S = Belt_SortArrayString
module N = Belt_internalAVLset
module A = Belt_Array
type t = value N.t
let rec has (t : t) (x : value) =
match N.toOpt t with
| None -> false
| Some n ->
let v = N.value n in
x = v || has (if x < v then N.left n else N.right n) x
let rec compareAux e1 e2 =
match (e1, e2) with
| h1 :: t1, h2 :: t2 ->
let (k1 : value), k2 = (N.value h1, N.value h2) in
if k1 = k2 then compareAux (N.stackAllLeft (N.right h1) t1) (N.stackAllLeft (N.right h2) t2)
else if k1 < k2 then -1
else 1
| _, _ -> 0
let cmp s1 s2 =
let len1, len2 = (N.size s1, N.size s2) in
if len1 = len2 then compareAux (N.stackAllLeft s1 []) (N.stackAllLeft s2 []) else if len1 < len2 then -1 else 1
let eq (s1 : t) s2 = cmp s1 s2 = 0
let rec subset (s1 : t) (s2 : t) =
match
let open N in
(toOpt s1, toOpt s2)
with
| None, _ -> true
| _, None -> false
| Some t1, Some t2 ->
let l1, v1, r1 =
let open N in
(left t1, value t1, right t1)
in
let l2, v2, r2 =
let open N in
(left t2, value t2, right t2)
in
if v1 = v2 then subset l1 l2 && subset r1 r2
else if v1 < v2 then
subset
(let open N in
create l1 v1 empty)
l2
&& subset r1 s2
else
subset
(let open N in
create empty v1 r1)
r2
&& subset l1 s2
let rec get (n : t) (x : value) =
match N.toOpt n with
| None -> None
| Some t ->
let v = N.value t in
if x = v then Some v else get (if x < v then N.left t else N.right t) x
let rec getUndefined (n : t) (x : value) =
match N.toOpt n with
| None -> Js.undefined
| Some t ->
let v = N.value t in
if x = v then Js.Undefined.return v else getUndefined (if x < v then N.left t else N.right t) x
let rec getExn (n : t) (x : value) =
match N.toOpt n with
| None -> Js.Exn.raiseError "File \"../others/internal_set.cppo.ml\", line 90, characters 14-20"
| Some t ->
let v = N.value t in
if x = v then v else getExn (if x < v then N.left t else N.right t) x
let rec addMutate t (x : value) =
match N.toOpt t with
| None -> N.singleton x
| Some nt ->
let k = N.value nt in
if x = k then t
else
let l, r =
let open N in
(left nt, right nt)
in
if x < k then N.leftSet nt (addMutate l x) else N.rightSet nt (addMutate r x);
N.return (N.balMutate nt)
let fromArray (xs : value array) =
let len = A.length xs in
if len = 0 then N.empty
else
let next = ref (S.strictlySortedLength xs) in
let result =
ref
(if !next >= 0 then N.fromSortedArrayAux xs 0 !next
else (
next := - !next;
N.fromSortedArrayRevAux xs (!next - 1) !next))
in
for i = !next to len - 1 do
result := addMutate !result (A.getUnsafe xs i)
done;
!result
================================================
FILE: packages/Belt/src/caml_hash.ml
================================================
[@@@ocaml.text " "]
let ( << ) = Nativeint.shift_left [@@ocaml.text " "]
let ( >>> ) = Nativeint.shift_right_logical
let ( |~ ) = Nativeint.logor
let ( ^ ) = Nativeint.logxor
external ( *~ ) : nativeint -> nativeint -> nativeint = "caml_int32_mul"
external ( +~ ) : nativeint -> nativeint -> nativeint = "caml_int32_add"
let rotl32 (x : nativeint) n = x << n |~ (x >>> 32 - n)
let caml_hash_mix_int h d =
let d = ref d in
d := !d *~ 3432918353n;
d := rotl32 !d 15;
d := !d *~ 461845907n;
let h = ref (h ^ !d) in
h := rotl32 !h 13;
!h +~ (!h << 2) +~ 3864292196n
let caml_hash_final_mix h =
let h = ref (h ^ (h >>> 16)) in
h := !h *~ 2246822507n;
h := !h ^ (!h >>> 13);
h := !h *~ 3266489909n;
!h ^ (!h >>> 16)
let caml_hash_mix_string h s =
let len = String.length s in
let block = (len / 4) - 1 in
let hash = ref h in
for i = 0 to block do
let j = 4 * i in
let w =
Char.code s.[j] lor (Char.code s.[j + 1] lsl 8) lor (Char.code s.[j + 2] lsl 16) lor (Char.code s.[j + 3] lsl 24)
in
hash := caml_hash_mix_int !hash (Nativeint.of_int w)
done;
let modulo = len land 3 in
(if modulo <> 0 then
let w =
if modulo = 3 then (Char.code s.[len - 1] lsl 16) lor (Char.code s.[len - 2] lsl 8) lor Char.code s.[len - 3]
else if modulo = 2 then (Char.code s.[len - 1] lsl 8) lor Char.code s.[len - 2]
else Char.code s.[len - 1]
in
hash := caml_hash_mix_int !hash (Nativeint.of_int w));
hash := !hash ^ Nativeint.of_int len;
!hash
================================================
FILE: packages/Belt/src/dune
================================================
(library
(name belt)
(foreign_stubs
(language c)
(names stubs))
(flags :standard -w -A)
(public_name server-reason-react.belt)
(libraries server-reason-react.js))
================================================
FILE: packages/Belt/src/stubs.c
================================================
#include
#include
#include
CAMLprim value belt_makemutablelist(value a, value l) {
CAMLparam2(a, l);
CAMLlocal1(box);
box = caml_alloc_small(2, 0);
Field(box, 0) = a;
Field(box, 1) = l;
CAMLreturn(box);
}
================================================
FILE: packages/Belt/test/Test_Belt_Array.ml
================================================
let suites =
[
( "Array",
[
test "get and set bounds" (fun () ->
let values = [| 1; 2 |] in
assert_option Alcotest.int (Some 1) (Belt.Array.get values 0);
assert_option Alcotest.int (Some 2) (Belt.Array.get values 1);
assert_option Alcotest.int None (Belt.Array.get values 2);
assert_option Alcotest.int None (Belt.Array.get values 3);
assert_option Alcotest.int None (Belt.Array.get values (-1));
assert_option Alcotest.int (Some 1) (Js.Undefined.toOption (Belt.Array.getUndefined values 0));
assert_option Alcotest.int None (Js.Undefined.toOption (Belt.Array.getUndefined values 2));
assert_raises_any (fun () -> ignore (Belt.Array.getExn [| 0; 1 |] (-1)));
assert_raises_any (fun () -> ignore (Belt.Array.getExn [| 0; 1 |] 2));
assert_int 0 (Belt.Array.getExn [| 0; 1 |] 0);
assert_int 1 (Belt.Array.getExn [| 0; 1 |] 1);
assert_raises_any (fun () -> Belt.Array.setExn [| 0; 1 |] (-1) 0);
assert_raises_any (fun () -> Belt.Array.setExn [| 0; 1 |] 2 0);
assert_bool false (Belt.Array.set [| 1; 2 |] 2 0);
let left = [| 1; 2 |] in
assert_bool true (Belt.Array.set left 0 0);
assert_int 0 (Belt.Array.getExn left 0);
let right = [| 1; 2 |] in
assert_bool true (Belt.Array.set right 1 0);
assert_int 0 (Belt.Array.getExn right 1);
let exn_left = [| 1; 2 |] in
Belt.Array.setExn exn_left 0 0;
assert_int 0 (Belt.Array.getExn exn_left 0);
let exn_right = [| 1; 2 |] in
Belt.Array.setExn exn_right 1 0;
assert_int 0 (Belt.Array.getExn exn_right 1));
test "shuffle preserves contents" (fun () ->
let original = Belt.Array.makeBy 3000 (fun i -> i) in
let shuffled = Belt.Array.shuffle original in
assert_bool false (original = shuffled);
assert_int (Belt.Array.reduce original 0 ( + )) (Belt.Array.reduce shuffled 0 ( + )));
test "range helpers" (fun () ->
assert_array Alcotest.int [| 0; 1; 2; 3 |] (Belt.Array.range 0 3);
assert_array Alcotest.int [||] (Belt.Array.range 3 0);
assert_array Alcotest.int [| 3 |] (Belt.Array.range 3 3);
assert_array Alcotest.int [| 0; 3; 6; 9 |] (Belt.Array.rangeBy 0 10 ~step:3);
assert_array Alcotest.int [| 0; 3; 6; 9; 12 |] (Belt.Array.rangeBy 0 12 ~step:3);
assert_array Alcotest.int [||] (Belt.Array.rangeBy 33 0 ~step:1);
assert_array Alcotest.int [||] (Belt.Array.rangeBy 33 0 ~step:(-1));
assert_array Alcotest.int [||] (Belt.Array.rangeBy 3 12 ~step:(-1));
assert_array Alcotest.int [||] (Belt.Array.rangeBy 3 3 ~step:0);
assert_array Alcotest.int [| 3 |] (Belt.Array.rangeBy 3 3 ~step:1));
test "reductions" (fun () ->
assert_int 100 (Belt.Array.reduceReverse [||] 100 ( - ));
assert_int 97 (Belt.Array.reduceReverse [| 1; 2 |] 100 ( - ));
assert_int 90 (Belt.Array.reduceReverse [| 1; 2; 3; 4 |] 100 ( - ));
assert_int 16 (Belt.Array.reduceWithIndex [| 1; 2; 3; 4 |] 0 (fun acc value index -> acc + value + index));
assert_int 6
(Belt.Array.reduceReverse2 [| 1; 2; 3 |] [| 1; 2 |] 0 (fun acc left right -> acc + left + right)));
test "construction copy and conversions" (fun () ->
let make_matrix width height value =
let outer = Belt.Array.makeUninitializedUnsafe width [||] in
for x = 0 to width - 1 do
let inner = Belt.Array.makeUninitializedUnsafe height value in
for y = 0 to height - 1 do
Belt.Array.setUnsafe inner y value
done;
Belt.Array.setUnsafe outer x inner
done;
outer
in
let undefined_array : int Js.undefined array = Belt.Array.makeUninitialized 1 in
assert_int 1 (Belt.Array.length undefined_array);
assert_array (Alcotest.option Alcotest.int) [| None |]
(Belt.Array.mapU undefined_array Js.Undefined.toOption);
assert_array Alcotest.int [||] (Belt.Array.makeBy 0 (fun _ -> 1));
assert_array Alcotest.int [| 0; 1; 2 |] (Belt.Array.makeBy 3 (fun i -> i));
assert_array (Alcotest.array Alcotest.int)
[| [| 1; 1; 1; 1 |]; [| 1; 1; 1; 1 |]; [| 1; 1; 1; 1 |] |]
(make_matrix 3 4 1);
assert_array (Alcotest.array Alcotest.int) [| [||]; [||]; [||] |] (make_matrix 3 0 0);
assert_array (Alcotest.array Alcotest.int) [||] (make_matrix 0 3 1);
assert_array (Alcotest.array Alcotest.int) [| [| 1 |] |] (make_matrix 1 1 1);
assert_array Alcotest.int [||] (Belt.Array.copy [||]);
assert_array Alcotest.int [||] (Belt.Array.map [||] succ);
assert_array Alcotest.int [||] (Belt.Array.mapWithIndex [||] ( + ));
assert_array Alcotest.int [| 1; 3; 5 |] (Belt.Array.mapWithIndex [| 1; 2; 3 |] ( + ));
assert_list Alcotest.int [] (Belt.List.fromArray [||]);
assert_list Alcotest.int [ 1 ] (Belt.List.fromArray [| 1 |]);
assert_list Alcotest.int [ 1; 2; 3 ] (Belt.List.fromArray [| 1; 2; 3 |]);
assert_array Alcotest.int [| 2; 3; 4 |] (Belt.Array.map [| 1; 2; 3 |] succ);
assert_array Alcotest.int [||] (Belt.List.toArray []);
assert_array Alcotest.int [| 1 |] (Belt.List.toArray [ 1 ]);
assert_array Alcotest.int [| 1; 2 |] (Belt.List.toArray [ 1; 2 ]);
assert_array Alcotest.int [| 1; 2; 3 |] (Belt.List.toArray [ 1; 2; 3 ]));
test "callback once regressions" (fun () ->
let seen_make = ref [] in
let made =
Belt.Array.makeByU 3 (fun i ->
seen_make := i :: !seen_make;
i * 2)
in
assert_array Alcotest.int [| 0; 2; 4 |] made;
assert_list Alcotest.int [ 0; 1; 2 ] (List.rev !seen_make);
let seen_map = ref [] in
let mapped =
Belt.Array.mapU [| 1; 2; 3 |] (fun value ->
seen_map := value :: !seen_map;
value + 10)
in
assert_array Alcotest.int [| 11; 12; 13 |] mapped;
assert_list Alcotest.int [ 1; 2; 3 ] (List.rev !seen_map);
let seen_map_with_index = ref [] in
let mapped_with_index =
Belt.Array.mapWithIndexU [| "a"; "b"; "c" |] (fun index value ->
seen_map_with_index := (index, value) :: !seen_map_with_index;
index)
in
assert_array Alcotest.int [| 0; 1; 2 |] mapped_with_index;
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (0, "a"); (1, "b"); (2, "c") ]
(List.rev !seen_map_with_index);
let seen_zip = ref [] in
let zipped =
Belt.Array.zipByU [| 1; 2; 3 |] [| 10; 20 |] (fun left right ->
seen_zip := (left, right) :: !seen_zip;
left + right)
in
assert_array Alcotest.int [| 11; 22 |] zipped;
assert_list (Alcotest.pair Alcotest.int Alcotest.int) [ (1, 10); (2, 20) ] (List.rev !seen_zip));
test "keep keepMap and partition" (fun () ->
let values = Belt.Array.makeBy 10 (fun i -> i) in
assert_array Alcotest.int [| 0; 2; 4; 6; 8 |] (Belt.Array.keep values (fun value -> value mod 2 = 0));
assert_array Alcotest.int [| 0; 3; 6; 9 |] (Belt.Array.keep values (fun value -> value mod 3 = 0));
assert_array Alcotest.int [| 1; 3; 5; 7; 9 |]
(Belt.Array.keepMap values (fun value -> if value mod 2 = 0 then Some (value + 1) else None));
let evens, odds = Belt.Array.partition [| 1; 2; 3; 4; 5 |] (fun value -> value mod 2 = 0) in
assert_array Alcotest.int [| 2; 4 |] evens;
assert_array Alcotest.int [| 1; 3; 5 |] odds;
let twos, rest = Belt.Array.partition [| 1; 2; 3; 4; 5 |] (fun value -> value = 2) in
assert_array Alcotest.int [| 2 |] twos;
assert_array Alcotest.int [| 1; 3; 4; 5 |] rest;
let yes, no = Belt.Array.partition [||] (fun _ -> false) in
assert_array Alcotest.int [||] yes;
assert_array Alcotest.int [||] no);
test "slice" (fun () ->
let values = [| 1; 2; 3; 4; 5 |] in
assert_array Alcotest.int [| 1; 2 |] (Belt.Array.slice values ~offset:0 ~len:2);
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.slice values ~offset:0 ~len:5);
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.slice values ~offset:0 ~len:15);
assert_array Alcotest.int [||] (Belt.Array.slice values ~offset:5 ~len:1);
assert_array Alcotest.int [| 5 |] (Belt.Array.slice values ~offset:4 ~len:1);
assert_array Alcotest.int [| 5 |] (Belt.Array.slice values ~offset:(-1) ~len:1);
assert_array Alcotest.int [| 5 |] (Belt.Array.slice values ~offset:(-1) ~len:2);
assert_array Alcotest.int [| 4 |] (Belt.Array.slice values ~offset:(-2) ~len:1);
assert_array Alcotest.int [| 4; 5 |] (Belt.Array.slice values ~offset:(-2) ~len:2);
assert_array Alcotest.int [| 4; 5 |] (Belt.Array.slice values ~offset:(-2) ~len:3);
assert_array Alcotest.int [| 1; 2; 3 |] (Belt.Array.slice values ~offset:(-10) ~len:3);
assert_array Alcotest.int [| 1; 2; 3; 4 |] (Belt.Array.slice values ~offset:(-10) ~len:4);
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.slice values ~offset:(-10) ~len:5);
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.slice values ~offset:(-10) ~len:6);
assert_array Alcotest.int [||] (Belt.Array.slice values ~offset:0 ~len:0);
assert_array Alcotest.int [||] (Belt.Array.slice values ~offset:0 ~len:(-1)));
test "sliceToEnd" (fun () ->
let values = [| 1; 2; 3; 4; 5 |] in
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.sliceToEnd values 0);
assert_array Alcotest.int [||] (Belt.Array.sliceToEnd values 5);
assert_array Alcotest.int [| 5 |] (Belt.Array.sliceToEnd values 4);
assert_array Alcotest.int [| 5 |] (Belt.Array.sliceToEnd values (-1));
assert_array Alcotest.int [| 4; 5 |] (Belt.Array.sliceToEnd values (-2));
assert_array Alcotest.int [| 1; 2; 3; 4; 5 |] (Belt.Array.sliceToEnd values (-10));
assert_array Alcotest.int [||] (Belt.Array.sliceToEnd values 6));
test "fill" (fun () ->
let values = Belt.Array.makeBy 10 (fun value -> value) in
Belt.Array.fill values ~offset:0 ~len:3 0;
assert_array Alcotest.int [| 0; 0; 0; 3; 4; 5; 6; 7; 8; 9 |] values;
Belt.Array.fill values ~offset:2 ~len:8 1;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 1; 1; 1 |] values;
Belt.Array.fill values ~offset:8 ~len:1 9;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 1; 9; 1 |] values;
Belt.Array.fill values ~offset:8 ~len:2 9;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 1; 9; 9 |] values;
Belt.Array.fill values ~offset:8 ~len:3 12;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 1; 12; 12 |] values;
Belt.Array.fill values ~offset:(-2) ~len:3 11;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 1; 11; 11 |] values;
Belt.Array.fill values ~offset:(-3) ~len:3 10;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 10; 10; 10 |] values;
Belt.Array.fill values ~offset:(-3) ~len:1 7;
assert_array Alcotest.int [| 0; 0; 1; 1; 1; 1; 1; 7; 10; 10 |] values;
Belt.Array.fill values ~offset:(-13) ~len:1 7;
assert_array Alcotest.int [| 7; 0; 1; 1; 1; 1; 1; 7; 10; 10 |] values;
Belt.Array.fill values ~offset:(-13) ~len:12 7;
assert_array Alcotest.int (Array.make 10 7) values;
Belt.Array.fill values ~offset:0 ~len:(-1) 2;
assert_array Alcotest.int (Array.make 10 7) values;
let small = [| 1; 2; 3 |] in
Belt.Array.fill small ~offset:0 ~len:0 0;
assert_array Alcotest.int [| 1; 2; 3 |] small;
Belt.Array.fill small ~offset:4 ~len:1 0;
assert_array Alcotest.int [| 1; 2; 3 |] small);
test "blit" (fun () ->
let src = Belt.Array.makeBy 10 (fun value -> value) in
let dst = Belt.Array.make 10 3 in
Belt.Array.blit ~src ~srcOffset:1 ~dst ~dstOffset:2 ~len:5;
assert_array Alcotest.int [| 3; 3; 1; 2; 3; 4; 5; 3; 3; 3 |] dst;
Belt.Array.blit ~src ~srcOffset:(-1) ~dst ~dstOffset:2 ~len:5;
assert_array Alcotest.int [| 3; 3; 9; 2; 3; 4; 5; 3; 3; 3 |] dst;
Belt.Array.blit ~src ~srcOffset:(-1) ~dst ~dstOffset:(-2) ~len:5;
assert_array Alcotest.int [| 3; 3; 9; 2; 3; 4; 5; 3; 9; 3 |] dst;
Belt.Array.blit ~src ~srcOffset:(-2) ~dst ~dstOffset:(-2) ~len:2;
assert_array Alcotest.int [| 3; 3; 9; 2; 3; 4; 5; 3; 8; 9 |] dst;
Belt.Array.blit ~src ~srcOffset:(-11) ~dst ~dstOffset:(-11) ~len:100;
assert_array Alcotest.int src dst;
Belt.Array.blit ~src ~srcOffset:(-11) ~dst ~dstOffset:(-11) ~len:2;
assert_array Alcotest.int src dst;
let overlap = Belt.Array.makeBy 10 (fun value -> value) in
Belt.Array.blit ~src:overlap ~srcOffset:(-1) ~dst:overlap ~dstOffset:1 ~len:2;
assert_array Alcotest.int [| 0; 9; 2; 3; 4; 5; 6; 7; 8; 9 |] overlap;
Belt.Array.blit ~src:overlap ~srcOffset:(-2) ~dst:overlap ~dstOffset:1 ~len:2;
assert_array Alcotest.int [| 0; 8; 9; 3; 4; 5; 6; 7; 8; 9 |] overlap;
Belt.Array.blit ~src:overlap ~srcOffset:(-5) ~dst:overlap ~dstOffset:4 ~len:3;
assert_array Alcotest.int [| 0; 8; 9; 3; 5; 6; 7; 7; 8; 9 |] overlap;
Belt.Array.blit ~src:overlap ~srcOffset:4 ~dst:overlap ~dstOffset:5 ~len:3;
assert_array Alcotest.int [| 0; 8; 9; 3; 5; 5; 6; 7; 8; 9 |] overlap;
assert_array Alcotest.int [||] (Belt.Array.make 0 3);
assert_array Alcotest.int [||] (Belt.Array.make (-1) 3);
let untouched = [| 0; 1; 2 |] in
Belt.Array.blit ~src:untouched ~srcOffset:4 ~dst:untouched ~dstOffset:1 ~len:1;
assert_array Alcotest.int [| 0; 1; 2 |] untouched);
test "zip and unzip" (fun () ->
assert_array
(Alcotest.pair Alcotest.int Alcotest.int)
[| (1, 2); (2, 3); (3, 4) |]
(Belt.Array.zip [| 1; 2; 3 |] [| 2; 3; 4; 1 |]);
assert_array
(Alcotest.pair Alcotest.int Alcotest.int)
[| (2, 1); (3, 2); (4, 3) |]
(Belt.Array.zip [| 2; 3; 4; 1 |] [| 1; 2; 3 |]);
assert_array Alcotest.int [| 1; 1; 1 |] (Belt.Array.zipBy [| 2; 3; 4; 1 |] [| 1; 2; 3 |] ( - ));
assert_array Alcotest.int [| -1; -1; -1 |] (Belt.Array.zipBy [| 1; 2; 3 |] [| 2; 3; 4; 1 |] ( - ));
let left, right = Belt.Array.unzip [| (1, 2); (2, 3); (3, 4) |] in
assert_array Alcotest.int [| 1; 2; 3 |] left;
assert_array Alcotest.int [| 2; 3; 4 |] right);
test "iteration predicates and reverse" (fun () ->
let sum = ref 0 in
Belt.Array.forEach [| 0; 1; 2; 3; 4 |] (fun value -> sum := !sum + value);
assert_int 10 !sum;
assert_bool false (Belt.Array.every [| 0; 1; 2; 3; 4 |] (fun value -> value > 2));
assert_bool true (Belt.Array.some [| 1; 3; 7; 8 |] (fun value -> value mod 2 = 0));
assert_bool false (Belt.Array.some [| 1; 3; 7 |] (fun value -> value mod 2 = 0));
assert_bool false (Belt.Array.eq [| 0; 1 |] [| 1 |] ( = ));
let indexed_sum = ref 0 in
Belt.Array.forEachWithIndex [| 1; 1; 1 |] (fun index value -> indexed_sum := !indexed_sum + index + value);
assert_int 6 !indexed_sum;
List.iter
(fun values ->
let reversed = Belt.Array.reverse values in
let in_place = Belt.Array.copy values in
Belt.Array.reverseInPlace in_place;
assert_array Alcotest.int reversed in_place)
[ [||]; [| 1 |]; [| 1; 2 |]; [| 1; 2; 3 |]; [| 1; 2; 3; 4 |] ]);
test "every2 and some2" (fun () ->
assert_bool true (Belt.Array.every2 [||] [| 1 |] (fun left right -> left > right));
assert_bool true (Belt.Array.every2 [| 2; 3 |] [| 1 |] (fun left right -> left > right));
assert_bool true (Belt.Array.every2 [| 2 |] [| 1 |] (fun left right -> left > right));
assert_bool false (Belt.Array.every2 [| 2; 3 |] [| 1; 4 |] (fun left right -> left > right));
assert_bool true (Belt.Array.every2 [| 2; 3 |] [| 1; 0 |] (fun left right -> left > right));
assert_bool false (Belt.Array.some2 [||] [| 1 |] (fun left right -> left > right));
assert_bool true (Belt.Array.some2 [| 2; 3 |] [| 1 |] (fun left right -> left > right));
assert_bool true (Belt.Array.some2 [| 2; 3 |] [| 1; 4 |] (fun left right -> left > right));
assert_bool false (Belt.Array.some2 [| 0; 3 |] [| 1; 4 |] (fun left right -> left > right));
assert_bool true (Belt.Array.some2 [| 0; 3 |] [| 3; 2 |] (fun left right -> left > right)));
test "concat and concatMany" (fun () ->
let left = [| 3; 4 |] in
let right = [| 1; 2 |] in
let empty_left = Belt.Array.concat [||] right in
let empty_right = Belt.Array.concat left [||] in
assert_array Alcotest.int [| 1; 2; 3 |] (Belt.Array.concat [||] [| 1; 2; 3 |]);
assert_array Alcotest.int [||] (Belt.Array.concat [||] [||]);
assert_array Alcotest.int [| 3; 2; 1; 2; 3 |] (Belt.Array.concat [| 3; 2 |] [| 1; 2; 3 |]);
assert_array Alcotest.int [| 3; 2; 1; 2; 3 |] (Belt.Array.concatMany [| [| 3; 2 |]; [| 1; 2; 3 |] |]);
assert_array Alcotest.int [| 3; 2; 1; 2; 3; 0 |]
(Belt.Array.concatMany [| [| 3; 2 |]; [| 1; 2; 3 |]; [||]; [| 0 |] |]);
assert_array Alcotest.int [| 3; 2; 1; 2; 3; 0 |]
(Belt.Array.concatMany [| [||]; [| 3; 2 |]; [| 1; 2; 3 |]; [||]; [| 0 |] |]);
assert_array Alcotest.int [||] (Belt.Array.concatMany [| [||]; [||] |]);
assert_array Alcotest.int [| 1; 2 |] empty_left;
assert_array Alcotest.int [| 3; 4 |] empty_right;
Belt.Array.setExn empty_left 0 99;
Belt.Array.setExn empty_right 0 42;
assert_array Alcotest.int [| 1; 2 |] right;
assert_array Alcotest.int [| 3; 4 |] left);
test "cmp and find helpers" (fun () ->
assert_bool true (Belt.Array.cmp [| 1; 2; 3 |] [| 0; 1; 2; 3 |] compare < 0);
assert_bool true (Belt.Array.cmp [| 0; 1; 2; 3 |] [| 1; 2; 3 |] compare > 0);
assert_bool true (Belt.Array.cmp [| 1; 2; 3 |] [| 0; 1; 2 |] compare > 0);
assert_bool true (Belt.Array.cmp [| 1; 2; 3 |] [| 1; 2; 3 |] compare = 0);
assert_bool true (Belt.Array.cmp [| 1; 2; 4 |] [| 1; 2; 3 |] compare > 0);
assert_option Alcotest.int (Some 2) (Belt.Array.getBy [| 1; 2; 3 |] (fun value -> value > 1));
assert_option Alcotest.int None (Belt.Array.getBy [| 1; 2; 3 |] (fun value -> value > 3));
assert_option Alcotest.int (Some 1) (Belt.Array.getIndexBy [| 1; 2; 3 |] (fun value -> value > 1));
assert_option Alcotest.int None (Belt.Array.getIndexBy [| 1; 2; 3 |] (fun value -> value > 3)));
test "unsafe allocation helpers" (fun () ->
let values = Belt.Array.makeUninitializedUnsafe 5 "lola" in
assert_string "lola" (Belt.Array.getUnsafe values 0);
assert_string "lola" (Belt.Array.getUnsafe values 1);
assert_string "lola" (Belt.Array.getUnsafe values 2);
assert_string "lola" (Belt.Array.getUnsafe values 3);
assert_string "lola" (Belt.Array.getUnsafe values 4);
let truncated = Belt.Array.truncateToLengthUnsafe values 3 in
assert_string "lola" (Belt.Array.getUnsafe truncated 0));
test "push unsupported in native" (fun () ->
let values = [||] in
assert_bool true
(match (Belt.Array.push [@alert "-not_implemented"]) values 3 with
| `Do_not_use_Array_push_in_native -> true);
assert_bool true
(match (Belt.Array.push [@alert "-not_implemented"]) values 2 with
| `Do_not_use_Array_push_in_native -> true);
assert_bool true
(match (Belt.Array.push [@alert "-not_implemented"]) values 1 with
| `Do_not_use_Array_push_in_native -> true);
assert_array Alcotest.int [||] values);
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Float.ml
================================================
let suites =
[
( "Float",
[
test "fromInt" (fun () ->
assert_float 1. (Belt.Float.fromInt 1);
assert_float (-1.) (Belt.Float.fromInt (-1)));
test "toInt" (fun () ->
assert_int 1 (Belt.Float.toInt 1.);
assert_int 1 (Belt.Float.toInt 1.3);
assert_int 1 (Belt.Float.toInt 1.7);
assert_int (-1) (Belt.Float.toInt (-1.));
assert_int (-1) (Belt.Float.toInt (-1.5));
assert_int (-1) (Belt.Float.toInt (-1.7)));
test "fromString" (fun () ->
assert_option float (Some 1.) (Belt.Float.fromString "1");
assert_option float (Some (-1.)) (Belt.Float.fromString "-1");
assert_option float (Some 1.7) (Belt.Float.fromString "1.7");
assert_option float (Some (-1.)) (Belt.Float.fromString "-1.0");
assert_option float (Some (-1.5)) (Belt.Float.fromString "-1.5");
assert_option float (Some (-1.7)) (Belt.Float.fromString "-1.7");
assert_option float None (Belt.Float.fromString "not a float"));
test "toString and operators" (fun () ->
assert_string "1" (Belt.Float.toString 1.);
assert_string "-1" (Belt.Float.toString (-1.));
assert_string "-1.5" (Belt.Float.toString (-1.5));
let open Belt.Float in
assert_float 5. (2. + 3.);
assert_float (-1.) (2. - 3.);
assert_float 6. (2. * 3.);
assert_float 1.5 (3. / 2.));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_HashMap.ml
================================================
let collision_hashmap () =
let values = Belt.HashMap.make ~hintSize:16 ~id:(module CollidingIntHash) in
Belt.HashMap.set values 0 "zero";
Belt.HashMap.set values 11 "eleven";
Belt.HashMap.set values 23 "twenty-three";
values
let assert_hashmap_state expected values =
assert_int (Array.length expected) (Belt.HashMap.size values);
let count = ref 0 in
Belt.HashMap.forEach values (fun _ _ -> incr count);
assert_int (Array.length expected) !count;
assert_array_unordered (Alcotest.pair Alcotest.int Alcotest.string) expected (Belt.HashMap.toArray values);
assert_array_unordered Alcotest.int (Array.map fst expected) (Belt.HashMap.keysToArray values);
assert_array_unordered Alcotest.string (Array.map snd expected) (Belt.HashMap.valuesToArray values)
let suites =
[
( "HashMap",
[
test "mergeMany overwrites duplicates" (fun () ->
let values = Belt.HashMap.make ~id:(module IntHash) ~hintSize:30 in
Belt.HashMap.mergeMany values [| (1, 1); (2, 3); (3, 3); (2, 2) |];
assert_option Alcotest.int (Some 2) (Belt.HashMap.get values 2);
assert_int 3 (Belt.HashMap.size values));
test "fromArray dedupes overlapping keys" (fun () ->
let input =
Array.map (fun value -> (value, value)) (Array.append (shuffled_range 30 100) (shuffled_range 40 120))
in
let values = Belt.HashMap.fromArray input ~id:(module IntHash) in
assert_int 91 (Belt.HashMap.size values);
let keys = Belt.HashMap.keysToArray values in
Belt.SortArray.Int.stableSortInPlace keys;
assert_array Alcotest.int (inclusive_range 30 120) keys);
test "remove stress" (fun () ->
let input =
Array.map (fun value -> (value, value)) (Array.append (shuffled_range 0 10_000) (shuffled_range 0 100))
in
let values = Belt.HashMap.make ~id:(module IntHash) ~hintSize:40 in
Belt.HashMap.mergeMany values input;
assert_int 10_001 (Belt.HashMap.size values);
for key = 0 to 1000 do
Belt.HashMap.remove values key
done;
assert_int 9_000 (Belt.HashMap.size values);
for key = 0 to 2000 do
Belt.HashMap.remove values key
done;
assert_int 8_000 (Belt.HashMap.size values);
for key = 2001 to 10_000 do
assert_bool true (Belt.HashMap.has values key)
done);
test "keepMapInPlace middle remove" (fun () ->
let values = collision_hashmap () in
Belt.HashMap.keepMapInPlace values (fun key value -> if key <> 11 then Some (value ^ "!") else None);
assert_option Alcotest.string (Some "zero!") (Belt.HashMap.get values 0);
assert_option Alcotest.string None (Belt.HashMap.get values 11);
assert_option Alcotest.string (Some "twenty-three!") (Belt.HashMap.get values 23);
assert_hashmap_state [| (0, "zero!"); (23, "twenty-three!") |] values);
test "keepMapInPlace head remove" (fun () ->
let values = collision_hashmap () in
Belt.HashMap.keepMapInPlace values (fun key value -> if key <> 23 then Some value else None);
assert_option Alcotest.string None (Belt.HashMap.get values 23);
assert_hashmap_state [| (0, "zero"); (11, "eleven") |] values);
test "keepMapInPlace tail remove" (fun () ->
let values = collision_hashmap () in
Belt.HashMap.keepMapInPlace values (fun key value -> if key <> 0 then Some value else None);
assert_option Alcotest.string None (Belt.HashMap.get values 0);
assert_hashmap_state [| (11, "eleven"); (23, "twenty-three") |] values);
test "keepMapInPlace consecutive remove" (fun () ->
let values = collision_hashmap () in
Belt.HashMap.keepMapInPlace values (fun key value -> if key = 0 then Some (value ^ "!") else None);
assert_hashmap_state [| (0, "zero!") |] values);
test "keepMapInPlace remove all" (fun () ->
let values = collision_hashmap () in
Belt.HashMap.keepMapInPlace values (fun _ _ -> None);
assert_hashmap_state [||] values);
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_HashMap_Int.ml
================================================
let suites =
[
( "HashMap.Int",
[
test "smoke" (fun () ->
let values = Belt.HashMap.Int.make ~hintSize:4 in
Belt.HashMap.Int.set values 1 "one";
assert_int 1 (Belt.HashMap.Int.size values);
assert_array_unordered
(Alcotest.pair Alcotest.int Alcotest.string)
[| (1, "one") |]
(Belt.HashMap.Int.toArray values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_HashMap_String.ml
================================================
let suites =
[
( "HashMap.String",
[
test "smoke" (fun () ->
let values = Belt.HashMap.String.make ~hintSize:4 in
Belt.HashMap.String.set values "one" 1;
assert_int 1 (Belt.HashMap.String.size values);
assert_array_unordered
(Alcotest.pair Alcotest.string Alcotest.int)
[| ("one", 1) |]
(Belt.HashMap.String.toArray values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_HashSet_Int.ml
================================================
let suites =
[
( "HashSet.Int",
[
test "dedupe reduce and forEach" (fun () ->
let values = Belt.HashSet.Int.fromArray (Array.append (shuffled_range 30 100) (shuffled_range 40 120)) in
assert_int 91 (Belt.HashSet.Int.size values);
let sorted = Belt.Set.Int.toArray (Belt.Set.Int.fromArray (Belt.HashSet.Int.toArray values)) in
assert_array Alcotest.int (inclusive_range 30 120) sorted;
let expected_sum = arithmetic_sum 30 120 in
assert_int expected_sum (Belt.HashSet.Int.reduce values 0 (fun acc value -> acc + value));
let total = ref 0 in
Belt.HashSet.Int.forEach values (fun value -> total := !total + value);
assert_int expected_sum !total);
test "size stress" (fun () ->
let values = Belt.HashSet.Int.make ~hintSize:40 in
Belt.HashSet.Int.mergeMany values (Array.append (shuffled_range 0 10_000) (shuffled_range 0 100));
assert_int 10_001 (Belt.HashSet.Int.size values);
for key = 0 to 1000 do
Belt.HashSet.Int.remove values key
done;
assert_int 9_000 (Belt.HashSet.Int.size values);
for key = 0 to 2000 do
Belt.HashSet.Int.remove values key
done;
assert_int 8_000 (Belt.HashSet.Int.size values));
test "copy independence" (fun () ->
let original = Belt.HashSet.Int.fromArray (shuffled_range 0 10_000) in
let copy = Belt.HashSet.Int.copy original in
assert_array_unordered Alcotest.int (Belt.HashSet.Int.toArray original) (Belt.HashSet.Int.toArray copy);
for key = 0 to 2000 do
Belt.HashSet.Int.remove copy key
done;
for key = 0 to 1000 do
Belt.HashSet.Int.remove original key
done;
let left = Array.append (inclusive_range 0 1000) (Belt.HashSet.Int.toArray original) in
let right = Array.append (inclusive_range 0 2000) (Belt.HashSet.Int.toArray copy) in
Belt.SortArray.Int.stableSortInPlace left;
Belt.SortArray.Int.stableSortInPlace right;
assert_array Alcotest.int left right);
test "bucket histogram sanity" (fun () ->
let values = Belt.HashSet.Int.fromArray (shuffled_range 0 10_000) in
assert_bool true (Array.length (Belt.HashSet.Int.getBucketHistogram values) <= 10));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_HashSet_String.ml
================================================
let suites =
[
( "HashSet.String",
[
test "smoke" (fun () ->
let values = Belt.HashSet.String.make ~hintSize:4 in
Belt.HashSet.String.add values "a";
assert_int 1 (Belt.HashSet.String.size values);
assert_array_unordered Alcotest.string [| "a" |] (Belt.HashSet.String.toArray values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Int.ml
================================================
let suites =
[
( "Int",
[
test "toFloat" (fun () ->
assert_float 1. (Belt.Int.toFloat 1);
assert_float (-1.) (Belt.Int.toFloat (-1)));
test "fromFloat" (fun () ->
assert_int 1 (Belt.Int.fromFloat 1.);
assert_int 1 (Belt.Int.fromFloat 1.3);
assert_int 1 (Belt.Int.fromFloat 1.7);
assert_int (-1) (Belt.Int.fromFloat (-1.));
assert_int (-1) (Belt.Int.fromFloat (-1.5));
assert_int (-1) (Belt.Int.fromFloat (-1.7)));
test "fromString" (fun () ->
assert_option Alcotest.int (Some 1) (Belt.Int.fromString "1");
assert_option Alcotest.int (Some (-1)) (Belt.Int.fromString "-1");
assert_option Alcotest.int (Some 1) (Belt.Int.fromString "1.7");
assert_option Alcotest.int (Some (-1)) (Belt.Int.fromString "-1.0");
assert_option Alcotest.int (Some (-1)) (Belt.Int.fromString "-1.5");
assert_option Alcotest.int (Some (-1)) (Belt.Int.fromString "-1.7");
assert_option Alcotest.int None (Belt.Int.fromString "not an int"));
test "toString and operators" (fun () ->
assert_string "1" (Belt.Int.toString 1);
assert_string "-1" (Belt.Int.toString (-1));
let open Belt.Int in
assert_int 5 (2 + 3);
assert_int (-1) (2 - 3);
assert_int 6 (2 * 3);
assert_int 0 (2 / 3));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_List.ml
================================================
let sum values =
let total = ref 0 in
Belt.List.forEach values (fun value -> total := !total + value);
!total
let sum2 left right =
let total = ref 0 in
Belt.List.forEach2 left right (fun x y -> total := !total + x + y);
!total
let mod2 value = value mod 2 = 0
let even_index _ index = index mod 2 = 0
let id value = value
let add left right = left + right
let succ value = value + 1
let length_10_id = Belt.List.makeBy 10 id
let length_8_id = Belt.List.makeBy 8 id
let suites =
[
( "List",
[
test "makeBy get and map" (fun () ->
let values = Belt.List.makeBy 5 (fun index -> index * index) in
for index = 0 to 4 do
assert_int (index * index) (Belt.List.getExn values index)
done;
assert_list Alcotest.int [ 1; 2; 5; 10; 17 ] (Belt.List.map values (fun value -> value + 1));
assert_option Alcotest.int (Some 4) (Belt.List.getBy [ 1; 4; 3; 2 ] (fun value -> value mod 2 = 0));
assert_option Alcotest.int None (Belt.List.getBy [ 1; 4; 3; 2 ] (fun value -> value mod 5 = 0)));
test "flatten" (fun () ->
assert_list Alcotest.int [ 1; 2; 3; 0; 1; 2; 3 ]
(Belt.List.flatten [ [ 1 ]; [ 2 ]; [ 3 ]; []; Belt.List.makeBy 4 (fun index -> index) ]);
assert_list Alcotest.int [] (Belt.List.flatten []);
assert_list Alcotest.int [ 2; 1; 2 ] (Belt.List.flatten [ []; []; [ 2 ]; [ 1 ]; [ 2 ]; [] ]));
test "concatMany" (fun () ->
assert_list Alcotest.int [ 1; 2; 3; 0; 1; 2; 3 ]
(Belt.List.concatMany [| [ 1 ]; [ 2 ]; [ 3 ]; []; Belt.List.makeBy 4 (fun index -> index) |]);
assert_list Alcotest.int [] (Belt.List.concatMany [||]);
assert_list Alcotest.int [ 2; 1; 2 ] (Belt.List.concatMany [| []; []; [ 2 ]; [ 1 ]; [ 2 ]; [] |]);
assert_list Alcotest.int [ 2; 3; 1; 2 ] (Belt.List.concatMany [| []; []; [ 2; 3 ]; [ 1 ]; [ 2 ]; [] |]);
assert_list Alcotest.int [ 1; 2; 3 ] (Belt.List.concatMany [| [ 1; 2; 3 ] |]));
test "concat" (fun () ->
assert_list Alcotest.int
(Array.to_list (Array.append (inclusive_range 0 99) (inclusive_range 0 99)))
(Belt.List.toArray
(Belt.List.concat
(Belt.List.makeBy 100 (fun index -> index))
(Belt.List.makeBy 100 (fun index -> index)))
|> Array.to_list);
assert_list Alcotest.int [ 1 ] (Belt.List.concat [ 1 ] []);
assert_list Alcotest.int [ 1 ] (Belt.List.concat [] [ 1 ]));
test "zip and zipBy" (fun () ->
assert_list
(Alcotest.pair Alcotest.int Alcotest.int)
[ (1, 3); (2, 4) ]
(Belt.List.zip [ 1; 2; 3 ] [ 3; 4 ]);
assert_list (Alcotest.pair Alcotest.int Alcotest.int) [] (Belt.List.zip [] [ 1 ]);
assert_list (Alcotest.pair Alcotest.int Alcotest.int) [] (Belt.List.zip [] []);
assert_list (Alcotest.pair Alcotest.int Alcotest.int) [] (Belt.List.zip [ 1; 2; 3 ] []);
assert_list
(Alcotest.pair Alcotest.int Alcotest.int)
[ (1, 2); (2, 3); (3, 4) ]
(Belt.List.zip [ 1; 2; 3 ] [ 2; 3; 4 ]);
let zip_by_add left right = Belt.List.zipBy left right add in
let doubled = Belt.List.makeBy 10 (fun index -> index * 2) in
assert_list Alcotest.int doubled (zip_by_add length_10_id length_10_id);
assert_list Alcotest.int [] (zip_by_add [] [ 1 ]);
assert_list Alcotest.int [] (zip_by_add [ 1 ] []);
assert_list Alcotest.int [] (zip_by_add [] []);
assert_list Alcotest.int
(Belt.List.concat (Belt.List.map length_8_id (fun value -> value * 2)) [ 16; 18 ])
(zip_by_add length_10_id length_10_id);
assert_list Alcotest.int
(Belt.List.mapWithIndex length_8_id (fun index value -> index + value))
(zip_by_add length_10_id length_8_id);
assert_list Alcotest.int
(Belt.List.map length_10_id (fun value -> value * 2))
(Belt.List.reverse (Belt.List.mapReverse2 length_10_id length_10_id add));
let reversed = Belt.List.reverse (Belt.List.mapReverse2 length_8_id length_10_id add) in
assert_int 8 (Belt.List.length reversed);
assert_list Alcotest.int (Belt.List.zipBy length_10_id length_8_id add) reversed;
assert_list Alcotest.int [ 4; 2 ]
(Belt.List.mapReverse2 [ 1; 2; 3 ] [ 1; 2 ] (fun left right -> left + right)));
test "partition" (fun () ->
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int)
([ 2; 2; 4 ], [ 1; 3; 3 ])
(Belt.List.partition [ 1; 2; 3; 2; 3; 4 ] mod2);
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int)
([ 2; 2; 2; 4 ], [])
(Belt.List.partition [ 2; 2; 2; 4 ] mod2);
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int)
([], [ 2; 2; 2; 4 ])
(Belt.List.partition [ 2; 2; 2; 4 ] (fun value -> not (mod2 value)));
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int) ([], []) (Belt.List.partition [] mod2));
test "unzip" (fun () ->
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int) ([], []) (Belt.List.unzip []);
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int) ([ 1 ], [ 2 ])
(Belt.List.unzip [ (1, 2) ]);
assert_pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int)
([ 1; 3 ], [ 2; 4 ])
(Belt.List.unzip [ (1, 2); (3, 4) ]));
test "keep and keepWithIndex" (fun () ->
assert_list Alcotest.int [ 2; 4 ] (Belt.List.keep [ 1; 2; 3; 4 ] mod2);
assert_list Alcotest.int [] (Belt.List.keep [ 1; 3; 41 ] mod2);
assert_list Alcotest.int [] (Belt.List.keep [] mod2);
assert_list Alcotest.int [ 2; 2; 2; 4; 6 ] (Belt.List.keep [ 2; 2; 2; 4; 6 ] mod2);
assert_list Alcotest.int [] (Belt.List.keepWithIndex [] even_index);
assert_list Alcotest.int [ 1; 3 ] (Belt.List.keepWithIndex [ 1; 2; 3; 4 ] even_index);
assert_list Alcotest.int [ 0; 2; 4; 6 ] (Belt.List.keepWithIndex [ 0; 1; 2; 3; 4; 5; 6; 7 ] even_index));
test "map" (fun () ->
assert_list Alcotest.int [ 0; 2; 4; 6; 8 ] (Belt.List.map (Belt.List.makeBy 5 id) (fun value -> value * 2));
assert_list Alcotest.int [] (Belt.List.map [] id);
assert_list Alcotest.int [ -1 ] (Belt.List.map [ 1 ] (fun value -> -value)));
test "take drop and splitAt" (fun () ->
assert_option (Alcotest.list Alcotest.int) (Some [ 1; 2 ]) (Belt.List.take [ 1; 2; 3 ] 2);
assert_option (Alcotest.list Alcotest.int) None (Belt.List.take [] 1);
assert_option (Alcotest.list Alcotest.int) None (Belt.List.take [ 1; 2 ] 3);
assert_option (Alcotest.list Alcotest.int) (Some [ 1; 2 ]) (Belt.List.take [ 1; 2 ] 2);
assert_option (Alcotest.list Alcotest.int) (Some length_8_id) (Belt.List.take length_10_id 8);
assert_option (Alcotest.list Alcotest.int) (Some []) (Belt.List.take length_10_id 0);
assert_option (Alcotest.list Alcotest.int) None (Belt.List.take length_8_id (-2));
assert_option (Alcotest.list Alcotest.int) (Some []) (Belt.List.drop length_10_id 10);
assert_option (Alcotest.list Alcotest.int) (Some [ 8; 9 ]) (Belt.List.drop length_10_id 8);
assert_option (Alcotest.list Alcotest.int) (Some length_10_id) (Belt.List.drop length_10_id 0);
assert_option (Alcotest.list Alcotest.int) None (Belt.List.drop length_8_id (-1));
let values = Belt.List.makeBy 5 id in
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
None (Belt.List.splitAt [] 1);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
None (Belt.List.splitAt values 6);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some (values, []))
(Belt.List.splitAt values 5);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some ([ 0; 1; 2; 3 ], [ 4 ]))
(Belt.List.splitAt values 4);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some ([ 0; 1; 2 ], [ 3; 4 ]))
(Belt.List.splitAt values 3);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some ([ 0; 1 ], [ 2; 3; 4 ]))
(Belt.List.splitAt values 2);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some ([ 0 ], [ 1; 2; 3; 4 ]))
(Belt.List.splitAt values 1);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
(Some ([], values))
(Belt.List.splitAt values 0);
assert_option
(Alcotest.pair (Alcotest.list Alcotest.int) (Alcotest.list Alcotest.int))
None (Belt.List.splitAt values (-1)));
test "association helpers" (fun () ->
let eq_int left right = (left : int) = right in
assert_bool true (Belt.List.hasAssoc [ (1, "1"); (2, "2"); (3, "3") ] 2 ( = ));
assert_bool false (Belt.List.hasAssoc [ (1, "1"); (2, "2"); (3, "3") ] 4 ( = ));
assert_bool true
(Belt.List.hasAssoc [ (1, "1"); (2, "2"); (3, "3") ] 4 (fun left right -> left + 1 = right));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1"); (2, "2") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 3 ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (2, "2"); (3, "3") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 1 ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1"); (3, "3") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 2 ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1"); (2, "2"); (3, "3") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 0 ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1"); (2, "2") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 3 eq_int);
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (2, "2"); (3, "3") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 1 eq_int);
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1"); (3, "3") ]
(Belt.List.removeAssoc [ (1, "1"); (2, "2"); (3, "3") ] 2 eq_int);
assert_list (Alcotest.pair Alcotest.int Alcotest.string) [] (Belt.List.removeAssoc [] 2 eq_int);
let values = [ (1, "1"); (2, "2"); (3, "3") ] in
let untouched = Belt.List.removeAssoc values 0 eq_int in
assert_same_physical values untouched;
let updated = Belt.List.setAssoc values 2 "22" ( = ) in
assert_list (Alcotest.pair Alcotest.int Alcotest.string) [ (1, "1"); (2, "22"); (3, "3") ] updated;
let added = Belt.List.setAssoc updated 22 "2" ( = ) in
assert_list (Alcotest.pair Alcotest.int Alcotest.string) ((22, "2") :: updated) added;
assert_same_physical updated (Belt.List.tailExn added);
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "a"); (2, "x"); (3, "c") ]
(Belt.List.setAssoc [ (1, "a"); (2, "b"); (3, "c") ] 2 "x" ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (2, "2"); (1, "a"); (3, "c") ]
(Belt.List.setAssoc [ (1, "a"); (3, "c") ] 2 "2" ( = ));
assert_list (Alcotest.pair Alcotest.int Alcotest.string) [ (1, "1") ] (Belt.List.setAssoc [] 1 "1" ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (1, "1") ]
(Belt.List.setAssoc [ (1, "2") ] 1 "1" ( = ));
assert_list
(Alcotest.pair Alcotest.int Alcotest.string)
[ (0, "0"); (1, "1") ]
(Belt.List.setAssoc [ (0, "0"); (1, "2") ] 1 "1" ( = ));
assert_option Alcotest.string (Some "b") (Belt.List.getAssoc [ (1, "a"); (2, "b"); (3, "c") ] 2 ( = ));
assert_option Alcotest.string None (Belt.List.getAssoc [ (1, "a"); (2, "b"); (3, "c") ] 4 ( = )));
test "head tail and access" (fun () ->
assert_pair (Alcotest.option Alcotest.int)
(Alcotest.option (Alcotest.list Alcotest.int))
(Some 0, Belt.List.drop length_10_id 1)
(Belt.List.head length_10_id, Belt.List.tail length_10_id);
assert_option Alcotest.int None (Belt.List.head []);
assert_raises_any (fun () -> ignore (Belt.List.headExn []));
assert_raises_any (fun () -> ignore (Belt.List.tailExn []));
assert_raises_any (fun () -> ignore (Belt.List.getExn [ 0; 1 ] (-1)));
assert_raises_any (fun () -> ignore (Belt.List.getExn [ 0; 1 ] 2));
assert_list Alcotest.int [ 0; 1 ] (Belt.List.map [ 0; 1 ] (fun index -> Belt.List.getExn [ 0; 1 ] index));
assert_int 1 (Belt.List.headExn [ 1 ]);
assert_list Alcotest.int [] (Belt.List.tailExn [ 1 ]);
Belt.List.forEachWithIndex length_10_id (fun index value ->
assert_option Alcotest.int (Some value) (Belt.List.get length_10_id index));
assert_option (Alcotest.list Alcotest.int) None (Belt.List.tail []);
assert_option (Alcotest.list Alcotest.int) None (Belt.List.drop [] 3);
assert_list Alcotest.int [] (Belt.List.mapWithIndex [] (fun index value -> index + value));
assert_option Alcotest.int None (Belt.List.get length_10_id (-1));
assert_option Alcotest.int None (Belt.List.get length_10_id 12);
assert_int 0 (sum []);
assert_int 45 (sum length_10_id);
assert_list Alcotest.int [] (Belt.List.makeBy 0 id);
assert_list Alcotest.int length_10_id (Belt.List.reverse (Belt.List.reverse length_10_id));
assert_list Alcotest.int length_8_id (Belt.List.reverse (Belt.List.reverse length_8_id));
assert_list Alcotest.int [] (Belt.List.reverse []);
assert_list Alcotest.int (Belt.List.map length_10_id succ)
(Belt.List.reverse (Belt.List.mapReverse length_10_id succ)));
test "reductions" (fun () ->
assert_int 45 (Belt.List.reduce length_10_id 0 add);
assert_int 45 (Belt.List.reduceReverse length_10_id 0 add);
assert_int (9999 * 5000) (Belt.List.reduceReverse (Belt.List.makeBy 10_000 (fun index -> index)) 0 ( + ));
assert_int 90 (sum2 length_10_id length_10_id);
assert_int 56 (sum2 length_8_id length_10_id);
assert_int 56 (sum2 length_10_id length_8_id);
assert_int 56 (Belt.List.reduce2 length_10_id length_8_id 0 (fun acc left right -> acc + left + right));
assert_int 18 (Belt.List.reduce2 [ 1; 2; 3 ] [ 2; 4; 6 ] 0 (fun acc left right -> acc + left + right));
assert_int 56
(Belt.List.reduceReverse2 length_10_id length_8_id 0 (fun acc left right -> acc + left + right));
assert_int 90
(Belt.List.reduceReverse2 length_10_id length_10_id 0 (fun acc left right -> acc + left + right));
assert_int 6 (Belt.List.reduceReverse2 [ 1; 2; 3 ] [ 1; 2 ] 0 (fun acc left right -> acc + left + right));
assert_int 10 (Belt.List.reduceReverse [ 1; 2; 3; 4 ] 0 ( + ));
assert_int 0 (Belt.List.reduceReverse [ 1; 2; 3; 4 ] 10 ( - ));
assert_list Alcotest.int [ 1; 2; 3; 4 ] (Belt.List.reduceReverse [ 1; 2; 3; 4 ] [] Belt.List.add);
assert_int 10 (Belt.List.reduce [ 1; 2; 3; 4 ] 0 ( + ));
assert_int 0 (Belt.List.reduce [ 1; 2; 3; 4 ] 10 ( - ));
assert_list Alcotest.int [ 4; 3; 2; 1 ] (Belt.List.reduce [ 1; 2; 3; 4 ] [] Belt.List.add);
assert_int 16 (Belt.List.reduceWithIndex [ 1; 2; 3; 4 ] 0 (fun acc value index -> acc + value + index));
assert_int 6 (Belt.List.reduceReverse2 [ 1; 2; 3 ] [ 1; 2 ] 0 (fun acc left right -> acc + left + right));
let values = Belt.List.makeBy 10_000 (fun index -> index) in
assert_int
((9999 * 10_000) - 9999)
(Belt.List.reduceReverse2 values (0 :: values) 0 (fun acc left right -> acc + left + right)));
test "predicates and comparison" (fun () ->
assert_bool true (Belt.List.every [ 2; 4; 6 ] mod2);
assert_bool false (Belt.List.every [ 1 ] mod2);
assert_bool true (Belt.List.every [] mod2);
assert_bool true (Belt.List.some [ 1; 2; 5 ] mod2);
assert_bool false (Belt.List.some [ 1; 3; 5 ] mod2);
assert_bool false (Belt.List.some [] mod2);
assert_bool true (Belt.List.has [ 1; 2; 3 ] "2" (fun value text -> string_of_int value = text));
assert_bool false (Belt.List.has [ 1; 2; 3 ] "0" (fun value text -> string_of_int value = text));
assert_bool true (Belt.List.every2 [] [ 1 ] (fun left right -> left > right));
assert_bool true (Belt.List.every2 [ 2; 3 ] [ 1 ] (fun left right -> left > right));
assert_bool true (Belt.List.every2 [ 2 ] [ 1 ] (fun left right -> left > right));
assert_bool false (Belt.List.every2 [ 2; 3 ] [ 1; 4 ] (fun left right -> left > right));
assert_bool true (Belt.List.every2 [ 2; 3 ] [ 1; 0 ] (fun left right -> left > right));
assert_bool false (Belt.List.some2 [] [ 1 ] (fun left right -> left > right));
assert_bool true (Belt.List.some2 [ 2; 3 ] [ 1 ] (fun left right -> left > right));
assert_bool true (Belt.List.some2 [ 2; 3 ] [ 1; 4 ] (fun left right -> left > right));
assert_bool false (Belt.List.some2 [ 0; 3 ] [ 1; 4 ] (fun left right -> left > right));
assert_bool true (Belt.List.some2 [ 0; 3 ] [ 3; 2 ] (fun left right -> left > right));
assert_bool false (Belt.List.some2 [ 1; 2; 3 ] [ -1; -2 ] (fun left right -> left = right));
assert_list Alcotest.int [ 2; 3 ] (Belt.List.add (Belt.List.add [] 3) 2);
assert_bool true (Belt.List.cmp [ 1; 2; 3 ] [ 0; 1; 2; 3 ] compare > 0);
assert_bool true (Belt.List.cmp [ 1; 2; 3; 4 ] [ 1; 2; 3 ] compare > 0);
assert_bool true (Belt.List.cmp [ 1; 2; 3 ] [ 1; 2; 3; 4 ] compare < 0);
assert_bool true (Belt.List.cmp [ 1; 2; 3 ] [ 0; 1; 2 ] compare > 0);
assert_bool true (Belt.List.cmp [ 1; 2; 3 ] [ 1; 2; 3 ] compare = 0);
assert_bool true (Belt.List.cmp [ 1; 2; 4 ] [ 1; 2; 3 ] compare > 0);
assert_bool true (Belt.List.cmpByLength [] [] = 0);
assert_bool true (Belt.List.cmpByLength [ 1 ] [] > 0);
assert_bool true (Belt.List.cmpByLength [] [ 1 ] < 0);
assert_bool true (Belt.List.cmpByLength [ 1; 2 ] [ 1 ] > 0);
assert_bool true (Belt.List.cmpByLength [ 1 ] [ 1; 2 ] < 0);
assert_bool true (Belt.List.cmpByLength [ 1; 3 ] [ 1; 2 ] = 0));
test "make sort eq and keepMap" (fun () ->
List.iter
(fun length -> assert_list Alcotest.int (Belt.List.makeBy length (fun _ -> 3)) (Belt.List.make length 3))
[ 0; 1; 2; 3 ];
let cmp left right = left - right in
assert_list Alcotest.int [ 2; 3; 4; 5 ] (Belt.List.sort [ 5; 4; 3; 2 ] cmp);
assert_list Alcotest.int [ 1; 3; 3; 9; 37 ] (Belt.List.sort [ 3; 9; 37; 3; 1 ] cmp);
assert_bool true (not (Belt.List.eq [ 1; 2; 3 ] [ 1; 2 ] ( = )));
assert_bool true (Belt.List.eq [ 1; 2; 3 ] [ 1; 2; 3 ] ( = ));
assert_bool true (not (Belt.List.eq [ 1; 2; 3 ] [ 1; 2; 4 ] ( = )));
assert_bool true (not (Belt.List.eq [ 1; 2; 3 ] [ 1; 2; 3; 4 ] ( = )));
let values = Belt.List.makeBy 20 (fun index -> index) in
assert_list Alcotest.int [ 1; 8; 15 ]
(Belt.List.keepMap values (fun value -> if value mod 7 = 0 then Some (value + 1) else None));
assert_list Alcotest.int [ -2; -4 ]
(Belt.List.keepMap [ 1; 2; 3; 4 ] (fun value -> if value mod 2 = 0 then Some (-value) else None));
assert_list Alcotest.int []
(Belt.List.keepMap [ 1; 2; 3; 4 ] (fun value -> if value mod 5 = 0 then Some value else None)));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Map.ml
================================================
let int_pairs start finish = Array.map (fun value -> (value, value)) (inclusive_range start finish)
let map_of_array values = Belt.Map.fromArray values ~id:(module IntCmp)
let set_of_keys values = Belt.Set.fromArray values ~id:(module IntCmp)
let merge_inter left right =
set_of_keys
(Belt.Map.keysToArray
(Belt.Map.merge left right (fun _ left_value right_value ->
match (left_value, right_value) with Some _, Some _ -> Some () | _ -> None)))
let merge_union left right =
set_of_keys
(Belt.Map.keysToArray
(Belt.Map.merge left right (fun _ left_value right_value ->
match (left_value, right_value) with None, None -> None | _ -> Some ())))
let merge_diff left right =
set_of_keys
(Belt.Map.keysToArray
(Belt.Map.merge left right (fun _ left_value right_value ->
match (left_value, right_value) with Some _, None -> Some () | _ -> None)))
let suites =
[
( "Map",
[
test "fromArray set get toArray and toList" (fun () ->
let values = map_of_array (Array.map (fun key -> (key, key)) (shuffled_range 0 39)) in
let updated = Belt.Map.set values 39 120 in
assert_array
(Alcotest.pair Alcotest.int Alcotest.int)
(Array.map (fun key -> (key, key)) (inclusive_range 0 39))
(Belt.Map.toArray values);
assert_list
(Alcotest.pair Alcotest.int Alcotest.int)
(Array.to_list (Array.map (fun key -> (key, key)) (inclusive_range 0 39)))
(Belt.Map.toList values);
assert_option Alcotest.int (Some 39) (Belt.Map.get values 39);
assert_option Alcotest.int (Some 120) (Belt.Map.get updated 39));
test "large fromArray sorts output" (fun () ->
let values = map_of_array (Array.map (fun key -> (key, key)) (shuffled_range 0 1_000)) in
assert_array
(Alcotest.pair Alcotest.int Alcotest.int)
(Array.map (fun key -> (key, key)) (inclusive_range 0 1_000))
(Belt.Map.toArray values));
test "merge variants" (fun () ->
let left = map_of_array (int_pairs 0 100) in
let right = map_of_array (int_pairs 30 120) in
assert_bool true (Belt.Set.eq (merge_inter left right) (set_of_keys (inclusive_range 30 100)));
assert_bool true (Belt.Set.eq (merge_union left right) (set_of_keys (inclusive_range 0 120)));
assert_bool true (Belt.Set.eq (merge_diff left right) (set_of_keys (inclusive_range 0 29)));
assert_bool true (Belt.Set.eq (merge_diff right left) (set_of_keys (inclusive_range 101 120))));
test "update removeMany and undefined access" (fun () ->
let base = map_of_array (int_pairs 0 10) in
let overwritten = Belt.Map.set base 3 33 in
let removed = Belt.Map.remove overwritten 3 in
let inserted = Belt.Map.update removed 3 (function Some value -> Some (value + 1) | None -> Some 11) in
let absent = Belt.Map.update removed 3 (function Some value -> Some (value + 1) | None -> None) in
let removed_once = Belt.Map.remove base 3 in
let removed_twice = Belt.Map.remove removed_once 3 in
assert_same_physical removed_once removed_twice;
assert_bool true (Belt.Map.has base 3);
assert_bool false (Belt.Map.has removed_once 3);
assert_undefined Alcotest.int (Some 3) (Belt.Map.getUndefined base 3);
assert_undefined Alcotest.int (Some 33) (Belt.Map.getUndefined overwritten 3);
assert_undefined Alcotest.int None (Belt.Map.getUndefined removed 3);
assert_undefined Alcotest.int (Some 11) (Belt.Map.getUndefined inserted 3);
assert_undefined Alcotest.int None (Belt.Map.getUndefined absent 3);
let leftovers = Belt.Map.removeMany base [| 7; 8; 0; 1; 3; 2; 4; 922; 4; 5; 6 |] in
assert_array Alcotest.int [| 9; 10 |] (Belt.Map.keysToArray leftovers);
let empty = Belt.Map.removeMany leftovers (inclusive_range 0 100) in
assert_bool true (Belt.Map.isEmpty empty));
test "set returns new map" (fun () ->
let values = map_of_array (int_pairs 0 100) in
let updated = Belt.Map.set values 3 32 in
assert_option Alcotest.int (Some 32) (Belt.Map.get updated 3);
assert_option Alcotest.int (Some 3) (Belt.Map.get values 3));
test "repeated update accumulation" (fun () ->
let values = Belt.Map.make ~id:(module IntCmp) in
let combined = Belt.Array.concat (shuffled_range 0 20) (shuffled_range 10 30) in
let accumulated =
Array.fold_left
(fun map key -> Belt.Map.update map key (function None -> Some 1 | Some count -> Some (count + 1)))
values combined
in
let expected = map_of_array (Array.init 31 (fun key -> (key, if key >= 10 && key <= 20 then 2 else 1))) in
assert_bool true (Belt.Map.eq accumulated expected ( = )));
test "mergeMany split and empty removals" (fun () ->
let merged = Belt.Map.mergeMany (Belt.Map.make ~id:(module IntCmp)) (int_pairs 0 1_000) in
let from_array = map_of_array (int_pairs 0 1_000) in
assert_bool true (Belt.Map.eq merged from_array ( = ));
let increment = function None -> Some 0 | Some value -> Some (value + 1) in
let updated = Belt.Map.update merged 10 increment in
let with_negative = Belt.Map.update updated (-10) increment in
let (left, right), present = Belt.Map.split updated 500 in
assert_option Alcotest.int (Some 11) (Belt.Map.get updated 10);
assert_option Alcotest.int None (Belt.Map.get updated (-10));
assert_option Alcotest.int (Some 0) (Belt.Map.get with_negative (-10));
assert_bool true (Belt.Map.isEmpty (Belt.Map.remove (Belt.Map.make ~id:(module IntCmp)) 0));
assert_bool true (Belt.Map.isEmpty (Belt.Map.removeMany (Belt.Map.make ~id:(module IntCmp)) [| 0 |]));
assert_option Alcotest.int (Some 500) present;
assert_array Alcotest.int (inclusive_range 0 499) (Belt.Map.keysToArray left);
assert_array Alcotest.int (inclusive_range 501 1_000) (Belt.Map.keysToArray right);
let removed = Belt.Map.remove updated 500 in
let (left_missing, right_missing), present_missing = Belt.Map.split removed 500 in
assert_option Alcotest.int None present_missing;
assert_array Alcotest.int (inclusive_range 0 499) (Belt.Map.keysToArray left_missing);
assert_array Alcotest.int (inclusive_range 501 1_000) (Belt.Map.keysToArray right_missing));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Map_Dict.ml
================================================
let suites =
[
( "Map.Dict",
[
test "packIdData and first class comparator" (fun () ->
let module Comparable = (val Belt.Id.comparable ~cmp:(compare : int -> int -> int)) in
let empty = Belt.Map.make ~id:(module Comparable) in
let id = Belt.Map.getId empty in
let module Cmp = (val id) in
let data = Belt.Map.getData empty in
let data = Belt.Map.Dict.set data 1 1 ~cmp:Cmp.cmp in
let data = Belt.Map.Dict.set data 2 2 ~cmp:Cmp.cmp in
let packed = Belt.Map.packIdData ~id ~data in
assert_array (Alcotest.pair Alcotest.int Alcotest.int) [| (1, 1); (2, 2) |] (Belt.Map.toArray packed));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Map_Int.ml
================================================
let suites =
[
( "Map.Int",
[
test "findFirstBy" (fun () ->
let values = Belt.Map.Int.fromArray [| (4, "four"); (1, "one"); (2, "two") |] in
assert_option
(Alcotest.pair Alcotest.int Alcotest.string)
(Some (2, "two"))
(Belt.Map.Int.findFirstBy values (fun key _ -> key mod 2 = 0 && key < 4));
assert_option
(Alcotest.pair Alcotest.int Alcotest.string)
None
(Belt.Map.Int.findFirstBy values (fun key _ -> key > 10)));
test "invariant after removals" (fun () ->
let shuffled = Array.map (fun key -> (key, key)) (shuffled_range 0 10_000) in
let values = Belt.Map.Int.fromArray shuffled in
Belt.Map.Int.checkInvariantInternal values;
let removed = Array.sub shuffled 0 2000 in
let reduced = Array.fold_left (fun map (key, _) -> Belt.Map.Int.remove map key) values removed in
Belt.Map.Int.checkInvariantInternal values;
Belt.Map.Int.checkInvariantInternal reduced;
Array.iter (fun (key, _) -> assert_option Alcotest.int None (Belt.Map.Int.get reduced key)) removed);
test "set get remove stress" (fun () ->
let values = ref Belt.Map.Int.empty in
let count = 10_000 in
for key = 0 to count do
values := Belt.Map.Int.set !values key key
done;
for key = 0 to count do
assert_bool true (Belt.Map.Int.get !values key <> None)
done;
for key = 0 to count do
values := Belt.Map.Int.remove !values key
done;
assert_bool true (Belt.Map.Int.isEmpty !values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Map_String.ml
================================================
let suites =
[
( "Map.String",
[
test "findFirstBy" (fun () ->
let values = Belt.Map.String.fromArray [| ("cc", 3); ("b", 1); ("aa", 2) |] in
assert_option
(Alcotest.pair Alcotest.string Alcotest.int)
(Some ("aa", 2))
(Belt.Map.String.findFirstBy values (fun key _ -> String.length key = 2));
assert_option
(Alcotest.pair Alcotest.string Alcotest.int)
None
(Belt.Map.String.findFirstBy values (fun key _ -> key = "zzz")));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableMap.ml
================================================
let random_pairs start finish = Array.map (fun value -> (value, value)) (shuffled_range start finish)
let make_map () = Belt.MutableMap.make ~id:(module IntCmp)
let suites =
[
( "MutableMap",
[
test "remove preserves successor values" (fun () ->
let values = make_map () in
Belt.MutableMap.set values 2 "two";
Belt.MutableMap.set values 1 "one";
Belt.MutableMap.set values 3 "three";
Belt.MutableMap.remove values 2;
assert_option Alcotest.string None (Belt.MutableMap.get values 2);
assert_option Alcotest.string (Some "one") (Belt.MutableMap.get values 1);
assert_option Alcotest.string (Some "three") (Belt.MutableMap.get values 3));
test "remove deep successor preserves values" (fun () ->
let values = make_map () in
List.iter
(fun (key, value) -> Belt.MutableMap.set values key value)
[ (5, "five"); (2, "two"); (8, "eight"); (6, "six"); (9, "nine"); (7, "seven") ];
Belt.MutableMap.remove values 5;
Belt.MutableMap.checkInvariantInternal values;
assert_option Alcotest.string None (Belt.MutableMap.get values 5);
assert_option Alcotest.string (Some "six") (Belt.MutableMap.get values 6);
assert_array
(Alcotest.pair Alcotest.int Alcotest.string)
[| (2, "two"); (6, "six"); (7, "seven"); (8, "eight"); (9, "nine") |]
(Belt.MutableMap.toArray values));
test "update remove preserves values" (fun () ->
let values = make_map () in
List.iter
(fun (key, value) -> Belt.MutableMap.set values key value)
[ (5, "five"); (2, "two"); (8, "eight"); (6, "six"); (9, "nine") ];
Belt.MutableMap.update values 5 (fun _ -> None);
Belt.MutableMap.checkInvariantInternal values;
assert_option Alcotest.string None (Belt.MutableMap.get values 5);
assert_option Alcotest.string (Some "six") (Belt.MutableMap.get values 6);
assert_array
(Alcotest.pair Alcotest.int Alcotest.string)
[| (2, "two"); (6, "six"); (8, "eight"); (9, "nine") |]
(Belt.MutableMap.toArray values));
test "removeMany exact keys" (fun () ->
let values = Belt.MutableMap.fromArray (random_pairs 0 10) ~id:(module IntCmp) in
Belt.MutableMap.set values 3 33;
assert_int 33 (Belt.MutableMap.getExn values 3);
Belt.MutableMap.removeMany values [| 7; 8; 0; 1; 3; 2; 4; 922; 4; 5; 6 |];
assert_array Alcotest.int [| 9; 10 |] (Belt.MutableMap.keysToArray values);
Belt.MutableMap.removeMany values (inclusive_range 0 100);
assert_bool true (Belt.MutableMap.isEmpty values));
test "trim to three entries" (fun () ->
let values = Belt.MutableMap.fromArray (random_pairs 0 10_000) ~id:(module IntCmp) in
Belt.MutableMap.set values 2000 33;
Belt.MutableMap.removeMany values (inclusive_range 0 1998);
Belt.MutableMap.removeMany values (inclusive_range 2002 11_000);
assert_array
(Alcotest.pair Alcotest.int Alcotest.int)
[| (1999, 1999); (2000, 33); (2001, 2001) |]
(Belt.MutableMap.toArray values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableMap_Int.ml
================================================
let suites =
[
( "MutableMap.Int",
[
test "smoke" (fun () ->
let values = Belt.MutableMap.Int.make () in
Belt.MutableMap.Int.set values 1 "one";
assert_option Alcotest.string (Some "one") (Belt.MutableMap.Int.get values 1));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableMap_String.ml
================================================
let suites =
[
( "MutableMap.String",
[
test "smoke" (fun () ->
let values = Belt.MutableMap.String.make () in
Belt.MutableMap.String.set values "one" 1;
assert_option Alcotest.int (Some 1) (Belt.MutableMap.String.get values "one"));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableQueue.ml
================================================
let does_raise operation queue = match operation queue with exception _ -> true | _ -> false
let suites =
[
( "MutableQueue",
[
test "push pop fifo" (fun () ->
let queue = Belt.MutableQueue.make () in
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray queue);
assert_int 0 (Belt.MutableQueue.size queue);
Belt.MutableQueue.add queue 1;
assert_array Alcotest.int [| 1 |] (Belt.MutableQueue.toArray queue);
assert_int 1 (Belt.MutableQueue.size queue);
Belt.MutableQueue.add queue 2;
Belt.MutableQueue.add queue 3;
Belt.MutableQueue.add queue 4;
assert_array Alcotest.int [| 1; 2; 3; 4 |] (Belt.MutableQueue.toArray queue);
assert_int 1 (Belt.MutableQueue.popExn queue);
assert_array Alcotest.int [| 2; 3; 4 |] (Belt.MutableQueue.toArray queue);
assert_int 2 (Belt.MutableQueue.popExn queue);
assert_array Alcotest.int [| 3; 4 |] (Belt.MutableQueue.toArray queue);
assert_int 3 (Belt.MutableQueue.popExn queue);
assert_array Alcotest.int [| 4 |] (Belt.MutableQueue.toArray queue);
assert_int 4 (Belt.MutableQueue.popExn queue);
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray queue);
assert_int 0 (Belt.MutableQueue.size queue);
assert_bool true (does_raise Belt.MutableQueue.popExn queue));
test "reuse after empty" (fun () ->
let queue = Belt.MutableQueue.make () in
Belt.MutableQueue.add queue 1;
assert_int 1 (Belt.MutableQueue.popExn queue);
assert_bool true (does_raise Belt.MutableQueue.popExn queue);
Belt.MutableQueue.add queue 2;
assert_int 2 (Belt.MutableQueue.popExn queue);
assert_bool true (does_raise Belt.MutableQueue.popExn queue);
assert_int 0 (Belt.MutableQueue.size queue));
test "peekExn" (fun () ->
let queue = Belt.MutableQueue.make () in
Belt.MutableQueue.add queue 1;
assert_int 1 (Belt.MutableQueue.peekExn queue);
Belt.MutableQueue.add queue 2;
assert_int 1 (Belt.MutableQueue.peekExn queue);
Belt.MutableQueue.add queue 3;
assert_int 1 (Belt.MutableQueue.peekExn queue);
assert_int 1 (Belt.MutableQueue.popExn queue);
assert_int 2 (Belt.MutableQueue.peekExn queue);
assert_int 2 (Belt.MutableQueue.popExn queue);
assert_int 3 (Belt.MutableQueue.peekExn queue);
assert_int 3 (Belt.MutableQueue.popExn queue);
assert_bool true (does_raise Belt.MutableQueue.peekExn queue);
assert_bool true (does_raise Belt.MutableQueue.peekExn queue));
test "clear" (fun () ->
let queue = Belt.MutableQueue.make () in
for value = 1 to 10 do
Belt.MutableQueue.add queue value
done;
Belt.MutableQueue.clear queue;
assert_int 0 (Belt.MutableQueue.size queue);
assert_bool true (does_raise Belt.MutableQueue.popExn queue);
assert_bool true (queue = Belt.MutableQueue.make ());
Belt.MutableQueue.add queue 42;
assert_int 42 (Belt.MutableQueue.popExn queue));
test "copy" (fun () ->
let source = Belt.MutableQueue.make () in
for value = 1 to 10 do
Belt.MutableQueue.add source value
done;
let copy = Belt.MutableQueue.copy source in
assert_array Alcotest.int [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |] (Belt.MutableQueue.toArray source);
assert_array Alcotest.int [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |] (Belt.MutableQueue.toArray copy);
assert_int 10 (Belt.MutableQueue.size source);
assert_int 10 (Belt.MutableQueue.size copy);
for value = 1 to 10 do
assert_int value (Belt.MutableQueue.popExn source)
done;
for value = 1 to 10 do
assert_int value (Belt.MutableQueue.popExn copy)
done);
test "isEmpty and size" (fun () ->
let queue = Belt.MutableQueue.make () in
assert_bool true (Belt.MutableQueue.isEmpty queue);
for value = 1 to 10 do
Belt.MutableQueue.add queue value;
assert_int value (Belt.MutableQueue.size queue);
assert_bool false (Belt.MutableQueue.isEmpty queue)
done;
for value = 10 downto 1 do
assert_int value (Belt.MutableQueue.size queue);
assert_bool false (Belt.MutableQueue.isEmpty queue);
ignore (Belt.MutableQueue.popExn queue)
done;
assert_int 0 (Belt.MutableQueue.size queue);
assert_bool true (Belt.MutableQueue.isEmpty queue));
test "forEach" (fun () ->
let queue = Belt.MutableQueue.make () in
for value = 1 to 10 do
Belt.MutableQueue.add queue value
done;
let expected = ref 1 in
Belt.MutableQueue.forEach queue (fun value ->
assert_int !expected value;
incr expected));
test "transfer" (fun () ->
let left = Belt.MutableQueue.make () in
let right = Belt.MutableQueue.make () in
Belt.MutableQueue.transfer left right;
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray left);
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray right);
for value = 1 to 4 do
Belt.MutableQueue.add left value
done;
Belt.MutableQueue.transfer left right;
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray left);
assert_array Alcotest.int [| 1; 2; 3; 4 |] (Belt.MutableQueue.toArray right);
for value = 5 to 8 do
Belt.MutableQueue.add right value
done;
Belt.MutableQueue.transfer left right;
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray left);
assert_array Alcotest.int [| 1; 2; 3; 4; 5; 6; 7; 8 |] (Belt.MutableQueue.toArray right));
test "transfer appends to nonempty queue" (fun () ->
let left = Belt.MutableQueue.make () in
let right = Belt.MutableQueue.make () in
for value = 1 to 4 do
Belt.MutableQueue.add left value
done;
for value = 5 to 8 do
Belt.MutableQueue.add right value
done;
Belt.MutableQueue.transfer left right;
let expected = [| 5; 6; 7; 8; 1; 2; 3; 4 |] in
assert_array Alcotest.int [||] (Belt.MutableQueue.toArray left);
assert_array Alcotest.int expected (Belt.MutableQueue.toArray right);
assert_int
(Belt.Array.reduce expected 0 (fun acc value -> acc - value))
(Belt.MutableQueue.reduce right 0 (fun acc value -> acc - value)));
test "fromArray and map" (fun () ->
let queue = Belt.MutableQueue.fromArray [| 1; 2; 3; 4 |] in
let mapped = Belt.MutableQueue.map queue (fun value -> value - 1) in
assert_array Alcotest.int [| 0; 1; 2; 3 |] (Belt.MutableQueue.toArray mapped);
assert_bool true (Belt.MutableQueue.isEmpty (Belt.MutableQueue.fromArray [||]));
assert_bool true
(Belt.MutableQueue.isEmpty
(Belt.MutableQueue.map (Belt.MutableQueue.fromArray [||]) (fun value -> value + 1))));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableSet.ml
================================================
let of_array values = Belt.MutableSet.fromArray values ~id:(module IntCmp)
let suites =
[
( "MutableSet",
[
test "remove add and split" (fun () ->
let values = of_array (inclusive_range 0 30) in
assert_bool true (Belt.MutableSet.removeCheck values 0);
assert_bool false (Belt.MutableSet.removeCheck values 0);
assert_bool true (Belt.MutableSet.removeCheck values 30);
assert_bool true (Belt.MutableSet.removeCheck values 20);
assert_int 28 (Belt.MutableSet.size values);
assert_undefined Alcotest.int (Some 29) (Belt.MutableSet.maxUndefined values);
assert_undefined Alcotest.int (Some 1) (Belt.MutableSet.minUndefined values);
Belt.MutableSet.add values 3;
Array.iter (Belt.MutableSet.remove values) (shuffled_range 0 30);
assert_bool true (Belt.MutableSet.isEmpty values);
Belt.MutableSet.add values 0;
Belt.MutableSet.add values 1;
Belt.MutableSet.add values 2;
Belt.MutableSet.add values 0;
assert_int 3 (Belt.MutableSet.size values);
Belt.MutableSet.mergeMany values (shuffled_range 0 20_000);
Belt.MutableSet.mergeMany values (shuffled_range 0 200);
assert_int 20_001 (Belt.MutableSet.size values);
Belt.MutableSet.removeMany values (shuffled_range 0 200);
assert_int 19_800 (Belt.MutableSet.size values);
Belt.MutableSet.removeMany values (shuffled_range 0 1000);
assert_int 19_000 (Belt.MutableSet.size values);
let values = of_array (shuffled_range 1000 2000) in
let (left, right), present = Belt.MutableSet.split values 1000 in
assert_bool true present;
assert_array Alcotest.int (inclusive_range 1000 2000) (Belt.MutableSet.toArray values);
assert_array Alcotest.int (inclusive_range 1001 2000) (Belt.MutableSet.toArray right);
assert_bool true (Belt.MutableSet.subset left values));
test "set algebra and partitions" (fun () ->
let left = of_array (shuffled_range 0 100) in
let right = of_array (shuffled_range 40 120) in
assert_bool true (Belt.MutableSet.eq (Belt.MutableSet.union left right) (of_array (inclusive_range 0 120)));
assert_bool true
(Belt.MutableSet.eq (Belt.MutableSet.intersect left right) (of_array (inclusive_range 40 100)));
assert_bool true (Belt.MutableSet.eq (Belt.MutableSet.diff left right) (of_array (inclusive_range 0 39)));
let values = of_array (shuffled_range 0 1000) in
let evens = Belt.MutableSet.keep values (fun value -> value mod 2 = 0) in
let odds = Belt.MutableSet.keep values (fun value -> value mod 2 <> 0) in
let evens_part, odds_part = Belt.MutableSet.partition values (fun value -> value mod 2 = 0) in
assert_bool true (Belt.MutableSet.eq evens evens_part);
assert_bool true (Belt.MutableSet.eq odds odds_part);
List.iter Belt.MutableSet.checkInvariantInternal [ values; evens; odds; evens_part; odds_part ]);
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableSet_Int.ml
================================================
let of_array = Belt.MutableSet.Int.fromArray
let suites =
[
( "MutableSet.Int",
[
test "removeCheck addCheck mergeMany and removeMany" (fun () ->
let values = of_array (inclusive_range 0 30) in
assert_bool true (Belt.MutableSet.Int.removeCheck values 0);
assert_bool false (Belt.MutableSet.Int.removeCheck values 0);
assert_bool true (Belt.MutableSet.Int.removeCheck values 30);
assert_bool true (Belt.MutableSet.Int.removeCheck values 20);
assert_int 28 (Belt.MutableSet.Int.size values);
assert_undefined Alcotest.int (Some 29) (Belt.MutableSet.Int.maxUndefined values);
assert_undefined Alcotest.int (Some 1) (Belt.MutableSet.Int.minUndefined values);
Belt.MutableSet.Int.add values 3;
Array.iter (Belt.MutableSet.Int.remove values) (shuffled_range 0 30);
assert_bool true (Belt.MutableSet.Int.isEmpty values);
Belt.MutableSet.Int.add values 0;
Belt.MutableSet.Int.add values 1;
Belt.MutableSet.Int.add values 2;
Belt.MutableSet.Int.add values 0;
assert_int 3 (Belt.MutableSet.Int.size values);
assert_bool false (Belt.MutableSet.Int.isEmpty values);
for key = 0 to 3 do
Belt.MutableSet.Int.remove values key
done;
assert_bool true (Belt.MutableSet.Int.isEmpty values);
Belt.MutableSet.Int.mergeMany values (shuffled_range 0 2_000);
Belt.MutableSet.Int.mergeMany values (shuffled_range 0 200);
assert_int 2_001 (Belt.MutableSet.Int.size values);
Belt.MutableSet.Int.removeMany values (shuffled_range 0 200);
assert_int 1_800 (Belt.MutableSet.Int.size values);
Belt.MutableSet.Int.removeMany values (shuffled_range 0 1000);
assert_int 1_000 (Belt.MutableSet.Int.size values);
Belt.MutableSet.Int.removeMany values (shuffled_range 0 1000);
assert_int 1_000 (Belt.MutableSet.Int.size values);
Belt.MutableSet.Int.removeMany values (shuffled_range 1000 1_500);
assert_int 500 (Belt.MutableSet.Int.size values);
Belt.MutableSet.Int.removeMany values (shuffled_range 1_500 1_999);
assert_int 1 (Belt.MutableSet.Int.size values);
assert_bool true (Belt.MutableSet.Int.has values 2_000);
Belt.MutableSet.Int.removeMany values (shuffled_range 1_500 3_000);
assert_bool true (Belt.MutableSet.Int.isEmpty values));
test "stats split and subset" (fun () ->
let values = of_array (shuffled_range 1000 2000) in
let removals =
Array.map (fun key -> Belt.MutableSet.Int.removeCheck values key) (shuffled_range 500 1499)
in
let removed_count = Array.fold_left (fun acc removed -> if removed then acc + 1 else acc) 0 removals in
assert_int 500 removed_count;
assert_int 501 (Belt.MutableSet.Int.size values);
let additions = Array.map (fun key -> Belt.MutableSet.Int.addCheck values key) (shuffled_range 500 2000) in
let added_count = Array.fold_left (fun acc added -> if added then acc + 1 else acc) 0 additions in
assert_int 1000 added_count;
assert_int 1501 (Belt.MutableSet.Int.size values);
assert_bool true (Belt.MutableSet.Int.isEmpty (Belt.MutableSet.Int.make ()));
assert_option Alcotest.int (Some 500) (Belt.MutableSet.Int.minimum values);
assert_option Alcotest.int (Some 2000) (Belt.MutableSet.Int.maximum values);
assert_undefined Alcotest.int (Some 500) (Belt.MutableSet.Int.minUndefined values);
assert_undefined Alcotest.int (Some 2000) (Belt.MutableSet.Int.maxUndefined values);
assert_int ((500 + 2000) / 2 * 1501) (Belt.MutableSet.Int.reduce values 0 (fun acc value -> acc + value));
assert_list Alcotest.int (Array.to_list (inclusive_range 500 2000)) (Belt.MutableSet.Int.toList values);
assert_array Alcotest.int (inclusive_range 500 2000) (Belt.MutableSet.Int.toArray values);
Belt.MutableSet.Int.checkInvariantInternal values;
assert_option Alcotest.int None (Belt.MutableSet.Int.get values 3);
assert_option Alcotest.int (Some 1200) (Belt.MutableSet.Int.get values 1200);
let (left, right), present = Belt.MutableSet.Int.split values 1000 in
assert_bool true present;
assert_array Alcotest.int (inclusive_range 500 999) (Belt.MutableSet.Int.toArray left);
assert_array Alcotest.int (inclusive_range 1001 2000) (Belt.MutableSet.Int.toArray right);
assert_bool true (Belt.MutableSet.Int.subset left values);
assert_bool true (Belt.MutableSet.Int.subset right values);
assert_bool true (Belt.MutableSet.Int.isEmpty (Belt.MutableSet.Int.intersect left right));
assert_bool true (Belt.MutableSet.Int.removeCheck values 1000);
let (left_missing, right_missing), present_missing = Belt.MutableSet.Int.split values 1000 in
assert_bool false present_missing;
assert_array Alcotest.int (inclusive_range 500 999) (Belt.MutableSet.Int.toArray left_missing);
assert_array Alcotest.int (inclusive_range 1001 2000) (Belt.MutableSet.Int.toArray right_missing));
test "set algebra and partitions" (fun () ->
let left = of_array (shuffled_range 0 100) in
let right = of_array (shuffled_range 40 120) in
assert_bool true
(Belt.MutableSet.Int.eq (Belt.MutableSet.Int.union left right) (of_array (inclusive_range 0 120)));
assert_bool true
(Belt.MutableSet.Int.eq
(Belt.MutableSet.Int.union (of_array (shuffled_range 0 20)) (of_array (shuffled_range 21 40)))
(of_array (inclusive_range 0 40)));
assert_bool true
(Belt.MutableSet.Int.eq (Belt.MutableSet.Int.intersect left right) (of_array (inclusive_range 40 100)));
assert_bool true
(Belt.MutableSet.Int.eq
(Belt.MutableSet.Int.intersect (of_array (shuffled_range 0 20)) (of_array (shuffled_range 21 40)))
(Belt.MutableSet.Int.make ()));
assert_bool true
(Belt.MutableSet.Int.eq
(Belt.MutableSet.Int.intersect (of_array [| 1; 3; 4; 5; 7; 9 |]) (of_array [| 2; 4; 5; 6; 8; 10 |]))
(of_array [| 4; 5 |]));
assert_bool true
(Belt.MutableSet.Int.eq (Belt.MutableSet.Int.diff left right) (of_array (inclusive_range 0 39)));
assert_bool true
(Belt.MutableSet.Int.eq (Belt.MutableSet.Int.diff right left) (of_array (inclusive_range 101 120)));
let values = of_array (shuffled_range 0 1000) in
let evens = Belt.MutableSet.Int.keep values (fun value -> value mod 2 = 0) in
let odds = Belt.MutableSet.Int.keep values (fun value -> value mod 2 <> 0) in
let evens_part, odds_part = Belt.MutableSet.Int.partition values (fun value -> value mod 2 = 0) in
assert_bool true (Belt.MutableSet.Int.eq evens evens_part);
assert_bool true (Belt.MutableSet.Int.eq odds odds_part);
List.iter Belt.MutableSet.Int.checkInvariantInternal [ values; evens; odds; evens_part; odds_part ]);
test "large add stress" (fun () ->
let values = Belt.MutableSet.Int.make () in
for key = 0 to 10_000 do
Belt.MutableSet.Int.add values key
done;
Belt.MutableSet.Int.checkInvariantInternal values;
for key = 0 to 10_000 do
assert_bool true (Belt.MutableSet.Int.has values key)
done;
assert_int 10_001 (Belt.MutableSet.Int.size values));
test "fromArray and removal stress" (fun () ->
let values = Belt.MutableSet.Int.make () in
Belt.MutableSet.Int.mergeMany values (Array.append (shuffled_range 30 100) (shuffled_range 40 120));
assert_int 91 (Belt.MutableSet.Int.size values);
assert_array Alcotest.int (inclusive_range 30 120) (Belt.MutableSet.Int.toArray values);
let values =
Belt.MutableSet.Int.fromArray (Array.append (shuffled_range 0 10_000) (shuffled_range 0 100))
in
assert_int 10_001 (Belt.MutableSet.Int.size values);
Array.iter (fun key -> Belt.MutableSet.Int.remove values key) (shuffled_range 5_000 8_000);
assert_int 7_000 (Belt.MutableSet.Int.size values);
Array.iter (fun key -> Belt.MutableSet.Int.remove values key) (shuffled_range 0 10_000);
assert_int 0 (Belt.MutableSet.Int.size values);
assert_bool true (Belt.MutableSet.Int.isEmpty values));
test "fromSortedArrayUnsafe and derived copies" (fun () ->
List.iter
(fun values ->
let set = Belt.MutableSet.Int.fromSortedArrayUnsafe values in
Belt.MutableSet.Int.checkInvariantInternal set;
assert_array Alcotest.int values (Belt.MutableSet.Int.toArray set))
[
[||];
[| 0 |];
[| 0; 1 |];
[| 0; 1; 2 |];
[| 0; 1; 2; 3 |];
[| 0; 1; 2; 3; 4 |];
[| 0; 1; 2; 3; 4; 5 |];
[| 0; 1; 2; 3; 4; 6 |];
[| 0; 1; 2; 3; 4; 6; 7 |];
[| 0; 1; 2; 3; 4; 6; 7; 8 |];
[| 0; 1; 2; 3; 4; 6; 7; 8; 9 |];
inclusive_range 0 1000;
];
let values = Belt.MutableSet.Int.fromArray (shuffled_range 0 1000) in
let copy = Belt.MutableSet.Int.keep values (fun value -> value mod 8 = 0) in
let keep_even, keep_odd = Belt.MutableSet.Int.partition values (fun value -> value mod 8 = 0) in
let rest = Belt.MutableSet.Int.keep values (fun value -> value mod 8 <> 0) in
for key = 0 to 200 do
Belt.MutableSet.Int.remove values key
done;
assert_int 126 (Belt.MutableSet.Int.size copy);
assert_array Alcotest.int (Array.init 126 (fun index -> index * 8)) (Belt.MutableSet.Int.toArray copy);
assert_int 800 (Belt.MutableSet.Int.size values);
assert_bool true (Belt.MutableSet.Int.eq copy keep_even);
assert_bool true (Belt.MutableSet.Int.eq rest keep_odd);
let values = Belt.MutableSet.Int.fromArray (shuffled_range 0 1000) in
let (left, right), _ = Belt.MutableSet.Int.split values 400 in
assert_bool true (Belt.MutableSet.Int.eq left (Belt.MutableSet.Int.fromArray (inclusive_range 0 399)));
assert_bool true (Belt.MutableSet.Int.eq right (Belt.MutableSet.Int.fromArray (inclusive_range 401 1000))));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableSet_String.ml
================================================
let suites =
[
( "MutableSet.String",
[
test "smoke" (fun () ->
let values = Belt.MutableSet.String.make () in
assert_bool true (Belt.MutableSet.String.addCheck values "a");
assert_bool false (Belt.MutableSet.String.addCheck values "a");
assert_array Alcotest.string [| "a" |] (Belt.MutableSet.String.toArray values));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_MutableStack.ml
================================================
type tree = { value : int; left : tree option; right : tree option }
let node ?left ?right value = { value; left; right }
let traversal_tree = node 1 ~left:(node 2 ~left:(node 4) ~right:(node 5)) ~right:(node 3)
let push_all_left start stack =
let current = ref start in
while Option.is_some !current do
let value = Option.get !current in
Belt.MutableStack.push stack value;
current := value.left
done
let in_order root =
let stack = Belt.MutableStack.make () in
let queue = Belt.MutableQueue.make () in
push_all_left (Some root) stack;
while not (Belt.MutableStack.isEmpty stack) do
match Belt.MutableStack.pop stack with
| None -> ()
| Some value ->
Belt.MutableQueue.add queue value.value;
push_all_left value.right stack
done;
Belt.MutableQueue.toArray queue
let in_order_dynamic root =
let stack = Belt.MutableStack.make () in
let queue = Belt.MutableQueue.make () in
push_all_left (Some root) stack;
Belt.MutableStack.dynamicPopIter stack (fun popped ->
Belt.MutableQueue.add queue popped.value;
push_all_left popped.right stack);
Belt.MutableQueue.toArray queue
let suites =
[
( "MutableStack",
[
test "inorder traversal" (fun () -> assert_array Alcotest.int [| 4; 2; 5; 1; 3 |] (in_order traversal_tree));
test "dynamicPopIter traversal" (fun () ->
assert_array Alcotest.int [| 4; 2; 5; 1; 3 |] (in_order_dynamic traversal_tree));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Option.ml
================================================
let suites =
[
( "Option",
[
test "keep" (fun () ->
assert_option Alcotest.int (Some 10) (Belt.Option.keep (Some 10) (fun x -> x > 5));
assert_option Alcotest.int None (Belt.Option.keep (Some 4) (fun x -> x > 5));
assert_option Alcotest.int None (Belt.Option.keep None (fun x -> x > 5)));
test "orElse" (fun () ->
assert_option Alcotest.int (Some 10) (Belt.Option.orElse (Some 10) (Some 20));
assert_option Alcotest.int (Some 20) (Belt.Option.orElse None (Some 20));
assert_option Alcotest.int None (Belt.Option.orElse None None));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Result.ml
================================================
let _result_alias_one : (string, string) result = Belt.Result.map (Ok "Test") (fun r -> "Value: " ^ r)
let _result_alias_two : string =
Belt.Result.getWithDefault (Belt.Result.map (Error "error") (fun r -> "Value: " ^ r)) "success"
let suites =
[
( "Result",
[
test "alias compatibility" (fun () ->
(match Belt.Result.map (Ok "Test") (fun r -> "Value: " ^ r) with
| Ok value -> assert_string "Value: Test" value
| Error _ -> Alcotest.fail "Expected Ok");
assert_string "success"
(Belt.Result.getWithDefault (Belt.Result.map (Error "error") (fun r -> "Value: " ^ r)) "success"));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Set.ml
================================================
let of_array values = Belt.Set.fromArray values ~id:(module IntCmp)
let suites =
[
( "Set",
[
test "remove add and minmax behavior" (fun () ->
let original = of_array (inclusive_range 0 30) in
let removed_zero = Belt.Set.remove original 0 in
let removed_zero_again = Belt.Set.remove removed_zero 0 in
let removed_tail = Belt.Set.remove removed_zero 30 in
let removed_mid = Belt.Set.remove removed_tail 20 in
let shuffled = shuffled_range 0 30 in
let added_back = Belt.Set.add removed_mid 3 in
let emptied = Belt.Set.removeMany added_back shuffled in
let rebuilt = Belt.Set.mergeMany emptied [| 0; 1; 2; 0 |] in
let emptied_again = Belt.Set.removeMany rebuilt [| 0; 1; 2; 3 |] in
let merged = Belt.Set.mergeMany emptied_again (shuffled_range 0 2_000) in
let merged_again = Belt.Set.mergeMany merged (shuffled_range 0 200) in
let removed_small = Belt.Set.removeMany merged_again (shuffled_range 0 200) in
let removed_medium = Belt.Set.removeMany removed_small (shuffled_range 0 1000) in
let removed_medium_again = Belt.Set.removeMany removed_medium (shuffled_range 0 1000) in
let removed_large = Belt.Set.removeMany removed_medium_again (shuffled_range 1000 1_500) in
let only_last = Belt.Set.removeMany removed_large (shuffled_range 1_500 1_999) in
let empty_final = Belt.Set.removeMany only_last (shuffled_range 2_000 2_100) in
assert_not_same_physical original removed_zero;
assert_same_physical removed_zero removed_zero_again;
assert_int 28 (Belt.Set.size removed_mid);
assert_undefined Alcotest.int (Some 29) (Belt.Set.maxUndefined removed_mid);
assert_undefined Alcotest.int (Some 1) (Belt.Set.minUndefined removed_mid);
assert_same_physical removed_mid added_back;
assert_bool true (Belt.Set.isEmpty emptied);
assert_int 3 (Belt.Set.size rebuilt);
assert_bool false (Belt.Set.isEmpty rebuilt);
assert_bool true (Belt.Set.isEmpty emptied_again);
assert_bool true (Belt.Set.has merged_again 20);
assert_bool true (Belt.Set.has merged_again 21);
assert_int 2_001 (Belt.Set.size merged_again);
assert_int 1_800 (Belt.Set.size removed_small);
assert_int 1_000 (Belt.Set.size removed_medium);
assert_int (Belt.Set.size removed_medium) (Belt.Set.size removed_medium_again);
assert_int 500 (Belt.Set.size removed_large);
assert_int 1 (Belt.Set.size only_last);
assert_bool true (Belt.Set.has only_last 2_000);
assert_bool false (Belt.Set.has only_last 500);
assert_bool true (Belt.Set.isEmpty empty_final));
test "union intersect diff subset and undefined access" (fun () ->
let left = of_array (shuffled_range 0 100) in
let right = of_array (shuffled_range 59 200) in
let unioned = Belt.Set.union left right in
let expected_union = of_array (inclusive_range 0 200) in
let intersected = Belt.Set.intersect left right in
let diff_left = Belt.Set.diff left right in
let diff_right = Belt.Set.diff right left in
let with_59 = Belt.Set.add diff_left 59 in
let singleton = Belt.Set.add (Belt.Set.make ~id:(module IntCmp)) 3 in
let even_values = of_array (Array.map (fun value -> value * 2) (shuffled_range 0 100)) in
let union_singleton = Belt.Set.union even_values singleton in
let expected_union_singleton =
Array.append (Array.map (fun value -> value * 2) (inclusive_range 0 100)) [| 3 |]
in
Array.sort compare expected_union_singleton;
assert_bool true (Belt.Set.eq unioned expected_union);
assert_array Alcotest.int expected_union_singleton (Belt.Set.toArray union_singleton);
assert_array Alcotest.int (inclusive_range 59 100) (Belt.Set.toArray intersected);
assert_array Alcotest.int (inclusive_range 0 58) (Belt.Set.toArray diff_left);
assert_bool true (Belt.Set.eq (Belt.Set.union right left) unioned);
assert_array Alcotest.int (inclusive_range 101 200) (Belt.Set.toArray diff_right);
assert_bool true (Belt.Set.subset diff_right right);
assert_bool false (Belt.Set.subset right diff_right);
assert_bool true (Belt.Set.subset diff_left left);
assert_bool true (Belt.Set.subset intersected left && Belt.Set.subset intersected right);
assert_undefined Alcotest.int (Some 47) (Belt.Set.getUndefined diff_left 47);
assert_option Alcotest.int (Some 47) (Belt.Set.get diff_left 47);
assert_undefined Alcotest.int None (Belt.Set.getUndefined diff_left 59);
assert_option Alcotest.int None (Belt.Set.get diff_left 59);
assert_int 60 (Belt.Set.size with_59);
assert_option Alcotest.int None (Belt.Set.minimum (Belt.Set.make ~id:(module IntCmp)));
assert_option Alcotest.int None (Belt.Set.maximum (Belt.Set.make ~id:(module IntCmp)));
assert_undefined Alcotest.int None (Belt.Set.minUndefined (Belt.Set.make ~id:(module IntCmp)));
assert_undefined Alcotest.int None (Belt.Set.maxUndefined (Belt.Set.make ~id:(module IntCmp))));
test "iteration every some cmp" (fun () ->
let values = of_array (shuffled_range 0 20) in
let removed = Belt.Set.remove values 17 in
let with_extra = Belt.Set.add removed 33 in
let collected = ref [] in
Belt.Set.forEach values (fun value -> collected := value :: !collected);
assert_list Alcotest.int (Array.to_list (inclusive_range 0 20)) (List.rev !collected);
assert_list Alcotest.int (Belt.Set.toList values) (Array.to_list (inclusive_range 0 20));
assert_bool true (Belt.Set.some values (fun value -> value = 17));
assert_bool false (Belt.Set.some removed (fun value -> value = 17));
assert_bool true (Belt.Set.every values (fun value -> value < 24));
assert_bool false (Belt.Set.every with_extra (fun value -> value < 24));
assert_bool false (Belt.Set.every (of_array [| 1; 2; 3 |]) (fun value -> value = 2));
assert_bool true (Belt.Set.cmp removed values < 0);
assert_bool true (Belt.Set.cmp values removed > 0));
test "keep partition getExn and split" (fun () ->
let values = of_array (shuffled_range 0 1000) in
let evens = Belt.Set.keep values (fun value -> value mod 2 = 0) in
let odds = Belt.Set.keep values (fun value -> value mod 2 <> 0) in
let evens_part, odds_part = Belt.Set.partition values (fun value -> value mod 2 = 0) in
assert_bool true (Belt.Set.eq evens evens_part);
assert_bool true (Belt.Set.eq odds odds_part);
assert_int 3 (Belt.Set.getExn values 3);
assert_int 4 (Belt.Set.getExn values 4);
assert_raises_any (fun () -> ignore (Belt.Set.getExn values 1002));
assert_raises_any (fun () -> ignore (Belt.Set.getExn values (-1)));
assert_int 1001 (Belt.Set.size values);
assert_bool false (Belt.Set.isEmpty values);
let (left, right), present = Belt.Set.split values 200 in
assert_bool true present;
assert_array Alcotest.int (inclusive_range 0 199) (Belt.Set.toArray left);
assert_list Alcotest.int (Array.to_list (inclusive_range 201 1000)) (Belt.Set.toList right);
let removed_200 = Belt.Set.remove values 200 in
let (left_missing, right_missing), present_missing = Belt.Set.split removed_200 200 in
assert_bool false present_missing;
assert_array Alcotest.int (inclusive_range 0 199) (Belt.Set.toArray left_missing);
assert_list Alcotest.int (Array.to_list (inclusive_range 201 1000)) (Belt.Set.toList right_missing);
assert_option Alcotest.int (Some 0) (Belt.Set.minimum left);
assert_option Alcotest.int (Some 201) (Belt.Set.minimum right));
test "empty keep and empty split" (fun () ->
let empty = Belt.Set.fromArray [||] ~id:(module IntCmp) in
assert_bool true (Belt.Set.isEmpty (Belt.Set.keep empty (fun value -> value mod 2 = 0)));
let (left, right), present = Belt.Set.split (Belt.Set.make ~id:(module IntCmp)) 0 in
assert_bool true (Belt.Set.isEmpty left);
assert_bool true (Belt.Set.isEmpty right);
assert_bool false present);
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Set_Dict.ml
================================================
let suites =
[
( "Set.Dict",
[
test "forEach and every" (fun () ->
let values = Belt.Set.fromArray (shuffled_range 0 20) ~id:(module IntCmp) in
let collected = ref [] in
Belt.Set.Dict.forEach (Belt.Set.getData values) (fun value -> collected := value :: !collected);
assert_list Alcotest.int (Array.to_list (inclusive_range 0 20)) (List.rev !collected);
assert_bool true (Belt.Set.Dict.every (Belt.Set.getData values) (fun value -> value < 24)));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Set_Int.ml
================================================
let of_array = Belt.Set.Int.fromArray
let suites =
[
( "Set.Int",
[
test "eq and partition" (fun () ->
assert_bool true (Belt.Set.Int.eq (of_array [| 1; 2; 3 |]) (of_array [| 3; 2; 1 |]));
let values = of_array (Array.append (inclusive_range 100 1000) (reverse_inclusive_range 400 1500)) in
assert_array Alcotest.int (inclusive_range 100 1500) (Belt.Set.Int.toArray values);
let left, right = Belt.Set.Int.partition values (fun value -> value mod 3 = 0) in
let expected_left = ref Belt.Set.Int.empty in
let expected_right = ref Belt.Set.Int.empty in
for value = 100 to 1500 do
if value mod 3 = 0 then expected_left := Belt.Set.Int.add !expected_left value
else expected_right := Belt.Set.Int.add !expected_right value
done;
assert_bool true (Belt.Set.Int.eq left !expected_left);
assert_bool true (Belt.Set.Int.eq right !expected_right));
test "set algebra" (fun () ->
assert_array Alcotest.int (inclusive_range 50 100)
(Belt.Set.Int.toArray
(Belt.Set.Int.intersect (of_array (inclusive_range 1 100)) (of_array (inclusive_range 50 200))));
assert_array Alcotest.int (inclusive_range 1 200)
(Belt.Set.Int.toArray
(Belt.Set.Int.union (of_array (inclusive_range 1 100)) (of_array (inclusive_range 50 200))));
assert_array Alcotest.int (inclusive_range 1 49)
(Belt.Set.Int.toArray
(Belt.Set.Int.diff (of_array (inclusive_range 1 100)) (of_array (inclusive_range 50 200))));
assert_array Alcotest.int (inclusive_range 50 100)
(Belt.Set.Int.toArray
(Belt.Set.Int.intersect
(of_array (reverse_inclusive_range 1 100))
(of_array (reverse_inclusive_range 50 200))));
assert_array Alcotest.int (inclusive_range 1 200)
(Belt.Set.Int.toArray
(Belt.Set.Int.union
(of_array (reverse_inclusive_range 1 100))
(of_array (reverse_inclusive_range 50 200))));
assert_array Alcotest.int (inclusive_range 1 49)
(Belt.Set.Int.toArray
(Belt.Set.Int.diff
(of_array (reverse_inclusive_range 1 100))
(of_array (reverse_inclusive_range 50 200)))));
test "min max reduce and emptiness" (fun () ->
let source = [| 1; 222; 3; 4; 2; 0; 33; -1 |] in
let values = of_array source in
assert_int (Array.fold_left ( + ) 0 source) (Belt.Set.Int.reduce values 0 (fun acc value -> acc + value));
assert_undefined Alcotest.int (Some (-1)) (Belt.Set.Int.minUndefined values);
assert_undefined Alcotest.int (Some 222) (Belt.Set.Int.maxUndefined values);
let values = Belt.Set.Int.remove values 3 in
assert_option Alcotest.int (Some (-1)) (Belt.Set.Int.minimum values);
assert_option Alcotest.int (Some 222) (Belt.Set.Int.maximum values);
let values = Belt.Set.Int.remove values 222 in
assert_option Alcotest.int (Some (-1)) (Belt.Set.Int.minimum values);
assert_option Alcotest.int (Some 33) (Belt.Set.Int.maximum values);
let values = Belt.Set.Int.remove values (-1) in
assert_option Alcotest.int (Some 0) (Belt.Set.Int.minimum values);
assert_option Alcotest.int (Some 33) (Belt.Set.Int.maximum values);
let values = Belt.Set.Int.remove values 0 in
let values = Belt.Set.Int.remove values 33 in
let values = Belt.Set.Int.remove values 2 in
let values = Belt.Set.Int.remove values 3 in
let values = Belt.Set.Int.remove values 4 in
let values = Belt.Set.Int.remove values 1 in
assert_bool true (Belt.Set.Int.isEmpty values));
test "invariant under large removals" (fun () ->
let shuffled = shuffled_range 0 10_000 in
let values = Belt.Set.Int.fromArray shuffled in
Belt.Set.Int.checkInvariantInternal values;
let removed = Array.sub shuffled 0 2000 in
let remaining = Array.fold_left (fun set value -> Belt.Set.Int.remove set value) values removed in
Belt.Set.Int.checkInvariantInternal values;
assert_bool true (Belt.Set.Int.eq (Belt.Set.Int.union (Belt.Set.Int.fromArray removed) remaining) values));
test "subset eq cmp get and add identity" (fun () ->
let base = Belt.Set.Int.fromArray (shuffled_range 0 100) in
let superset = Belt.Set.Int.fromArray (shuffled_range 0 200) in
let membership_set = Belt.Set.Int.fromArray (shuffled_range 0 500) in
let right_only = Belt.Set.Int.fromArray (shuffled_range 120 200) in
let unioned = Belt.Set.Int.union base right_only in
let checks = Array.map (fun value -> Belt.Set.Int.has membership_set value) (shuffled_range 200 700) in
let present_count = Array.fold_left (fun acc present -> if present then acc + 1 else acc) 0 checks in
assert_int 301 present_count;
assert_bool true (Belt.Set.Int.subset base superset);
assert_bool true (Belt.Set.Int.subset unioned superset);
assert_same_physical unioned (Belt.Set.Int.add unioned 200);
assert_same_physical unioned (Belt.Set.Int.add unioned 0);
assert_bool false (Belt.Set.Int.subset (Belt.Set.Int.add unioned 201) superset);
let equal_left = Belt.Set.Int.fromArray (shuffled_range 0 100) in
let equal_right = Belt.Set.Int.fromArray (shuffled_range 0 100) in
let with_extra = Belt.Set.Int.add equal_right 101 in
let removed = Belt.Set.Int.remove equal_right 99 in
let changed = Belt.Set.Int.add removed 101 in
assert_bool true (Belt.Set.Int.eq equal_left equal_right);
assert_bool false (Belt.Set.Int.eq equal_left with_extra);
assert_bool false (Belt.Set.Int.eq removed with_extra);
assert_bool false (Belt.Set.Int.eq equal_right changed);
assert_bool true (Belt.Set.Int.cmp equal_left equal_right = 0);
assert_bool true (Belt.Set.Int.cmp equal_left with_extra < 0);
assert_bool true
(Belt.Set.Int.cmp
(Belt.Set.Int.fromArray (shuffled_range 0 500))
(Belt.Set.Int.fromArray (shuffled_range 3 502))
> 0);
assert_option Alcotest.int (Some 30) (Belt.Set.Int.get (Belt.Set.Int.fromArray (shuffled_range 0 500)) 30);
assert_option Alcotest.int None (Belt.Set.Int.get (Belt.Set.Int.fromArray (shuffled_range 0 500)) 3000));
test "mergeMany removeMany and split" (fun () ->
let merged = Belt.Set.Int.mergeMany Belt.Set.Int.empty (shuffled_range 0 100) in
let trimmed = Belt.Set.Int.removeMany merged (shuffled_range 40 100) in
let expected_trimmed = Belt.Set.Int.fromArray (inclusive_range 0 39) in
let (left, right), present = Belt.Set.Int.split merged 40 in
assert_bool true (Belt.Set.Int.eq merged (Belt.Set.Int.fromArray (inclusive_range 0 100)));
assert_bool true (Belt.Set.Int.eq trimmed expected_trimmed);
assert_bool true present;
assert_bool true (Belt.Set.Int.eq expected_trimmed left);
let right_without_40 = Belt.Set.Int.remove (Belt.Set.Int.removeMany merged (inclusive_range 0 39)) 40 in
assert_bool true (Belt.Set.Int.eq right right_without_40);
let removed_40 = Belt.Set.Int.remove merged 40 in
let (left_missing, right_missing), present_missing = Belt.Set.Int.split removed_40 40 in
assert_bool false present_missing;
assert_bool true (Belt.Set.Int.eq left left_missing);
assert_bool true (Belt.Set.Int.eq right right_missing);
let single = Belt.Set.Int.removeMany right (inclusive_range 42 2000) in
assert_int 1 (Belt.Set.Int.size single);
assert_bool true (Belt.Set.Int.isEmpty (Belt.Set.Int.removeMany right (inclusive_range 0 2000))));
test "empty split" (fun () ->
let (left, right), present = Belt.Set.Int.split Belt.Set.Int.empty 0 in
assert_bool true (Belt.Set.Int.isEmpty left);
assert_bool true (Belt.Set.Int.isEmpty right);
assert_bool false present);
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Set_String.ml
================================================
let suites =
[
( "Set.String",
[
test "smoke" (fun () ->
assert_array Alcotest.string [| "a"; "b"; "c" |]
(Belt.Set.String.toArray (Belt.Set.String.fromArray [| "c"; "a"; "b"; "b" |])));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_SortArray.ml
================================================
let int_cmp = compare
let union xs ys =
let output = Belt.Array.makeUninitializedUnsafe (Array.length xs + Array.length ys) 0 in
let written = Belt.SortArray.union xs 0 (Array.length xs) ys 0 (Array.length ys) output 0 int_cmp in
Belt.Array.truncateToLengthUnsafe output written
let intersect xs ys =
let output = Belt.Array.makeUninitializedUnsafe (Array.length xs) 0 in
let written = Belt.SortArray.intersect xs 0 (Array.length xs) ys 0 (Array.length ys) output 0 int_cmp in
Belt.Array.truncateToLengthUnsafe output written
let diff xs ys =
let output = Belt.Array.makeUninitializedUnsafe (Array.length xs) 0 in
let written = Belt.SortArray.diff xs 0 (Array.length xs) ys 0 (Array.length ys) output 0 int_cmp in
Belt.Array.truncateToLengthUnsafe output written
let suites =
[
( "SortArray",
[
test "union" (fun () ->
assert_array Alcotest.int (inclusive_range 1 13) (union (inclusive_range 1 10) (inclusive_range 3 13));
assert_array Alcotest.int (inclusive_range 1 13) (union (inclusive_range 1 10) (inclusive_range 9 13));
assert_array Alcotest.int (inclusive_range 8 13) (union (inclusive_range 8 10) (inclusive_range 9 13));
assert_array Alcotest.int [| 0; 1; 2; 4; 5; 6; 7 |] (union (inclusive_range 0 2) (inclusive_range 4 7)));
test "intersect" (fun () ->
assert_array Alcotest.int (inclusive_range 3 10) (intersect (inclusive_range 1 10) (inclusive_range 3 13));
assert_array Alcotest.int (inclusive_range 9 10) (intersect (inclusive_range 1 10) (inclusive_range 9 13));
assert_array Alcotest.int (inclusive_range 9 10) (intersect (inclusive_range 8 10) (inclusive_range 9 13));
assert_array Alcotest.int [||] (intersect (inclusive_range 0 2) (inclusive_range 4 7)));
test "diff" (fun () ->
assert_array Alcotest.int [| 1; 2 |] (diff (inclusive_range 1 10) (inclusive_range 3 13));
assert_array Alcotest.int (inclusive_range 1 8) (diff (inclusive_range 1 10) (inclusive_range 9 13));
assert_array Alcotest.int [| 8 |] (diff (inclusive_range 8 10) (inclusive_range 9 13));
assert_array Alcotest.int [| 0; 1; 2 |] (diff (inclusive_range 0 2) (inclusive_range 4 7)));
test "isSorted and stableSortInPlace" (fun () ->
for upper = 0 to 200 do
let values = shuffled_range 0 upper in
Belt.SortArray.stableSortInPlaceBy values int_cmp;
assert_bool true (Belt.SortArray.isSorted values int_cmp)
done;
assert_bool true (Belt.SortArray.isSorted [||] int_cmp);
assert_bool true (Belt.SortArray.isSorted [| 0 |] int_cmp);
assert_bool true (Belt.SortArray.isSorted [| 0; 1 |] int_cmp);
assert_bool false (Belt.SortArray.isSorted [| 1; 0 |] int_cmp));
test "specialized stable sorts" (fun () ->
let values = shuffled_range 0 10_000 in
let copy1 = Array.copy values in
let copy2 = Array.copy values in
Belt.SortArray.stableSortInPlaceBy values int_cmp;
assert_bool true (Belt.SortArray.isSorted values int_cmp);
Belt.SortArray.Int.stableSortInPlace copy1;
assert_bool true (Belt.SortArray.isSorted copy1 int_cmp);
Belt.SortArray.stableSortInPlaceBy copy2 int_cmp;
assert_bool true (Belt.SortArray.isSorted copy2 int_cmp));
test "stableSortBy" (fun () ->
assert_array
(Alcotest.pair Alcotest.int Alcotest.string)
[| (1, "a"); (1, "b"); (2, "a") |]
(Belt.SortArray.stableSortBy
[| (1, "a"); (1, "b"); (2, "a") |]
(fun (left, _) (right, _) -> left - right));
assert_array
(Alcotest.pair Alcotest.int Alcotest.string)
[| (1, "b"); (1, "a"); (1, "b"); (2, "a") |]
(Belt.SortArray.stableSortBy
[| (1, "b"); (1, "a"); (1, "b"); (2, "a") |]
(fun (left, _) (right, _) -> left - right));
assert_array
(Alcotest.pair Alcotest.int Alcotest.string)
[| (1, "c"); (1, "b"); (1, "a"); (1, "b"); (1, "c"); (2, "a") |]
(Belt.SortArray.stableSortBy
[| (1, "c"); (1, "b"); (1, "a"); (1, "b"); (1, "c"); (2, "a") |]
(fun (left, _) (right, _) -> left - right)));
test "binarySearch" (fun () ->
assert_int 2 (lnot (Belt.SortArray.binarySearchBy [| 1; 3; 5; 7 |] 4 compare));
assert_int 4 (Belt.SortArray.binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 33 int_cmp);
assert_int 0 (Belt.SortArray.binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 1 int_cmp);
assert_int 1 (Belt.SortArray.binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 2 int_cmp);
assert_int 2 (Belt.SortArray.binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 3 int_cmp);
assert_int 3 (Belt.SortArray.binarySearchBy [| 1; 2; 3; 4; 33; 35; 36 |] 4 int_cmp);
let values = inclusive_range 0 1000 in
for index = 0 to 1000 do
assert_int index (Belt.SortArray.binarySearchBy values index int_cmp)
done;
let evens = Array.map (fun value -> value * 2) (inclusive_range 0 2000) in
assert_int 2001 (lnot (Belt.SortArray.binarySearchBy evens 5000 int_cmp));
assert_int 0 (lnot (Belt.SortArray.binarySearchBy evens (-1) int_cmp));
assert_int 0 (Belt.SortArray.binarySearchBy evens 0 int_cmp);
assert_int 1 (lnot (Belt.SortArray.binarySearchBy evens 1 int_cmp));
for index = 0 to 1999 do
assert_int (index + 1) (lnot (Belt.SortArray.binarySearchBy evens ((2 * index) + 1) int_cmp))
done);
test "strictlySortedLength" (fun () ->
let less left right = left < right in
assert_int 0 (Belt.SortArray.strictlySortedLength [||] less);
assert_int 1 (Belt.SortArray.strictlySortedLength [| 1 |] less);
assert_int 1 (Belt.SortArray.strictlySortedLength [| 1; 1 |] less);
assert_int 1 (Belt.SortArray.strictlySortedLength [| 1; 1; 2 |] less);
assert_int 2 (Belt.SortArray.strictlySortedLength [| 1; 2 |] less);
assert_int 4 (Belt.SortArray.strictlySortedLength [| 1; 2; 3; 4; 3 |] less);
assert_int 1 (Belt.SortArray.strictlySortedLength [| 4; 4; 3; 2; 1 |] less);
assert_int (-4) (Belt.SortArray.strictlySortedLength [| 4; 3; 2; 1 |] less);
assert_int (-5) (Belt.SortArray.strictlySortedLength [| 4; 3; 2; 1; 0 |] less));
] );
]
================================================
FILE: packages/Belt/test/Test_Belt_SortArray_Int.ml
================================================
let suites =
[
("SortArray.Int", [ test "binarySearch" (fun () -> assert_int 1 (Belt.SortArray.Int.binarySearch [| 1; 3; 5 |] 3)) ]);
]
================================================
FILE: packages/Belt/test/Test_Belt_SortArray_String.ml
================================================
let suites =
[
( "SortArray.String",
[ test "binarySearch" (fun () -> assert_int 1 (Belt.SortArray.String.binarySearch [| "a"; "c"; "e" |] "c")) ] );
]
================================================
FILE: packages/Belt/test/Test_Belt_Support.ml
================================================
let test title fn = Alcotest.test_case title `Quick fn
let slow_test title fn = Alcotest.test_case title `Slow fn
let float = Alcotest.float 0.
let assert_string expected actual = Alcotest.check Alcotest.string "should be equal" expected actual
let assert_int expected actual = Alcotest.check Alcotest.int "should be equal" expected actual
let assert_float expected actual = Alcotest.check float "should be equal" expected actual
let assert_bool expected actual = Alcotest.check Alcotest.bool "should be equal" expected actual
let assert_option ty expected actual = Alcotest.check (Alcotest.option ty) "should be equal" expected actual
let assert_array ty expected actual = Alcotest.check (Alcotest.array ty) "should be equal" expected actual
let assert_list ty expected actual = Alcotest.check (Alcotest.list ty) "should be equal" expected actual
let assert_pair left_ty right_ty expected actual =
Alcotest.check (Alcotest.pair left_ty right_ty) "should be equal" expected actual
let assert_array_unordered ty expected actual =
let expected = Array.copy expected in
let actual = Array.copy actual in
Array.sort compare expected;
Array.sort compare actual;
Alcotest.check (Alcotest.array ty) "should be equal" expected actual
let assert_same_physical left right = assert_bool true (left == right)
let assert_not_same_physical left right = assert_bool false (left == right)
let assert_undefined ty expected actual = assert_option ty expected (Js.Undefined.toOption actual)
let assert_raises_any f = match f () with exception _ -> () | _ -> Alcotest.fail "Expected an exception"
let assert_raises_js_error f =
match f () with exception Js.Exn.Error _ -> () | _ -> Alcotest.fail "Expected Js.Exn.Error"
let inclusive_range start finish = if finish < start then [||] else Array.init (finish - start + 1) (fun i -> start + i)
let reverse_inclusive_range start finish = inclusive_range start finish |> Array.to_list |> List.rev |> Array.of_list
let arithmetic_sum start finish = if finish < start then 0 else (start + finish) * (finish - start + 1) / 2
let shuffled_copy values =
let values = Array.copy values in
let state = Random.State.make [| 0x5eed; Array.length values |] in
for i = Array.length values - 1 downto 1 do
let j = Random.State.int state (i + 1) in
let tmp = values.(i) in
values.(i) <- values.(j);
values.(j) <- tmp
done;
values
let shuffled_range start finish = shuffled_copy (inclusive_range start finish)
let shuffled_pairs start finish = Array.map (fun i -> (i, i)) (shuffled_range start finish)
module IntCmp = Belt.Id.MakeComparable (struct
type t = int
let cmp = compare
end)
module IntCmpDesc = Belt.Id.MakeComparable (struct
type t = int
let cmp left right = compare right left
end)
module StringCmp = Belt.Id.MakeComparable (struct
type t = string
let cmp = compare
end)
module IntHash = Belt.Id.MakeHashable (struct
type t = int
let hash = Hashtbl.hash
let eq = ( = )
end)
module CollidingIntHash = Belt.Id.MakeHashable (struct
type t = int
let hash _ = 0
let eq = ( = )
end)
================================================
FILE: packages/Belt/test/benchmark.ml
================================================
open Bechamel
(* From our function [make_list], we make an indexed (by [args]) test. It's a list
of tests which are applied with [args] such as:
{[
let test =
[ make_list 0
; make_list 10
; make_list 100
; make_list 400
; make_list 1000 ]
]} *)
let static_array = [| 33 |]
let test =
Test.make_indexed ~name:"Belt.Array.push" ~fmt:"%s %d" ~args:[ 0; 100; 500; 1000; 10000 ] (fun words ->
Staged.stage @@ fun () -> Belt.Array.push static_array words)
(* From our test, we can start to benchmark it!
A benchmark is a /run/ of your test multiple times. From results given by
[Benchmark.all], an analyse is needed to infer measures of one call of your
test.
[Bechamel] asks 3 things:
- what you want to record (see [instances])
- how you want to analyse (see [ols])
- how you want to benchmark your test (see [cfg])
The core of [Bechamel] (see [Bechamel.Toolkit]) has some possible measures
such as the [monotonic-clock] to see time performances.
The analyse can be OLS (Ordinary Least Square) or RANSAC. In this example, we
use only one.
Finally, to launch the benchmark, we need some others details such as:
- should we stabilise the GC?
- how many /run/ you want
- the maximum of time allowed by the benchmark
- etc.
[raw_results] is what the benchmark produced. [results] is what the analyse
can infer. The first one is used to show graphs or to let the user (with
[Measurement_raw]) to infer something else than what [ols] did. The second is
mostly what you want: a synthesis of /samples/. *)
let benchmark () =
let ols = Analyze.ols ~bootstrap:0 ~r_square:true ~predictors:Measure.[| run |] in
let instances = Toolkit.Instance.[ minor_allocated; major_allocated; monotonic_clock ] in
let cfg = Benchmark.cfg ~limit:2000 ~quota:(Time.second 0.5) ~kde:(Some 1000) () in
let raw_results = Benchmark.all cfg instances test in
let results = List.map (fun instance -> Analyze.all ols instance raw_results) instances in
let results = Analyze.merge ols instances results in
(results, raw_results)
let () =
List.iter
(fun v -> Bechamel_notty.Unit.add v (Measure.unit v))
Toolkit.Instance.[ minor_allocated; major_allocated; monotonic_clock ]
let img (window, results) = Bechamel_notty.Multiple.image_of_ols_results ~rect:window ~predictor:Measure.run results
open Notty_unix
let () =
let window =
match winsize Unix.stdout with Some (w, h) -> { Bechamel_notty.w; h } | None -> { Bechamel_notty.w = 80; h = 1 }
in
let results, _ = benchmark () in
img (window, results) |> eol |> output_image
================================================
FILE: packages/Belt/test/dune
================================================
(library
(name test_belt_support)
(wrapped false)
(modules Test_Belt_Support)
(libraries alcotest fmt belt js))
(test
(name test)
(modules
(:standard \ Test_Belt_Support))
(flags
(:standard -open Test_Belt_Support))
(libraries alcotest fmt belt js test_belt_support))
; (executable
; (name benchmark)
; (modules benchmark)
; (public_name belt_benchmark)
; (libraries belt bechamel-notty bechamel unix notty.unix))
================================================
FILE: packages/Belt/test/test.ml
================================================
let () =
Alcotest.run "Belt"
(Test_Belt_Int.suites @ Test_Belt_Float.suites @ Test_Belt_Option.suites @ Test_Belt_Result.suites
@ Test_Belt_Array.suites @ Test_Belt_List.suites @ Test_Belt_Map.suites @ Test_Belt_Map_Dict.suites
@ Test_Belt_Map_Int.suites @ Test_Belt_Map_String.suites @ Test_Belt_Set.suites @ Test_Belt_Set_Dict.suites
@ Test_Belt_Set_Int.suites @ Test_Belt_Set_String.suites @ Test_Belt_SortArray.suites
@ Test_Belt_SortArray_Int.suites @ Test_Belt_SortArray_String.suites @ Test_Belt_MutableMap.suites
@ Test_Belt_MutableMap_Int.suites @ Test_Belt_MutableMap_String.suites @ Test_Belt_MutableSet.suites
@ Test_Belt_MutableSet_Int.suites @ Test_Belt_MutableSet_String.suites @ Test_Belt_HashMap.suites
@ Test_Belt_HashMap_Int.suites @ Test_Belt_HashMap_String.suites @ Test_Belt_HashSet_Int.suites
@ Test_Belt_HashSet_String.suites @ Test_Belt_MutableQueue.suites @ Test_Belt_MutableStack.suites)
================================================
FILE: packages/Dom/Dom.ml
================================================
type _baseClass
type animation (* Web Animations API *)
(* TODO: Should we bother with this indirection?
(* core *)
type domString = string
type domTimestamp = float
*)
(* css *)
type cssStyleDeclaration
type cssStyleSheet
(* events (early) *)
type 'a eventTarget_like
type eventTarget = _baseClass eventTarget_like
(* nodes *)
type 'a _node
type 'a node_like = 'a _node eventTarget_like
type node = _baseClass node_like
type _attr
type attr = _attr node_like
type 'a _characterData
type 'a characterData_like = 'a _characterData node_like
type characterData = _baseClass characterData_like
type _cdataSection
type cdataSection = _cdataSection characterData_like
type _comment
type comment = _comment characterData_like
type 'a _document
type 'a document_like = 'a _document node_like
type document = _baseClass document_like
type _documentFragment
type documentFragment = _documentFragment node_like
type _documentType
type documentType = _documentType node_like
type domImplementation
type 'a _element
type 'a element_like = 'a _element node_like
type element = _baseClass element_like
type htmlCollection
type htmlFormControlsCollection
type htmlOptionsCollection
type intersectionObserver
type intersectionObserverEntry
type mutationObserver
type mutationRecord
type performanceObserver
type performanceObserverEntryList
type reportingObserver
type reportingObserverOptions
type resizeObserver
type resizeObserverEntry
type namedNodeMap
type nodeList
type radioNodeList
type processingInstruction
type _shadowRoot
type shadowRoot = _shadowRoot node_like
type _text
type text = _text characterData_like
(* geometry *)
type domRect
(* html *)
type dataTransfer (* Drag and Drop API *)
type domStringMap
type history
type _htmlDocument
type htmlDocument = _htmlDocument document_like
type 'a _htmlElement
type 'a htmlElement_like = 'a _htmlElement element_like
type htmlElement = _baseClass htmlElement_like
type _htmlAnchorElement
type htmlAnchorElement = _htmlAnchorElement htmlElement_like
type _htmlAreaElement
type htmlAreaElement = _htmlAreaElement htmlElement_like
type _htmlAudioElement
type htmlAudioElement = _htmlAudioElement htmlElement_like
type _htmlBaseElement
type htmlBaseElement = _htmlBaseElement htmlElement_like
type _htmlBodyElement
type htmlBodyElement = _htmlBodyElement htmlElement_like
type _htmlBrElement
type htmlBrElement = _htmlBrElement htmlElement_like
type _htmlButtonElement
type htmlButtonElement = _htmlButtonElement htmlElement_like
type _htmlCanvasElement
type htmlCanvasElement = _htmlCanvasElement htmlElement_like
type _htmlDataElement
type htmlDataElement = _htmlDataElement htmlElement_like
type _htmlDataListElement
type htmlDataListElement = _htmlDataListElement htmlElement_like
type _htmlDialogElement
type htmlDialogElement = _htmlDialogElement htmlElement_like
type _htmlDivElement
type htmlDivElement = _htmlDivElement htmlElement_like
type _htmlDlistElement
type htmlDlistElement = _htmlDlistElement htmlElement_like
type _htmlEmbedElement
type htmlEmbedElement = _htmlEmbedElement htmlElement_like
type _htmlFieldSetElement
type htmlFieldSetElement = _htmlFieldSetElement htmlElement_like
type _htmlFormElement
type htmlFormElement = _htmlFormElement htmlElement_like
type _htmlHeadElement
type htmlHeadElement = _htmlHeadElement htmlElement_like
type _htmlHeadingElement
type htmlHeadingElement = _htmlHeadingElement htmlElement_like
type _htmlHrElement
type htmlHrElement = _htmlHrElement htmlElement_like
type _htmlHtmlElement
type htmlHtmlElement = _htmlHtmlElement htmlElement_like
type _htmlIframeElement
type htmlIframeElement = _htmlIframeElement htmlElement_like
type _htmlImageElement
type htmlImageElement = _htmlImageElement htmlElement_like
type _htmlInputElement
type htmlInputElement = _htmlInputElement htmlElement_like
type _htmlLabelElement
type htmlLabelElement = _htmlLabelElement htmlElement_like
type _htmlLegendElement
type htmlLegendElement = _htmlLegendElement htmlElement_like
type _htmlLiElement
type htmlLiElement = _htmlLiElement htmlElement_like
type _htmlLinkElement
type htmlLinkElement = _htmlLinkElement htmlElement_like
type _htmlMapElement
type htmlMapElement = _htmlMapElement htmlElement_like
type _htmlMediaElement
type htmlMediaElement = _htmlMediaElement htmlElement_like
type _htmlMenuElement
type htmlMenuElement = _htmlMenuElement htmlElement_like
type _htmlMetaElement
type htmlMetaElement = _htmlMetaElement htmlElement_like
type _htmlMeterElement
type htmlMeterElement = _htmlMeterElement htmlElement_like
type _htmlModElement
type htmlModElement = _htmlModElement htmlElement_like
type _htmlOListElement
type htmlOListElement = _htmlOListElement htmlElement_like
type _htmlObjectElement
type htmlObjectElement = _htmlObjectElement htmlElement_like
type _htmlOptGroupElement
type htmlOptGroupElement = _htmlOptGroupElement htmlElement_like
type _htmlOptionElement
type htmlOptionElement = _htmlOptionElement htmlElement_like
type _htmlOutputElement
type htmlOutputElement = _htmlOutputElement htmlElement_like
type _htmlParagraphElement
type htmlParagraphElement = _htmlParagraphElement htmlElement_like
type _htmlParamElement
type htmlParamElement = _htmlParamElement htmlElement_like
type _htmlPreElement
type htmlPreElement = _htmlPreElement htmlElement_like
type _htmlProgressElement
type htmlProgressElement = _htmlProgressElement htmlElement_like
type _htmlQuoteElement
type htmlQuoteElement = _htmlQuoteElement htmlElement_like
type _htmlScriptElement
type htmlScriptElement = _htmlScriptElement htmlElement_like
type _htmlSelectElement
type htmlSelectElement = _htmlSelectElement htmlElement_like
type _htmlSlotElement
type htmlSlotElement = _htmlSlotElement htmlElement_like
type _htmlSourceElement
type htmlSourceElement = _htmlSourceElement htmlElement_like
type _htmlSpanElement
type htmlSpanElement = _htmlSpanElement htmlElement_like
type _htmlStyleElement
type htmlStyleElement = _htmlStyleElement htmlElement_like
type _htmlTableCaptionElement
type htmlTableCaptionElement = _htmlTableCaptionElement htmlElement_like
type _htmlTableCellElement
type htmlTableCellElement = _htmlTableCellElement htmlElement_like
type _htmlTableColElement
type htmlTableColElement = _htmlTableColElement htmlElement_like
type _htmlTableDataCellElement
type htmlTableDataCellElement = _htmlTableDataCellElement htmlElement_like
type _htmlTableElement
type htmlTableElement = _htmlTableElement htmlElement_like
type _htmlTableHeaderCellElement
type htmlTableHeaderCellElement = _htmlTableHeaderCellElement htmlElement_like
type _htmlTableRowElement
type htmlTableRowElement = _htmlTableRowElement htmlElement_like
type _htmlTableSectionElement
type htmlTableSectionElement = _htmlTableSectionElement htmlElement_like
type _htmlTextAreaElement
type htmlTextAreaElement = _htmlTextAreaElement htmlElement_like
type _htmlTimeElement
type htmlTimeElement = _htmlTimeElement htmlElement_like
type _htmlTitleElement
type htmlTitleElement = _htmlTitleElement htmlElement_like
type _htmlTrackElement
type htmlTrackElement = _htmlTrackElement htmlElement_like
type _htmlUlistElement
type htmlUlistElement = _htmlUlistElement htmlElement_like
type _htmlUnknownElement
type htmlUnknownElement = _htmlUnknownElement htmlElement_like
type _htmlVideoElement
type htmlVideoElement = _htmlVideoElement htmlElement_like
type location
type window
type _xmlDocument
type xmlDocument = _xmlDocument document_like
(* events *)
type 'a event_like
type event = _baseClass event_like
type 'a _uiEvent
type 'a uiEvent_like = 'a _uiEvent event_like
type uiEvent = _baseClass uiEvent_like
type _animationEvent
type animationEvent = _animationEvent event_like
type _beforeUnloadEvent
type beforeUnloadEvent = _beforeUnloadEvent event_like
type _clipboardEvent
type clipboardEvent = _clipboardEvent event_like
type _closeEvent
type closeEvent = _closeEvent event_like
type _compositionEvent
type compositionEvent = _compositionEvent uiEvent_like
type _customEvent
type customEvent = _customEvent event_like
type _dragEvent
type dragEvent = _dragEvent event_like
type _errorEvent
type errorEvent = _errorEvent event_like
type _focusEvent
type focusEvent = _focusEvent uiEvent_like
type _idbVersionChangeEvent
type idbVersionChangeEvent = _idbVersionChangeEvent event_like
type _inputEvent
type inputEvent = _inputEvent uiEvent_like
type _keyboardEvent
type keyboardEvent = _keyboardEvent uiEvent_like
type 'a _mouseEvent
type 'a mouseEvent_like = 'a _mouseEvent uiEvent_like
type mouseEvent = _baseClass mouseEvent_like
type _pageTransitionEvent
type pageTransitionEvent = _pageTransitionEvent event_like
type _pointerEvent
type pointerEvent = _pointerEvent mouseEvent_like
type _popStateEvent
type popStateEvent = _popStateEvent event_like
type _progressEvent
type progressEvent = _progressEvent event_like
type _relatedEvent
type relatedEvent = _relatedEvent event_like
type _storageEvent
type storageEvent = _storageEvent event_like
type _svgZoomEvent
type svgZoomEvent = _svgZoomEvent event_like
type _timeEvent
type timeEvent = _timeEvent event_like
type _touchEvent
type touchEvent = _touchEvent uiEvent_like
type _trackEvent
type trackEvent = _trackEvent event_like
type _transitionEvent
type transitionEvent = _transitionEvent event_like
type _webGlContextEvent
type webGlContextEvent = _webGlContextEvent event_like
type _wheelEvent
type wheelEvent = _wheelEvent uiEvent_like
(* ranges *)
type range
(* selection (TODO: move out of dom?) *)
type selection
(* sets *)
type domTokenList
type domSettableTokenList
(* traversal *)
type nodeFilter = {
acceptNode : element -> int; (* return type should be NodeFilter.action, but that would create a cycle *)
}
type nodeIterator
type treeWalker
(* SVG *)
type svgRect
type svgPoint
(* special *)
type eventPointerId
module Storage = Dom_storage
================================================
FILE: packages/Dom/Dom_storage.ml
================================================
type t
external getItem : string -> string option = "getItem" [@@mel.send.pipe: t] [@@mel.return null_to_opt]
external setItem : string -> string -> unit = "setItem" [@@mel.send.pipe: t]
external removeItem : string -> unit = "removeItem" [@@mel.send.pipe: t]
external clear : unit = "clear" [@@mel.send.pipe: t]
external key : int -> string option = "key" [@@mel.send.pipe: t] [@@mel.return null_to_opt]
external length : t -> int = "length" [@@mel.get]
(* external localStorage : t = "localStorage" *)
(* external sessionStorage : t = "sessionStorage" *)
================================================
FILE: packages/Dom/dune
================================================
(library
(name dom)
(public_name server-reason-react.dom)
(preprocess
(pps melange_native_ppx)))
================================================
FILE: packages/Js/lib/Js.ml
================================================
(** The Js equivalent library (very unsafe) *)
include Js_internal
type 'a t = < .. > as 'a
module Fn = struct
type 'a arity0 = { i0 : unit -> 'a [@internal] }
type 'a arity1 = { i1 : 'a [@internal] }
type 'a arity2 = { i2 : 'a [@internal] }
type 'a arity3 = { i3 : 'a [@internal] }
type 'a arity4 = { i4 : 'a [@internal] }
type 'a arity5 = { i5 : 'a [@internal] }
type 'a arity6 = { i6 : 'a [@internal] }
type 'a arity7 = { i7 : 'a [@internal] }
type 'a arity8 = { i8 : 'a [@internal] }
type 'a arity9 = { i9 : 'a [@internal] }
type 'a arity10 = { i10 : 'a [@internal] }
type 'a arity11 = { i11 : 'a [@internal] }
type 'a arity12 = { i12 : 'a [@internal] }
type 'a arity13 = { i13 : 'a [@internal] }
type 'a arity14 = { i14 : 'a [@internal] }
type 'a arity15 = { i15 : 'a [@internal] }
type 'a arity16 = { i16 : 'a [@internal] }
type 'a arity17 = { i17 : 'a [@internal] }
type 'a arity18 = { i18 : 'a [@internal] }
type 'a arity19 = { i19 : 'a [@internal] }
type 'a arity20 = { i20 : 'a [@internal] }
type 'a arity21 = { i21 : 'a [@internal] }
type 'a arity22 = { i22 : 'a [@internal] }
end
(**/**)
(* module MapperRt = Js_mapperRt *)
module Internal = struct
(* open Fn *)
(* Use opaque instead of [._n] to prevent some optimizations happening *)
end
(**/**)
type +'a null = 'a Js_internal.null
type +'a undefined = 'a Js_internal.undefined
type +'a nullable = 'a Js_internal.nullable
external toOption : 'a null -> 'a option = "%identity"
external nullToOption : 'a null -> 'a option = "%identity"
external undefinedToOption : 'a null -> 'a option = "%identity"
external fromOpt : 'a option -> 'a undefined = "%identity"
(** The same as [empty] {!Js.Undefined} will be compiled as [undefined]*)
let undefined = None
(** The same as [empty] in {!Js.Null} will be compiled as [null]*)
let null = None
let empty = None
type (+'a, +'e) promise
(* external eqNull : 'a -> 'a null -> bool = "%bs_equal_null" *)
(* let eqNull : 'a -> 'a null -> bool = fun x -> x == None *)
(* external eqUndefined : 'a -> 'a undefined -> bool = "%bs_equal_undefined" *)
(* let eqUndefined : 'a -> 'a undefined -> bool = function
| Some _ -> false
| None -> true *)
(* external eqNullable : 'a -> 'a nullable -> bool = "%bs_equal_nullable" *)
(* let eqNullable : 'a -> 'a nullable -> bool = function
| Some _ -> false
| None -> true *)
(** [typeof x] will be compiled as [typeof x] in JS Please consider functions in {!Types} for a type safe way of
reflection *)
let typeof _ = notImplemented "Js" "typeof"
(** {4 operators}*)
(* external unsafe_lt : 'a -> 'a -> bool = "#unsafe_lt" *)
(** [unsafe_lt a b] will be compiled as [a < b]. It is marked as unsafe, since it is impossible to give a proper
semantics for comparision which applies to any type *)
(* external unsafe_le : 'a -> 'a -> bool = "#unsafe_le" *)
(** [unsafe_le a b] will be compiled as [a <= b]. See also {!unsafe_lt} *)
(* external unsafe_gt : 'a -> 'a -> bool = "#unsafe_gt" *)
(** [unsafe_gt a b] will be compiled as [a > b]. See also {!unsafe_lt} *)
(* external unsafe_ge : 'a -> 'a -> bool = "#unsafe_ge" *)
(** [unsafe_ge a b] will be compiled as [a >= b]. See also {!unsafe_lt} *)
(** {12 nested modules}*)
module Null = Js_null
module Undefined = Js_undefined
module Nullable = Js_nullable
module Null_undefined = Nullable
module Exn = Js_exn
module Array = Js_array
module Re = Js_re
module String = Js_string
module Promise = Js_promise
module Date = Js_date
module Dict = Js_dict
module Global = Js_global
module Types = Js_types
module Json = Js_json
module Math = Js_math
module Obj = Js_obj
module Typed_array = Js_typed_array
module TypedArray2 = Js_typed_array2
module Float = Js_float
module Int = Js_int
module Bigint = Js_bigint
module Vector = Js_vector
module Console = Js_console
let log = Console.log
let log2 = Console.log2
let log3 = Console.log3
let log4 = Console.log4
let logMany = Console.logMany
module Set = Js_set
module WeakSet = Js_weakset
module Map = Js_map
module WeakMap = Js_weakmap
module FormData = Js_formdata
================================================
FILE: packages/Js/lib/Js.mli
================================================
(** The Js equivalent library (very unsafe) *)
include module type of Js_internal
type 'a t = 'a constraint 'a = < .. >
module Fn : sig
type 'a arity0 = { i0 : unit -> 'a }
type 'a arity1 = { i1 : 'a }
type 'a arity2 = { i2 : 'a }
type 'a arity3 = { i3 : 'a }
type 'a arity4 = { i4 : 'a }
type 'a arity5 = { i5 : 'a }
type 'a arity6 = { i6 : 'a }
type 'a arity7 = { i7 : 'a }
type 'a arity8 = { i8 : 'a }
type 'a arity9 = { i9 : 'a }
type 'a arity10 = { i10 : 'a }
type 'a arity11 = { i11 : 'a }
type 'a arity12 = { i12 : 'a }
type 'a arity13 = { i13 : 'a }
type 'a arity14 = { i14 : 'a }
type 'a arity15 = { i15 : 'a }
type 'a arity16 = { i16 : 'a }
type 'a arity17 = { i17 : 'a }
type 'a arity18 = { i18 : 'a }
type 'a arity19 = { i19 : 'a }
type 'a arity20 = { i20 : 'a }
type 'a arity21 = { i21 : 'a }
type 'a arity22 = { i22 : 'a }
end
external toOption : 'a null -> 'a option = "%identity"
external nullToOption : 'a null -> 'a option = "%identity"
external undefinedToOption : 'a null -> 'a option = "%identity"
external fromOpt : 'a option -> 'a undefined = "%identity"
val undefined : 'a option
val null : 'a option
val empty : 'a option
type (+'a, +'e) promise
val typeof : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
module Null : module type of Js_null
module Undefined : module type of Js_undefined
module Nullable : module type of Js_nullable
module Null_undefined = Nullable
module Exn : module type of Js_exn
module Array : module type of Js_array
module Re : module type of Js_re
module String : module type of Js_string
module Promise : module type of Js_promise
module Date : module type of Js_date
module Dict : module type of Js_dict
module Global : module type of Js_global
module Types : module type of Js_types
module Json : module type of Js_json
module Math : module type of Js_math
module Obj : module type of Js_obj
module Typed_array : module type of Js_typed_array
module TypedArray2 : module type of Js_typed_array2
module Float : module type of Js_float
module Int : module type of Js_int
module Bigint : module type of Js_bigint
module Vector : module type of Js_vector
module Console : module type of Js_console
val log : string -> unit
val log2 : string -> string -> unit
val log3 : string -> string -> string -> unit
val log4 : string -> string -> string -> string -> unit
val logMany : string array -> unit
module Set : module type of Js_set
module WeakSet : module type of Js_weakset
module Map : module type of Js_map
module WeakMap : module type of Js_weakmap
module FormData : module type of Js_formdata
================================================
FILE: packages/Js/lib/Js_array.ml
================================================
(** JavaScript Array API *)
type 'a t = 'a array
type 'a array_like
let from _ = Js_internal.notImplemented "Js.Array" "from"
let fromMap _ ~f:_ = Js_internal.notImplemented "Js.Array" "fromMap"
(* This doesn't behave the same as melange-js, since it's a runtime check so lists are represented as arrays in the runtime: isArray([1, 2]) == true *)
let isArray (_arr : 'a) = true
let length arr = Stdlib.Array.length arr
(* Mutator functions *)
let copyWithin ~to_:_ ?start:_ ?end_:_ _ = Js_internal.notImplemented "Js.Array" "copyWithin"
let fill ~value:_ ?start:_ ?end_:_ _ = Js_internal.notImplemented "Js.Array" "fill"
let pop _ = Js_internal.notImplemented "Js.Array" "pop"
let push ~value:_ _ = Js_internal.notImplemented "Js.Array" "push"
let pushMany ~values:_ _ = Js_internal.notImplemented "Js.Array" "pushMany"
let reverseInPlace _ = Js_internal.notImplemented "Js.Array" "reverseInPlace"
let sortInPlace _ = Js_internal.notImplemented "Js.Array" "sortInPlace"
let sortInPlaceWith ~f:_ _ = Js_internal.notImplemented "Js.Array" "sortInPlaceWith"
let spliceInPlace ~start:_ ~remove:_ ~add:_ _ = Js_internal.notImplemented "Js.Array" "spliceInPlace"
let removeFromInPlace ~start:_ _ = Js_internal.notImplemented "Js.Array" "removeFromInPlace"
let removeCountInPlace ~start:_ ~count:_ _ = Js_internal.notImplemented "Js.Array" "removeCountInPlace"
let shift _ = Js_internal.notImplemented "Js.Array" "shift"
let unshift ~value:_ _ = Js_internal.notImplemented "Js.Array" "unshift"
let unshiftMany ~values:_ _ = Js_internal.notImplemented "Js.Array" "unshiftMany"
(* Accessor functions *)
let concat ~other:second first = Stdlib.Array.append first second
let concatMany ~arrays arr = Stdlib.Array.concat (arr :: Stdlib.Array.to_list arrays)
let includes ~value arr = Stdlib.Array.exists (fun x -> x = value) arr
let indexOf ~value ?start arr =
let rec aux idx = if idx >= Stdlib.Array.length arr then -1 else if arr.(idx) = value then idx else aux (idx + 1) in
match start with None -> aux 0 | Some from -> if from < 0 || from >= Stdlib.Array.length arr then -1 else aux from
let join ?sep arr =
(* js bindings can really take in `'a array`, while native is constrained to `string array` *)
match sep with
| None -> Stdlib.Array.to_list arr |> String.concat ","
| Some sep -> Stdlib.Array.to_list arr |> String.concat sep
let lastIndexOf ~value arr =
let rec aux idx = if idx < 0 then -1 else if arr.(idx) = value then idx else aux (idx - 1) in
aux (Stdlib.Array.length arr - 1)
let lastIndexOfFrom ~value ~start arr =
let rec aux idx = if idx < 0 then -1 else if arr.(idx) = value then idx else aux (idx - 1) in
if start < 0 || start >= Stdlib.Array.length arr then -1 else aux start
let slice ?start ?end_ arr =
let len = Stdlib.Array.length arr in
let start = match start with None -> 0 | Some s -> s in
let end_ = match end_ with None -> Stdlib.Array.length arr | Some e -> e in
let s = max 0 (if start < 0 then len + start else start) in
let e = min len (if end_ < 0 then len + end_ else end_) in
if s >= e then [||] else Stdlib.Array.sub arr s (e - s)
let copy = Stdlib.Array.copy
let toString _ = Js_internal.notImplemented "Js.Array" "toString"
let toLocaleString _ = Js_internal.notImplemented "Js.Array" "toLocaleString"
(* Iteration functions *)
let everyi ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then true else if f arr.(idx) idx then aux (idx + 1) else false in
aux 0
let every ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then true else if f arr.(idx) then aux (idx + 1) else false in
aux 0
let filter ~f arr = arr |> Stdlib.Array.to_list |> List.filter f |> Stdlib.Array.of_list
let filteri ~f arr = arr |> Stdlib.Array.to_list |> List.filteri (fun i a -> f a i) |> Stdlib.Array.of_list
let findi ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then None else if f arr.(idx) idx then Some arr.(idx) else aux (idx + 1) in
aux 0
let find ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then None else if f arr.(idx) then Some arr.(idx) else aux (idx + 1) in
aux 0
let findIndexi ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then -1 else if f arr.(idx) idx then idx else aux (idx + 1) in
aux 0
let findIndex ~f arr =
let len = Stdlib.Array.length arr in
let rec aux idx = if idx >= len then -1 else if f arr.(idx) then idx else aux (idx + 1) in
aux 0
let forEach ~f arr = Stdlib.Array.iter f arr
let forEachi ~f arr = Stdlib.Array.iteri (fun i a -> f a i) arr
let map ~f arr = Stdlib.Array.map f arr
let mapi ~f arr = Stdlib.Array.mapi (fun i a -> f a i) arr
let reduce ~f ~init arr =
let r = ref init in
for i = 0 to length arr - 1 do
r := f !r (Stdlib.Array.unsafe_get arr i)
done;
!r
let reducei ~f ~init arr =
let r = ref init in
for i = 0 to length arr - 1 do
r := f !r (Stdlib.Array.unsafe_get arr i) i
done;
!r
let reduceRight ~f ~init arr =
let r = ref init in
for i = length arr - 1 downto 0 do
r := f !r (Stdlib.Array.unsafe_get arr i)
done;
!r
let reduceRighti ~f ~init arr =
let r = ref init in
for i = length arr - 1 downto 0 do
r := f !r (Stdlib.Array.unsafe_get arr i) i
done;
!r
let some ~f arr =
let n = Stdlib.Array.length arr in
let rec loop i = if i = n then false else if f (Stdlib.Array.unsafe_get arr i) then true else loop (succ i) in
loop 0
let somei ~f arr =
let n = Stdlib.Array.length arr in
let rec loop i = if i = n then false else if f (Stdlib.Array.unsafe_get arr i) i then true else loop (succ i) in
loop 0
let unsafe_get arr idx = Stdlib.Array.unsafe_get arr idx
let unsafe_set arr idx item = Stdlib.Array.unsafe_set arr idx item
================================================
FILE: packages/Js/lib/Js_array.mli
================================================
(** JavaScript Array API *)
type 'a t = 'a array
type 'a array_like
val from : 'a array_like -> 'a t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fromMap : 'a array_like -> f:('a -> 'b) -> 'b t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val isArray : 'a array -> bool
val length : 'a array -> int
val copyWithin : to_:int -> ?start:int -> ?end_:int -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fill : value:'a -> ?start:int -> ?end_:int -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val pop : 'a t -> 'a Js_internal.nullable
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val push : value:'a -> 'a t -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val pushMany : values:'a t -> 'a t -> int
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val reverseInPlace : 'a t -> 'a t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val shift : 'a t -> 'a option [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sortInPlace : 'a t -> 'a t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sortInPlaceWith : f:('a -> 'a -> int) -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val spliceInPlace : start:int -> remove:int -> add:'a t -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val removeFromInPlace : start:int -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val removeCountInPlace : start:int -> count:int -> 'a t -> 'a t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unshift : value:'a -> 'a t -> int
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unshiftMany : values:'a t -> 'a t -> int
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val concat : other:'a t -> 'a t -> 'a t
val concatMany : arrays:'a t t -> 'a t -> 'a t
val includes : value:'a -> 'a t -> bool
val indexOf : value:'a -> ?start:int -> 'a t -> int
val join : ?sep:string -> string t -> string
val lastIndexOf : value:'a -> 'a t -> int
val lastIndexOfFrom : value:'a -> start:int -> 'a t -> int
val slice : ?start:int -> ?end_:int -> 'a t -> 'a t
val copy : 'a array -> 'a array
val toString : 'a t -> string [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val toLocaleString : 'a t -> string
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val everyi : f:('a -> int -> bool) -> 'a t -> bool
val every : f:('a -> bool) -> 'a t -> bool
val filter : f:('a -> bool) -> 'a t -> 'a t
val filteri : f:('a -> int -> bool) -> 'a t -> 'a t
val findi : f:('a -> int -> bool) -> 'a t -> 'a Js_internal.nullable
val find : f:('a -> bool) -> 'a t -> 'a Js_internal.nullable
val findIndexi : f:('a -> int -> bool) -> 'a t -> int
val findIndex : f:('a -> bool) -> 'a t -> int
val forEach : f:('a -> unit) -> 'a t -> unit
val forEachi : f:('a -> int -> unit) -> 'a t -> unit
val map : f:('a -> 'b) -> 'a t -> 'b t
val mapi : f:('a -> int -> 'b) -> 'a t -> 'b t
val reduce : f:('b -> 'a -> 'b) -> init:'b -> 'a t -> 'b
val reducei : f:('b -> 'a -> int -> 'b) -> init:'b -> 'a t -> 'b
val reduceRight : f:('b -> 'a -> 'b) -> init:'b -> 'a t -> 'b
val reduceRighti : f:('b -> 'a -> int -> 'b) -> init:'b -> 'a t -> 'b
val some : f:('a -> bool) -> 'a t -> bool
val somei : f:('a -> int -> bool) -> 'a t -> bool
val unsafe_get : 'a array -> int -> 'a
val unsafe_set : 'a array -> int -> 'a -> unit
================================================
FILE: packages/Js/lib/Js_bigint.ml
================================================
(** Provide utilities for bigint *)
type t = Z.t
(* {1 Constructors} *)
let of_int = Z.of_int
let of_int64 = Z.of_int64
(* Helper to check if a character is whitespace *)
let is_whitespace c = c = ' ' || c = '\t' || c = '\n' || c = '\r'
(* Trim whitespace from both ends of a string *)
let trim s =
let len = String.length s in
let i = ref 0 in
while !i < len && is_whitespace (String.get s !i) do
incr i
done;
let j = ref (len - 1) in
while !j >= !i && is_whitespace (String.get s !j) do
decr j
done;
if !i > !j then "" else String.sub s !i (!j - !i + 1)
(* Check if string contains only valid chars for given base, after sign *)
let is_valid_for_base s base start_idx =
let valid_char c =
match base with
| 2 -> c = '0' || c = '1'
| 8 -> c >= '0' && c <= '7'
| 10 -> c >= '0' && c <= '9'
| 16 -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
| _ -> false
in
let len = String.length s in
if start_idx >= len then false
else
let result = ref true in
for i = start_idx to len - 1 do
if not (valid_char (String.get s i)) then result := false
done;
!result
(* Parse string with JS BigInt semantics (strict version that raises) *)
let of_string_exn s =
let s = trim s in
let len = String.length s in
if len = 0 then failwith "BigInt: cannot convert empty string";
(* Check for sign-only strings *)
if s = "+" || s = "-" then failwith "BigInt: cannot convert sign-only string";
(* Check for null character *)
if String.contains s '\x00' then failwith "BigInt: invalid character";
(* Check for decimal point or scientific notation *)
if String.contains s '.' then failwith "BigInt: cannot have decimal point";
if String.contains s 'e' || String.contains s 'E' then failwith "BigInt: cannot use scientific notation";
(* Determine sign and starting position *)
let negative = len > 0 && String.get s 0 = '-' in
let has_sign = len > 0 && (String.get s 0 = '-' || String.get s 0 = '+') in
let start = if has_sign then 1 else 0 in
if start >= len then failwith "BigInt: invalid format";
(* Check for radix prefix *)
let has_prefix =
len > start + 1
&& String.get s start = '0'
&&
let c = String.get s (start + 1) in
c = 'x' || c = 'X' || c = 'b' || c = 'B' || c = 'o' || c = 'O'
in
let base, num_start =
if has_prefix then
let c = String.get s (start + 1) in
match c with
| 'x' | 'X' -> (16, start + 2)
| 'b' | 'B' -> (2, start + 2)
| 'o' | 'O' -> (8, start + 2)
| _ -> (10, start)
else (10, start)
in
(* Validate the numeric part *)
if num_start >= len then failwith "BigInt: missing digits after prefix";
if not (is_valid_for_base s base num_start) then failwith "BigInt: invalid characters for base";
(* Parse the number *)
let num_str = String.sub s num_start (len - num_start) in
let abs_value = Z.of_string_base base num_str in
if negative then Z.neg abs_value else abs_value
(* Parse string with JS BigInt semantics (lenient version) *)
let of_string s =
let s = trim s in
if String.length s = 0 then Z.zero
else
try of_string_exn s
with Failure _ ->
(* For the lenient version, invalid strings just return 0 *)
Z.zero
(* {1 Conversions} *)
(* Convert a digit to its character representation for bases up to 36 *)
let digit_to_char d = if d < 10 then Char.chr (d + Char.code '0') else Char.chr (d - 10 + Char.code 'a')
(* Convert BigInt to string with given radix *)
let to_string ?(radix = 10) n =
if radix < 2 || radix > 36 then invalid_arg "to_string: radix must be between 2 and 36";
if Z.equal n Z.zero then "0"
else
let negative = Z.sign n < 0 in
let n = Z.abs n in
let radix_z = Z.of_int radix in
let buf = Buffer.create 64 in
let rec loop n =
if Z.equal n Z.zero then ()
else
let q, r = Z.div_rem n radix_z in
Buffer.add_char buf (digit_to_char (Z.to_int r));
loop q
in
loop n;
if negative then Buffer.add_char buf '-';
(* Reverse the buffer contents *)
let s = Buffer.contents buf in
let len = String.length s in
String.init len (fun i -> String.get s (len - 1 - i))
let toString = to_string ~radix:10
let to_float = Z.to_float
(* {1 Arithmetic operations} *)
let neg = Z.neg
let abs = Z.abs
let add = Z.add
let sub = Z.sub
let mul = Z.mul
(* Division truncating toward zero - this is what Z.div does *)
let div a b = if Z.equal b Z.zero then raise Division_by_zero else Z.div a b
(* Remainder with sign following dividend - this is what Z.rem does *)
let rem a b = if Z.equal b Z.zero then raise Division_by_zero else Z.rem a b
(* Power - raises on negative exponent *)
let pow base exp =
if Z.sign exp < 0 then invalid_arg "BigInt.pow: negative exponent"
else
let exp_int = Z.to_int exp in
Z.pow base exp_int
(* {1 Bitwise operations} *)
let logand = Z.logand
let logor = Z.logor
let logxor = Z.logxor
let lognot = Z.lognot
let shift_left = Z.shift_left
let shift_right = Z.shift_right
(* {1 Comparison operations} *)
let compare a b =
let c = Z.compare a b in
if c < 0 then -1 else if c > 0 then 1 else 0
let equal = Z.equal
let lt a b = Z.compare a b < 0
let le a b = Z.compare a b <= 0
let gt a b = Z.compare a b > 0
let ge a b = Z.compare a b >= 0
(* {1 Bit width conversion} *)
(* asUintN: wrap to unsigned n-bit integer *)
let as_uint_n bits x =
if bits = 0 then Z.zero
else
let modulus = Z.shift_left Z.one bits in
Z.erem x modulus
(* asIntN: wrap to signed n-bit integer *)
let as_int_n bits x =
if bits = 0 then Z.zero
else
let modulus = Z.shift_left Z.one bits in
let half = Z.shift_left Z.one (bits - 1) in
let wrapped = Z.erem x modulus in
(* If wrapped >= 2^(bits-1), subtract 2^bits to get negative *)
if Z.compare wrapped half >= 0 then Z.sub wrapped modulus else wrapped
================================================
FILE: packages/Js/lib/Js_bigint.mli
================================================
(** Provide utilities for bigint *)
type t
(** The BigInt type, representing arbitrary precision integers *)
(** {1 Constructors} *)
val of_int : int -> t
(** [of_int n] creates a BigInt from an OCaml integer *)
val of_int64 : int64 -> t
(** [of_int64 n] creates a BigInt from an OCaml int64 *)
val of_string : string -> t
(** [of_string s] creates a BigInt from a string representation. Supports decimal, hexadecimal (0x prefix), binary (0b
prefix), and octal (0o prefix) formats. Whitespace is trimmed. Empty string returns 0. Invalid strings return 0. *)
val of_string_exn : string -> t
(** [of_string_exn s] creates a BigInt from a string representation. Like [of_string] but raises [Failure] on invalid
input. *)
(** {1 Conversions} *)
val to_string : ?radix:int -> t -> string
(** [to_string ?radix bigint] returns a string representation. [radix] can be 2-36 (default 10). *)
val toString : t -> string
(** Alias for [to_string] with default radix 10 *)
val to_float : t -> float
(** [to_float bigint] converts to float. May lose precision for large values. *)
(** {1 Arithmetic operations} *)
val neg : t -> t
(** [neg x] returns the negation of [x] *)
val abs : t -> t
(** [abs x] returns the absolute value of [x] *)
val add : t -> t -> t
(** [add x y] returns [x + y] *)
val sub : t -> t -> t
(** [sub x y] returns [x - y] *)
val mul : t -> t -> t
(** [mul x y] returns [x * y] *)
val div : t -> t -> t
(** [div x y] returns [x / y], truncated toward zero. Raises [Division_by_zero] if [y] is zero. *)
val rem : t -> t -> t
(** [rem x y] returns the remainder of [x / y]. The sign follows the dividend (JavaScript semantics). Raises
[Division_by_zero] if [y] is zero. *)
val pow : t -> t -> t
(** [pow base exp] returns [base] raised to the power [exp]. Raises [Invalid_argument] if [exp] is negative. *)
(** {1 Bitwise operations} *)
val logand : t -> t -> t
(** [logand x y] returns the bitwise AND of [x] and [y] *)
val logor : t -> t -> t
(** [logor x y] returns the bitwise OR of [x] and [y] *)
val logxor : t -> t -> t
(** [logxor x y] returns the bitwise XOR of [x] and [y] *)
val lognot : t -> t
(** [lognot x] returns the bitwise NOT of [x] (two's complement) *)
val shift_left : t -> int -> t
(** [shift_left x n] returns [x] shifted left by [n] bits *)
val shift_right : t -> int -> t
(** [shift_right x n] returns [x] arithmetically shifted right by [n] bits. Sign-extending for negative numbers. *)
(** {1 Comparison operations} *)
val compare : t -> t -> int
(** [compare x y] returns -1 if [x < y], 0 if [x = y], 1 if [x > y] *)
val equal : t -> t -> bool
(** [equal x y] returns [true] if [x = y] *)
val lt : t -> t -> bool
(** [lt x y] returns [true] if [x < y] *)
val le : t -> t -> bool
(** [le x y] returns [true] if [x <= y] *)
val gt : t -> t -> bool
(** [gt x y] returns [true] if [x > y] *)
val ge : t -> t -> bool
(** [ge x y] returns [true] if [x >= y] *)
(** {1 Bit width conversion} *)
val as_int_n : int -> t -> t
(** [as_int_n bits x] wraps [x] to a signed integer of [bits] bits. Equivalent to JavaScript's BigInt.asIntN. *)
val as_uint_n : int -> t -> t
(** [as_uint_n bits x] wraps [x] to an unsigned integer of [bits] bits. Equivalent to JavaScript's BigInt.asUintN. *)
================================================
FILE: packages/Js/lib/Js_console.ml
================================================
let log _ = ()
let log2 _ _ = ()
let log3 _ _ _ = ()
let log4 _ _ _ _ = ()
let logMany _arr = ()
let info = log
let info2 = log2
let info3 = log3
let info4 = log4
let infoMany = logMany
let error = log
let error2 = log2
let error3 = log3
let error4 = log4
let errorMany = logMany
let warn = log
let warn2 = log2
let warn3 = log3
let warn4 = log4
let warnMany = logMany
let trace () = ()
let timeStart _ = ()
let timeEnd _ = ()
================================================
FILE: packages/Js/lib/Js_console.mli
================================================
val log : string -> unit
val log2 : string -> string -> unit
val log3 : string -> string -> string -> unit
val log4 : string -> string -> string -> string -> unit
val logMany : string array -> unit
val info : string -> unit
val info2 : string -> string -> unit
val info3 : string -> string -> string -> unit
val info4 : string -> string -> string -> string -> unit
val infoMany : string array -> unit
val error : string -> unit
val error2 : string -> string -> unit
val error3 : string -> string -> string -> unit
val error4 : string -> string -> string -> string -> unit
val errorMany : string array -> unit
val warn : string -> unit
val warn2 : string -> string -> unit
val warn3 : string -> string -> string -> unit
val warn4 : string -> string -> string -> string -> unit
val warnMany : string array -> unit
val trace : unit -> unit [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val timeStart : 'a -> unit [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val timeEnd : 'a -> unit [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
================================================
FILE: packages/Js/lib/Js_date.ml
================================================
type t = float
let ms_per_second = 1000.
let ms_per_minute = 60000.
let ms_per_hour = 3600000.
let ms_per_day = 86400000.
let max_time_value = 8.64e15
let is_valid_time t = (not (Float.is_nan t)) && Float.abs t <= max_time_value
let day t = Float.floor (t /. ms_per_day)
let time_within_day t =
let r = Float.rem t ms_per_day in
if r < 0. then r +. ms_per_day else r
let days_in_year y =
if Float.rem y 4. <> 0. then 365
else if Float.rem y 100. <> 0. then 366
else if Float.rem y 400. <> 0. then 365
else 366
let day_from_year y =
let y = y -. 1970. in
(365. *. y) +. Float.floor ((y +. 1.) /. 4.) -. Float.floor ((y +. 69.) /. 100.) +. Float.floor ((y +. 369.) /. 400.)
let year_from_time t =
if Float.is_nan t then nan
else
let d = day t in
let estimate = 1970. +. Float.floor (d /. 365.2425) in
let rec search lo hi =
if lo >= hi then lo
else
let mid = Float.floor ((lo +. hi +. 1.) /. 2.) in
if day_from_year mid <= d then search mid hi else search lo (mid -. 1.)
in
search (estimate -. 2.) (estimate +. 2.)
let in_leap_year t = days_in_year (year_from_time t) = 366
let day_within_year t =
let d = day t in
let y = year_from_time t in
d -. day_from_year y
let month_start_days = [| 0; 31; 59; 90; 120; 151; 181; 212; 243; 273; 304; 334; 365 |]
let month_start_days_leap = [| 0; 31; 60; 91; 121; 152; 182; 213; 244; 274; 305; 335; 366 |]
let month_from_time t =
if Float.is_nan t then nan
else
let d = int_of_float (day_within_year t) in
let table = if in_leap_year t then month_start_days_leap else month_start_days in
let rec find_month m = if m >= 11 then 11 else if d < table.(m + 1) then m else find_month (m + 1) in
Float.of_int (find_month 0)
let date_from_time t =
if Float.is_nan t then nan
else
let d = int_of_float (day_within_year t) in
let m = int_of_float (month_from_time t) in
let table = if in_leap_year t then month_start_days_leap else month_start_days in
Float.of_int (d - table.(m) + 1)
let week_day t =
if Float.is_nan t then nan
else
let d = day t +. 4. in
let r = Float.rem d 7. in
if r < 0. then r +. 7. else r
let hour_from_time t =
if Float.is_nan t then nan
else
let r = Float.rem (Float.floor (t /. ms_per_hour)) 24. in
if r < 0. then r +. 24. else r
let min_from_time t =
if Float.is_nan t then nan
else
let r = Float.rem (Float.floor (t /. ms_per_minute)) 60. in
if r < 0. then r +. 60. else r
let sec_from_time t =
if Float.is_nan t then nan
else
let r = Float.rem (Float.floor (t /. ms_per_second)) 60. in
if r < 0. then r +. 60. else r
let ms_from_time t =
if Float.is_nan t then nan
else
let r = Float.rem t ms_per_second in
if r < 0. then r +. ms_per_second else r
let make_time ~hour ~min ~sec ~ms =
if Float.is_nan hour || Float.is_nan min || Float.is_nan sec || Float.is_nan ms then nan
else
let h = Float.trunc hour in
let m = Float.trunc min in
let s = Float.trunc sec in
let milli = Float.trunc ms in
(h *. ms_per_hour) +. (m *. ms_per_minute) +. (s *. ms_per_second) +. milli
let make_day ~year ~month ~date =
if Float.is_nan year || Float.is_nan month || Float.is_nan date then nan
else if (not (Float.is_finite year)) || (not (Float.is_finite month)) || not (Float.is_finite date) then nan
else
let y = Float.trunc year in
let m = Float.trunc month in
let dt = Float.trunc date in
let ym = y +. Float.floor (m /. 12.) in
let mn = Float.rem m 12. in
let mn = if mn < 0. then mn +. 12. else mn in
let d = day_from_year ym in
let is_leap = days_in_year ym = 366 in
let month_table = if is_leap then month_start_days_leap else month_start_days in
let d = d +. Float.of_int month_table.(int_of_float mn) in
d +. dt -. 1.
let make_date ~day ~time =
if Float.is_nan day || Float.is_nan time then nan
else if (not (Float.is_finite day)) || not (Float.is_finite time) then nan
else (day *. ms_per_day) +. time
let time_clip t =
if Float.is_nan t then nan
else if not (Float.is_finite t) then nan
else if Float.abs t > max_time_value then nan
else Float.trunc t
let local_tz_offset_ms utc_time =
if Float.is_nan utc_time then 0.
else
let seconds = utc_time /. 1000. in
try
let local_tm = Unix.localtime seconds in
let utc_tm = Unix.gmtime seconds in
let local_secs = (local_tm.Unix.tm_hour * 3600) + (local_tm.Unix.tm_min * 60) + local_tm.Unix.tm_sec in
let utc_secs = (utc_tm.Unix.tm_hour * 3600) + (utc_tm.Unix.tm_min * 60) + utc_tm.Unix.tm_sec in
let day_diff = local_tm.Unix.tm_yday - utc_tm.Unix.tm_yday in
let day_diff = if day_diff > 1 then -1 else if day_diff < -1 then 1 else day_diff in
Float.of_int ((day_diff * 86400) + local_secs - utc_secs) *. 1000.
with _ -> 0.
let utc_to_local t = if Float.is_nan t then nan else t +. local_tz_offset_ms t
let local_to_utc t = if Float.is_nan t then nan else t -. local_tz_offset_ms t
let compute_utc ~year ?(month = 0.) ?(date = 1.) ?(hours = 0.) ?(minutes = 0.) ?(seconds = 0.) ?(ms = 0.) () =
let y =
if Float.is_nan year then nan
else
let y = Float.trunc year in
if y >= 0. && y <= 99. then 1900. +. y else y
in
let m = if Float.is_nan month then nan else Float.trunc month in
let d = make_day ~year:y ~month:m ~date in
let t = make_time ~hour:hours ~min:minutes ~sec:seconds ~ms in
time_clip (make_date ~day:d ~time:t)
let now () =
let t = Unix.gettimeofday () in
Float.trunc (t *. 1000.)
let utc ~year ?(month = 0.) ?(date = 1.) ?(hours = 0.) ?(minutes = 0.) ?(seconds = 0.) () =
compute_utc ~year ~month ~date ~hours ~minutes ~seconds ()
let make ?year ?month ?date ?hours ?minutes ?seconds () =
match year with
| None -> time_clip (now ())
| Some year ->
let month = match month with Some m -> m | None -> 0. in
let date = match date with Some d -> d | None -> 1. in
let hours = match hours with Some h -> h | None -> 0. in
let minutes = match minutes with Some m -> m | None -> 0. in
let seconds = match seconds with Some s -> s | None -> 0. in
let y = if year >= 0. && year <= 99. then 1900. +. year else year in
let d = make_day ~year:y ~month ~date in
let t = make_time ~hour:hours ~min:minutes ~sec:seconds ~ms:0. in
time_clip (local_to_utc (make_date ~day:d ~time:t))
let fromFloat ms = time_clip ms
let valueOf t = t
let getTime t = t
let getUTCFullYear t = if Float.is_nan t then nan else year_from_time t
let getUTCMonth t = if Float.is_nan t then nan else month_from_time t
let getUTCDate t = if Float.is_nan t then nan else date_from_time t
let getUTCDay t = if Float.is_nan t then nan else week_day t
let getUTCHours t = if Float.is_nan t then nan else hour_from_time t
let getUTCMinutes t = if Float.is_nan t then nan else min_from_time t
let getUTCSeconds t = if Float.is_nan t then nan else sec_from_time t
let getUTCMilliseconds t = if Float.is_nan t then nan else ms_from_time t
let getFullYear t = if Float.is_nan t then nan else year_from_time (utc_to_local t)
let getMonth t = if Float.is_nan t then nan else month_from_time (utc_to_local t)
let getDate t = if Float.is_nan t then nan else date_from_time (utc_to_local t)
let getDay t = if Float.is_nan t then nan else week_day (utc_to_local t)
let getHours t = if Float.is_nan t then nan else hour_from_time (utc_to_local t)
let getMinutes t = if Float.is_nan t then nan else min_from_time (utc_to_local t)
let getSeconds t = if Float.is_nan t then nan else sec_from_time (utc_to_local t)
let getMilliseconds t = if Float.is_nan t then nan else ms_from_time (utc_to_local t)
let getTimezoneOffset t = if Float.is_nan t then nan else -.local_tz_offset_ms t /. ms_per_minute
let pad n i =
let s = string_of_int (abs i) in
let len = String.length s in
if len >= n then s else String.make (n - len) '0' ^ s
let format_year year =
let y = int_of_float year in
if y >= 0 && y <= 9999 then pad 4 y
else if y < 0 then Printf.sprintf "-%s" (pad 6 (-y))
else Printf.sprintf "+%s" (pad 6 y)
let toISOString t =
if Float.is_nan t then raise (Invalid_argument "Invalid Date")
else if not (is_valid_time t) then raise (Invalid_argument "Invalid Date")
else
let year = year_from_time t in
let month = int_of_float (month_from_time t) + 1 in
let day = int_of_float (date_from_time t) in
let hours = int_of_float (hour_from_time t) in
let minutes = int_of_float (min_from_time t) in
let seconds = int_of_float (sec_from_time t) in
let ms = int_of_float (ms_from_time t) in
Printf.sprintf "%s-%s-%sT%s:%s:%s.%sZ" (format_year year) (pad 2 month) (pad 2 day) (pad 2 hours) (pad 2 minutes)
(pad 2 seconds) (pad 3 ms)
let toJSON t = if Float.is_nan t || not (is_valid_time t) then None else Some (toISOString t)
let toJSONUnsafe t = toISOString t
let day_names = [| "Sun"; "Mon"; "Tue"; "Wed"; "Thu"; "Fri"; "Sat" |]
let month_names = [| "Jan"; "Feb"; "Mar"; "Apr"; "May"; "Jun"; "Jul"; "Aug"; "Sep"; "Oct"; "Nov"; "Dec" |]
let format_tz_offset offset_ms =
let offset_min = int_of_float (offset_ms /. ms_per_minute) in
let sign = if offset_min >= 0 then "+" else "-" in
let abs_offset = abs offset_min in
let hours = abs_offset / 60 in
let mins = abs_offset mod 60 in
Printf.sprintf "GMT%s%s%s" sign (pad 2 hours) (pad 2 mins)
let toUTCString t =
if Float.is_nan t then "Invalid Date"
else
let day_name = day_names.(int_of_float (week_day t)) in
let day = int_of_float (date_from_time t) in
let month_name = month_names.(int_of_float (month_from_time t)) in
let year = int_of_float (year_from_time t) in
let hours = int_of_float (hour_from_time t) in
let minutes = int_of_float (min_from_time t) in
let seconds = int_of_float (sec_from_time t) in
Printf.sprintf "%s, %s %s %d %s:%s:%s GMT" day_name (pad 2 day) month_name year (pad 2 hours) (pad 2 minutes)
(pad 2 seconds)
let toDateString t =
if Float.is_nan t then "Invalid Date"
else
let local = utc_to_local t in
let day_name = day_names.(int_of_float (week_day local)) in
let month_name = month_names.(int_of_float (month_from_time local)) in
let day = int_of_float (date_from_time local) in
let year = int_of_float (year_from_time local) in
Printf.sprintf "%s %s %s %d" day_name month_name (pad 2 day) year
let toTimeString t =
if Float.is_nan t then "Invalid Date"
else
let local = utc_to_local t in
let hours = int_of_float (hour_from_time local) in
let minutes = int_of_float (min_from_time local) in
let seconds = int_of_float (sec_from_time local) in
let tz = format_tz_offset (local_tz_offset_ms t) in
Printf.sprintf "%s:%s:%s %s" (pad 2 hours) (pad 2 minutes) (pad 2 seconds) tz
let toString t = if Float.is_nan t then "Invalid Date" else Printf.sprintf "%s %s" (toDateString t) (toTimeString t)
let toLocaleString t = toString t
let toLocaleDateString t = toDateString t
let toLocaleTimeString t = toTimeString t
let parse_int_opt s = try Some (int_of_string s) with _ -> None
let ( let* ) = Option.bind
let guard condition = if condition then Some () else None
let read_chars s ~pos ~len:n = if pos + n > String.length s then None else Some (String.sub s pos n)
let parse_int_in_range s ~min ~max =
let* value = parse_int_opt s in
let* () = guard (value >= min && value <= max) in
Some value
let parse_year s =
let len = String.length s in
if len = 0 then None
else
let sign, start, digits = match s.[0] with '+' -> (1., 1, 6) | '-' -> (-1., 1, 6) | _ -> (1., 0, 4) in
let* year_str = read_chars s ~pos:start ~len:digits in
let* year_int = parse_int_opt year_str in
let* () = guard (not (sign < 0. && year_int = 0)) in
let year = sign *. Float.of_int year_int in
let next_pos = start + digits in
Some (year, next_pos)
let parse_2digit_component s ~pos ~delimiter ~min ~max =
let len = String.length s in
if pos >= len then None
else if s.[pos] <> delimiter then None
else
let pos = pos + 1 in
let* component_str = read_chars s ~pos ~len:2 in
let* value = parse_int_in_range component_str ~min ~max in
Some (value, pos + 2)
let parse_seconds s ~pos =
let len = String.length s in
if pos >= len || s.[pos] <> ':' then Some (0., pos)
else
let pos = pos + 1 in
match read_chars s ~pos ~len:2 with
| None -> None
| Some sec_str -> (
match parse_int_in_range sec_str ~min:0 ~max:59 with
| None -> None
| Some sec -> Some (Float.of_int sec, pos + 2))
let parse_milliseconds s ~pos =
let len = String.length s in
if pos >= len || s.[pos] <> '.' then (0., pos)
else
let pos = pos + 1 in
let ms_start = pos in
let rec count_digits p = if p < len && s.[p] >= '0' && s.[p] <= '9' then count_digits (p + 1) else p in
let ms_end = count_digits ms_start in
let digit_count = ms_end - ms_start in
if digit_count = 0 then (0., ms_end)
else
let ms_str = String.sub s ms_start (min digit_count 3) in
let ms_str =
let pad_len = 3 - String.length ms_str in
if pad_len > 0 then ms_str ^ String.make pad_len '0' else ms_str
in
let ms = match parse_int_opt ms_str with Some v -> Float.of_int v | None -> 0. in
(ms, ms_end)
let parse_timezone s ~pos =
let len = String.length s in
if pos >= len then (0., pos)
else
match s.[pos] with
| 'Z' -> (0., pos + 1)
| ('+' | '-') as sign_char ->
let sign = if sign_char = '-' then -1. else 1. in
let pos = pos + 1 in
let tz_hours, pos =
match read_chars s ~pos ~len:2 with
| Some h_str -> ( match parse_int_opt h_str with Some h -> (Float.of_int h, pos + 2) | None -> (0., pos))
| None -> (0., pos)
in
let tz_minutes, pos =
if pos < len && s.[pos] = ':' then
let pos = pos + 1 in
match read_chars s ~pos ~len:2 with
| Some m_str -> ( match parse_int_opt m_str with Some m -> (Float.of_int m, pos + 2) | None -> (0., pos))
| None -> (0., pos)
else (0., pos)
in
let offset_ms = sign *. ((tz_hours *. ms_per_hour) +. (tz_minutes *. ms_per_minute)) in
(offset_ms, pos)
| _ -> (0., pos)
let parse_time_component s ~pos ~year ~month ~date =
let len = String.length s in
if pos >= len then Some (compute_utc ~year ~month ~date ())
else if s.[pos] <> 'T' && s.[pos] <> ' ' then None
else
let pos = pos + 1 in
let* hours_str = read_chars s ~pos ~len:2 in
let* hours_int = parse_int_in_range hours_str ~min:0 ~max:24 in
let hours = Float.of_int hours_int in
let pos = pos + 2 in
let* () = guard (pos < len && s.[pos] = ':') in
let pos = pos + 1 in
let* minutes_str = read_chars s ~pos ~len:2 in
let* minutes_int = parse_int_in_range minutes_str ~min:0 ~max:59 in
let minutes = Float.of_int minutes_int in
let pos = pos + 2 in
let* seconds, pos = parse_seconds s ~pos in
let ms, pos = parse_milliseconds s ~pos in
let tz_offset_ms, _pos = parse_timezone s ~pos in
let* () = guard (not (hours_int = 24 && (minutes_int <> 0 || seconds <> 0. || ms <> 0.))) in
let result = compute_utc ~year ~month ~date ~hours ~minutes ~seconds ~ms () in
Some (result -. tz_offset_ms)
let parse_iso8601 s =
let len = String.length s in
if len = 0 then None
else
let* year, pos = parse_year s in
if pos >= len then Some (compute_utc ~year ~month:0. ())
else
let* month_int, pos = parse_2digit_component s ~pos ~delimiter:'-' ~min:1 ~max:12 in
let month = Float.of_int (month_int - 1) in
if pos >= len then Some (compute_utc ~year ~month ())
else
let* date_int, pos = parse_2digit_component s ~pos ~delimiter:'-' ~min:1 ~max:31 in
let date = Float.of_int date_int in
parse_time_component s ~pos ~year ~month ~date
let weekdays = [ "Sun"; "Mon"; "Tue"; "Wed"; "Thu"; "Fri"; "Sat" ]
let strip_weekday s =
let parts = String.split_on_char ' ' (String.trim s) in
match parts with day :: rest when List.mem day weekdays -> String.concat " " rest | _ -> s
let array_find_index pred arr =
let len = Array.length arr in
let rec loop i = if i >= len then None else if pred arr.(i) then Some i else loop (i + 1) in
loop 0
let parse_month_name name = array_find_index (fun m -> String.equal m name) month_names |> Option.map Float.of_int
let parse_gmt_offset tz_str =
let len = String.length tz_str in
if len < 3 || String.sub tz_str 0 3 <> "GMT" then 0.
else
let tz_part = String.sub tz_str 3 (len - 3) in
if String.length tz_part < 5 then 0.
else
let sign = if tz_part.[0] = '-' then -1. else 1. in
let h_str = String.sub tz_part 1 2 in
let m_str = String.sub tz_part 3 2 in
match (parse_int_opt h_str, parse_int_opt m_str) with
| Some h, Some m -> sign *. ((Float.of_int h *. ms_per_hour) +. (Float.of_int m *. ms_per_minute))
| _ -> 0.
let parse_time_string time_str =
match String.split_on_char ':' time_str with
| [ h; m; s ] -> (
match (parse_int_opt h, parse_int_opt m, parse_int_opt s) with
| Some hi, Some mi, Some si -> Some (Float.of_int hi, Float.of_int mi, Float.of_int si)
| _ -> None)
| _ -> None
let parse_legacy_time_and_tz rest =
match rest with
| [] -> (0., 0., 0., 0.)
| time_str :: tz_rest -> (
match parse_time_string time_str with
| Some (hours, minutes, seconds) ->
let tz_offset = match tz_rest with [] -> 0. | tz_str :: _ -> parse_gmt_offset tz_str in
(hours, minutes, seconds, tz_offset)
| None -> (0., 0., 0., 0.))
let parse_legacy s =
let s = strip_weekday s in
let parts = String.split_on_char ' ' (String.trim s) in
match parts with
| month_str :: day_str :: year_str :: rest ->
let* month = parse_month_name month_str in
let* day_int = parse_int_opt day_str in
let* year_int = parse_int_opt year_str in
let date = Float.of_int day_int in
let year = Float.of_int year_int in
let hours, minutes, seconds, tz_offset = parse_legacy_time_and_tz rest in
let result = compute_utc ~year ~month ~date ~hours ~minutes ~seconds () in
Some (result -. tz_offset)
| _ -> None
let parse s =
let s = String.trim s in
if String.length s = 0 then nan
else match parse_iso8601 s with Some t -> t | None -> ( match parse_legacy s with Some t -> t | None -> nan)
let parseAsFloat = parse
let fromString s = time_clip (parse s)
let setTime ~time _t = time_clip time
let setUTCTime ~time _t = time_clip time
let setUTCMilliseconds ~milliseconds t =
if Float.is_nan t then nan
else
let d = day t in
let time = time_within_day t in
let new_time = time -. ms_from_time t +. Float.trunc milliseconds in
time_clip (make_date ~day:d ~time:new_time)
let setUTCSeconds ~seconds ?milliseconds t =
if Float.is_nan t then nan
else
let h = hour_from_time t in
let m = min_from_time t in
let s = Float.trunc seconds in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time t in
let d = day t in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (make_date ~day:d ~time)
let setUTCMinutes ~minutes ?seconds ?milliseconds t =
if Float.is_nan t then nan
else
let h = hour_from_time t in
let m = Float.trunc minutes in
let s = match seconds with Some s -> Float.trunc s | None -> sec_from_time t in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time t in
let d = day t in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (make_date ~day:d ~time)
let setUTCHours ~hours ?minutes ?seconds ?milliseconds t =
if Float.is_nan t then nan
else
let h = Float.trunc hours in
let m = match minutes with Some m -> Float.trunc m | None -> min_from_time t in
let s = match seconds with Some s -> Float.trunc s | None -> sec_from_time t in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time t in
let d = day t in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (make_date ~day:d ~time)
let setUTCDate ~date t =
if Float.is_nan t then nan
else
let year = year_from_time t in
let month = month_from_time t in
let d = make_day ~year ~month ~date:(Float.trunc date) in
let time = time_within_day t in
time_clip (make_date ~day:d ~time)
let setUTCMonth ~month ?date t =
if Float.is_nan t then nan
else
let year = year_from_time t in
let dt = match date with Some d -> Float.trunc d | None -> date_from_time t in
let d = make_day ~year ~month:(Float.trunc month) ~date:dt in
let time = time_within_day t in
time_clip (make_date ~day:d ~time)
let setUTCFullYear ~year ?month ?date t =
let t = if Float.is_nan t then 0. else t in
let m = match month with Some m -> Float.trunc m | None -> month_from_time t in
let dt = match date with Some d -> Float.trunc d | None -> date_from_time t in
let d = make_day ~year:(Float.trunc year) ~month:m ~date:dt in
let time = time_within_day t in
time_clip (make_date ~day:d ~time)
let setMilliseconds ~milliseconds t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let d = day local in
let time = time_within_day local in
let new_time = time -. ms_from_time local +. Float.trunc milliseconds in
time_clip (local_to_utc (make_date ~day:d ~time:new_time))
let setSeconds ~seconds ?milliseconds t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let h = hour_from_time local in
let m = min_from_time local in
let s = Float.trunc seconds in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time local in
let d = day local in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (local_to_utc (make_date ~day:d ~time))
let setMinutes ~minutes ?seconds ?milliseconds t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let h = hour_from_time local in
let m = Float.trunc minutes in
let s = match seconds with Some s -> Float.trunc s | None -> sec_from_time local in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time local in
let d = day local in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (local_to_utc (make_date ~day:d ~time))
let setHours ~hours ?minutes ?seconds ?milliseconds t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let h = Float.trunc hours in
let m = match minutes with Some m -> Float.trunc m | None -> min_from_time local in
let s = match seconds with Some s -> Float.trunc s | None -> sec_from_time local in
let ms = match milliseconds with Some ms -> Float.trunc ms | None -> ms_from_time local in
let d = day local in
let time = make_time ~hour:h ~min:m ~sec:s ~ms in
time_clip (local_to_utc (make_date ~day:d ~time))
let setDate ~date t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let year = year_from_time local in
let month = month_from_time local in
let d = make_day ~year ~month ~date:(Float.trunc date) in
let time = time_within_day local in
time_clip (local_to_utc (make_date ~day:d ~time))
let setMonth ~month ?date t =
if Float.is_nan t then nan
else
let local = utc_to_local t in
let year = year_from_time local in
let dt = match date with Some d -> Float.trunc d | None -> date_from_time local in
let d = make_day ~year ~month:(Float.trunc month) ~date:dt in
let time = time_within_day local in
time_clip (local_to_utc (make_date ~day:d ~time))
let setFullYear ~year ?month ?date t =
let t = if Float.is_nan t then 0. else t in
let local = utc_to_local t in
let m = match month with Some m -> Float.trunc m | None -> month_from_time local in
let dt = match date with Some d -> Float.trunc d | None -> date_from_time local in
let d = make_day ~year:(Float.trunc year) ~month:m ~date:dt in
let time = time_within_day local in
time_clip (local_to_utc (make_date ~day:d ~time))
================================================
FILE: packages/Js/lib/Js_date.mli
================================================
(** JavaScript Date API *)
type t = float
val valueOf : t -> float
val fromFloat : float -> t
val fromString : string -> t
val make : ?year:float -> ?month:float -> ?date:float -> ?hours:float -> ?minutes:float -> ?seconds:float -> unit -> t
val utc : year:float -> ?month:float -> ?date:float -> ?hours:float -> ?minutes:float -> ?seconds:float -> unit -> float
val now : unit -> float
val parseAsFloat : string -> float
val getDate : t -> float
val getDay : t -> float
val getFullYear : t -> float
val getHours : t -> float
val getMilliseconds : t -> float
val getMinutes : t -> float
val getMonth : t -> float
val getSeconds : t -> float
val getTime : t -> float
val getTimezoneOffset : t -> float
val getUTCDate : t -> float
val getUTCDay : t -> float
val getUTCFullYear : t -> float
val getUTCHours : t -> float
val getUTCMilliseconds : t -> float
val getUTCMinutes : t -> float
val getUTCMonth : t -> float
val getUTCSeconds : t -> float
val setDate : date:float -> t -> float
val setFullYear : year:float -> ?month:float -> ?date:float -> t -> float
val setHours : hours:float -> ?minutes:float -> ?seconds:float -> ?milliseconds:float -> t -> float
val setMilliseconds : milliseconds:float -> t -> float
val setMinutes : minutes:float -> ?seconds:float -> ?milliseconds:float -> t -> float
val setMonth : month:float -> ?date:float -> t -> float
val setSeconds : seconds:float -> ?milliseconds:float -> t -> float
val setTime : time:float -> t -> float
val setUTCDate : date:float -> t -> float
val setUTCFullYear : year:float -> ?month:float -> ?date:float -> t -> float
val setUTCHours : hours:float -> ?minutes:float -> ?seconds:float -> ?milliseconds:float -> t -> float
val setUTCMilliseconds : milliseconds:float -> t -> float
val setUTCMinutes : minutes:float -> ?seconds:float -> ?milliseconds:float -> t -> float
val setUTCMonth : month:float -> ?date:float -> t -> float
val setUTCSeconds : seconds:float -> ?milliseconds:float -> t -> float
val setUTCTime : time:float -> t -> float
val toDateString : t -> string
val toISOString : t -> string
val toJSON : t -> string option
val toJSONUnsafe : t -> string
val toLocaleDateString : t -> string
val toLocaleString : t -> string
val toLocaleTimeString : t -> string
val toString : t -> string
val toTimeString : t -> string
val toUTCString : t -> string
================================================
FILE: packages/Js/lib/Js_dict.ml
================================================
(** Provide utilities for JS dictionary object *)
type key = string
type 'a t = (key, 'a) Hashtbl.t
let empty () : 'a t = Hashtbl.create 10
let entries (dict : 'a t) : (string * 'a) array =
Hashtbl.fold (fun k v acc -> (k, v) :: acc) dict [] |> Stdlib.Array.of_list
let get (dict : 'a t) (k : key) : 'a option = try Some (Hashtbl.find dict k) with Not_found -> None
let map ~(f : 'a -> 'b) (dict : 'a t) =
Hashtbl.fold
(fun k v acc ->
Hashtbl.add acc k (f v);
acc)
dict (empty ())
let set (dict : 'a t) (k : key) (x : 'a) : unit = Hashtbl.replace dict k x
let fromList (lst : (key * 'a) list) : 'a t =
let length = Stdlib.List.length lst in
let dict = Hashtbl.create length in
Stdlib.List.iter (fun (k, v) -> Hashtbl.add dict k v) lst;
dict
let fromArray (arr : (key * 'a) array) : 'a t =
let length = Stdlib.Array.length arr in
let dict = Hashtbl.create length in
Stdlib.Array.iter (fun (k, v) -> Hashtbl.add dict k v) arr;
dict
let keys (dict : 'a t) = Hashtbl.fold (fun k _ acc -> k :: acc) dict [] |> Stdlib.Array.of_list
let values (dict : 'a t) = Hashtbl.fold (fun _k value acc -> value :: acc) dict [] |> Stdlib.Array.of_list
let unsafeGet (dict : 'a t) (k : key) : 'a = Hashtbl.find dict k
let unsafeDeleteKey (dict : 'a t) (key : key) = Hashtbl.remove dict key
================================================
FILE: packages/Js/lib/Js_dict.mli
================================================
(** Provide utilities for JS dictionary object *)
type 'a t
(** Dictionary type *)
type key = string
(** Key type *)
val get : 'a t -> key -> 'a option
(** [get dict key] returns [None] if the [key] is not found in the dictionary, [Some value] otherwise *)
val unsafeGet : 'a t -> key -> 'a
val set : 'a t -> key -> 'a -> unit
(** [set dict key value] sets the [key]/[value] in [dict] *)
val keys : 'a t -> string array
(** [keys dict] returns all the keys in the dictionary [dict]*)
val empty : unit -> 'a t
(** [empty ()] returns an empty dictionary *)
val unsafeDeleteKey : string t -> string -> unit
(** Experimental internal function *)
val entries : 'a t -> (key * 'a) array
(** [entries dict] returns the key value pairs in [dict] *)
val values : 'a t -> 'a array
(** [values dict] returns the values in [dict] *)
val fromList : (key * 'a) list -> 'a t
(** [fromList entries] creates a new dictionary containing each [(key, value)] pair in [entries] *)
val fromArray : (key * 'a) array -> 'a t
(** [fromArray entries] creates a new dictionary containing each [(key, value)] pair in [entries] *)
val map : f:('a -> 'b) -> 'a t -> 'b t
(** [map f dict] maps [dict] to a new dictionary with the same keys, using [f] to map each value *)
================================================
FILE: packages/Js/lib/Js_exn.ml
================================================
type t
type exn +=
| Error of string
| EvalError of string
| RangeError of string
| ReferenceError of string
| SyntaxError of string
| TypeError of string
| UriError of string
let asJsExn _ = Js_internal.notImplemented "Js.Exn" "asJsExn"
let stack _ = Js_internal.notImplemented "Js.Exn" "stack"
let message _ = Js_internal.notImplemented "Js.Exn" "message"
let name _ = Js_internal.notImplemented "Js.Exn" "name"
let fileName _ = Js_internal.notImplemented "Js.Exn" "fileName"
let anyToExnInternal _ = Js_internal.notImplemented "Js.Exn" "anyToExnInternal"
let isCamlExceptionOrOpenVariant _ = Js_internal.notImplemented "Js.Exn" "isCamlExceptionOrOpenVariant"
let raiseError str = raise (Error str)
let raiseEvalError str = raise (EvalError str)
let raiseRangeError str = raise (RangeError str)
let raiseReferenceError str = raise (ReferenceError str)
let raiseSyntaxError str = raise (SyntaxError str)
let raiseTypeError str = raise (TypeError str)
let raiseUriError str = raise (UriError str)
================================================
FILE: packages/Js/lib/Js_exn.mli
================================================
type t
type exn +=
| Error of string
| EvalError of string
| RangeError of string
| ReferenceError of string
| SyntaxError of string
| TypeError of string
| UriError of string
val asJsExn : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val stack : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val message : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val name : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fileName : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val anyToExnInternal : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val isCamlExceptionOrOpenVariant : 'a -> 'b
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val raiseError : string -> 'a
val raiseEvalError : string -> 'a
val raiseRangeError : string -> 'a
val raiseReferenceError : string -> 'a
val raiseSyntaxError : string -> 'a
val raiseTypeError : string -> 'a
val raiseUriError : string -> 'a
================================================
FILE: packages/Js/lib/Js_float.ml
================================================
type t = float
module SpecialValues = struct
let _NaN = Stdlib.Float.nan
let isNaN float = Stdlib.Float.is_nan float
let fromString str =
match str with
| "NaN" -> _NaN
| "Infinity" -> infinity
| "-Infinity" -> neg_infinity
| _ -> raise (Failure "Invalid special value")
end
let _NaN = SpecialValues._NaN
let isNaN = SpecialValues.isNaN
let isFinite float = Stdlib.Float.is_finite float
let isInteger float = Stdlib.Float.is_finite float && Stdlib.Float.is_integer float
let toExponential ?digits f =
match digits with
| None -> Quickjs.Number.Prototype.to_string f
| Some d ->
if d < 0 || d > 100 then raise (Invalid_argument "toExponential() digits argument must be between 0 and 100")
else Quickjs.Number.Prototype.to_exponential d f
let toFixed ?(digits = 0) f =
if digits < 0 || digits > 100 then raise (Failure "toFixed() digits argument must be between 0 and 100")
else Quickjs.Number.Prototype.to_fixed digits f
let toPrecision ?digits f =
match digits with
| None -> Quickjs.Number.Prototype.to_string f
| Some d ->
if d < 1 || d > 100 then raise (Invalid_argument "toPrecision() digits argument must be between 1 and 100")
else Quickjs.Number.Prototype.to_precision d f
let toString ?radix f =
match radix with
| None -> Quickjs.Number.Prototype.to_string f
| Some r ->
if r < 2 || r > 36 then raise (Invalid_argument "toString() radix must be between 2 and 36")
else Quickjs.Number.Prototype.to_radix r f
let fromString str = try SpecialValues.fromString str with _ -> Stdlib.float_of_string str
================================================
FILE: packages/Js/lib/Js_float.mli
================================================
(** Provides functions for inspecting and manipulating [float]s *)
type t = float
val _NaN : t
val isNaN : t -> bool
val isFinite : t -> bool
val isInteger : t -> bool
(** Returns true if the value is a finite number with no fractional part *)
val toExponential : ?digits:int -> t -> string
(** Formats a number in exponential notation.
@raise Invalid_argument if digits is not in range 0-100 *)
val toFixed : ?digits:int -> t -> string
(** Formats a number with fixed-point notation.
@raise Failure if digits is not in range 0-100 *)
val toPrecision : ?digits:int -> t -> string
(** Formats a number with the specified number of significant digits.
@raise Invalid_argument if digits is not in range 1-100 *)
val toString : ?radix:int -> t -> string
(** Converts a number to a string. Optionally specify a radix (2-36).
@raise Invalid_argument if radix is not in range 2-36 *)
val fromString : string -> t
================================================
FILE: packages/Js/lib/Js_formdata.ml
================================================
(* TODO: This is a bad implementation for FormData, and not compatible with the Js.FormData from melange.js *)
type entryValue = [ `String of string ]
type t = (string, entryValue) Hashtbl.t
let make = (fun () -> Hashtbl.create 10 : unit -> t)
let append = (fun formData key value -> Hashtbl.add formData key value : t -> string -> entryValue -> unit)
let get = (fun formData key -> Hashtbl.find formData key : t -> string -> entryValue)
let entries : t -> (string * entryValue) list =
fun formData -> Hashtbl.fold (fun key value acc -> (key, value) :: acc) formData []
================================================
FILE: packages/Js/lib/Js_formdata.mli
================================================
(* TODO: This is a bad implementation for FormData, and not compatible with the Js.FormData from melange.js *)
type entryValue = [ `String of string ]
type t = (string, entryValue) Hashtbl.t
val make : unit -> t
val append : t -> string -> entryValue -> unit
val get : t -> string -> entryValue
val entries : t -> (string * entryValue) list
================================================
FILE: packages/Js/lib/Js_global.ml
================================================
(** Contains functions available in the global scope ([window] in a browser context) *)
type intervalId
(** Identify an interval started by {! setInterval} *)
type timeoutId
(** Identify timeout started by {! setTimeout} *)
let clearInterval _intervalId = Js_internal.notImplemented "Js.Global" "clearInterval"
let clearTimeout _timeoutId = Js_internal.notImplemented "Js.Global" "clearTimeout"
let setInterval ~f:_ _ = Js_internal.notImplemented "Js.Global" "setInterval"
let setIntervalFloat ~f:_ _ = Js_internal.notImplemented "Js.Global" "setInterval"
let setTimeout ~f:_ _ = Js_internal.notImplemented "Js.Global" "setTimeout"
let setTimeoutFloat ~f:_ _ = Js_internal.notImplemented "Js.Global" "setTimeout"
module URI = struct
let int_of_hex_opt str = try Some (Scanf.sscanf str "%x%!" (fun x -> x)) with _ -> None
let hex_decode str pos =
if pos + 2 >= String.length str then Error "Expecting Hex digit"
else
let first = int_of_hex_opt (Stdlib.String.sub str (pos + 1) 1) in
let second = int_of_hex_opt (Stdlib.String.sub str (pos + 2) 1) in
match (first, second) with
| Some first, Some second -> Ok ((first lsl 4) lor second)
| _ -> Error "Invalid hex digit"
let is_uri_reserved c = Stdlib.String.contains ";/?:@&=+$,#" c
let decode_uri ~component s =
let buf = Buffer.create (String.length s) in
let decode_utf8 pos char n c_min =
let rec loop pos char n =
if n <= 0 then Some (pos, char)
else
match hex_decode s pos with
| Ok c1 when c1 land 0xc0 = 0x80 -> loop (pos + 3) ((char lsl 6) lor (c1 land 0x3f)) (n - 1)
| _ -> raise (Invalid_argument "Invalid hex encoding")
in
match loop pos char n with
| Some (new_pos, char) when char >= c_min && char <= 0x10FFFF && (char < 0xd800 || char >= 0xe000) ->
(new_pos, char)
| _ -> raise (Invalid_argument "Malformed UTF-8")
in
let rec loop pos =
if pos >= String.length s then Buffer.contents buf
else
match Stdlib.String.get s pos with
| '%' -> (
match hex_decode s pos with
| Ok hex when hex >= 0 ->
if hex < 0x80 then
let c = Char.chr hex in
if (not component) && is_uri_reserved c then (
Buffer.add_char buf '%';
Buffer.add_string buf (Stdlib.String.sub s (pos + 1) 2);
loop (pos + 3))
else (
Buffer.add_char buf c;
loop (pos + 3))
else
let new_pos, decoded_char =
if hex >= 0xc0 && hex <= 0xdf then decode_utf8 (pos + 3) (hex land 0x1f) 1 0x80
else if hex >= 0xe0 && hex <= 0xef then decode_utf8 (pos + 3) (hex land 0x0f) 2 0x800
else if hex >= 0xf0 && hex <= 0xf7 then decode_utf8 (pos + 3) (hex land 0x07) 3 0x10000
else raise (Invalid_argument "Invalid UTF-8 start byte")
in
Buffer.add_utf_8_uchar buf (Uchar.of_int decoded_char);
loop new_pos
| _ -> raise (Invalid_argument "Invalid hex encoding"))
| c ->
Buffer.add_char buf c;
loop (pos + 1)
in
try loop 0 with error -> raise error
let is_uri_unescaped c is_component =
c < 0x100
&& ((c >= 0x61 && c <= 0x7a)
|| (c >= 0x41 && c <= 0x5a)
|| (c >= 0x30 && c <= 0x39)
|| Stdlib.String.contains "-_.!~*'()" (Char.chr c)
|| ((not is_component) && is_uri_reserved (Char.chr c)))
let hex_of_int_opt c =
let char_code = if c < 10 then Char.code '0' + c else Char.code 'A' + (c - 10) in
try Some (Char.chr char_code) with _ -> None
let encode_hex value =
let first_digit = hex_of_int_opt (value lsr 4) in
let second_digit = hex_of_int_opt (value land 0x0F) in
match (first_digit, second_digit) with
| Some first_digit, Some second_digit -> Ok (Printf.sprintf "%%%c%c" first_digit second_digit)
| _ -> Error (Printf.sprintf "Invalid hex encoding: %d" value)
let uri_char_escaped c =
match c with
| '\'' -> "'" (* treat single quote as a regular character *)
| c ->
(* use Char.escaped for other special characters that need escaping *)
let escaped = Char.escaped c in
if c = '\\' then Stdlib.String.sub escaped 1 (String.length escaped - 1) else escaped
let encode_uri ~component s =
let buf = Buffer.create (String.length s * 3) in
let rec loop pos =
if pos >= String.length s then Buffer.contents buf
else
let new_pos, encoded_char =
let c = Char.code (Stdlib.String.get s pos) in
let new_pos = pos + 1 in
if is_uri_unescaped c component then
let encoded_char =
try Ok (Char.chr c |> uri_char_escaped) with _ -> raise (Invalid_argument "invalid character")
in
(new_pos, encoded_char)
else if c >= 0xdc00 && c <= 0xdfff then raise (Invalid_argument "invalid character")
else if c >= 0xd800 && c <= 0xdbff then (
if new_pos >= String.length s then raise (Invalid_argument "expecting surrogate pair");
let c1 = Char.code (Stdlib.String.get s new_pos) in
if c1 < 0xdc00 || c1 > 0xdfff then raise (Invalid_argument "expecting surrogate pair");
let c = (((c land 0x3ff) lsl 10) lor (c1 land 0x3ff)) + 0x10000 in
(new_pos + 1, encode_hex c))
else (new_pos, encode_hex c)
in
match encoded_char with
| Ok encoded_char ->
Buffer.add_string buf encoded_char;
loop new_pos
| Error msg -> raise (Invalid_argument msg)
in
loop 0
end
let encodeURI = URI.encode_uri ~component:false
let decodeURI = URI.decode_uri ~component:false
let encodeURIComponent = URI.encode_uri ~component:true
let decodeURIComponent = URI.decode_uri ~component:true
let is_js_whitespace c =
(* JavaScript whitespace characters per ECMAScript spec *)
match c with
| '\x09' (* Tab *)
| '\x0A' (* Line feed *)
| '\x0B' (* Vertical tab *)
| '\x0C' (* Form feed *)
| '\x0D' (* Carriage return *)
| '\x20' (* Space *)
| '\xA0' (* No-break space (Latin-1 encoded) *) ->
true
| _ -> false
let strip_leading_js_whitespace str =
(* Strip leading JavaScript whitespace from string *)
let len = String.length str in
let rec find_start i =
if i >= len then len
else
let c = String.get str i in
if is_js_whitespace c then find_start (i + 1)
else if c = '\xC2' && i + 1 < len && String.get str (i + 1) = '\xA0' then
(* UTF-8 encoded non-breaking space U+00A0 *)
find_start (i + 2)
else if c = '\xEF' && i + 2 < len && String.get str (i + 1) = '\xBB' && String.get str (i + 2) = '\xBF' then
(* UTF-8 BOM *)
find_start (i + 3)
else i
in
let start = find_start 0 in
if start >= len then "" else String.sub str start (len - start)
let parseFloat str =
(* JavaScript's parseFloat behavior:
- Skip leading whitespace (JS whitespace, not just ASCII)
- Parse as much as valid number as possible
- Return NaN if no valid number at start *)
let trimmed = strip_leading_js_whitespace str in
match Quickjs.Global.parse_float trimmed with Some f -> f | None -> nan
let parseInt ?radix str =
(* JavaScript's parseInt behavior:
- Skip leading whitespace (JS whitespace, not just ASCII)
- Auto-detect hex from 0x/0X prefix when radix not specified
- Does NOT accept 0o/0b prefixes (unlike ES6 literals)
- Parse as much as valid number as possible
- Return NaN if no valid number at start *)
let trimmed = strip_leading_js_whitespace str in
let radix =
match radix with
| Some r -> Some r
| None ->
(* Check for 0x/0X prefix for hex auto-detection *)
let len = String.length trimmed in
if len >= 2 then
let first = String.get trimmed 0 in
let second = String.get trimmed 1 in
if first = '0' && (second = 'x' || second = 'X') then Some 16
else if (first = '-' || first = '+') && len >= 3 then
let third = String.get trimmed 2 in
if String.get trimmed 1 = '0' && (third = 'x' || third = 'X') then Some 16 else None
else None
else None
in
match Quickjs.Global.parse_int ?radix trimmed with Some i -> Float.of_int i | None -> nan
================================================
FILE: packages/Js/lib/Js_global.mli
================================================
(** Contains functions available in the global scope ([window] in a browser context) *)
type intervalId
(** Identify an interval started by {! setInterval} *)
type timeoutId
(** Identify timeout started by {! setTimeout} *)
val clearInterval : intervalId -> unit
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val clearTimeout : timeoutId -> unit
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val setInterval : f:(unit -> unit) -> int -> intervalId
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val setIntervalFloat : f:(unit -> unit) -> float -> intervalId
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val setTimeout : f:(unit -> unit) -> int -> timeoutId
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val setTimeoutFloat : f:(unit -> unit) -> float -> timeoutId
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val encodeURI : string -> string
val decodeURI : string -> string
val encodeURIComponent : string -> string
val decodeURIComponent : string -> string
val parseFloat : string -> float
(** Parses a string and returns a floating point number. Returns NaN if parsing fails. *)
val parseInt : ?radix:int -> string -> float
(** Parses a string and returns an integer. Returns NaN if parsing fails.
@param radix The base (2-36) to use for parsing. Default is 10. *)
================================================
FILE: packages/Js/lib/Js_int.ml
================================================
type t = int
let toExponential ?digits int =
let f = Stdlib.float_of_int int in
match digits with
| None -> Quickjs.Number.Prototype.to_string f
| Some d ->
if d < 0 || d > 100 then raise (Invalid_argument "toExponential() digits argument must be between 0 and 100")
else Quickjs.Number.Prototype.to_exponential d f
let toPrecision ?digits int =
let f = Stdlib.float_of_int int in
match digits with
| None -> Quickjs.Number.Prototype.to_string f
| Some d ->
if d < 1 || d > 100 then raise (Invalid_argument "toPrecision() digits argument must be between 1 and 100")
else Quickjs.Number.Prototype.to_precision d f
let toString ?radix int =
match radix with
| None -> Stdlib.string_of_int int
| Some r ->
if r < 2 || r > 36 then raise (Invalid_argument "toString() radix must be between 2 and 36")
else Quickjs.Number.of_int_radix ~radix:r int
let toFloat int = Stdlib.float_of_int int
let equal = Stdlib.Int.equal
let max = 2147483647
let min = -2147483648
================================================
FILE: packages/Js/lib/Js_int.mli
================================================
(** Provides functions for inspecting and manipulating [int]s *)
type t = int
val toExponential : ?digits:t -> t -> string
(** Formats a number in exponential notation.
@raise Invalid_argument if digits is not in range 0-100 *)
val toPrecision : ?digits:t -> t -> string
(** Formats a number with the specified number of significant digits.
@raise Invalid_argument if digits is not in range 1-100 *)
val toString : ?radix:t -> t -> string
(** Converts an integer to a string. Optionally specify a radix (2-36).
@raise Invalid_argument if radix is not in range 2-36 *)
val toFloat : int -> float
val equal : t -> t -> bool
val max : int
val min : int
================================================
FILE: packages/Js/lib/Js_internal.ml
================================================
exception Not_implemented of string
let notImplemented module_ function_ =
let msg =
Printf.sprintf
"'%s.%s' is not implemented in native on `server-reason-react.js`. You are running code that depends on the \
browser, this is not supported. If this case should run on native and there's no browser dependency, please \
open an issue at %s"
module_ function_ "https://github.com/ml-in-barcelona/server-reason-react/issues"
in
raise (Not_implemented msg)
type 'a null = 'a option
type 'a undefined = 'a option
type 'a nullable = 'a option
================================================
FILE: packages/Js/lib/Js_internal.mli
================================================
exception Not_implemented of string
val notImplemented : string -> string -> 'a
type 'a null = 'a option
type 'a undefined = 'a option
type 'a nullable = 'a option
================================================
FILE: packages/Js/lib/Js_json.ml
================================================
(* Efficient JSON encoding using JavaScript API *)
type t
type _ kind =
| String : Js_string.t kind
| Number : float kind
| Object : t Js_dict.t kind
| Array : t array kind
| Boolean : bool kind
| Null : Js_types.null_val kind
type tagged_t =
| JSONFalse
| JSONTrue
| JSONNull
| JSONString of string
| JSONNumber of float
| JSONObject of t Js_dict.t
| JSONArray of t array
let classify (_x : t) : tagged_t = Js_internal.notImplemented "Js.Json" "classify"
let test _ : bool = Js_internal.notImplemented "Js.Json" "test"
let decodeString _json = Js_internal.notImplemented "Js.Json" "decodeString"
let decodeNumber _json = Js_internal.notImplemented "Js.Json" "decodeNumber"
let decodeObject _json = Js_internal.notImplemented "Js.Json" "decodeObject"
let decodeArray _json = Js_internal.notImplemented "Js.Json" "decodeArray"
let decodeBoolean (_json : t) = Js_internal.notImplemented "Js.Json" "decodeBoolean"
let decodeNull _json = Js_internal.notImplemented "Js.Json" "decodeNull"
let parseExn _ = Js_internal.notImplemented "Js.Json" "parseExn"
let stringifyAny _ = Js_internal.notImplemented "Js.Json" "stringifyAny"
let null _ = Js_internal.notImplemented "Js.Json" "null"
let string _ = Js_internal.notImplemented "Js.Json" "string"
let number _ = Js_internal.notImplemented "Js.Json" "number"
let boolean _ = Js_internal.notImplemented "Js.Json" "boolean"
let object_ _ = Js_internal.notImplemented "Js.Json" "object_"
let array _ = Js_internal.notImplemented "Js.Json" "array"
let stringArray _ = Js_internal.notImplemented "Js.Json" "stringArray"
let numberArray _ = Js_internal.notImplemented "Js.Json" "numberArray"
let booleanArray _ = Js_internal.notImplemented "Js.Json" "booleanArray"
let objectArray _ = Js_internal.notImplemented "Js.Json" "objectArray"
let stringify _ = Js_internal.notImplemented "Js.Json" "stringify"
let stringifyWithSpace _ = Js_internal.notImplemented "Js.Json" "stringifyWithSpace"
let patch _ = Js_internal.notImplemented "Js.Json" "patch"
let serializeExn (_x : t) : string = Js_internal.notImplemented "Js.Json" "serializeExn"
let deserializeUnsafe (_s : string) : 'a = Js_internal.notImplemented "Js.Json" "deserializeUnsafe"
================================================
FILE: packages/Js/lib/Js_json.mli
================================================
(* Efficient JSON encoding using JavaScript API *)
type t
type _ kind =
| String : string kind
| Number : float kind
| Object : t Js_dict.t kind
| Array : t array kind
| Boolean : bool kind
| Null : Js_types.null_val kind
type tagged_t =
| JSONFalse
| JSONTrue
| JSONNull
| JSONString of string
| JSONNumber of float
| JSONObject of t Js_dict.t
| JSONArray of t array
val classify : t -> tagged_t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val test : 'a -> bool [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeString : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeNumber : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeObject : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeArray : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeBoolean : t -> 'a [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val decodeNull : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val parseExn : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val stringifyAny : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val null : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val string : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val number : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val boolean : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val object_ : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val array : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val stringArray : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val numberArray : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val booleanArray : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val objectArray : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val stringify : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val stringifyWithSpace : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val patch : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val serializeExn : t -> string [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val deserializeUnsafe : string -> 'a
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
================================================
FILE: packages/Js/lib/Js_map.ml
================================================
(** Provides bindings for ES6 Map *)
type ('k, 'v) t
================================================
FILE: packages/Js/lib/Js_map.mli
================================================
(** Provides bindings for ES6 Map *)
type ('k, 'v) t
================================================
FILE: packages/Js/lib/Js_math.ml
================================================
(** JavaScript Math API *)
(** Euler's number *)
let _E = 2.718281828459045
(** natural logarithm of 2 *)
let _LN2 = 0.6931471805599453
(** natural logarithm of 10 *)
let _LN10 = 2.302585092994046
(** base 2 logarithm of E *)
let _LOG2E = 1.4426950408889634
(** base 10 logarithm of E *)
let _LOG10E = 0.4342944819032518
(** Pi... (ratio of the circumference and diameter of a circle) *)
let _PI = 3.141592653589793
(** square root of 1/2 *)
let _SQRT1_2 = 0.7071067811865476
(** square root of 2 *)
let _SQRT2 = 1.41421356237
(** absolute value *)
let abs_int _ = Js_internal.notImplemented "Js.Math" "abs_int"
let abs_float _ = Js_internal.notImplemented "Js.Math" "abs_float"
let acos _ = Js_internal.notImplemented "Js.Math" "acos"
let acosh _ = Js_internal.notImplemented "Js.Math" "acosh"
let asin _ = Js_internal.notImplemented "Js.Math" "asin"
let asinh _ = Js_internal.notImplemented "Js.Math" "asinh"
let atan _ = Js_internal.notImplemented "Js.Math" "atan"
let atanh _ = Js_internal.notImplemented "Js.Math" "atanh"
let atan2 ~y:_ ~x:_ = Js_internal.notImplemented "Js.Math" "atan2"
let cbrt _ = Js_internal.notImplemented "Js.Math" "cbrt"
let unsafe_ceil_int _ = Js_internal.notImplemented "Js.Math" "unsafe_ceil_int"
let ceil_int _ = Js_internal.notImplemented "Js.Math" "ceil_int"
let ceil_float _ = Js_internal.notImplemented "Js.Math" "ceil_float"
let clz32 _ = Js_internal.notImplemented "Js.Math" "clz32"
let cos = cos
let cosh _ = Js_internal.notImplemented "Js.Math" "cosh"
let exp _ = Js_internal.notImplemented "Js.Math" "exp"
let expm1 _ = Js_internal.notImplemented "Js.Math" "expm1"
let unsafe_floor_int _ = Js_internal.notImplemented "Js.Math" "unsafe_floor_int"
let floor_int _f = Js_internal.notImplemented "Js.Math" "floor_int"
let floor_float _ = Js_internal.notImplemented "Js.Math" "floor_float"
let fround _ = Js_internal.notImplemented "Js.Math" "fround"
let hypot _ = Js_internal.notImplemented "Js.Math" "hypot"
let hypotMany _ = Js_internal.notImplemented "Js.Math" "hypotMany"
let imul _ = Js_internal.notImplemented "Js.Math" "imul"
let log _ = Js_internal.notImplemented "Js.Math" "log"
let log1p _ = Js_internal.notImplemented "Js.Math" "log1p"
let log10 _ = Js_internal.notImplemented "Js.Math" "log10"
let log2 _ = Js_internal.notImplemented "Js.Math" "log2"
let max_int (a : int) (b : int) = Stdlib.max a b
let maxMany_int _ = Js_internal.notImplemented "Js.Math" "maxMany_int"
let max_float (a : float) (b : float) = Stdlib.max a b
let maxMany_float _ = Js_internal.notImplemented "Js.Math" "maxMany_float"
let min_int (a : int) (b : int) = Stdlib.min a b
let minMany_int _ = Js_internal.notImplemented "Js.Math" "minMany_int"
let min_float (a : float) (b : float) = Stdlib.min a b
let minMany_float _ = Js_internal.notImplemented "Js.Math" "minMany_float"
let pow_float ~base:_ ~exp:_ = Js_internal.notImplemented "Js.Math" "pow_float"
let random _ = Js_internal.notImplemented "Js.Math" "random"
let random_int _min _max = Js_internal.notImplemented "Js.Math" "random_int"
let unsafe_round _ = Js_internal.notImplemented "Js.Math" "unsafe_round"
let round _ = Js_internal.notImplemented "Js.Math" "round"
let sign_int _ = Js_internal.notImplemented "Js.Math" "sign_int"
let sign_float _ = Js_internal.notImplemented "Js.Math" "sign_float"
let sin = sin
let sinh _ = Js_internal.notImplemented "Js.Math" "sinh"
let sqrt _ = Js_internal.notImplemented "Js.Math" "sqrt"
let tan _ = Js_internal.notImplemented "Js.Math" "tan"
let tanh _ = Js_internal.notImplemented "Js.Math" "tanh"
let unsafe_trunc _ = Js_internal.notImplemented "Js.Math" "unsafe_trunc"
let trunc _ = Js_internal.notImplemented "Js.Math" "trunc"
================================================
FILE: packages/Js/lib/Js_math.mli
================================================
(** JavaScript Math API *)
val _E : float
val _LN2 : float
val _LN10 : float
val _LOG2E : float
val _LOG10E : float
val _PI : float
val _SQRT1_2 : float
val _SQRT2 : float
val abs_int : int -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val abs_float : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val acos : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val acosh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val asin : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val asinh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val atan : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val atanh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val atan2 : y:float -> x:float -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val cbrt : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafe_ceil_int : float -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val ceil_int : float -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val ceil_float : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val clz32 : int -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val cos : float -> float
val cosh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val exp : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val expm1 : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafe_floor_int : float -> int
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val floor_int : float -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val floor_float : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fround : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val hypot : float -> float -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val hypotMany : float array -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val imul : int -> int -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val log : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val log1p : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val log10 : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val log2 : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val max_int : int -> int -> int
val maxMany_int : int array -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val max_float : float -> float -> float
val maxMany_float : float array -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val min_int : int -> int -> int
val minMany_int : int array -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val min_float : float -> float -> float
val minMany_float : float array -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val pow_float : base:float -> exp:float -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val random : unit -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val random_int : int -> int -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafe_round : float -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val round : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sign_int : int -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sign_float : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sin : float -> float
val sinh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val sqrt : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val tan : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val tanh : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafe_trunc : float -> int [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val trunc : float -> float [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
================================================
FILE: packages/Js/lib/Js_null.ml
================================================
type 'a t = 'a Js_internal.nullable
external toOption : 'a t -> 'a option = "%identity"
external fromOpt : 'a option -> 'a t = "%identity"
let empty = None
let return a = Some a
let getUnsafe a = match toOption a with None -> assert false | Some a -> a
let test = function None -> true | Some _ -> false
let getExn _ = Js_internal.notImplemented "Js.Null" "getExn"
let bind _ _ = Js_internal.notImplemented "Js.Null" "bind"
let iter _ _ = Js_internal.notImplemented "Js.Null" "iter"
let fromOption = fromOpt
let from_opt = fromOpt
================================================
FILE: packages/Js/lib/Js_null.mli
================================================
type 'a t = 'a Js_internal.nullable
external toOption : 'a t -> 'a Js_internal.nullable = "%identity"
external fromOpt : 'a Js_internal.nullable -> 'a t = "%identity"
val empty : 'a Js_internal.nullable
val return : 'a -> 'a Js_internal.nullable
val getUnsafe : 'a t -> 'a
val test : 'a Js_internal.nullable -> bool
val getExn : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val bind : 'a -> 'b -> 'c [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val iter : 'a -> 'b -> 'c [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fromOption : 'a Js_internal.nullable -> 'a t
val from_opt : 'a Js_internal.nullable -> 'a t
================================================
FILE: packages/Js/lib/Js_nullable.ml
================================================
type 'a t = 'a option
external toOption : 'a t -> 'a option = "%identity"
external to_opt : 'a t -> 'a option = "%identity"
let return : 'a -> 'a t = fun x -> Some x
let isNullable : 'a t -> bool = function Some _ -> false | None -> true
let null : 'a t = None
let undefined : 'a t = None
let bind x f = match to_opt x with None -> ((x : 'a t) : 'b t) | Some x -> return (f x)
let iter x f = match to_opt x with None -> () | Some x -> f x
let fromOption x = match x with None -> undefined | Some x -> return x
let from_opt = fromOption
================================================
FILE: packages/Js/lib/Js_nullable.mli
================================================
type 'a t = 'a Js_internal.nullable
external toOption : 'a t -> 'a Js_internal.nullable = "%identity"
external to_opt : 'a t -> 'a Js_internal.nullable = "%identity"
val return : 'a -> 'a t
val isNullable : 'a t -> bool
val null : 'a t
val undefined : 'a t
val bind : 'b t -> ('b -> 'b) -> 'b t
val iter : 'a t -> ('a -> unit) -> unit
val fromOption : 'a Js_internal.nullable -> 'a t
val from_opt : 'a Js_internal.nullable -> 'a t
================================================
FILE: packages/Js/lib/Js_obj.ml
================================================
(** Provide utilities for {!Js.t} *)
module Internal = struct
module Registry = Ephemeron.K1.Make (struct
type t = Obj.t
let equal (left : t) (right : t) = left == right
let hash = Hashtbl.hash
end)
type entry = {
method_name : string;
js_name : string;
mutable present : bool;
get_boxed : unit -> Obj.t;
set_boxed : Obj.t -> unit;
}
type metadata = {
mutable order_rev : string list;
mutable cached_keys : string array option;
entries : (string, entry) Hashtbl.t;
}
let registry : metadata Registry.t = Registry.create 16
let empty_metadata () = { order_rev = []; cached_keys = Some [||]; entries = Hashtbl.create 8 }
let add_key_in_order metadata js_name =
metadata.order_rev <- js_name :: metadata.order_rev;
metadata.cached_keys <- None
let keys_in_order metadata =
match metadata.cached_keys with
| Some keys -> keys
| None ->
let keys = Array.of_list (List.rev metadata.order_rev) in
metadata.cached_keys <- Some keys;
keys
let raw_entry ~method_name ~js_name ~present value =
let cell = ref value in
{ method_name; js_name; present; get_boxed = (fun () -> !cell); set_boxed = (fun next -> cell := next) }
let slot_ref ~method_name ~js_name ~present initial =
let cell = ref initial in
let entry =
{
method_name;
js_name;
present;
get_boxed = (fun () -> Obj.repr !cell);
set_boxed = (fun next -> cell := Obj.obj next);
}
in
(cell, entry)
let get_metadata object_ = Registry.find_opt registry (Obj.repr object_)
let ensure_metadata object_ =
match get_metadata object_ with
| Some metadata -> metadata
| None ->
let metadata = empty_metadata () in
Registry.add registry (Obj.repr object_) metadata;
metadata
let register_entry metadata entry =
let was_present =
match Hashtbl.find_opt metadata.entries entry.js_name with Some existing -> existing.present | None -> false
in
Hashtbl.replace metadata.entries entry.js_name entry;
if entry.present && not was_present then add_key_in_order metadata entry.js_name
let register_structural object_ entries =
let metadata = empty_metadata () in
List.iter (register_entry metadata) entries;
Registry.replace registry (Obj.repr object_) metadata;
object_
let clone_entry entry =
raw_entry ~method_name:entry.method_name ~js_name:entry.js_name ~present:entry.present (entry.get_boxed ())
let present_entries metadata =
Array.fold_right
(fun key acc ->
match Hashtbl.find_opt metadata.entries key with
| Some entry when entry.present -> entry :: acc
| Some _ | None -> acc)
(keys_in_order metadata) []
let build metadata =
let present_entries = present_entries metadata in
let object_ : < .. > =
match present_entries with
| [] -> ((object end : < >) :> < .. >)
| _ ->
let table =
CamlinternalOO.create_table (Array.of_list (List.map (fun entry -> entry.method_name) present_entries))
in
CamlinternalOO.init_class table;
let object_ = CamlinternalOO.create_object table in
List.iter
(fun entry ->
let label = CamlinternalOO.get_method_label table entry.method_name in
let closure : CamlinternalOO.meth =
Obj.obj (Obj.repr (fun (_self : CamlinternalOO.obj) -> entry.get_boxed ()))
in
CamlinternalOO.set_method table label closure)
present_entries;
CamlinternalOO.run_initializers object_ table;
Obj.obj (Obj.repr object_)
in
Registry.replace registry (Obj.repr object_) metadata;
object_
let copy_present_entries_into target_metadata source =
match get_metadata source with
| None -> ()
| Some source_metadata ->
Array.iter
(fun key ->
match Hashtbl.find_opt source_metadata.entries key with
| None -> ()
| Some source_entry -> (
match Hashtbl.find_opt target_metadata.entries key with
| Some target_entry ->
target_entry.set_boxed (source_entry.get_boxed ());
if not target_entry.present then add_key_in_order target_metadata key;
target_entry.present <- true
| None ->
let cloned_entry = clone_entry source_entry in
Hashtbl.add target_metadata.entries key cloned_entry;
if cloned_entry.present then add_key_in_order target_metadata key))
(keys_in_order source_metadata)
let assign_into target source =
let target_metadata = ensure_metadata target in
copy_present_entries_into target_metadata source;
target
let register_abstract object_ entries = Obj.obj (Obj.repr (register_structural object_ entries))
end
let empty () : < .. > = Internal.register_abstract ((object end : < >) :> < .. >) []
let assign target source = Internal.assign_into target source
let merge () left right : < .. > =
let metadata = Internal.empty_metadata () in
Internal.copy_present_entries_into metadata left;
Internal.copy_present_entries_into metadata right;
let object_ : < .. > = Internal.build metadata in
Obj.obj (Obj.repr object_)
let keys object_ =
match Internal.get_metadata object_ with
| None -> [||]
| Some metadata -> Array.copy (Internal.keys_in_order metadata)
================================================
FILE: packages/Js/lib/Js_obj.mli
================================================
(** Provide utilities for {!Js.t} *)
val empty : unit -> < .. >
val assign : (< .. > as 'a) -> < .. > -> 'a
val merge : unit -> < .. > -> < .. > -> < .. >
val keys : _ -> string array
(**/**)
module Internal : sig
type entry
val slot_ref : method_name:string -> js_name:string -> present:bool -> 'a -> 'a ref * entry
val register_structural : (< .. > as 'a) -> entry list -> 'a
val register_abstract : < .. > -> entry list -> 'a
end
================================================
FILE: packages/Js/lib/Js_promise.ml
================================================
type +'a t = 'a Lwt.t
type error = exn
let make (fn : resolve:('a -> unit) -> reject:(exn -> unit) -> unit) : 'a t =
let promise, resolver = Lwt.task () in
let resolve value = Lwt.wakeup_later resolver value in
let reject exn = Lwt.wakeup_later_exn resolver exn in
fn ~resolve ~reject;
promise
let resolve = Lwt.return
let reject = Lwt.fail
let all (promises : 'a t array) : 'a array t = Lwt.map Stdlib.Array.of_list (Lwt.all (Stdlib.Array.to_list promises))
let all2 (a, b) =
let%lwt res_a = a in
let%lwt res_b = b in
Lwt.return (res_a, res_b)
let all3 (a, b, c) =
let%lwt res_a = a in
let%lwt res_b = b in
let%lwt res_c = c in
Lwt.return (res_a, res_b, res_c)
let all4 (a, b, c, d) =
let%lwt res_a = a in
let%lwt res_b = b in
let%lwt res_c = c in
let%lwt res_d = d in
Lwt.return (res_a, res_b, res_c, res_d)
let all5 (a, b, c, d, e) =
let%lwt res_a = a in
let%lwt res_b = b in
let%lwt res_c = c in
let%lwt res_d = d in
let%lwt res_e = e in
Lwt.return (res_a, res_b, res_c, res_d, res_e)
let all6 (a, b, c, d, e, f) =
let%lwt res_a = a in
let%lwt res_b = b in
let%lwt res_c = c in
let%lwt res_d = d in
let%lwt res_e = e in
let%lwt res_f = f in
Lwt.return (res_a, res_b, res_c, res_d, res_e, res_f)
let race (promises : 'a t array) : 'a t = Lwt.pick (Stdlib.Array.to_list promises)
let then_ p fn = Lwt.bind fn p
let catch (handler : exn -> 'a t) (promise : 'a t) : 'a t = Lwt.catch (fun () -> promise) handler
================================================
FILE: packages/Js/lib/Js_promise.mli
================================================
type 'a t = 'a Lwt.t
type error = exn
val make : (resolve:('a -> unit) -> reject:(exn -> unit) -> unit) -> 'a Lwt.t
val resolve : 'a -> 'a Lwt.t
val reject : exn -> 'a Lwt.t
val all : 'a Lwt.t array -> 'a array Lwt.t
val all2 : 'a Lwt.t * 'b Lwt.t -> ('a * 'b) Lwt.t
val all3 : 'a Lwt.t * 'b Lwt.t * 'c Lwt.t -> ('a * 'b * 'c) Lwt.t
val all4 : 'a Lwt.t * 'b Lwt.t * 'c Lwt.t * 'd Lwt.t -> ('a * 'b * 'c * 'd) Lwt.t
val all5 : 'a Lwt.t * 'b Lwt.t * 'c Lwt.t * 'd Lwt.t * 'e Lwt.t -> ('a * 'b * 'c * 'd * 'e) Lwt.t
val all6 : 'a Lwt.t * 'b Lwt.t * 'c Lwt.t * 'd Lwt.t * 'e Lwt.t * 'f Lwt.t -> ('a * 'b * 'c * 'd * 'e * 'f) Lwt.t
val race : 'a Lwt.t array -> 'a Lwt.t
val then_ : ('a -> 'b Lwt.t) -> 'a Lwt.t -> 'b Lwt.t
val catch : (exn -> 'a Lwt.t) -> 'a Lwt.t -> 'a Lwt.t
================================================
FILE: packages/Js/lib/Js_re.ml
================================================
(** Provide bindings to Js regex expression *)
(* The RegExp object *)
type t = Quickjs.RegExp.t
(* The result of a executing a RegExp on a string. *)
type result = Quickjs.RegExp.result
(* Maps with nullable since Melange does too: https://melange.re/v3.0.0/api/re/melange/Js/Re/index.html#val-captures *)
let captures : result -> string Js_internal.nullable array =
fun result -> Quickjs.RegExp.captures result |> Stdlib.Array.map (fun x -> Some x)
let index : result -> int = Quickjs.RegExp.index
let input : result -> string = Quickjs.RegExp.input
let source : t -> string = Quickjs.RegExp.source
let fromString : string -> t =
fun str ->
match Quickjs.RegExp.compile str ~flags:"" with
| Ok regex -> regex
| Error err -> raise (Invalid_argument (Quickjs.RegExp.compile_error_to_string err))
let fromStringWithFlags : string -> flags:string -> t =
fun str ~flags ->
match Quickjs.RegExp.compile str ~flags with
| Ok regex -> regex
| Error err -> raise (Invalid_argument (Quickjs.RegExp.compile_error_to_string err))
let flags : t -> string = fun regexp -> Quickjs.RegExp.flags regexp
let global : t -> bool = fun regexp -> Quickjs.RegExp.global regexp
let ignoreCase : t -> bool = fun regexp -> Quickjs.RegExp.ignorecase regexp
let multiline : t -> bool = fun regexp -> Quickjs.RegExp.multiline regexp
let sticky : t -> bool = fun regexp -> Quickjs.RegExp.sticky regexp
let unicode : t -> bool = fun regexp -> Quickjs.RegExp.unicode regexp
let dotAll : t -> bool = fun regexp -> Quickjs.RegExp.dotall regexp
let lastIndex : t -> int = fun regex -> Quickjs.RegExp.last_index regex
let setLastIndex : t -> int -> unit = fun regex index -> Quickjs.RegExp.set_last_index regex index
let exec : str:string -> t -> result option =
fun ~str rex -> match Quickjs.RegExp.exec rex str with result -> Some result | exception _ -> None
let test_ : t -> string -> bool = fun regexp str -> Quickjs.RegExp.test regexp str
let test : str:string -> t -> bool = fun ~str regex -> test_ regex str
(* Named capture groups *)
let groups : result -> (string * string) list = Quickjs.RegExp.groups
let group : string -> result -> string option = Quickjs.RegExp.group
================================================
FILE: packages/Js/lib/Js_re.mli
================================================
type t
type result
val index : result -> int
val input : result -> string
val fromString : string -> t
val fromStringWithFlags : string -> flags:string -> t
val flags : t -> string
val global : t -> bool
val ignoreCase : t -> bool
val lastIndex : t -> int
val setLastIndex : t -> int -> unit
val multiline : t -> bool
val source : t -> string
val sticky : t -> bool
val unicode : t -> bool
val dotAll : t -> bool
(** Returns whether the dotAll (s) flag is set *)
val exec : str:string -> t -> result option
val test : str:string -> t -> bool
val captures : result -> string Js_internal.nullable array
val groups : result -> (string * string) list
(** Returns all named capture groups as a list of (name, value) pairs *)
val group : string -> result -> string option
(** Returns the value of a named capture group, or None if not found *)
================================================
FILE: packages/Js/lib/Js_set.ml
================================================
(** Provides bindings for ES6 Set *)
type 'a t
================================================
FILE: packages/Js/lib/Js_set.mli
================================================
(** Provides bindings for ES6 Set *)
type 'a t
================================================
FILE: packages/Js/lib/Js_string.ml
================================================
(** JavaScript String API *)
type t = string
let make _whatever = Js_internal.notImplemented "Js.String" "make"
let fromCharCode code =
let uchar = Uchar.of_int code in
let char_value = Uchar.to_char uchar in
Stdlib.String.make 1 char_value
let fromCharCodeMany _ = Js_internal.notImplemented "Js.String" "fromCharCodeMany"
let fromCodePoint code_point =
let ch = Char.chr code_point in
Stdlib.String.make 1 ch
let fromCodePointMany _ = Js_internal.notImplemented "Js.String" "fromCodePointMany"
let length = Stdlib.String.length
let get str index =
let ch = Stdlib.String.get str index in
Stdlib.String.make 1 ch
(* TODO (davesnx): If the string contains characters outside the range [\u0000-\uffff], it will return the first 16-bit value at that position in the string. *)
let charAt ~index str =
if index < 0 || index >= Stdlib.String.length str then ""
else
let ch = Stdlib.String.get str index in
Stdlib.String.make 1 ch
let charCodeAt ~index:n s =
if n < 0 || n >= Stdlib.String.length s then nan else float_of_int (Stdlib.Char.code (Stdlib.String.get s n))
let codePointAt ~index str =
let str_length = Stdlib.String.length str in
if index >= 0 && index < str_length then
let uchar = Uchar.of_char (Stdlib.String.get str index) in
Some (Uchar.to_int uchar)
else None
let concat ~other:str2 str1 = Stdlib.String.concat "" [ str1; str2 ]
let concatMany ~strings:many original =
let many_list = Stdlib.Array.to_list many in
Stdlib.String.concat "" (original :: many_list)
let endsWith ~suffix ?len str =
let str_length = Stdlib.String.length str in
let end_idx = match len with Some i -> Stdlib.min str_length i | None -> str_length in
let sub_str = Stdlib.String.sub str 0 end_idx in
Stdlib.String.ends_with ~suffix sub_str
let includes ~search ?start str =
let str_length = Stdlib.String.length str in
let search_length = Stdlib.String.length search in
let rec includes_helper idx =
if idx + search_length > str_length then false
else if Stdlib.String.sub str idx search_length = search then true
else includes_helper (idx + 1)
in
let from = match start with None -> 0 | Some f -> f in
includes_helper from
let indexOf ~search ?start str =
let str_length = Stdlib.String.length str in
let search_length = Stdlib.String.length search in
let rec index_helper idx =
if idx + search_length > str_length then -1
else if Stdlib.String.sub str idx search_length = search then idx
else index_helper (idx + 1)
in
let from = match start with None -> 0 | Some f -> f in
index_helper from
let lastIndexOf ~search ?(start = max_int) str =
let len = String.length str in
let rec find_index i =
if i < 0 || i > start then -1
else
let sub_len = min (len - i) (String.length search) in
if String.sub str i sub_len = search then i else find_index (i - 1)
in
find_index (min (len - 1) start)
let localeCompare ~other:_ _ = Js_internal.notImplemented "Js.String" "localeCompare"
let match_ ~regexp str =
let match_next str regex =
match Js_re.exec ~str regex with None -> None | Some result -> Some (Js_re.captures result)
in
let match_all : t -> Js_re.t -> t Js_internal.nullable array Js_internal.nullable =
fun str regex ->
match match_next str regex with
| None -> None
| Some result -> (
match match_next str regex with None -> Some result | Some second -> Some (Stdlib.Array.append result second))
in
if Js_re.global regexp then match_all str regexp else match_next str regexp
let normalize ?(form = `NFC) str =
let normalization =
match form with
| `NFC -> Quickjs.String.NFC
| `NFD -> Quickjs.String.NFD
| `NFKC -> Quickjs.String.NFKC
| `NFKD -> Quickjs.String.NFKD
in
match Quickjs.String.Prototype.normalize normalization str with Some s -> s | None -> str
(* TODO(davesnx): RangeError *)
let repeat ~count str =
let rec repeat' str acc remaining = if remaining <= 0 then acc else repeat' str (str ^ acc) (remaining - 1) in
repeat' str "" count
(* If pattern is a string, only the first occurrence will be replaced.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace *)
let replace ~search ~replacement str =
let search_regexp = Str.regexp_string search in
Str.replace_first search_regexp replacement str
(* Process replacement string with backreferences like $1, $2, $&, $$, $`, $' *)
let process_replacement ~replacement ~matches ~prefix ~suffix =
let len = String.length replacement in
let buf = Buffer.create len in
let i = ref 0 in
while !i < len do
if replacement.[!i] = '$' && !i + 1 < len then (
let next = replacement.[!i + 1] in
match next with
| '$' ->
(* $$ -> literal $ *)
Buffer.add_char buf '$';
i := !i + 2
| '&' ->
(* $& -> the matched substring *)
let matched = Stdlib.Array.get matches 0 |> Option.value ~default:"" in
Buffer.add_string buf matched;
i := !i + 2
| '`' ->
(* $` -> portion before the match *)
Buffer.add_string buf prefix;
i := !i + 2
| '\'' ->
(* $' -> portion after the match *)
Buffer.add_string buf suffix;
i := !i + 2
| '0' .. '9' ->
(* $n or $nn -> capturing group *)
let start_digit = !i + 1 in
(* Check for two-digit group number *)
let group_num, advance =
if !i + 2 < len then
match replacement.[!i + 2] with
| '0' .. '9' ->
let two_digit = int_of_string (String.sub replacement start_digit 2) in
if two_digit < Array.length matches then (two_digit, 3) else (Char.code next - Char.code '0', 2)
| _ -> (Char.code next - Char.code '0', 2)
else (Char.code next - Char.code '0', 2)
in
if group_num > 0 && group_num < Array.length matches then
let group_value = Stdlib.Array.get matches group_num |> Option.value ~default:"" in
Buffer.add_string buf group_value
else (
(* Invalid group reference, keep as literal *)
Buffer.add_char buf '$';
Buffer.add_char buf next;
if advance = 3 then Buffer.add_char buf replacement.[!i + 2]);
i := !i + advance
| _ ->
(* Unknown $ sequence, keep as literal *)
Buffer.add_char buf '$';
incr i)
else (
Buffer.add_char buf replacement.[!i];
incr i)
done;
Buffer.contents buf
let replaceByRe ~regexp ~replacement str =
let rec replace_all str =
Js_re.setLastIndex regexp 0;
match Js_re.exec ~str regexp with
| None -> str
| Some result when Stdlib.Array.length (Js_re.captures result) == 0 -> str
| Some result ->
let matches = Js_re.captures result in
let matched_str = Stdlib.Array.get matches 0 |> Option.get in
let prefix = Stdlib.String.sub str 0 (Js_re.index result) in
let suffix_start = Js_re.index result + String.length matched_str in
let suffix = Stdlib.String.sub str suffix_start (String.length str - suffix_start) in
let processed_replacement = process_replacement ~replacement ~matches ~prefix ~suffix in
Js_re.setLastIndex regexp suffix_start;
prefix ^ processed_replacement ^ replace_all suffix
in
let replace_first str =
match Js_re.exec ~str regexp with
| None -> str
| Some result ->
let matches = Js_re.captures result in
let matched_str = Stdlib.Array.get matches 0 |> Option.get in
let prefix = Stdlib.String.sub str 0 (Js_re.index result) in
let suffix_start = Js_re.index result + String.length matched_str in
let suffix = Stdlib.String.sub str suffix_start (String.length str - suffix_start) in
let processed_replacement = process_replacement ~replacement ~matches ~prefix ~suffix in
prefix ^ processed_replacement ^ suffix
in
if Js_re.global regexp then replace_all str else replace_first str
let unsafeReplaceBy0 ~regexp:_ ~f:_ _ = Js_internal.notImplemented "Js.String" "unsafeReplaceBy0"
let unsafeReplaceBy1 ~regexp:_ ~f:_ _ = Js_internal.notImplemented "Js.String" "unsafeReplaceBy1"
let unsafeReplaceBy2 ~regexp:_ ~f:_ _ = Js_internal.notImplemented "Js.String" "unsafeReplaceBy2"
let unsafeReplaceBy3 ~regexp:_ ~f:_ _ = Js_internal.notImplemented "Js.String" "unsafeReplaceBy3"
let search ~regexp str =
(* Save and reset lastIndex for consistent behavior *)
let saved_last_index = Js_re.lastIndex regexp in
Js_re.setLastIndex regexp 0;
let result =
if Js_re.test ~str regexp then (
(* Reset lastIndex again since test modified it *)
Js_re.setLastIndex regexp 0;
match Js_re.exec ~str regexp with Some result -> Js_re.index result | None -> -1)
else -1
in
Js_re.setLastIndex regexp saved_last_index;
result
let slice ?start ?end_ str =
let str_length = Stdlib.String.length str in
let start = match start with None -> 0 | Some s -> s in
let end_ = match end_ with None -> str_length | Some s -> s in
let start_idx = Stdlib.max 0 (Stdlib.min start str_length) in
let end_idx = Stdlib.max start_idx (Stdlib.min end_ str_length) in
if start_idx >= end_idx then "" else Stdlib.String.sub str start_idx (end_idx - start_idx)
let split ?sep ?limit str =
let sep = Option.value sep ~default:str in
let regexp = Str.regexp_string sep in
(* On js split, it don't return an empty string on end when separator is an empty string *)
(* but "split_delim" does *)
(* https://melange.re/unstable/playground/?language=OCaml&code=SnMubG9nKEpzLlN0cmluZy5zcGxpdCB%2Bc2VwOiIiICJzdGFydCIpOw%3D%3D&live=off *)
let split = if sep <> "" then Str.split_delim else Str.split in
let items = split regexp str |> Stdlib.Array.of_list in
let limit = Option.value limit ~default:(Stdlib.Array.length items) in
match limit with
| limit when limit >= 0 && limit < Stdlib.Array.length items -> Stdlib.Array.sub items 0 limit
| _ -> items
let splitByRe ~regexp ?limit str =
let rev_array arr = arr |> Stdlib.Array.to_list |> Stdlib.List.rev |> Stdlib.Array.of_list in
let rec split_all str acc =
Js_re.setLastIndex regexp 0;
match Js_re.exec ~str regexp with
| Some result when Stdlib.Array.length (Js_re.captures result) = 0 ->
Stdlib.Array.append [| Some str |] acc |> rev_array
| None -> Stdlib.Array.append [| Some str |] acc |> rev_array
| Some result ->
let matches = Js_re.captures result in
let matched_str = Stdlib.Array.get matches 0 |> Option.get in
let prefix = String.sub str 0 (Js_re.index result) in
let suffix_start = Js_re.index result + String.length matched_str in
let suffix = String.sub str suffix_start (String.length str - suffix_start) in
let suffix_matches = Stdlib.Array.append [| Some prefix |] acc in
split_all suffix suffix_matches
in
let split_next str acc =
Js_re.setLastIndex regexp 0;
match Js_re.exec ~str regexp with
| None -> Stdlib.Array.append [| Some str |] acc |> rev_array
| Some result ->
let matches = Js_re.captures result in
let matched_str = Stdlib.Array.get matches 0 |> Option.get in
let index = Js_re.index result in
let prefix = String.sub str 0 index in
let suffix_start = index + String.length matched_str in
let suffix = String.sub str suffix_start (String.length str - suffix_start) in
Stdlib.Array.append [| Some prefix |] (split_all suffix acc)
in
let _ = limit in
if Js_re.global regexp then split_all str [||] else split_next str [||]
let startsWith ~prefix ?(start = 0) str =
let len_prefix = String.length prefix in
let len_str = String.length str in
if start < 0 || start > len_str then false
else
let rec compare_prefix i =
i = len_prefix || (i < len_str && prefix.[i] = str.[start + i] && compare_prefix (i + 1))
in
compare_prefix 0
let substr ?(start = 0) ?len str =
let str_length = Stdlib.String.length str in
let len = match len with None -> str_length | Some s -> s in
let start_idx = max 0 (min start str_length) in
let end_idx = min (start_idx + len) str_length in
if start_idx >= end_idx then "" else Stdlib.String.sub str start_idx (end_idx - start_idx)
let substring ?start ?end_ str =
let str_length = Stdlib.String.length str in
let start = match start with None -> 0 | Some s -> s in
let end_ = match end_ with None -> str_length | Some s -> s in
let start_idx = max 0 (min start str_length) in
let end_idx = max 0 (min end_ str_length) in
if start_idx >= end_idx then Stdlib.String.sub str end_idx (start_idx - end_idx)
else Stdlib.String.sub str start_idx (end_idx - start_idx)
let case_to_utf_8 case_map s =
let rec loop buf s i max =
if i > max then Buffer.contents buf
else
let dec = String.get_utf_8_uchar s i in
let u = Uchar.utf_decode_uchar dec in
(match case_map u with
| `Self -> Buffer.add_utf_8_uchar buf u
| `Uchars us -> List.iter (Buffer.add_utf_8_uchar buf) us);
loop buf s (i + Uchar.utf_decode_length dec) max
in
let buf = Buffer.create (String.length s * 2) in
loop buf s 0 (String.length s - 1)
let toLowerCase s = case_to_utf_8 Uucp.Case.Map.to_lower s
let toLocaleLowerCase _ = Js_internal.notImplemented "Js.String" "toLocaleLowerCase"
let toUpperCase s = case_to_utf_8 Uucp.Case.Map.to_upper s
let toLocaleUpperCase _ = Js_internal.notImplemented "Js.String" "toLocaleUpperCase"
let trim str =
let whitespace = " \t\n\r" in
let is_whitespace c = Stdlib.String.contains whitespace c in
let length = Stdlib.String.length str in
let rec trim_start idx =
if idx >= length then length else if is_whitespace (Stdlib.String.get str idx) then trim_start (idx + 1) else idx
in
let rec trim_end idx =
if idx <= 0 then 0 else if is_whitespace (Stdlib.String.get str (idx - 1)) then trim_end (idx - 1) else idx
in
let start_idx = trim_start 0 in
let end_idx = trim_end length in
if start_idx >= end_idx then "" else Stdlib.String.sub str start_idx (end_idx - start_idx)
let anchor ~name:_ _ = Js_internal.notImplemented "Js.String" "anchor"
let link ~href:_ _ = Js_internal.notImplemented "Js.String" "link"
================================================
FILE: packages/Js/lib/Js_string.mli
================================================
(** JavaScript String API *)
type t = string
val make : 'a -> t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fromCharCode : int -> t
val fromCharCodeMany : int array -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fromCodePoint : int -> t
val fromCodePointMany : int array -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val length : t -> int
val get : t -> int -> t
val charAt : index:int -> t -> t
val charCodeAt : index:int -> t -> float
val codePointAt : index:int -> t -> int Js_internal.nullable
val concat : other:t -> t -> t
val concatMany : strings:t array -> t -> t
val endsWith : suffix:t -> ?len:int -> t -> bool
val includes : search:t -> ?start:int -> t -> bool
val indexOf : search:t -> ?start:int -> t -> int
val lastIndexOf : search:t -> ?start:int -> t -> int
val localeCompare : other:t -> t -> float
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val match_ : regexp:Js_re.t -> t -> t Js_internal.nullable array Js_internal.nullable
val normalize : ?form:[ `NFC | `NFD | `NFKC | `NFKD ] -> t -> t
(** Returns the Unicode Normalization Form of a string. *)
val repeat : count:int -> t -> t
val replace : search:t -> replacement:t -> t -> t
val replaceByRe : regexp:Js_re.t -> replacement:t -> t -> t
val unsafeReplaceBy0 : regexp:Js_re.t -> f:(t -> int -> t -> t) -> t -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafeReplaceBy1 : regexp:Js_re.t -> f:(t -> t -> int -> t -> t) -> t -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafeReplaceBy2 : regexp:Js_re.t -> f:(t -> t -> t -> int -> t -> t) -> t -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val unsafeReplaceBy3 : regexp:Js_re.t -> f:(t -> t -> t -> t -> int -> t -> t) -> t -> t
[@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val search : regexp:Js_re.t -> t -> int
(** Searches for a match in a string. Returns the index of the first match, or -1 if not found. *)
val slice : ?start:int -> ?end_:int -> t -> t
val split : ?sep:t -> ?limit:int -> t -> t array
val splitByRe : regexp:Js_re.t -> ?limit:int -> t -> t Js_internal.nullable array
val startsWith : prefix:t -> ?start:int -> t -> bool
val substr : ?start:int -> ?len:int -> t -> t
val substring : ?start:int -> ?end_:int -> t -> t
val toLowerCase : t -> t
val toLocaleLowerCase : t -> t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val toUpperCase : t -> t
val toLocaleUpperCase : t -> t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val trim : t -> t
val anchor : name:t -> t -> t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val link : href:t -> t -> t [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
================================================
FILE: packages/Js/lib/Js_typed_array.ml
================================================
(** Provide bindings for JS typed array *)
module Uint16Array = struct
type t
end
module Uint8ClampedArray = struct
type t
end
module Float32Array = struct
type t
end
================================================
FILE: packages/Js/lib/Js_typed_array.mli
================================================
(** Provide bindings for JS typed array *)
module Uint16Array : sig
type t
end
module Uint8ClampedArray : sig
type t
end
module Float32Array : sig
type t
end
================================================
FILE: packages/Js/lib/Js_typed_array2.ml
================================================
(** Provide bindings for JS typed array *)
================================================
FILE: packages/Js/lib/Js_typed_array2.mli
================================================
(** Provide bindings for JS typed array *)
================================================
FILE: packages/Js/lib/Js_types.ml
================================================
(** Provide utilities for manipulating JS types *)
type symbol
(**Js symbol type only available in ES6 *)
type bigint_val
(** Js bigint type only available in ES2020 *)
type obj_val
type undefined_val
(** This type has only one value [undefined] *)
type null_val
(** This type has only one value [null] *)
type function_val
type _ t =
| Undefined : undefined_val t
| Null : null_val t
| Boolean : bool t
| Number : float t
| String : string t
| Function : function_val t
| Object : obj_val t
| Symbol : symbol t
| BigInt : bigint_val t
(** {[
test "x" String = true
]}*)
let test _ _ = Js_internal.notImplemented "Js.Types" "test"
type tagged_t =
| JSFalse
| JSTrue
| JSNull
| JSUndefined
| JSNumber of float
| JSString of string
| JSFunction of function_val
| JSObject of obj_val
| JSSymbol of symbol
| JSBigInt of bigint_val
let classify _ = Js_internal.notImplemented "Js.Types" "classify"
================================================
FILE: packages/Js/lib/Js_types.mli
================================================
(** Provide utilities for manipulating JS types *)
type symbol
type bigint_val
type obj_val
type undefined_val
type null_val
type function_val
type _ t =
| Undefined : undefined_val t
| Null : null_val t
| Boolean : bool t
| Number : float t
| String : string t
| Function : function_val t
| Object : obj_val t
| Symbol : symbol t
| BigInt : bigint_val t
val test : 'a -> 'b -> 'c [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
type tagged_t =
| JSFalse
| JSTrue
| JSNull
| JSUndefined
| JSNumber of float
| JSString of string
| JSFunction of function_val
| JSObject of obj_val
| JSSymbol of symbol
| JSBigInt of bigint_val
val classify : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
================================================
FILE: packages/Js/lib/Js_undefined.ml
================================================
type 'a t = 'a Js_internal.undefined
let return a = Some a
let empty = None
external toOption : 'a t -> 'a option = "%identity"
external fromOpt : 'a option -> 'a t = "%identity"
let getExn _ = Js_internal.notImplemented "Js.Undefined" "getExn"
let getUnsafe a = match toOption a with None -> assert false | Some a -> a
let bind _ _ = Js_internal.notImplemented "Js.Undefined" "bind"
let iter _ _ = Js_internal.notImplemented "Js.Undefined" "iter"
let testAny _ = Js_internal.notImplemented "Js.Undefined" "testAny"
let test _ = Js_internal.notImplemented "Js.Undefined" "test"
let fromOption = fromOpt
let from_opt = fromOpt
================================================
FILE: packages/Js/lib/Js_undefined.mli
================================================
type 'a t = 'a Js_internal.nullable
val return : 'a -> 'a t
val empty : 'a Js_internal.nullable
external toOption : 'a t -> 'a Js_internal.nullable = "%identity"
external fromOpt : 'a Js_internal.nullable -> 'a t = "%identity"
val getExn : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val getUnsafe : 'a t -> 'a
val bind : 'a -> 'b -> 'c [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val iter : 'a -> 'b -> 'c [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val testAny : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val test : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val fromOption : 'a Js_internal.nullable -> 'a t
val from_opt : 'a Js_internal.nullable -> 'a t
================================================
FILE: packages/Js/lib/Js_vector.ml
================================================
(** Provide utilities for Vector *)
type 'a t = 'a array
let filterInPlace _ = Js_internal.notImplemented "Js.Vector" "filterInPlace"
let empty _ = Js_internal.notImplemented "Js.Vector" "empty"
let pushBack _ = Js_internal.notImplemented "Js.Vector" "pushBack"
let copy _ = Js_internal.notImplemented "Js.Vector" "copy"
let memByRef _ = Js_internal.notImplemented "Js.Vector" "memByRef"
let iter _ = Js_internal.notImplemented "Js.Vector" "iter"
let iteri _ = Js_internal.notImplemented "Js.Vector" "iteri"
let toList _ = Js_internal.notImplemented "Js.Vector" "toList"
let map _ = Js_internal.notImplemented "Js.Vector" "map"
let mapi _ = Js_internal.notImplemented "Js.Vector" "mapi"
let foldLeft _ = Js_internal.notImplemented "Js.Vector" "foldLeft"
let foldRight _ = Js_internal.notImplemented "Js.Vector" "foldRight"
external length : 'a t -> int = "%array_length"
external get : 'a t -> int -> 'a = "%array_safe_get"
external set : 'a t -> int -> 'a -> unit = "%array_safe_set"
external make : int -> 'a -> 'a t = "caml_make_vect"
let init _ = Js_internal.notImplemented "Js.Vector" "init"
let append _ = Js_internal.notImplemented "Js.Vector" "append"
external unsafe_get : 'a t -> int -> 'a = "%array_unsafe_get"
external unsafe_set : 'a t -> int -> 'a -> unit = "%array_unsafe_set"
================================================
FILE: packages/Js/lib/Js_vector.mli
================================================
(** Provide utilities for Vector *)
type 'a t = 'a array
val filterInPlace : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val empty : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val pushBack : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val copy : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val memByRef : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val iter : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val iteri : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val toList : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val map : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val mapi : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val foldLeft : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val foldRight : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
external length : 'a t -> int = "%array_length"
external get : 'a t -> int -> 'a = "%array_safe_get"
external set : 'a t -> int -> 'a -> unit = "%array_safe_set"
external make : int -> 'a -> 'a t = "caml_make_vect"
val init : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
val append : 'a -> 'b [@@alert not_implemented "is not implemented in native under server-reason-react.js"]
external unsafe_get : 'a t -> int -> 'a = "%array_unsafe_get"
external unsafe_set : 'a t -> int -> 'a -> unit = "%array_unsafe_set"
================================================
FILE: packages/Js/lib/Js_weakmap.ml
================================================
(** Provides bindings for ES6 WeakMap *)
type ('k, 'v) t
================================================
FILE: packages/Js/lib/Js_weakmap.mli
================================================
(** Provides bindings for ES6 WeakMap *)
type ('k, 'v) t
================================================
FILE: packages/Js/lib/Js_weakset.ml
================================================
(** Provides bindings for ES6 WeakSet *)
type 'a t
================================================
FILE: packages/Js/lib/Js_weakset.mli
================================================
(** Provides bindings for ES6 WeakSet *)
type 'a t
================================================
FILE: packages/Js/lib/dune
================================================
(library
(name js)
(public_name server-reason-react.js)
(libraries quickjs lwt str uucp zarith unix)
(preprocess
(pps lwt_ppx)))
================================================
FILE: packages/Js/test/bigint_tests/arithmetic.ml
================================================
(** TC39 Test262: BigInt arithmetic tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt
Tests for BigInt arithmetic operations: add, sub, mul, div, rem, pow, neg, abs *)
open Helpers
(* ===================================================================
Addition
=================================================================== *)
let add_positive () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int 20 in
assert_bigint (BigInt.add a b) (BigInt.of_int 30)
let add_negative () =
let a = BigInt.of_int (-10) in
let b = BigInt.of_int (-20) in
assert_bigint (BigInt.add a b) (BigInt.of_int (-30))
let add_mixed () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int (-20) in
assert_bigint (BigInt.add a b) (BigInt.of_int (-10))
let add_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.add a b) (BigInt.of_int 42)
let add_large () =
(* Large number addition *)
let a = BigInt.of_string "9007199254740992" in
(* 2^53 *)
let b = BigInt.of_int 1 in
assert_bigint_string (BigInt.add a b) "9007199254740993"
(* ===================================================================
Subtraction
=================================================================== *)
let sub_positive () =
let a = BigInt.of_int 30 in
let b = BigInt.of_int 10 in
assert_bigint (BigInt.sub a b) (BigInt.of_int 20)
let sub_negative_result () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int 30 in
assert_bigint (BigInt.sub a b) (BigInt.of_int (-20))
let sub_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.sub a b) (BigInt.of_int 42)
let sub_self () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.sub a a) (BigInt.of_int 0)
(* ===================================================================
Multiplication
=================================================================== *)
let mul_positive () =
let a = BigInt.of_int 6 in
let b = BigInt.of_int 7 in
assert_bigint (BigInt.mul a b) (BigInt.of_int 42)
let mul_negative () =
let a = BigInt.of_int (-6) in
let b = BigInt.of_int 7 in
assert_bigint (BigInt.mul a b) (BigInt.of_int (-42))
let mul_both_negative () =
let a = BigInt.of_int (-6) in
let b = BigInt.of_int (-7) in
assert_bigint (BigInt.mul a b) (BigInt.of_int 42)
let mul_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.mul a b) (BigInt.of_int 0)
let mul_one () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 1 in
assert_bigint (BigInt.mul a b) (BigInt.of_int 42)
let mul_large () =
(* From QuickJS: 3^100 *)
let three = BigInt.of_int 3 in
let result = ref (BigInt.of_int 1) in
for _ = 1 to 100 do
result := BigInt.mul !result three
done;
assert_bigint_string !result "515377520732011331036461129765621272702107522001"
(* ===================================================================
Division (truncates toward zero)
=================================================================== *)
let div_exact () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 6 in
assert_bigint (BigInt.div a b) (BigInt.of_int 7)
let div_truncate_positive () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int 3 in
assert_bigint (BigInt.div a b) (BigInt.of_int 3)
let div_truncate_negative () =
(* -10 / 3 = -3 (truncate toward zero, not floor) *)
let a = BigInt.of_int (-10) in
let b = BigInt.of_int 3 in
assert_bigint (BigInt.div a b) (BigInt.of_int (-3))
let div_negative_divisor () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int (-3) in
assert_bigint (BigInt.div a b) (BigInt.of_int (-3))
let div_both_negative () =
let a = BigInt.of_int (-10) in
let b = BigInt.of_int (-3) in
assert_bigint (BigInt.div a b) (BigInt.of_int 3)
let div_large () =
(* From QuickJS test *)
let a = BigInt.of_string "3213213213213213432453243" in
let b = BigInt.of_string "123434343439" in
assert_bigint_string (BigInt.div a b) "26031760073331"
let div_large_negative () =
let a = BigInt.of_string "-3213213213213213432453243" in
let b = BigInt.of_string "123434343439" in
assert_bigint_string (BigInt.div a b) "-26031760073331"
let div_by_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint_raises (fun () -> ignore (BigInt.div a b))
(* ===================================================================
Remainder
=================================================================== *)
let rem_positive () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int 3 in
assert_bigint (BigInt.rem a b) (BigInt.of_int 1)
let rem_negative_dividend () =
(* -10 % 3 = -1 (sign follows dividend in JS) *)
let a = BigInt.of_int (-10) in
let b = BigInt.of_int 3 in
assert_bigint (BigInt.rem a b) (BigInt.of_int (-1))
let rem_negative_divisor () =
(* 10 % -3 = 1 (sign follows dividend) *)
let a = BigInt.of_int 10 in
let b = BigInt.of_int (-3) in
assert_bigint (BigInt.rem a b) (BigInt.of_int 1)
let rem_both_negative () =
(* -10 % -3 = -1 *)
let a = BigInt.of_int (-10) in
let b = BigInt.of_int (-3) in
assert_bigint (BigInt.rem a b) (BigInt.of_int (-1))
let rem_large () =
(* From QuickJS test *)
let a = BigInt.of_string "-3213213213213213432453243" in
let b = BigInt.of_string "-123434343439" in
assert_bigint_string (BigInt.rem a b) "-26953727934"
let rem_large_positive () =
let a = BigInt.of_string "3213213213213213432453243" in
let b = BigInt.of_string "123434343439" in
assert_bigint_string (BigInt.rem a b) "26953727934"
let rem_by_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint_raises (fun () -> ignore (BigInt.rem a b))
(* ===================================================================
Exponentiation
=================================================================== *)
let pow_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.pow a b) (BigInt.of_int 1)
let pow_one () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 1 in
assert_bigint (BigInt.pow a b) (BigInt.of_int 42)
let pow_positive () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 10 in
assert_bigint (BigInt.pow a b) (BigInt.of_int 1024)
let pow_negative_base () =
(* (-2)^127 *)
let a = BigInt.of_int (-2) in
let b = BigInt.of_int 127 in
assert_bigint_string (BigInt.pow a b) "-170141183460469231731687303715884105728"
let pow_large () =
(* 2^127 *)
let a = BigInt.of_int 2 in
let b = BigInt.of_int 127 in
assert_bigint_string (BigInt.pow a b) "170141183460469231731687303715884105728"
let pow_negative_exponent () =
(* Negative exponent should raise - BigInt doesn't support fractions *)
let a = BigInt.of_int 2 in
let b = BigInt.of_int (-1) in
assert_bigint_raises (fun () -> ignore (BigInt.pow a b))
let pow_from_quickjs_1 () =
(* (-256)^11 *)
let a = BigInt.of_int (-256) in
let b = BigInt.of_int 11 in
assert_bigint_string (BigInt.pow a b) "-309485009821345068724781056"
let pow_from_quickjs_2 () =
(* 7^20 *)
let a = BigInt.of_int 7 in
let b = BigInt.of_int 20 in
assert_bigint_string (BigInt.pow a b) "79792266297612001"
(* ===================================================================
Unary negation
=================================================================== *)
let neg_positive () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.neg a) (BigInt.of_int (-42))
let neg_negative () =
let a = BigInt.of_int (-42) in
assert_bigint (BigInt.neg a) (BigInt.of_int 42)
let neg_zero () =
let a = BigInt.of_int 0 in
assert_bigint (BigInt.neg a) (BigInt.of_int 0)
(* ===================================================================
Absolute value
=================================================================== *)
let abs_positive () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.abs a) (BigInt.of_int 42)
let abs_negative () =
let a = BigInt.of_int (-42) in
assert_bigint (BigInt.abs a) (BigInt.of_int 42)
let abs_zero () =
let a = BigInt.of_int 0 in
assert_bigint (BigInt.abs a) (BigInt.of_int 0)
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* Addition *)
test "add: positive + positive" add_positive;
test "add: negative + negative" add_negative;
test "add: positive + negative" add_mixed;
test "add: with zero" add_zero;
test "add: large numbers" add_large;
(* Subtraction *)
test "sub: positive result" sub_positive;
test "sub: negative result" sub_negative_result;
test "sub: with zero" sub_zero;
test "sub: self" sub_self;
(* Multiplication *)
test "mul: positive * positive" mul_positive;
test "mul: negative * positive" mul_negative;
test "mul: negative * negative" mul_both_negative;
test "mul: with zero" mul_zero;
test "mul: with one" mul_one;
test "mul: 3^100" mul_large;
(* Division *)
test "div: exact division" div_exact;
test "div: truncate positive" div_truncate_positive;
test "div: truncate negative (toward zero)" div_truncate_negative;
test "div: negative divisor" div_negative_divisor;
test "div: both negative" div_both_negative;
test "div: large numbers" div_large;
test "div: large negative" div_large_negative;
test "div: by zero throws" div_by_zero;
(* Remainder *)
test "rem: positive % positive" rem_positive;
test "rem: negative dividend" rem_negative_dividend;
test "rem: negative divisor" rem_negative_divisor;
test "rem: both negative" rem_both_negative;
test "rem: large numbers" rem_large;
test "rem: large positive" rem_large_positive;
test "rem: by zero throws" rem_by_zero;
(* Exponentiation *)
test "pow: exponent zero" pow_zero;
test "pow: exponent one" pow_one;
test "pow: 2^10" pow_positive;
test "pow: (-2)^127" pow_negative_base;
test "pow: 2^127" pow_large;
test "pow: negative exponent throws" pow_negative_exponent;
test "pow: (-256)^11" pow_from_quickjs_1;
test "pow: 7^20" pow_from_quickjs_2;
(* Negation *)
test "neg: positive" neg_positive;
test "neg: negative" neg_negative;
test "neg: zero" neg_zero;
(* Absolute value *)
test "abs: positive" abs_positive;
test "abs: negative" abs_negative;
test "abs: zero" abs_zero;
]
================================================
FILE: packages/Js/test/bigint_tests/as_int_n.ml
================================================
(** TC39 Test262: BigInt.asIntN tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt/asIntN
BigInt.asIntN(bits, bigint) wraps a BigInt value to a signed integer within the given number of bits. *)
open Helpers
module BigInt = Js.Bigint
(* ===================================================================
Basic asIntN tests
=================================================================== *)
let as_int_n_zero_bits () =
(* asIntN(0, x) always returns 0n *)
assert_bigint_equal (BigInt.as_int_n 0 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 0 (BigInt.of_int 1)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 0 (BigInt.of_int (-1))) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 0 (BigInt.of_int 100)) (BigInt.of_int 0)
let as_int_n_1_bit () =
(* asIntN(1, x) returns 0n or -1n (sign bit only) *)
assert_bigint_equal (BigInt.as_int_n 1 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 1 (BigInt.of_int 1)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 1 (BigInt.of_int 2)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 1 (BigInt.of_int 3)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 1 (BigInt.of_int (-1))) (BigInt.of_int (-1))
let as_int_n_8_bit () =
(* asIntN(8, x) wraps to signed 8-bit integer (-128 to 127) *)
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 127)) (BigInt.of_int 127);
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 128)) (BigInt.of_int (-128));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 255)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 256)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-1))) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-128))) (BigInt.of_int (-128));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-129))) (BigInt.of_int 127)
let as_int_n_16_bit () =
(* asIntN(16, x) wraps to signed 16-bit integer (-32768 to 32767) *)
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int 32767)) (BigInt.of_int 32767);
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int 32768)) (BigInt.of_int (-32768));
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int 65535)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int 65536)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int (-1))) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int (-32768))) (BigInt.of_int (-32768));
assert_bigint_equal (BigInt.as_int_n 16 (BigInt.of_int (-32769))) (BigInt.of_int 32767)
let as_int_n_32_bit () =
(* asIntN(32, x) wraps to signed 32-bit integer *)
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_int (-1))) (BigInt.of_int (-1));
(* 2^31 - 1 = 2147483647 *)
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_string "2147483647")) (BigInt.of_string "2147483647");
(* 2^31 = 2147483648 -> -2147483648 *)
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_string "2147483648")) (BigInt.of_string "-2147483648");
(* 2^32 - 1 = 4294967295 -> -1 *)
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_string "4294967295")) (BigInt.of_int (-1));
(* 2^32 = 4294967296 -> 0 *)
assert_bigint_equal (BigInt.as_int_n 32 (BigInt.of_string "4294967296")) (BigInt.of_int 0)
let as_int_n_64_bit () =
(* asIntN(64, x) wraps to signed 64-bit integer *)
assert_bigint_equal (BigInt.as_int_n 64 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 64 (BigInt.of_int (-1))) (BigInt.of_int (-1));
(* 2^63 - 1 stays the same *)
let max_int64 = BigInt.of_string "9223372036854775807" in
assert_bigint_equal (BigInt.as_int_n 64 max_int64) max_int64;
(* 2^63 wraps to negative *)
let two_63 = BigInt.of_string "9223372036854775808" in
let neg_two_63 = BigInt.of_string "-9223372036854775808" in
assert_bigint_equal (BigInt.as_int_n 64 two_63) neg_two_63
let as_int_n_preserves_small_positive () =
(* Small positive values within range are preserved *)
for i = 0 to 127 do
let n = BigInt.of_int i in
assert_bigint_equal (BigInt.as_int_n 8 n) n
done
let as_int_n_preserves_small_negative () =
(* Small negative values within range are preserved *)
for i = -128 to -1 do
let n = BigInt.of_int i in
assert_bigint_equal (BigInt.as_int_n 8 n) n
done
let as_int_n_wrapping () =
(* Test wrapping behavior *)
(* 300 in 8-bit signed = 300 - 256 = 44 *)
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int 300)) (BigInt.of_int 44);
(* -300 in 8-bit signed = -300 + 256 = -44 *)
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-300))) (BigInt.of_int (-44))
let as_int_n_large_bits () =
(* Test with larger bit sizes *)
let x = BigInt.of_string "123456789012345678901234567890" in
(* With very large bits, value should be preserved *)
assert_bigint_equal (BigInt.as_int_n 256 x) x
let as_int_n_negative_input () =
(* asIntN with negative input *)
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-1))) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-128))) (BigInt.of_int (-128));
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-129))) (BigInt.of_int 127);
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-256))) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 8 (BigInt.of_int (-257))) (BigInt.of_int (-1))
(* ===================================================================
Edge cases
=================================================================== *)
let as_int_n_bit_boundary_2 () =
(* 2-bit signed: -2 to 1 *)
assert_bigint_equal (BigInt.as_int_n 2 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 2 (BigInt.of_int 1)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_int_n 2 (BigInt.of_int 2)) (BigInt.of_int (-2));
assert_bigint_equal (BigInt.as_int_n 2 (BigInt.of_int 3)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 2 (BigInt.of_int 4)) (BigInt.of_int 0)
let as_int_n_bit_boundary_3 () =
(* 3-bit signed: -4 to 3 *)
assert_bigint_equal (BigInt.as_int_n 3 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 3 (BigInt.of_int 3)) (BigInt.of_int 3);
assert_bigint_equal (BigInt.as_int_n 3 (BigInt.of_int 4)) (BigInt.of_int (-4));
assert_bigint_equal (BigInt.as_int_n 3 (BigInt.of_int 7)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 3 (BigInt.of_int 8)) (BigInt.of_int 0)
let as_int_n_bit_boundary_4 () =
(* 4-bit signed: -8 to 7 *)
assert_bigint_equal (BigInt.as_int_n 4 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_int_n 4 (BigInt.of_int 7)) (BigInt.of_int 7);
assert_bigint_equal (BigInt.as_int_n 4 (BigInt.of_int 8)) (BigInt.of_int (-8));
assert_bigint_equal (BigInt.as_int_n 4 (BigInt.of_int 15)) (BigInt.of_int (-1));
assert_bigint_equal (BigInt.as_int_n 4 (BigInt.of_int 16)) (BigInt.of_int 0)
let as_int_n_identity_for_zero () =
(* asIntN(n, 0) = 0 for any n > 0 *)
let zero = BigInt.of_int 0 in
assert_bigint_equal (BigInt.as_int_n 1 zero) zero;
assert_bigint_equal (BigInt.as_int_n 8 zero) zero;
assert_bigint_equal (BigInt.as_int_n 16 zero) zero;
assert_bigint_equal (BigInt.as_int_n 32 zero) zero;
assert_bigint_equal (BigInt.as_int_n 64 zero) zero;
assert_bigint_equal (BigInt.as_int_n 128 zero) zero
let as_int_n_minus_one () =
(* asIntN(n, -1) = -1 for any n > 0 *)
let neg_one = BigInt.of_int (-1) in
assert_bigint_equal (BigInt.as_int_n 1 neg_one) neg_one;
assert_bigint_equal (BigInt.as_int_n 8 neg_one) neg_one;
assert_bigint_equal (BigInt.as_int_n 16 neg_one) neg_one;
assert_bigint_equal (BigInt.as_int_n 32 neg_one) neg_one;
assert_bigint_equal (BigInt.as_int_n 64 neg_one) neg_one
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
test "asIntN 0 bits" as_int_n_zero_bits;
test "asIntN 1 bit" as_int_n_1_bit;
test "asIntN 8 bit" as_int_n_8_bit;
test "asIntN 16 bit" as_int_n_16_bit;
test "asIntN 32 bit" as_int_n_32_bit;
test "asIntN 64 bit" as_int_n_64_bit;
test "asIntN preserves small positive" as_int_n_preserves_small_positive;
test "asIntN preserves small negative" as_int_n_preserves_small_negative;
test "asIntN wrapping" as_int_n_wrapping;
test "asIntN large bits" as_int_n_large_bits;
test "asIntN negative input" as_int_n_negative_input;
test "asIntN 2-bit boundary" as_int_n_bit_boundary_2;
test "asIntN 3-bit boundary" as_int_n_bit_boundary_3;
test "asIntN 4-bit boundary" as_int_n_bit_boundary_4;
test "asIntN identity for zero" as_int_n_identity_for_zero;
test "asIntN minus one" as_int_n_minus_one;
]
================================================
FILE: packages/Js/test/bigint_tests/as_uint_n.ml
================================================
(** TC39 Test262: BigInt.asUintN tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt/asUintN
BigInt.asUintN(bits, bigint) wraps a BigInt value to an unsigned integer within the given number of bits. *)
open Helpers
module BigInt = Js.Bigint
(* ===================================================================
Basic asUintN tests
=================================================================== *)
let as_uint_n_zero_bits () =
(* asUintN(0, x) always returns 0n *)
assert_bigint_equal (BigInt.as_uint_n 0 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 0 (BigInt.of_int 1)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 0 (BigInt.of_int (-1))) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 0 (BigInt.of_int 100)) (BigInt.of_int 0)
let as_uint_n_1_bit () =
(* asUintN(1, x) returns 0n or 1n *)
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 1)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 2)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 3)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int (-1))) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int (-2))) (BigInt.of_int 0)
let as_uint_n_8_bit () =
(* asUintN(8, x) wraps to unsigned 8-bit integer (0 to 255) *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 127)) (BigInt.of_int 127);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 128)) (BigInt.of_int 128);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 255)) (BigInt.of_int 255);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 256)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 257)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-1))) (BigInt.of_int 255);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-2))) (BigInt.of_int 254);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-128))) (BigInt.of_int 128);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-256))) (BigInt.of_int 0)
let as_uint_n_16_bit () =
(* asUintN(16, x) wraps to unsigned 16-bit integer (0 to 65535) *)
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int 32767)) (BigInt.of_int 32767);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int 32768)) (BigInt.of_int 32768);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int 65535)) (BigInt.of_int 65535);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int 65536)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int (-1))) (BigInt.of_int 65535);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int (-32768))) (BigInt.of_int 32768);
assert_bigint_equal (BigInt.as_uint_n 16 (BigInt.of_int (-65536))) (BigInt.of_int 0)
let as_uint_n_32_bit () =
(* asUintN(32, x) wraps to unsigned 32-bit integer *)
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_int (-1))) (BigInt.of_string "4294967295");
(* 2^31 - 1 = 2147483647 *)
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_string "2147483647")) (BigInt.of_string "2147483647");
(* 2^31 = 2147483648 *)
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_string "2147483648")) (BigInt.of_string "2147483648");
(* 2^32 - 1 = 4294967295 *)
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_string "4294967295")) (BigInt.of_string "4294967295");
(* 2^32 = 4294967296 -> 0 *)
assert_bigint_equal (BigInt.as_uint_n 32 (BigInt.of_string "4294967296")) (BigInt.of_int 0)
let as_uint_n_64_bit () =
(* asUintN(64, x) wraps to unsigned 64-bit integer *)
assert_bigint_equal (BigInt.as_uint_n 64 (BigInt.of_int 0)) (BigInt.of_int 0);
(* 2^63 - 1 *)
let max_int63 = BigInt.of_string "9223372036854775807" in
assert_bigint_equal (BigInt.as_uint_n 64 max_int63) max_int63;
(* 2^64 - 1 *)
let max_uint64 = BigInt.of_string "18446744073709551615" in
assert_bigint_equal (BigInt.as_uint_n 64 max_uint64) max_uint64;
(* 2^64 -> 0 *)
let two_64 = BigInt.of_string "18446744073709551616" in
assert_bigint_equal (BigInt.as_uint_n 64 two_64) (BigInt.of_int 0);
(* -1 -> 2^64 - 1 *)
assert_bigint_equal (BigInt.as_uint_n 64 (BigInt.of_int (-1))) max_uint64
let as_uint_n_preserves_small_values () =
(* Small positive values within range are preserved *)
for i = 0 to 255 do
let n = BigInt.of_int i in
assert_bigint_equal (BigInt.as_uint_n 8 n) n
done
let as_uint_n_wrapping () =
(* Test wrapping behavior *)
(* 300 in 8-bit unsigned = 300 mod 256 = 44 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 300)) (BigInt.of_int 44);
(* 512 in 8-bit unsigned = 0 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 512)) (BigInt.of_int 0);
(* 513 in 8-bit unsigned = 1 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 513)) (BigInt.of_int 1)
let as_uint_n_large_bits () =
(* Test with larger bit sizes *)
let x = BigInt.of_string "123456789012345678901234567890" in
(* With very large bits, positive value should be preserved *)
assert_bigint_equal (BigInt.as_uint_n 256 x) x
let as_uint_n_negative_becomes_positive () =
(* Negative numbers become their two's complement unsigned representation *)
(* -1 in 8-bit unsigned = 255 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-1))) (BigInt.of_int 255);
(* -128 in 8-bit unsigned = 128 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-128))) (BigInt.of_int 128);
(* -129 in 8-bit unsigned = 127 *)
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int (-129))) (BigInt.of_int 127)
(* ===================================================================
Edge cases
=================================================================== *)
let as_uint_n_bit_boundary_2 () =
(* 2-bit unsigned: 0 to 3 *)
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 1)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 2)) (BigInt.of_int 2);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 3)) (BigInt.of_int 3);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 4)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 5)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int (-1))) (BigInt.of_int 3);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int (-2))) (BigInt.of_int 2)
let as_uint_n_bit_boundary_3 () =
(* 3-bit unsigned: 0 to 7 *)
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 7)) (BigInt.of_int 7);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 8)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 9)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int (-1))) (BigInt.of_int 7)
let as_uint_n_bit_boundary_4 () =
(* 4-bit unsigned: 0 to 15 *)
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 0)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 15)) (BigInt.of_int 15);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 16)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 17)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int (-1))) (BigInt.of_int 15)
let as_uint_n_identity_for_zero () =
(* asUintN(n, 0) = 0 for any n *)
let zero = BigInt.of_int 0 in
assert_bigint_equal (BigInt.as_uint_n 0 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 1 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 8 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 16 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 32 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 64 zero) zero;
assert_bigint_equal (BigInt.as_uint_n 128 zero) zero
let as_uint_n_one () =
(* asUintN(n, 1) = 1 for any n > 0 *)
let one = BigInt.of_int 1 in
assert_bigint_equal (BigInt.as_uint_n 1 one) one;
assert_bigint_equal (BigInt.as_uint_n 8 one) one;
assert_bigint_equal (BigInt.as_uint_n 16 one) one;
assert_bigint_equal (BigInt.as_uint_n 32 one) one;
assert_bigint_equal (BigInt.as_uint_n 64 one) one
let as_uint_n_power_of_two () =
(* asUintN(n, 2^n) = 0 *)
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 2)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 4)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 8)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 16)) (BigInt.of_int 0);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 256)) (BigInt.of_int 0)
let as_uint_n_power_of_two_minus_one () =
(* asUintN(n, 2^n - 1) = 2^n - 1 *)
assert_bigint_equal (BigInt.as_uint_n 1 (BigInt.of_int 1)) (BigInt.of_int 1);
assert_bigint_equal (BigInt.as_uint_n 2 (BigInt.of_int 3)) (BigInt.of_int 3);
assert_bigint_equal (BigInt.as_uint_n 3 (BigInt.of_int 7)) (BigInt.of_int 7);
assert_bigint_equal (BigInt.as_uint_n 4 (BigInt.of_int 15)) (BigInt.of_int 15);
assert_bigint_equal (BigInt.as_uint_n 8 (BigInt.of_int 255)) (BigInt.of_int 255)
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
test "asUintN 0 bits" as_uint_n_zero_bits;
test "asUintN 1 bit" as_uint_n_1_bit;
test "asUintN 8 bit" as_uint_n_8_bit;
test "asUintN 16 bit" as_uint_n_16_bit;
test "asUintN 32 bit" as_uint_n_32_bit;
test "asUintN 64 bit" as_uint_n_64_bit;
test "asUintN preserves small values" as_uint_n_preserves_small_values;
test "asUintN wrapping" as_uint_n_wrapping;
test "asUintN large bits" as_uint_n_large_bits;
test "asUintN negative becomes positive" as_uint_n_negative_becomes_positive;
test "asUintN 2-bit boundary" as_uint_n_bit_boundary_2;
test "asUintN 3-bit boundary" as_uint_n_bit_boundary_3;
test "asUintN 4-bit boundary" as_uint_n_bit_boundary_4;
test "asUintN identity for zero" as_uint_n_identity_for_zero;
test "asUintN one" as_uint_n_one;
test "asUintN power of two" as_uint_n_power_of_two;
test "asUintN power of two minus one" as_uint_n_power_of_two_minus_one;
]
================================================
FILE: packages/Js/test/bigint_tests/bitwise.ml
================================================
(** TC39 Test262: BigInt bitwise operation tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt
Tests for BigInt bitwise operations:
- Bitwise AND (&)
- Bitwise OR (|)
- Bitwise XOR (^)
- Bitwise NOT (~)
- Left shift (<<)
- Right shift (>>) - arithmetic shift, sign-extending *)
open Helpers
(* ===================================================================
Bitwise AND
=================================================================== *)
let and_basic () =
(* From QuickJS: 0x5a463ca6 & 0x67376856 = 1107699718 *)
let a = BigInt.of_string "0x5a463ca6" in
let b = BigInt.of_string "0x67376856" in
assert_bigint (BigInt.logand a b) (BigInt.of_int 1107699718)
let and_zero () =
let a = BigInt.of_int 0xFF in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.logand a b) (BigInt.of_int 0)
let and_all_ones () =
let a = BigInt.of_int 0b1010 in
let b = BigInt.of_int 0b1111 in
assert_bigint (BigInt.logand a b) (BigInt.of_int 0b1010)
let and_negative () =
(* -1 in two's complement is all 1s *)
let a = BigInt.of_int 0xFF in
let b = BigInt.of_int (-1) in
assert_bigint (BigInt.logand a b) (BigInt.of_int 0xFF)
(* ===================================================================
Bitwise OR
=================================================================== *)
let or_basic () =
(* From QuickJS: 0x5a463ca6 | 0x67376856 = 2138537206 *)
let a = BigInt.of_string "0x5a463ca6" in
let b = BigInt.of_string "0x67376856" in
assert_bigint (BigInt.logor a b) (BigInt.of_int 2138537206)
let or_zero () =
let a = BigInt.of_int 0b1010 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.logor a b) (BigInt.of_int 0b1010)
let or_disjoint () =
let a = BigInt.of_int 0b1010 in
let b = BigInt.of_int 0b0101 in
assert_bigint (BigInt.logor a b) (BigInt.of_int 0b1111)
(* ===================================================================
Bitwise XOR
=================================================================== *)
let xor_basic () =
(* From QuickJS: 0x5a463ca6 ^ 0x67376856 = 1030837488 *)
let a = BigInt.of_string "0x5a463ca6" in
let b = BigInt.of_string "0x67376856" in
assert_bigint (BigInt.logxor a b) (BigInt.of_int 1030837488)
let xor_same () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.logxor a a) (BigInt.of_int 0)
let xor_zero () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 0 in
assert_bigint (BigInt.logxor a b) (BigInt.of_int 42)
(* ===================================================================
Bitwise NOT
=================================================================== *)
let not_basic () =
(* From QuickJS: ~0x5a653ca6 = -1516584103 *)
let a = BigInt.of_string "0x5a653ca6" in
assert_bigint (BigInt.lognot a) (BigInt.of_int (-1516584103))
let not_zero () =
let a = BigInt.of_int 0 in
assert_bigint (BigInt.lognot a) (BigInt.of_int (-1))
let not_negative_one () =
let a = BigInt.of_int (-1) in
assert_bigint (BigInt.lognot a) (BigInt.of_int 0)
let not_positive () =
(* ~5 = -6 *)
let a = BigInt.of_int 5 in
assert_bigint (BigInt.lognot a) (BigInt.of_int (-6))
let not_negative () =
(* ~(-6) = 5 *)
let a = BigInt.of_int (-6) in
assert_bigint (BigInt.lognot a) (BigInt.of_int 5)
(* ===================================================================
Left shift
=================================================================== *)
let shift_left_basic () =
let a = BigInt.of_int 1 in
assert_bigint (BigInt.shift_left a 10) (BigInt.of_int 1024)
let shift_left_31 () =
(* From QuickJS: 1 << 31 = 2147483648 *)
let a = BigInt.of_int 1 in
assert_bigint_string (BigInt.shift_left a 31) "2147483648"
let shift_left_32 () =
(* From QuickJS: 1 << 32 = 4294967296 *)
let a = BigInt.of_int 1 in
assert_bigint_string (BigInt.shift_left a 32) "4294967296"
let shift_left_100 () =
(* From QuickJS: 1 << 100 = 1267650600228229401496703205376 *)
let a = BigInt.of_int 1 in
assert_bigint_string (BigInt.shift_left a 100) "1267650600228229401496703205376"
let shift_left_large () =
(* From QuickJS: 0x5a4653ca673768565b41f775 << 78 *)
let a = BigInt.of_string "0x5a4653ca673768565b41f775" in
assert_bigint_string (BigInt.shift_left a 78) "8443945299673273647701379149826607537748959488376832"
let shift_left_negative () =
(* From QuickJS: -0x5a4653ca673768565b41f775 << 78 *)
let a = BigInt.of_string "-0x5a4653ca673768565b41f775" in
assert_bigint_string (BigInt.shift_left a 78) "-8443945299673273647701379149826607537748959488376832"
let shift_left_zero () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.shift_left a 0) (BigInt.of_int 42)
(* ===================================================================
Right shift (arithmetic - sign extending)
=================================================================== *)
let shift_right_basic () =
let a = BigInt.of_int 1024 in
assert_bigint (BigInt.shift_right a 5) (BigInt.of_int 32)
let shift_right_large () =
(* From QuickJS: 0x5a4653ca673768565b41f775 >> 78 = 92441 *)
let a = BigInt.of_string "0x5a4653ca673768565b41f775" in
assert_bigint (BigInt.shift_right a 78) (BigInt.of_int 92441)
let shift_right_negative () =
(* From QuickJS: -0x5a4653ca673768565b41f775 >> 78 = -92442 *)
(* Arithmetic shift extends sign bit *)
let a = BigInt.of_string "-0x5a4653ca673768565b41f775" in
assert_bigint (BigInt.shift_right a 78) (BigInt.of_int (-92442))
let shift_right_zero () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.shift_right a 0) (BigInt.of_int 42)
let shift_right_negative_small () =
(* -8 >> 2 = -2 (arithmetic shift) *)
let a = BigInt.of_int (-8) in
assert_bigint (BigInt.shift_right a 2) (BigInt.of_int (-2))
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* AND *)
test "and: basic" and_basic;
test "and: with zero" and_zero;
test "and: with all ones" and_all_ones;
test "and: negative (two's complement)" and_negative;
(* OR *)
test "or: basic" or_basic;
test "or: with zero" or_zero;
test "or: disjoint bits" or_disjoint;
(* XOR *)
test "xor: basic" xor_basic;
test "xor: same value" xor_same;
test "xor: with zero" xor_zero;
(* NOT *)
test "not: basic" not_basic;
test "not: zero" not_zero;
test "not: -1" not_negative_one;
test "not: positive" not_positive;
test "not: negative" not_negative;
(* Left shift *)
test "shift_left: basic" shift_left_basic;
test "shift_left: 1 << 31" shift_left_31;
test "shift_left: 1 << 32" shift_left_32;
test "shift_left: 1 << 100" shift_left_100;
test "shift_left: large number" shift_left_large;
test "shift_left: negative number" shift_left_negative;
test "shift_left: by zero" shift_left_zero;
(* Right shift *)
test "shift_right: basic" shift_right_basic;
test "shift_right: large number" shift_right_large;
test "shift_right: negative (arithmetic)" shift_right_negative;
test "shift_right: by zero" shift_right_zero;
test "shift_right: small negative" shift_right_negative_small;
]
================================================
FILE: packages/Js/test/bigint_tests/comparison.ml
================================================
(** TC39 Test262: BigInt comparison tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt
Tests for BigInt comparison operations:
- Equal (=)
- Less than (<)
- Less than or equal (<=)
- Greater than (>)
- Greater than or equal (>=)
- Compare (returns -1, 0, 1) *)
open Helpers
(* ===================================================================
Equality
=================================================================== *)
let equal_same () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 42 in
assert_bool (BigInt.equal a b) true
let equal_different () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int 43 in
assert_bool (BigInt.equal a b) false
let equal_negative () =
let a = BigInt.of_int (-42) in
let b = BigInt.of_int (-42) in
assert_bool (BigInt.equal a b) true
let equal_opposite_sign () =
let a = BigInt.of_int 42 in
let b = BigInt.of_int (-42) in
assert_bool (BigInt.equal a b) false
let equal_zero () =
let a = BigInt.of_int 0 in
let b = BigInt.of_int 0 in
assert_bool (BigInt.equal a b) true
let equal_large () =
(* From QuickJS test *)
let a = BigInt.of_string "515377520732011331036461129765621272702107522001" in
let b = BigInt.of_string "515377520732011331036461129765621272702107522001" in
assert_bool (BigInt.equal a b) true
let equal_large_different () =
let a = BigInt.of_string "515377520732011331036461129765621272702107522001" in
let b = BigInt.of_string "515377520732011331036461129765621272702107522000" in
assert_bool (BigInt.equal a b) false
(* ===================================================================
Compare (returns -1, 0, 1)
=================================================================== *)
let compare_less () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 3 in
assert_int (BigInt.compare a b) (-1)
let compare_greater () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 2 in
assert_int (BigInt.compare a b) 1
let compare_equal () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 3 in
assert_int (BigInt.compare a b) 0
let compare_negative () =
let a = BigInt.of_int (-5) in
let b = BigInt.of_int (-3) in
assert_int (BigInt.compare a b) (-1)
let compare_mixed_sign () =
let a = BigInt.of_int (-1) in
let b = BigInt.of_int 1 in
assert_int (BigInt.compare a b) (-1)
(* ===================================================================
Less than
=================================================================== *)
let lt_true () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.lt a b) true
let lt_false_greater () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 2 in
assert_bool (BigInt.lt a b) false
let lt_false_equal () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.lt a b) false
let lt_negative () =
let a = BigInt.of_int (-5) in
let b = BigInt.of_int (-3) in
assert_bool (BigInt.lt a b) true
(* ===================================================================
Less than or equal
=================================================================== *)
let le_true_less () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.le a b) true
let le_true_equal () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.le a b) true
let le_false () =
let a = BigInt.of_int 4 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.le a b) false
(* ===================================================================
Greater than
=================================================================== *)
let gt_true () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 2 in
assert_bool (BigInt.gt a b) true
let gt_false_less () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.gt a b) false
let gt_false_equal () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.gt a b) false
(* ===================================================================
Greater than or equal
=================================================================== *)
let ge_true_greater () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 2 in
assert_bool (BigInt.ge a b) true
let ge_true_equal () =
let a = BigInt.of_int 3 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.ge a b) true
let ge_false () =
let a = BigInt.of_int 2 in
let b = BigInt.of_int 3 in
assert_bool (BigInt.ge a b) false
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* Equality *)
test "equal: same value" equal_same;
test "equal: different values" equal_different;
test "equal: negative values" equal_negative;
test "equal: opposite signs" equal_opposite_sign;
test "equal: zeros" equal_zero;
test "equal: large same" equal_large;
test "equal: large different" equal_large_different;
(* Compare *)
test "compare: less" compare_less;
test "compare: greater" compare_greater;
test "compare: equal" compare_equal;
test "compare: negative" compare_negative;
test "compare: mixed sign" compare_mixed_sign;
(* Less than *)
test "lt: true" lt_true;
test "lt: false (greater)" lt_false_greater;
test "lt: false (equal)" lt_false_equal;
test "lt: negative" lt_negative;
(* Less than or equal *)
test "le: true (less)" le_true_less;
test "le: true (equal)" le_true_equal;
test "le: false" le_false;
(* Greater than *)
test "gt: true" gt_true;
test "gt: false (less)" gt_false_less;
test "gt: false (equal)" gt_false_equal;
(* Greater than or equal *)
test "ge: true (greater)" ge_true_greater;
test "ge: true (equal)" ge_true_equal;
test "ge: false" ge_false;
]
================================================
FILE: packages/Js/test/bigint_tests/constructor.ml
================================================
(** TC39 Test262: BigInt constructor tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt
ECMA-262 Section: BigInt(value)
The BigInt constructor:
- Converts strings to BigInt (decimal, hex with 0x prefix)
- Converts integers to BigInt
- Throws for non-integer numbers
- Throws for invalid string formats *)
open Helpers
(* ===================================================================
From string - basic decimal parsing
=================================================================== *)
let from_string_empty () =
(* BigInt("") should return 0n in JavaScript *)
assert_bigint (BigInt.of_string "") (BigInt.of_int 0)
let from_string_zero () = assert_bigint (BigInt.of_string "0") (BigInt.of_int 0)
let from_string_positive () = assert_bigint (BigInt.of_string "123") (BigInt.of_int 123)
let from_string_negative () = assert_bigint (BigInt.of_string "-123") (BigInt.of_int (-123))
let from_string_positive_sign () = assert_bigint (BigInt.of_string "+456") (BigInt.of_int 456)
let from_string_large () =
(* A number larger than MAX_SAFE_INTEGER *)
let large = BigInt.of_string "9007199254740993" in
assert_bigint_string large "9007199254740993"
let from_string_very_large () =
(* 3^100 from QuickJS test *)
let expected = "515377520732011331036461129765621272702107522001" in
let result = BigInt.of_string expected in
assert_bigint_string result expected
(* ===================================================================
From string - whitespace handling
=================================================================== *)
let from_string_leading_space () = assert_bigint (BigInt.of_string " 123") (BigInt.of_int 123)
let from_string_trailing_space () = assert_bigint (BigInt.of_string "123 ") (BigInt.of_int 123)
let from_string_both_space () = assert_bigint (BigInt.of_string " 123 ") (BigInt.of_int 123)
let from_string_tabs () = assert_bigint (BigInt.of_string "\t123\t") (BigInt.of_int 123)
let from_string_newlines () = assert_bigint (BigInt.of_string "\n123\n") (BigInt.of_int 123)
(* ===================================================================
From string - hexadecimal
=================================================================== *)
let from_string_hex_lower () = assert_bigint (BigInt.of_string "0xff") (BigInt.of_int 255)
let from_string_hex_upper () = assert_bigint (BigInt.of_string "0XFF") (BigInt.of_int 255)
let from_string_hex_large () =
(* From QuickJS test *)
let result = BigInt.of_string "0x5a4653ca673768565b41f775d6947d55cf3813d1" in
assert_bigint_string result "515377520732011331036461129765621272702107522001"
let from_string_hex_negative () = assert_bigint (BigInt.of_string "-0x10") (BigInt.of_int (-16))
(* ===================================================================
From string - binary (0b prefix)
=================================================================== *)
let from_string_binary () = assert_bigint (BigInt.of_string "0b1010") (BigInt.of_int 10)
let from_string_binary_upper () = assert_bigint (BigInt.of_string "0B1111") (BigInt.of_int 15)
(* ===================================================================
From string - octal (0o prefix)
=================================================================== *)
let from_string_octal () = assert_bigint (BigInt.of_string "0o77") (BigInt.of_int 63)
let from_string_octal_upper () = assert_bigint (BigInt.of_string "0O777") (BigInt.of_int 511)
(* ===================================================================
From string - invalid inputs (should raise)
=================================================================== *)
let from_string_invalid_sign_only () =
(* BigInt("+") and BigInt("-") should throw SyntaxError *)
assert_bigint_raises (fun () -> BigInt.of_string_exn "+");
assert_bigint_raises (fun () -> BigInt.of_string_exn "-")
let from_string_invalid_trailing_chars () =
(* BigInt(" 123 r") should throw SyntaxError *)
assert_bigint_raises (fun () -> BigInt.of_string_exn "123r");
assert_bigint_raises (fun () -> BigInt.of_string_exn " 123 r")
let from_string_invalid_null_char () =
(* BigInt("\x00a") should throw SyntaxError *)
assert_bigint_raises (fun () -> BigInt.of_string_exn "\x00a")
let from_string_invalid_decimal_point () =
(* BigInt("1.5") should throw - no decimals allowed *)
assert_bigint_raises (fun () -> BigInt.of_string_exn "1.5")
let from_string_invalid_float_notation () =
(* BigInt("1e10") should throw *)
assert_bigint_raises (fun () -> BigInt.of_string_exn "1e10")
(* ===================================================================
From integers
=================================================================== *)
let from_int_zero () = assert_bigint (BigInt.of_int 0) (BigInt.of_string "0")
let from_int_positive () = assert_bigint (BigInt.of_int 42) (BigInt.of_string "42")
let from_int_negative () = assert_bigint (BigInt.of_int (-42)) (BigInt.of_string "-42")
let from_int_max_int () =
let max = max_int in
assert_bigint (BigInt.of_int max) (BigInt.of_string (string_of_int max))
let from_int_min_int () =
let min = min_int in
assert_bigint (BigInt.of_int min) (BigInt.of_string (string_of_int min))
(* ===================================================================
From int64
=================================================================== *)
let from_int64_large () =
let large = 9007199254740993L in
(* larger than MAX_SAFE_INTEGER *)
assert_bigint (BigInt.of_int64 large) (BigInt.of_string "9007199254740993")
let from_int64_negative () = assert_bigint (BigInt.of_int64 (-9007199254740993L)) (BigInt.of_string "-9007199254740993")
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* From string - basic *)
test "from_string: empty string returns 0" from_string_empty;
test "from_string: zero" from_string_zero;
test "from_string: positive decimal" from_string_positive;
test "from_string: negative decimal" from_string_negative;
test "from_string: positive sign" from_string_positive_sign;
test "from_string: large number" from_string_large;
test "from_string: very large number (3^100)" from_string_very_large;
(* From string - whitespace *)
test "from_string: leading whitespace" from_string_leading_space;
test "from_string: trailing whitespace" from_string_trailing_space;
test "from_string: both whitespace" from_string_both_space;
test "from_string: tabs" from_string_tabs;
test "from_string: newlines" from_string_newlines;
(* From string - hex *)
test "from_string: hex lowercase" from_string_hex_lower;
test "from_string: hex uppercase" from_string_hex_upper;
test "from_string: hex large" from_string_hex_large;
test "from_string: hex negative" from_string_hex_negative;
(* From string - binary *)
test "from_string: binary lowercase" from_string_binary;
test "from_string: binary uppercase" from_string_binary_upper;
(* From string - octal *)
test "from_string: octal lowercase" from_string_octal;
test "from_string: octal uppercase" from_string_octal_upper;
(* From string - invalid *)
test "from_string: sign only throws" from_string_invalid_sign_only;
test "from_string: trailing chars throws" from_string_invalid_trailing_chars;
test "from_string: null char throws" from_string_invalid_null_char;
test "from_string: decimal point throws" from_string_invalid_decimal_point;
test "from_string: float notation throws" from_string_invalid_float_notation;
(* From integers *)
test "from_int: zero" from_int_zero;
test "from_int: positive" from_int_positive;
test "from_int: negative" from_int_negative;
test "from_int: max_int" from_int_max_int;
test "from_int: min_int" from_int_min_int;
(* From int64 *)
test "from_int64: large positive" from_int64_large;
test "from_int64: large negative" from_int64_negative;
]
================================================
FILE: packages/Js/test/bigint_tests/conversion.ml
================================================
(** TC39 Test262: BigInt conversion tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt
Tests for BigInt conversion operations:
- toString with various radixes
- toNumber (to float)
- asIntN / asUintN (wrapping to fixed bit widths) *)
open Helpers
(* ===================================================================
toString - decimal (default)
=================================================================== *)
let to_string_zero () =
let a = BigInt.of_int 0 in
assert_string (BigInt.to_string a) "0"
let to_string_positive () =
let a = BigInt.of_int 123 in
assert_string (BigInt.to_string a) "123"
let to_string_negative () =
let a = BigInt.of_int (-123) in
assert_string (BigInt.to_string a) "-123"
let to_string_large () =
(* From QuickJS test: (1 << 100).toString(10) *)
let a = BigInt.shift_left (BigInt.of_int 1) 100 in
assert_string (BigInt.to_string a) "1267650600228229401496703205376"
(* ===================================================================
toString - with radix
=================================================================== *)
let to_string_radix_2 () =
let a = BigInt.of_int 10 in
assert_string (BigInt.to_string ~radix:2 a) "1010"
let to_string_radix_8 () =
(* From QuickJS: (1 << 100).toString(8) *)
let a = BigInt.shift_left (BigInt.of_int 1) 100 in
assert_string (BigInt.to_string ~radix:8 a) "2000000000000000000000000000000000"
let to_string_radix_16 () =
let a = BigInt.of_int 255 in
assert_string (BigInt.to_string ~radix:16 a) "ff"
let to_string_radix_36 () =
(* From QuickJS: (-1 << 100).toString(36) *)
let a = BigInt.shift_left (BigInt.of_int (-1)) 100 in
assert_string (BigInt.to_string ~radix:36 a) "-3ewfdnca0n6ld1ggvfgg"
let to_string_radix_16_large () =
let a = BigInt.of_string "515377520732011331036461129765621272702107522001" in
assert_string (BigInt.to_string ~radix:16 a) "5a4653ca673768565b41f775d6947d55cf3813d1"
(* ===================================================================
toNumber (to float) - with precision loss for large values
=================================================================== *)
let to_float_small () =
let a = BigInt.of_int 42 in
assert_float (BigInt.to_float a) 42.0
let to_float_negative () =
let a = BigInt.of_int (-42) in
assert_float (BigInt.to_float a) (-42.0)
let to_float_zero () =
let a = BigInt.of_int 0 in
assert_float (BigInt.to_float a) 0.0
let to_float_max_safe_int () =
let a = BigInt.of_string "9007199254740991" in
assert_float (BigInt.to_float a) 9007199254740991.0
let to_float_large () =
(* From QuickJS: Number(0xffffffffffffffffn) = 18446744073709552000 *)
let a = BigInt.of_string "0xffffffffffffffff" in
assert_float (BigInt.to_float a) 18446744073709552000.0
let to_float_large_negative () =
(* From QuickJS: Number(-0xffffffffffffffffn) = -18446744073709552000 *)
let a = BigInt.of_string "-0xffffffffffffffff" in
assert_float (BigInt.to_float a) (-18446744073709552000.0)
(* ===================================================================
asIntN - wraps BigInt to signed N-bit integer
=================================================================== *)
let as_int_n_positive () =
(* 127 fits in 8 bits signed *)
let a = BigInt.of_int 127 in
assert_bigint (BigInt.as_int_n 8 a) (BigInt.of_int 127)
let as_int_n_wrap_positive () =
(* 128 in 8-bit signed wraps to -128 *)
let a = BigInt.of_int 128 in
assert_bigint (BigInt.as_int_n 8 a) (BigInt.of_int (-128))
let as_int_n_wrap_large () =
(* 255 in 8-bit signed wraps to -1 *)
let a = BigInt.of_int 255 in
assert_bigint (BigInt.as_int_n 8 a) (BigInt.of_int (-1))
let as_int_n_negative () =
(* -128 fits in 8 bits signed *)
let a = BigInt.of_int (-128) in
assert_bigint (BigInt.as_int_n 8 a) (BigInt.of_int (-128))
let as_int_n_wrap_negative () =
(* -129 in 8-bit signed wraps to 127 *)
let a = BigInt.of_int (-129) in
assert_bigint (BigInt.as_int_n 8 a) (BigInt.of_int 127)
let as_int_n_64 () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.as_int_n 64 a) (BigInt.of_int 42)
(* ===================================================================
asUintN - wraps BigInt to unsigned N-bit integer
=================================================================== *)
let as_uint_n_fits () =
let a = BigInt.of_int 255 in
assert_bigint (BigInt.as_uint_n 8 a) (BigInt.of_int 255)
let as_uint_n_wrap () =
(* 256 in 8-bit unsigned wraps to 0 *)
let a = BigInt.of_int 256 in
assert_bigint (BigInt.as_uint_n 8 a) (BigInt.of_int 0)
let as_uint_n_wrap_large () =
(* 257 in 8-bit unsigned wraps to 1 *)
let a = BigInt.of_int 257 in
assert_bigint (BigInt.as_uint_n 8 a) (BigInt.of_int 1)
let as_uint_n_negative () =
(* -1 in 8-bit unsigned wraps to 255 *)
let a = BigInt.of_int (-1) in
assert_bigint (BigInt.as_uint_n 8 a) (BigInt.of_int 255)
let as_uint_n_64 () =
let a = BigInt.of_int 42 in
assert_bigint (BigInt.as_uint_n 64 a) (BigInt.of_int 42)
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* toString decimal *)
test "toString: zero" to_string_zero;
test "toString: positive" to_string_positive;
test "toString: negative" to_string_negative;
test "toString: large (1 << 100)" to_string_large;
(* toString with radix *)
test "toString: radix 2" to_string_radix_2;
test "toString: radix 8" to_string_radix_8;
test "toString: radix 16" to_string_radix_16;
test "toString: radix 36" to_string_radix_36;
test "toString: radix 16 large" to_string_radix_16_large;
(* toFloat *)
test "toFloat: small" to_float_small;
test "toFloat: negative" to_float_negative;
test "toFloat: zero" to_float_zero;
test "toFloat: MAX_SAFE_INTEGER" to_float_max_safe_int;
test "toFloat: large (0xffffffffffffffff)" to_float_large;
test "toFloat: large negative" to_float_large_negative;
(* asIntN *)
test "asIntN: positive fits" as_int_n_positive;
test "asIntN: positive wraps" as_int_n_wrap_positive;
test "asIntN: 255 -> -1" as_int_n_wrap_large;
test "asIntN: negative fits" as_int_n_negative;
test "asIntN: negative wraps" as_int_n_wrap_negative;
test "asIntN: 64 bits" as_int_n_64;
(* asUintN *)
test "asUintN: fits" as_uint_n_fits;
test "asUintN: wraps to 0" as_uint_n_wrap;
test "asUintN: wraps to 1" as_uint_n_wrap_large;
test "asUintN: negative wraps" as_uint_n_negative;
test "asUintN: 64 bits" as_uint_n_64;
]
================================================
FILE: packages/Js/test/bigint_tests/prototype.ml
================================================
(** TC39 Test262: BigInt.prototype tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/BigInt/prototype
Tests for BigInt.prototype.toString, BigInt.prototype.valueOf, BigInt.prototype.toLocaleString *)
open Helpers
module BigInt = Js.Bigint
(* ===================================================================
BigInt.prototype.toString tests
=================================================================== *)
let to_string_default_radix () =
(* Default radix is 10 *)
assert_string_equal (BigInt.toString (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.toString (BigInt.of_int 1)) "1";
assert_string_equal (BigInt.toString (BigInt.of_int 10)) "10";
assert_string_equal (BigInt.toString (BigInt.of_int 100)) "100";
assert_string_equal (BigInt.toString (BigInt.of_int (-1))) "-1";
assert_string_equal (BigInt.toString (BigInt.of_int (-10))) "-10"
let to_string_radix_2 () =
(* Binary representation *)
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 1)) "1";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 2)) "10";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 3)) "11";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 4)) "100";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int 255)) "11111111";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int (-1))) "-1";
assert_string_equal (BigInt.to_string ~radix:2 (BigInt.of_int (-2))) "-10"
let to_string_radix_8 () =
(* Octal representation *)
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int 7)) "7";
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int 8)) "10";
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int 63)) "77";
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int 64)) "100";
assert_string_equal (BigInt.to_string ~radix:8 (BigInt.of_int (-8))) "-10"
let to_string_radix_10 () =
(* Decimal representation (explicit) *)
assert_string_equal (BigInt.to_string ~radix:10 (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.to_string ~radix:10 (BigInt.of_int 123)) "123";
assert_string_equal (BigInt.to_string ~radix:10 (BigInt.of_int (-456))) "-456";
assert_string_equal (BigInt.to_string ~radix:10 (BigInt.of_string "9999999999")) "9999999999"
let to_string_radix_16 () =
(* Hexadecimal representation *)
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 10)) "a";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 15)) "f";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 16)) "10";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 255)) "ff";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int 256)) "100";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int (-1))) "-1";
assert_string_equal (BigInt.to_string ~radix:16 (BigInt.of_int (-255))) "-ff"
let to_string_radix_36 () =
(* Base 36 (max radix) *)
assert_string_equal (BigInt.to_string ~radix:36 (BigInt.of_int 0)) "0";
assert_string_equal (BigInt.to_string ~radix:36 (BigInt.of_int 35)) "z";
assert_string_equal (BigInt.to_string ~radix:36 (BigInt.of_int 36)) "10";
assert_string_equal (BigInt.to_string ~radix:36 (BigInt.of_int 1295)) "zz";
assert_string_equal (BigInt.to_string ~radix:36 (BigInt.of_int 1296)) "100"
let to_string_various_radixes () =
(* Test various radixes *)
let n = BigInt.of_int 100 in
assert_string_equal (BigInt.to_string ~radix:2 n) "1100100";
assert_string_equal (BigInt.to_string ~radix:3 n) "10201";
assert_string_equal (BigInt.to_string ~radix:4 n) "1210";
assert_string_equal (BigInt.to_string ~radix:5 n) "400";
assert_string_equal (BigInt.to_string ~radix:6 n) "244";
assert_string_equal (BigInt.to_string ~radix:7 n) "202";
assert_string_equal (BigInt.to_string ~radix:8 n) "144";
assert_string_equal (BigInt.to_string ~radix:9 n) "121";
assert_string_equal (BigInt.to_string ~radix:10 n) "100"
let to_string_large_numbers () =
(* Test large numbers *)
let large = BigInt.of_string "123456789012345678901234567890" in
let s10 = BigInt.to_string ~radix:10 large in
assert_string_equal s10 "123456789012345678901234567890";
(* Verify hex conversion works *)
let s16 = BigInt.to_string ~radix:16 large in
assert_true "hex string non-empty" (String.length s16 > 0)
let to_string_negative_large () =
let neg_large = BigInt.of_string "-123456789012345678901234567890" in
let s = BigInt.to_string ~radix:10 neg_large in
assert_string_equal s "-123456789012345678901234567890"
let to_string_zero () =
(* Zero in various radixes *)
let zero = BigInt.of_int 0 in
for r = 2 to 36 do
assert_string_equal (BigInt.to_string ~radix:r zero) "0"
done
(* ===================================================================
BigInt conversion tests (to_float)
=================================================================== *)
let to_float_small () =
assert_float_exact (BigInt.to_float (BigInt.of_int 0)) 0.;
assert_float_exact (BigInt.to_float (BigInt.of_int 1)) 1.;
assert_float_exact (BigInt.to_float (BigInt.of_int (-1))) (-1.);
assert_float_exact (BigInt.to_float (BigInt.of_int 100)) 100.;
assert_float_exact (BigInt.to_float (BigInt.of_int (-100))) (-100.)
let to_float_large () =
(* Large numbers may lose precision *)
let large = BigInt.of_string "9007199254740992" in
(* 2^53 *)
let f = BigInt.to_float large in
assert_true "large to_float is finite" (Float.is_finite f)
let to_float_very_large () =
(* Very large numbers become infinity *)
let huge = BigInt.of_string "1" in
let shifted = BigInt.shift_left huge 10000 in
let f = BigInt.to_float shifted in
assert_true "huge number becomes infinity" (f = Float.infinity || f = Float.neg_infinity)
(* ===================================================================
Constructor edge cases
=================================================================== *)
let of_string_edge_cases () =
(* Empty string *)
assert_bigint_equal (BigInt.of_string "") (BigInt.of_int 0);
(* Whitespace *)
assert_bigint_equal (BigInt.of_string " 123 ") (BigInt.of_int 123);
(* Leading zeros *)
assert_bigint_equal (BigInt.of_string "00123") (BigInt.of_int 123);
(* Negative with leading zeros *)
assert_bigint_equal (BigInt.of_string "-00123") (BigInt.of_int (-123))
let of_string_hex () =
assert_bigint_equal (BigInt.of_string "0x10") (BigInt.of_int 16);
assert_bigint_equal (BigInt.of_string "0xFF") (BigInt.of_int 255);
assert_bigint_equal (BigInt.of_string "0xABCD") (BigInt.of_int 43981)
let of_string_binary () =
assert_bigint_equal (BigInt.of_string "0b1010") (BigInt.of_int 10);
assert_bigint_equal (BigInt.of_string "0b11111111") (BigInt.of_int 255)
let of_string_octal () =
assert_bigint_equal (BigInt.of_string "0o10") (BigInt.of_int 8);
assert_bigint_equal (BigInt.of_string "0o777") (BigInt.of_int 511)
(* ===================================================================
Comparison with different representations
=================================================================== *)
let compare_equal () =
let a = BigInt.of_int 42 in
let b = BigInt.of_string "42" in
assert_true "42 == 42" (BigInt.equal a b);
assert_true "compare returns 0" (BigInt.compare a b = 0)
let compare_less () =
let a = BigInt.of_int 10 in
let b = BigInt.of_int 20 in
assert_true "10 < 20" (BigInt.lt a b);
assert_true "compare returns negative" (BigInt.compare a b < 0)
let compare_greater () =
let a = BigInt.of_int 20 in
let b = BigInt.of_int 10 in
assert_true "20 > 10" (BigInt.gt a b);
assert_true "compare returns positive" (BigInt.compare a b > 0)
let compare_negative () =
let a = BigInt.of_int (-10) in
let b = BigInt.of_int 10 in
assert_true "-10 < 10" (BigInt.lt a b);
let c = BigInt.of_int (-20) in
assert_true "-20 < -10" (BigInt.lt c a)
let compare_large () =
let a = BigInt.of_string "123456789012345678901234567890" in
let b = BigInt.of_string "123456789012345678901234567891" in
assert_true "large a < large b" (BigInt.lt a b);
assert_true "large b > large a" (BigInt.gt b a)
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* toString *)
test "toString default radix" to_string_default_radix;
test "toString radix 2 (binary)" to_string_radix_2;
test "toString radix 8 (octal)" to_string_radix_8;
test "toString radix 10 (decimal)" to_string_radix_10;
test "toString radix 16 (hex)" to_string_radix_16;
test "toString radix 36 (max)" to_string_radix_36;
test "toString various radixes" to_string_various_radixes;
test "toString large numbers" to_string_large_numbers;
test "toString negative large" to_string_negative_large;
test "toString zero all radixes" to_string_zero;
(* to_float *)
test "to_float small" to_float_small;
test "to_float large" to_float_large;
test "to_float very large" to_float_very_large;
(* of_string edge cases *)
test "of_string edge cases" of_string_edge_cases;
test "of_string hex" of_string_hex;
test "of_string binary" of_string_binary;
test "of_string octal" of_string_octal;
(* comparison *)
test "compare equal" compare_equal;
test "compare less" compare_less;
test "compare greater" compare_greater;
test "compare negative" compare_negative;
test "compare large" compare_large;
]
================================================
FILE: packages/Js/test/date_tests/getters.ml
================================================
(** TC39 Test262: Date getter tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype
Tests for Date.prototype getter methods:
- getFullYear, getUTCFullYear
- getMonth, getUTCMonth
- getDate, getUTCDate
- getDay, getUTCDay
- getHours, getUTCHours
- getMinutes, getUTCMinutes
- getSeconds, getUTCSeconds
- getMilliseconds, getUTCMilliseconds
- getTime
- getTimezoneOffset *)
open Helpers
(* A known timestamp for testing: 2017-09-22T16:37:38.091Z (Friday)
epoch ms: 1506098258091 *)
let known_timestamp = 1506098258091.
(* ===================================================================
getTime / valueOf
=================================================================== *)
let get_time_returns_epoch_ms () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getTime d) known_timestamp
let get_time_nan_for_invalid () =
let d = Date.fromFloat nan in
assert_nan (Date.getTime d)
(* ===================================================================
UTC Getters - these don't depend on timezone
=================================================================== *)
let get_utc_full_year () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCFullYear d) 2017.
let get_utc_month () =
(* September = month 8 (0-indexed) *)
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCMonth d) 8.
let get_utc_date () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCDate d) 22.
let get_utc_day () =
(* Friday = day 5 (0 = Sunday) *)
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCDay d) 5.
let get_utc_hours () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCHours d) 16.
let get_utc_minutes () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCMinutes d) 37.
let get_utc_seconds () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCSeconds d) 38.
let get_utc_milliseconds () =
let d = Date.fromFloat known_timestamp in
assert_float_exact (Date.getUTCMilliseconds d) 91.
(* ===================================================================
UTC Getters - boundary cases
=================================================================== *)
let get_utc_epoch () =
(* Jan 1, 1970 00:00:00.000 UTC *)
let d = Date.fromFloat 0. in
assert_float_exact (Date.getUTCFullYear d) 1970.;
assert_float_exact (Date.getUTCMonth d) 0.;
assert_float_exact (Date.getUTCDate d) 1.;
assert_float_exact (Date.getUTCDay d) 4.;
(* Thursday *)
assert_float_exact (Date.getUTCHours d) 0.;
assert_float_exact (Date.getUTCMinutes d) 0.;
assert_float_exact (Date.getUTCSeconds d) 0.;
assert_float_exact (Date.getUTCMilliseconds d) 0.
let get_utc_before_epoch () =
(* Dec 31, 1969 23:59:59.999 UTC = -1ms *)
let d = Date.fromFloat (-1.) in
assert_float_exact (Date.getUTCFullYear d) 1969.;
assert_float_exact (Date.getUTCMonth d) 11.;
(* December *)
assert_float_exact (Date.getUTCDate d) 31.;
assert_float_exact (Date.getUTCHours d) 23.;
assert_float_exact (Date.getUTCMinutes d) 59.;
assert_float_exact (Date.getUTCSeconds d) 59.;
assert_float_exact (Date.getUTCMilliseconds d) 999.
let get_utc_y2k () =
(* Jan 1, 2000 00:00:00.000 UTC *)
let d = Date.fromFloat 946684800000. in
assert_float_exact (Date.getUTCFullYear d) 2000.;
assert_float_exact (Date.getUTCMonth d) 0.;
assert_float_exact (Date.getUTCDate d) 1.;
assert_float_exact (Date.getUTCDay d) 6. (* Saturday *)
let get_utc_leap_day () =
(* Feb 29, 2020 12:00:00.000 UTC *)
let d = Date.fromFloat 1582977600000. in
assert_float_exact (Date.getUTCFullYear d) 2020.;
assert_float_exact (Date.getUTCMonth d) 1.;
(* February *)
assert_float_exact (Date.getUTCDate d) 29.
(* ===================================================================
NaN handling - all getters return NaN for invalid date
=================================================================== *)
let get_utc_nan_full_year () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCFullYear d)
let get_utc_nan_month () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCMonth d)
let get_utc_nan_date () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCDate d)
let get_utc_nan_day () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCDay d)
let get_utc_nan_hours () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCHours d)
let get_utc_nan_minutes () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCMinutes d)
let get_utc_nan_seconds () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCSeconds d)
let get_utc_nan_milliseconds () =
let d = Date.fromFloat nan in
assert_nan (Date.getUTCMilliseconds d)
(* ===================================================================
End of year / start of year transitions
=================================================================== *)
let get_utc_new_year_transition () =
(* Dec 31, 2019 23:59:59.999 UTC *)
let d1 = Date.fromFloat 1577836799999. in
assert_float_exact (Date.getUTCFullYear d1) 2019.;
assert_float_exact (Date.getUTCMonth d1) 11.;
assert_float_exact (Date.getUTCDate d1) 31.;
(* Jan 1, 2020 00:00:00.000 UTC *)
let d2 = Date.fromFloat 1577836800000. in
assert_float_exact (Date.getUTCFullYear d2) 2020.;
assert_float_exact (Date.getUTCMonth d2) 0.;
assert_float_exact (Date.getUTCDate d2) 1.
(* ===================================================================
Month boundaries
=================================================================== *)
let get_utc_month_lengths () =
(* Jan has 31 days, Feb 28/29, etc *)
(* Last day of January 2020 *)
let jan31 = Date.fromFloat (Date.utc ~year:2020. ~month:0. ~date:31. ()) in
assert_float_exact (Date.getUTCMonth jan31) 0.;
assert_float_exact (Date.getUTCDate jan31) 31.;
(* Feb 1 *)
let feb1 = Date.fromFloat (Date.utc ~year:2020. ~month:1. ~date:1. ()) in
assert_float_exact (Date.getUTCMonth feb1) 1.;
assert_float_exact (Date.getUTCDate feb1) 1.
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* getTime *)
test "getTime returns epoch ms" get_time_returns_epoch_ms;
test "getTime NaN for invalid date" get_time_nan_for_invalid;
(* UTC getters for known timestamp *)
test "getUTCFullYear" get_utc_full_year;
test "getUTCMonth" get_utc_month;
test "getUTCDate" get_utc_date;
test "getUTCDay" get_utc_day;
test "getUTCHours" get_utc_hours;
test "getUTCMinutes" get_utc_minutes;
test "getUTCSeconds" get_utc_seconds;
test "getUTCMilliseconds" get_utc_milliseconds;
(* UTC getters - boundary cases *)
test "UTC getters at epoch" get_utc_epoch;
test "UTC getters before epoch" get_utc_before_epoch;
test "UTC getters at Y2K" get_utc_y2k;
test "UTC getters on leap day" get_utc_leap_day;
(* NaN handling *)
test "getUTCFullYear NaN" get_utc_nan_full_year;
test "getUTCMonth NaN" get_utc_nan_month;
test "getUTCDate NaN" get_utc_nan_date;
test "getUTCDay NaN" get_utc_nan_day;
test "getUTCHours NaN" get_utc_nan_hours;
test "getUTCMinutes NaN" get_utc_nan_minutes;
test "getUTCSeconds NaN" get_utc_nan_seconds;
test "getUTCMilliseconds NaN" get_utc_nan_milliseconds;
(* Transitions *)
test "new year transition" get_utc_new_year_transition;
test "month lengths" get_utc_month_lengths;
]
================================================
FILE: packages/Js/test/date_tests/local_getters.ml
================================================
(** TC39 Test262: Date local time getter tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/get*
Tests for local time getters: getDate, getDay, getFullYear, getHours, getMinutes, getSeconds, getMilliseconds,
getMonth, getTime, getTimezoneOffset *)
open Helpers
module Date = Js.Date
(* ===================================================================
Helper: Create dates in UTC and test local getters
Note: These tests use UTC dates to avoid timezone dependency
=================================================================== *)
(* ===================================================================
getTime tests
=================================================================== *)
let get_time_basic () =
let d = Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ~seconds:11. () +. 91. in
assert_float_exact (Date.getTime d) 1508695811091.
let get_time_epoch () = assert_float_exact (Date.getTime 0.) 0.
let get_time_negative () =
(* 1969-12-31T23:59:59.000Z = -1000 ms *)
assert_float_exact (Date.getTime (-1000.)) (-1000.)
let get_time_nan () = assert_nan (Date.getTime nan)
(* ===================================================================
valueOf tests - should be identical to getTime
=================================================================== *)
let valueof_basic () =
let d = Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ~seconds:11. () +. 91. in
assert_float_exact (Date.valueOf d) 1508695811091.
let valueof_epoch () = assert_float_exact (Date.valueOf 0.) 0.
let valueof_nan () = assert_nan (Date.valueOf nan)
let valueof_equals_gettime () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. () in
assert_float_exact (Date.valueOf d) (Date.getTime d)
(* ===================================================================
getUTCFullYear tests (comprehensive - already partially covered)
=================================================================== *)
let get_utc_full_year_2017 () =
let d = Date.utc ~year:2017. ~month:9. ~date:22. () in
assert_float_exact (Date.getUTCFullYear d) 2017.
let get_utc_full_year_1970 () = assert_float_exact (Date.getUTCFullYear 0.) 1970.
let get_utc_full_year_1969 () =
let d = Date.utc ~year:1969. ~month:11. ~date:31. ~hours:23. ~minutes:59. ~seconds:59. () in
assert_float_exact (Date.getUTCFullYear d) 1969.
let get_utc_full_year_nan () = assert_nan (Date.getUTCFullYear nan)
let get_utc_full_year_y2k () =
let d = Date.utc ~year:2000. ~month:0. ~date:1. () in
assert_float_exact (Date.getUTCFullYear d) 2000.
let get_utc_full_year_leap () =
let d = Date.utc ~year:2024. ~month:1. ~date:29. () in
assert_float_exact (Date.getUTCFullYear d) 2024.
(* ===================================================================
getUTCMonth tests
=================================================================== *)
let get_utc_month_january () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
assert_float_exact (Date.getUTCMonth d) 0.
let get_utc_month_december () =
let d = Date.utc ~year:2020. ~month:11. ~date:25. () in
assert_float_exact (Date.getUTCMonth d) 11.
let get_utc_month_nan () = assert_nan (Date.getUTCMonth nan)
let get_utc_month_epoch () = assert_float_exact (Date.getUTCMonth 0.) 0.
(* ===================================================================
getUTCDate tests
=================================================================== *)
let get_utc_date_first () =
let d = Date.utc ~year:2020. ~month:5. ~date:1. () in
assert_float_exact (Date.getUTCDate d) 1.
let get_utc_date_31st () =
let d = Date.utc ~year:2020. ~month:0. ~date:31. () in
assert_float_exact (Date.getUTCDate d) 31.
let get_utc_date_nan () = assert_nan (Date.getUTCDate nan)
let get_utc_date_epoch () = assert_float_exact (Date.getUTCDate 0.) 1.
(* ===================================================================
getUTCDay tests (day of week)
=================================================================== *)
let get_utc_day_thursday_epoch () =
(* Jan 1, 1970 was a Thursday (day 4) *)
assert_float_exact (Date.getUTCDay 0.) 4.
let get_utc_day_sunday () =
(* Find a known Sunday - Jan 3, 2021 was Sunday *)
let d = Date.utc ~year:2021. ~month:0. ~date:3. () in
assert_float_exact (Date.getUTCDay d) 0.
let get_utc_day_saturday () =
(* Jan 2, 2021 was Saturday *)
let d = Date.utc ~year:2021. ~month:0. ~date:2. () in
assert_float_exact (Date.getUTCDay d) 6.
let get_utc_day_nan () = assert_nan (Date.getUTCDay nan)
(* ===================================================================
getUTCHours tests
=================================================================== *)
let get_utc_hours_zero () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. () in
assert_float_exact (Date.getUTCHours d) 0.
let get_utc_hours_23 () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:23. () in
assert_float_exact (Date.getUTCHours d) 23.
let get_utc_hours_nan () = assert_nan (Date.getUTCHours nan)
let get_utc_hours_epoch () = assert_float_exact (Date.getUTCHours 0.) 0.
(* ===================================================================
getUTCMinutes tests
=================================================================== *)
let get_utc_minutes_zero () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. () in
assert_float_exact (Date.getUTCMinutes d) 0.
let get_utc_minutes_59 () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:59. () in
assert_float_exact (Date.getUTCMinutes d) 59.
let get_utc_minutes_nan () = assert_nan (Date.getUTCMinutes nan)
(* ===================================================================
getUTCSeconds tests
=================================================================== *)
let get_utc_seconds_zero () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
assert_float_exact (Date.getUTCSeconds d) 0.
let get_utc_seconds_59 () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:59. () in
assert_float_exact (Date.getUTCSeconds d) 59.
let get_utc_seconds_nan () = assert_nan (Date.getUTCSeconds nan)
(* ===================================================================
getUTCMilliseconds tests
=================================================================== *)
let get_utc_ms_zero () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
assert_float_exact (Date.getUTCMilliseconds d) 0.
let get_utc_ms_999 () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 999. in
assert_float_exact (Date.getUTCMilliseconds d) 999.
let get_utc_ms_nan () = assert_nan (Date.getUTCMilliseconds nan)
let get_utc_ms_middle () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 456. in
assert_float_exact (Date.getUTCMilliseconds d) 456.
(* ===================================================================
getTimezoneOffset tests
Note: This is timezone-dependent, so we just verify it returns a number
=================================================================== *)
let get_timezone_offset_returns_number () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let offset = Date.getTimezoneOffset d in
(* Offset should be a finite number *)
assert_true "timezone offset should be finite" (Float.is_finite offset)
let get_timezone_offset_nan () = assert_nan (Date.getTimezoneOffset nan)
let get_timezone_offset_range () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let offset = Date.getTimezoneOffset d in
(* Timezone offsets range from -720 (UTC+12) to +840 (UTC-14) *)
assert_true "offset in valid range" (offset >= -720. && offset <= 840.)
(* ===================================================================
Local time getter tests
Note: These depend on the system timezone, so we test consistency
=================================================================== *)
let local_getters_consistent () =
(* Create a date and verify local getters return consistent values *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 123. in
let year = Date.getFullYear d in
let month = Date.getMonth d in
let date = Date.getDate d in
let hours = Date.getHours d in
let minutes = Date.getMinutes d in
let seconds = Date.getSeconds d in
let ms = Date.getMilliseconds d in
(* All should be finite *)
assert_true "year finite" (Float.is_finite year);
assert_true "month finite" (Float.is_finite month);
assert_true "date finite" (Float.is_finite date);
assert_true "hours finite" (Float.is_finite hours);
assert_true "minutes finite" (Float.is_finite minutes);
assert_true "seconds finite" (Float.is_finite seconds);
assert_true "ms finite" (Float.is_finite ms);
(* Check ranges *)
assert_true "month 0-11" (month >= 0. && month <= 11.);
assert_true "date 1-31" (date >= 1. && date <= 31.);
assert_true "hours 0-23" (hours >= 0. && hours <= 23.);
assert_true "minutes 0-59" (minutes >= 0. && minutes <= 59.);
assert_true "seconds 0-59" (seconds >= 0. && seconds <= 59.);
assert_true "ms 0-999" (ms >= 0. && ms <= 999.)
let local_getters_nan () =
(* All local getters should return NaN for invalid dates *)
assert_nan (Date.getFullYear nan);
assert_nan (Date.getMonth nan);
assert_nan (Date.getDate nan);
assert_nan (Date.getDay nan);
assert_nan (Date.getHours nan);
assert_nan (Date.getMinutes nan);
assert_nan (Date.getSeconds nan);
assert_nan (Date.getMilliseconds nan)
let get_day_range () =
(* getDay should return 0-6 *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let day = Date.getDay d in
assert_true "day 0-6" (day >= 0. && day <= 6.)
(* ===================================================================
Edge case tests
=================================================================== *)
let getters_large_positive_date () =
(* Test with a large date - year 275760 (near max) *)
let d = Date.utc ~year:275760. ~month:8. ~date:13. () in
assert_float_exact (Date.getUTCFullYear d) 275760.
let getters_large_negative_date () =
(* Test with a date before epoch *)
let d = Date.utc ~year:1900. ~month:0. ~date:1. () in
assert_float_exact (Date.getUTCFullYear d) 1900.
let getters_boundary_milliseconds () =
(* Test at millisecond boundary *)
let d = Date.utc ~year:2020. ~month:0. ~date:1. ~hours:0. ~minutes:0. ~seconds:0. () +. 999. in
assert_float_exact (Date.getUTCMilliseconds d) 999.
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* getTime *)
test "getTime basic" get_time_basic;
test "getTime epoch" get_time_epoch;
test "getTime negative" get_time_negative;
test "getTime NaN" get_time_nan;
(* valueOf *)
test "valueOf basic" valueof_basic;
test "valueOf epoch" valueof_epoch;
test "valueOf NaN" valueof_nan;
test "valueOf equals getTime" valueof_equals_gettime;
(* getUTCFullYear *)
test "getUTCFullYear 2017" get_utc_full_year_2017;
test "getUTCFullYear 1970 (epoch)" get_utc_full_year_1970;
test "getUTCFullYear 1969" get_utc_full_year_1969;
test "getUTCFullYear NaN" get_utc_full_year_nan;
test "getUTCFullYear Y2K" get_utc_full_year_y2k;
test "getUTCFullYear leap year" get_utc_full_year_leap;
(* getUTCMonth *)
test "getUTCMonth January (0)" get_utc_month_january;
test "getUTCMonth December (11)" get_utc_month_december;
test "getUTCMonth NaN" get_utc_month_nan;
test "getUTCMonth epoch" get_utc_month_epoch;
(* getUTCDate *)
test "getUTCDate first" get_utc_date_first;
test "getUTCDate 31st" get_utc_date_31st;
test "getUTCDate NaN" get_utc_date_nan;
test "getUTCDate epoch" get_utc_date_epoch;
(* getUTCDay *)
test "getUTCDay Thursday (epoch)" get_utc_day_thursday_epoch;
test "getUTCDay Sunday" get_utc_day_sunday;
test "getUTCDay Saturday" get_utc_day_saturday;
test "getUTCDay NaN" get_utc_day_nan;
(* getUTCHours *)
test "getUTCHours zero" get_utc_hours_zero;
test "getUTCHours 23" get_utc_hours_23;
test "getUTCHours NaN" get_utc_hours_nan;
test "getUTCHours epoch" get_utc_hours_epoch;
(* getUTCMinutes *)
test "getUTCMinutes zero" get_utc_minutes_zero;
test "getUTCMinutes 59" get_utc_minutes_59;
test "getUTCMinutes NaN" get_utc_minutes_nan;
(* getUTCSeconds *)
test "getUTCSeconds zero" get_utc_seconds_zero;
test "getUTCSeconds 59" get_utc_seconds_59;
test "getUTCSeconds NaN" get_utc_seconds_nan;
(* getUTCMilliseconds *)
test "getUTCMilliseconds zero" get_utc_ms_zero;
test "getUTCMilliseconds 999" get_utc_ms_999;
test "getUTCMilliseconds NaN" get_utc_ms_nan;
test "getUTCMilliseconds middle" get_utc_ms_middle;
(* getTimezoneOffset *)
test "getTimezoneOffset returns number" get_timezone_offset_returns_number;
test "getTimezoneOffset NaN" get_timezone_offset_nan;
test "getTimezoneOffset in valid range" get_timezone_offset_range;
(* Local getters *)
test "local getters consistent" local_getters_consistent;
test "local getters NaN" local_getters_nan;
test "getDay range 0-6" get_day_range;
(* Edge cases *)
test "getters large positive date" getters_large_positive_date;
test "getters large negative date" getters_large_negative_date;
test "getters boundary milliseconds" getters_boundary_milliseconds;
]
================================================
FILE: packages/Js/test/date_tests/now.ml
================================================
(** TC39 Test262: Date.now tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/now
ECMA-262 Section: Date.now()
Returns the current time as milliseconds since the Unix epoch. *)
open Helpers
(* ===================================================================
Basic functionality
=================================================================== *)
let now_returns_number () =
(* Date.now() should return a finite number *)
let result = Date.now () in
assert_not_nan result;
assert_bool (Float.is_finite result) true
let now_returns_positive () =
(* Date.now() should be positive (we're well past 1970) *)
let result = Date.now () in
assert_bool (result > 0.) true
let now_is_recent () =
(* Date.now() should be reasonably recent (after year 2020) *)
let result = Date.now () in
let year_2020 = 1577836800000. in
(* Jan 1, 2020 00:00:00 UTC *)
assert_bool (result > year_2020) true
let now_increases () =
(* Two calls to Date.now() should not decrease *)
let t1 = Date.now () in
(* Small busy wait - not ideal but tests the concept *)
let t2 = Date.now () in
assert_bool (t2 >= t1) true
let now_is_integer_like () =
(* Date.now() should return an integer value (no fractional ms) *)
let result = Date.now () in
assert_bool (Float.is_integer result) true
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
test "now returns finite number" now_returns_number;
test "now returns positive" now_returns_positive;
test "now is recent (after 2020)" now_is_recent;
test "now is monotonic" now_increases;
test "now returns integer ms" now_is_integer_like;
]
================================================
FILE: packages/Js/test/date_tests/parse.ml
================================================
(** TC39 Test262: Date.parseAsFloat tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/parse
ECMA-262 Section: Date.parse(string)
Date.parseAsFloat returns the time value (epoch milliseconds) from a string. Returns NaN if the string is not a
valid date. *)
open Helpers
(* ===================================================================
ISO 8601 format: YYYY-MM-DDTHH:mm:ss.sssZ
=================================================================== *)
let parse_empty_string () =
(* Date.parse("") returns NaN *)
assert_nan (Date.parseAsFloat "")
let parse_year_only () =
(* Date.parse("2000") = Jan 1, 2000 00:00:00 UTC *)
assert_float_exact (Date.parseAsFloat "2000") 946684800000.
let parse_year_month () =
(* Date.parse("2000-01") = Jan 1, 2000 00:00:00 UTC *)
assert_float_exact (Date.parseAsFloat "2000-01") 946684800000.
let parse_full_date () =
(* Date.parse("2000-01-01") = Jan 1, 2000 00:00:00 UTC *)
assert_float_exact (Date.parseAsFloat "2000-01-01") 946684800000.
let parse_date_time_utc () =
(* Date.parse("2000-01-01T00:00Z") *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00Z") 946684800000.
let parse_date_time_seconds () =
(* Date.parse("2000-01-01T00:00:00Z") *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00Z") 946684800000.
let parse_date_time_millis_1 () =
(* Date.parse("2000-01-01T00:00:00.1Z") = 100ms after midnight *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00.1Z") 946684800100.
let parse_date_time_millis_2 () =
(* Date.parse("2000-01-01T00:00:00.10Z") = 100ms *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00.10Z") 946684800100.
let parse_date_time_millis_3 () =
(* Date.parse("2000-01-01T00:00:00.100Z") = 100ms *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00.100Z") 946684800100.
let parse_date_time_millis_4 () =
(* Date.parse("2000-01-01T00:00:00.1000Z") = implementation-defined, but QuickJS returns 100ms *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00.1000Z") 946684800100.
let parse_timezone_offset () =
(* Date.parse("2000-01-01T00:00:00+00:00") *)
assert_float_exact (Date.parseAsFloat "2000-01-01T00:00:00+00:00") 946684800000.
(* ===================================================================
A known timestamp: 2017-09-22T16:37:38.091Z
=================================================================== *)
let parse_known_iso_timestamp () =
(* This is a specific timestamp from QuickJS tests *)
assert_float_exact (Date.parseAsFloat "2017-09-22T16:37:38.091Z") 1506098258091.
let parse_roundtrip () =
(* Parse an ISO string, format it back, should get same value *)
let original = "2020-01-01T01:01:01.123Z" in
let parsed = Date.parseAsFloat original in
assert_float_exact parsed 1577840461123.
(* ===================================================================
Millisecond parsing edge cases
=================================================================== *)
let parse_millis_single_digit () =
(* .1Z should be 100ms *)
assert_float_exact (Date.parseAsFloat "2020-01-01T01:01:01.1Z") 1577840461100.
let parse_millis_two_digits () =
(* .12Z should be 120ms *)
assert_float_exact (Date.parseAsFloat "2020-01-01T01:01:01.12Z") 1577840461120.
let parse_millis_four_digits () =
(* .1234Z truncates to 123ms *)
assert_float_exact (Date.parseAsFloat "2020-01-01T01:01:01.1234Z") 1577840461123.
let parse_millis_many_digits () =
(* .9999Z truncates to 999ms (no rounding) *)
assert_float_exact (Date.parseAsFloat "2020-01-01T01:01:01.9999Z") 1577840461999.
(* ===================================================================
Expanded years (6-digit years with +/- prefix)
=================================================================== *)
let parse_expanded_year_positive () =
(* +002000 is year 2000 *)
assert_float_exact (Date.parseAsFloat "+002000-01-01T00:00:00Z") 946684800000.
let parse_expanded_year_negative () =
(* -000001 is year -1 (2 BCE) *)
let result = Date.parseAsFloat "-000001-01-01T00:00:00Z" in
assert_not_nan result
let parse_expanded_year_zero_invalid () =
(* -000000 is explicitly invalid per spec *)
assert_nan (Date.parseAsFloat "-000000-01-01T00:00:00Z")
(* ===================================================================
Non-ISO formats (toString/toUTCString style)
=================================================================== *)
let parse_month_name_format () =
(* "Jan 1 2000" style *)
let result = Date.parseAsFloat "Jan 1 2000 00:00:00 GMT" in
assert_float_exact result 946684800000.
let parse_with_weekday () =
(* "Sat Jan 1 2000" style *)
let result = Date.parseAsFloat "Sat Jan 1 2000 00:00:00 GMT" in
assert_float_exact result 946684800000.
let parse_timezone_abbreviation () =
(* GMT+0100 style offset *)
let result = Date.parseAsFloat "Jan 1 2000 00:00:00 GMT+0100" in
(* 1 hour before UTC midnight = Dec 31 1999 23:00 UTC *)
assert_float_exact result (946684800000. -. 3600000.)
let parse_timezone_abbreviation_2 () =
(* GMT+0200 *)
let result = Date.parseAsFloat "Jan 1 2000 00:00:00 GMT+0200" in
assert_float_exact result (946684800000. -. 7200000.)
(* ===================================================================
Invalid strings
=================================================================== *)
let parse_invalid_gibberish () = assert_nan (Date.parseAsFloat "not a date")
let parse_invalid_partial () = assert_nan (Date.parseAsFloat "2000-")
let parse_invalid_month () =
(* Month 13 is invalid *)
assert_nan (Date.parseAsFloat "2000-13-01")
let parse_invalid_day () =
(* Day 32 is invalid *)
assert_nan (Date.parseAsFloat "2000-01-32")
let parse_invalid_hour () =
(* Hour 25 is invalid *)
assert_nan (Date.parseAsFloat "2000-01-01T25:00:00Z")
let parse_invalid_minute () =
(* Minute 60 is invalid *)
assert_nan (Date.parseAsFloat "2000-01-01T00:60:00Z")
let parse_invalid_second () =
(* Second 60 is invalid (except leap seconds, not supported) *)
assert_nan (Date.parseAsFloat "2000-01-01T00:00:60Z")
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* ISO 8601 format *)
test "empty string returns NaN" parse_empty_string;
test "year only: 2000" parse_year_only;
test "year-month: 2000-01" parse_year_month;
test "full date: 2000-01-01" parse_full_date;
test "date with time UTC: 2000-01-01T00:00Z" parse_date_time_utc;
test "date with seconds: 2000-01-01T00:00:00Z" parse_date_time_seconds;
test "milliseconds .1Z = 100ms" parse_date_time_millis_1;
test "milliseconds .10Z = 100ms" parse_date_time_millis_2;
test "milliseconds .100Z = 100ms" parse_date_time_millis_3;
test "milliseconds .1000Z = 100ms" parse_date_time_millis_4;
test "timezone offset +00:00" parse_timezone_offset;
(* Known timestamp *)
test "known ISO timestamp" parse_known_iso_timestamp;
test "roundtrip parsing" parse_roundtrip;
(* Millisecond edge cases *)
test "millis: single digit .1" parse_millis_single_digit;
test "millis: two digits .12" parse_millis_two_digits;
test "millis: four digits .1234 truncates" parse_millis_four_digits;
test "millis: many digits .9999 truncates" parse_millis_many_digits;
(* Expanded years *)
test "expanded year +002000" parse_expanded_year_positive;
test "expanded year -000001" parse_expanded_year_negative;
test "expanded year -000000 is invalid" parse_expanded_year_zero_invalid;
(* Non-ISO formats *)
test "month name format: Jan 1 2000" parse_month_name_format;
test "with weekday: Sat Jan 1 2000" parse_with_weekday;
test "timezone GMT+0100" parse_timezone_abbreviation;
test "timezone GMT+0200" parse_timezone_abbreviation_2;
(* Invalid strings *)
test "invalid: gibberish" parse_invalid_gibberish;
test "invalid: partial 2000-" parse_invalid_partial;
test "invalid: month 13" parse_invalid_month;
test "invalid: day 32" parse_invalid_day;
test "invalid: hour 25" parse_invalid_hour;
test "invalid: minute 60" parse_invalid_minute;
test "invalid: second 60" parse_invalid_second;
]
================================================
FILE: packages/Js/test/date_tests/setters.ml
================================================
(** TC39 Test262: Date setter tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/set*
Tests for setters: setDate, setFullYear, setHours, setMinutes, setSeconds, setMilliseconds, setMonth, setTime and
their UTC variants *)
open Helpers
module Date = Js.Date
(* ===================================================================
setTime tests
=================================================================== *)
let set_time_basic () =
let result = Date.setTime ~time:1508695811091. 0. in
assert_float_exact result 1508695811091.
let set_time_epoch () =
let result = Date.setTime ~time:0. 1000. in
assert_float_exact result 0.
let set_time_negative () =
let result = Date.setTime ~time:(-1000.) 0. in
assert_float_exact result (-1000.)
let set_time_nan_value () =
let result = Date.setTime ~time:nan 0. in
assert_nan result
let set_time_on_nan_date () =
let result = Date.setTime ~time:1000. nan in
assert_float_exact result 1000.
(* ===================================================================
setUTCMilliseconds tests
=================================================================== *)
let set_utc_ms_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let result = Date.setUTCMilliseconds ~milliseconds:500. d in
assert_float_exact (Date.getUTCMilliseconds result) 500.
let set_utc_ms_overflow () =
(* Setting ms to 1000 should roll over to next second *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let result = Date.setUTCMilliseconds ~milliseconds:1000. d in
assert_float_exact (Date.getUTCMilliseconds result) 0.;
assert_float_exact (Date.getUTCSeconds result) 46.
let set_utc_ms_negative () =
(* Negative ms should roll back *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 500. in
let result = Date.setUTCMilliseconds ~milliseconds:(-1.) d in
assert_float_exact (Date.getUTCMilliseconds result) 999.;
assert_float_exact (Date.getUTCSeconds result) 44.
let set_utc_ms_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCMilliseconds ~milliseconds:nan d in
assert_nan result
(* ===================================================================
setUTCSeconds tests
=================================================================== *)
let set_utc_seconds_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
let result = Date.setUTCSeconds ~seconds:45. d in
assert_float_exact (Date.getUTCSeconds result) 45.
let set_utc_seconds_overflow () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
let result = Date.setUTCSeconds ~seconds:60. d in
assert_float_exact (Date.getUTCSeconds result) 0.;
assert_float_exact (Date.getUTCMinutes result) 31.
let set_utc_seconds_with_ms () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
let result = Date.setUTCSeconds ~seconds:45. ~milliseconds:123. d in
assert_float_exact (Date.getUTCSeconds result) 45.;
assert_float_exact (Date.getUTCMilliseconds result) 123.
let set_utc_seconds_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCSeconds ~seconds:nan d in
assert_nan result
(* ===================================================================
setUTCMinutes tests
=================================================================== *)
let set_utc_minutes_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. () in
let result = Date.setUTCMinutes ~minutes:45. d in
assert_float_exact (Date.getUTCMinutes result) 45.
let set_utc_minutes_overflow () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. () in
let result = Date.setUTCMinutes ~minutes:60. d in
assert_float_exact (Date.getUTCMinutes result) 0.;
assert_float_exact (Date.getUTCHours result) 13.
let set_utc_minutes_with_seconds () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. ~seconds:0. () in
let result = Date.setUTCMinutes ~minutes:30. ~seconds:45. d in
assert_float_exact (Date.getUTCMinutes result) 30.;
assert_float_exact (Date.getUTCSeconds result) 45.
let set_utc_minutes_with_seconds_ms () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. ~seconds:0. () in
let result = Date.setUTCMinutes ~minutes:30. ~seconds:45. ~milliseconds:123. d in
assert_float_exact (Date.getUTCMinutes result) 30.;
assert_float_exact (Date.getUTCSeconds result) 45.;
assert_float_exact (Date.getUTCMilliseconds result) 123.
let set_utc_minutes_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCMinutes ~minutes:nan d in
assert_nan result
(* ===================================================================
setUTCHours tests
=================================================================== *)
let set_utc_hours_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. () in
let result = Date.setUTCHours ~hours:18. d in
assert_float_exact (Date.getUTCHours result) 18.
let set_utc_hours_overflow () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. () in
let result = Date.setUTCHours ~hours:24. d in
assert_float_exact (Date.getUTCHours result) 0.;
assert_float_exact (Date.getUTCDate result) 16.
let set_utc_hours_with_minutes () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. () in
let result = Date.setUTCHours ~hours:18. ~minutes:30. d in
assert_float_exact (Date.getUTCHours result) 18.;
assert_float_exact (Date.getUTCMinutes result) 30.
let set_utc_hours_with_minutes_seconds () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. ~seconds:0. () in
let result = Date.setUTCHours ~hours:18. ~minutes:30. ~seconds:45. d in
assert_float_exact (Date.getUTCHours result) 18.;
assert_float_exact (Date.getUTCMinutes result) 30.;
assert_float_exact (Date.getUTCSeconds result) 45.
let set_utc_hours_all () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. ~seconds:0. () in
let result = Date.setUTCHours ~hours:18. ~minutes:30. ~seconds:45. ~milliseconds:123. d in
assert_float_exact (Date.getUTCHours result) 18.;
assert_float_exact (Date.getUTCMinutes result) 30.;
assert_float_exact (Date.getUTCSeconds result) 45.;
assert_float_exact (Date.getUTCMilliseconds result) 123.
let set_utc_hours_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCHours ~hours:nan d in
assert_nan result
(* ===================================================================
setUTCDate tests
=================================================================== *)
let set_utc_date_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:1. () in
let result = Date.setUTCDate ~date:15. d in
assert_float_exact (Date.getUTCDate result) 15.
let set_utc_date_overflow () =
(* June has 30 days, setting to 31 should roll to July 1 *)
let d = Date.utc ~year:2020. ~month:5. ~date:1. () in
let result = Date.setUTCDate ~date:31. d in
assert_float_exact (Date.getUTCDate result) 1.;
assert_float_exact (Date.getUTCMonth result) 6.
let set_utc_date_zero () =
(* Day 0 means last day of previous month *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCDate ~date:0. d in
assert_float_exact (Date.getUTCDate result) 31.;
assert_float_exact (Date.getUTCMonth result) 4.
let set_utc_date_negative () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCDate ~date:(-1.) d in
assert_float_exact (Date.getUTCDate result) 30.;
assert_float_exact (Date.getUTCMonth result) 4.
let set_utc_date_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCDate ~date:nan d in
assert_nan result
(* ===================================================================
setUTCMonth tests
=================================================================== *)
let set_utc_month_basic () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
let result = Date.setUTCMonth ~month:5. d in
assert_float_exact (Date.getUTCMonth result) 5.
let set_utc_month_overflow () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
let result = Date.setUTCMonth ~month:12. d in
assert_float_exact (Date.getUTCMonth result) 0.;
assert_float_exact (Date.getUTCFullYear result) 2021.
let set_utc_month_negative () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCMonth ~month:(-1.) d in
assert_float_exact (Date.getUTCMonth result) 11.;
assert_float_exact (Date.getUTCFullYear result) 2019.
let set_utc_month_with_date () =
let d = Date.utc ~year:2020. ~month:0. ~date:1. () in
let result = Date.setUTCMonth ~month:5. ~date:15. d in
assert_float_exact (Date.getUTCMonth result) 5.;
assert_float_exact (Date.getUTCDate result) 15.
let set_utc_month_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCMonth ~month:nan d in
assert_nan result
(* ===================================================================
setUTCFullYear tests
=================================================================== *)
let set_utc_full_year_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCFullYear ~year:2025. d in
assert_float_exact (Date.getUTCFullYear result) 2025.
let set_utc_full_year_with_month () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
let result = Date.setUTCFullYear ~year:2025. ~month:5. d in
assert_float_exact (Date.getUTCFullYear result) 2025.;
assert_float_exact (Date.getUTCMonth result) 5.
let set_utc_full_year_with_month_date () =
let d = Date.utc ~year:2020. ~month:0. ~date:1. () in
let result = Date.setUTCFullYear ~year:2025. ~month:5. ~date:15. d in
assert_float_exact (Date.getUTCFullYear result) 2025.;
assert_float_exact (Date.getUTCMonth result) 5.;
assert_float_exact (Date.getUTCDate result) 15.
let set_utc_full_year_leap_to_non_leap () =
(* Feb 29 in leap year -> set to non-leap year *)
let d = Date.utc ~year:2020. ~month:1. ~date:29. () in
let result = Date.setUTCFullYear ~year:2021. d in
(* Should roll over to March 1 *)
assert_float_exact (Date.getUTCFullYear result) 2021.;
assert_float_exact (Date.getUTCMonth result) 2.;
assert_float_exact (Date.getUTCDate result) 1.
let set_utc_full_year_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setUTCFullYear ~year:nan d in
assert_nan result
(* ===================================================================
Local time setters tests
Note: These are timezone-dependent, so we test basic functionality
=================================================================== *)
let set_milliseconds_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let result = Date.setMilliseconds ~milliseconds:500. d in
(* Should have changed the ms *)
let utc_ms = Date.getUTCMilliseconds result in
(* Due to timezone, this might wrap around, but should be a valid ms value *)
assert_true "ms in range" (utc_ms >= 0. && utc_ms <= 999.)
let set_seconds_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
let result = Date.setSeconds ~seconds:30. d in
assert_true "result is finite" (Float.is_finite result)
let set_minutes_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. () in
let result = Date.setMinutes ~minutes:45. d in
assert_true "result is finite" (Float.is_finite result)
let set_hours_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. () in
let result = Date.setHours ~hours:18. d in
assert_true "result is finite" (Float.is_finite result)
let set_date_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:1. () in
let result = Date.setDate ~date:15. d in
assert_true "result is finite" (Float.is_finite result)
let set_month_basic () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
let result = Date.setMonth ~month:5. d in
assert_true "result is finite" (Float.is_finite result)
let set_full_year_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setFullYear ~year:2025. d in
assert_true "result is finite" (Float.is_finite result)
(* Local setters with multiple args *)
let set_seconds_with_ms () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:0. () in
let result = Date.setSeconds ~seconds:45. ~milliseconds:123. d in
assert_true "result is finite" (Float.is_finite result)
let set_minutes_with_seconds () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. ~seconds:0. () in
let result = Date.setMinutes ~minutes:30. ~seconds:45. d in
assert_true "result is finite" (Float.is_finite result)
let set_minutes_with_seconds_ms () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:0. ~seconds:0. () in
let result = Date.setMinutes ~minutes:30. ~seconds:45. ~milliseconds:123. d in
assert_true "result is finite" (Float.is_finite result)
let set_hours_with_minutes () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. () in
let result = Date.setHours ~hours:18. ~minutes:30. d in
assert_true "result is finite" (Float.is_finite result)
let set_hours_with_minutes_seconds () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. ~seconds:0. () in
let result = Date.setHours ~hours:18. ~minutes:30. ~seconds:45. d in
assert_true "result is finite" (Float.is_finite result)
let set_hours_all () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:0. ~minutes:0. ~seconds:0. () in
let result = Date.setHours ~hours:18. ~minutes:30. ~seconds:45. ~milliseconds:123. d in
assert_true "result is finite" (Float.is_finite result)
let set_month_with_date () =
let d = Date.utc ~year:2020. ~month:0. ~date:1. () in
let result = Date.setMonth ~month:5. ~date:15. d in
assert_true "result is finite" (Float.is_finite result)
let set_full_year_with_month () =
let d = Date.utc ~year:2020. ~month:0. ~date:15. () in
let result = Date.setFullYear ~year:2025. ~month:5. d in
assert_true "result is finite" (Float.is_finite result)
let set_full_year_with_month_date () =
let d = Date.utc ~year:2020. ~month:0. ~date:1. () in
let result = Date.setFullYear ~year:2025. ~month:5. ~date:15. d in
assert_true "result is finite" (Float.is_finite result)
(* NaN tests for local setters *)
let set_milliseconds_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setMilliseconds ~milliseconds:nan d in
assert_nan result
let set_seconds_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setSeconds ~seconds:nan d in
assert_nan result
let set_minutes_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setMinutes ~minutes:nan d in
assert_nan result
let set_hours_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setHours ~hours:nan d in
assert_nan result
let set_date_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setDate ~date:nan d in
assert_nan result
let set_month_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setMonth ~month:nan d in
assert_nan result
let set_full_year_nan () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let result = Date.setFullYear ~year:nan d in
assert_nan result
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* setTime *)
test "setTime basic" set_time_basic;
test "setTime epoch" set_time_epoch;
test "setTime negative" set_time_negative;
test "setTime NaN value" set_time_nan_value;
test "setTime on NaN date" set_time_on_nan_date;
(* setUTCMilliseconds *)
test "setUTCMilliseconds basic" set_utc_ms_basic;
test "setUTCMilliseconds overflow" set_utc_ms_overflow;
test "setUTCMilliseconds negative" set_utc_ms_negative;
test "setUTCMilliseconds NaN" set_utc_ms_nan;
(* setUTCSeconds *)
test "setUTCSeconds basic" set_utc_seconds_basic;
test "setUTCSeconds overflow" set_utc_seconds_overflow;
test "setUTCSeconds with ms" set_utc_seconds_with_ms;
test "setUTCSeconds NaN" set_utc_seconds_nan;
(* setUTCMinutes *)
test "setUTCMinutes basic" set_utc_minutes_basic;
test "setUTCMinutes overflow" set_utc_minutes_overflow;
test "setUTCMinutes with seconds" set_utc_minutes_with_seconds;
test "setUTCMinutes with seconds and ms" set_utc_minutes_with_seconds_ms;
test "setUTCMinutes NaN" set_utc_minutes_nan;
(* setUTCHours *)
test "setUTCHours basic" set_utc_hours_basic;
test "setUTCHours overflow" set_utc_hours_overflow;
test "setUTCHours with minutes" set_utc_hours_with_minutes;
test "setUTCHours with minutes and seconds" set_utc_hours_with_minutes_seconds;
test "setUTCHours all components" set_utc_hours_all;
test "setUTCHours NaN" set_utc_hours_nan;
(* setUTCDate *)
test "setUTCDate basic" set_utc_date_basic;
test "setUTCDate overflow" set_utc_date_overflow;
test "setUTCDate zero" set_utc_date_zero;
test "setUTCDate negative" set_utc_date_negative;
test "setUTCDate NaN" set_utc_date_nan;
(* setUTCMonth *)
test "setUTCMonth basic" set_utc_month_basic;
test "setUTCMonth overflow" set_utc_month_overflow;
test "setUTCMonth negative" set_utc_month_negative;
test "setUTCMonth with date" set_utc_month_with_date;
test "setUTCMonth NaN" set_utc_month_nan;
(* setUTCFullYear *)
test "setUTCFullYear basic" set_utc_full_year_basic;
test "setUTCFullYear with month" set_utc_full_year_with_month;
test "setUTCFullYear with month and date" set_utc_full_year_with_month_date;
test "setUTCFullYear leap to non-leap" set_utc_full_year_leap_to_non_leap;
test "setUTCFullYear NaN" set_utc_full_year_nan;
(* Local setters *)
test "setMilliseconds basic" set_milliseconds_basic;
test "setSeconds basic" set_seconds_basic;
test "setMinutes basic" set_minutes_basic;
test "setHours basic" set_hours_basic;
test "setDate basic" set_date_basic;
test "setMonth basic" set_month_basic;
test "setFullYear basic" set_full_year_basic;
(* Local setters with multiple args *)
test "setSeconds with ms" set_seconds_with_ms;
test "setMinutes with seconds" set_minutes_with_seconds;
test "setMinutes with seconds and ms" set_minutes_with_seconds_ms;
test "setHours with minutes" set_hours_with_minutes;
test "setHours with minutes and seconds" set_hours_with_minutes_seconds;
test "setHours all components" set_hours_all;
test "setMonth with date" set_month_with_date;
test "setFullYear with month" set_full_year_with_month;
test "setFullYear with month and date" set_full_year_with_month_date;
(* NaN tests for local setters *)
test "setMilliseconds NaN" set_milliseconds_nan;
test "setSeconds NaN" set_seconds_nan;
test "setMinutes NaN" set_minutes_nan;
test "setHours NaN" set_hours_nan;
test "setDate NaN" set_date_nan;
test "setMonth NaN" set_month_nan;
test "setFullYear NaN" set_full_year_nan;
]
================================================
FILE: packages/Js/test/date_tests/to_iso_string.ml
================================================
(** TC39 Test262: Date.prototype.toISOString tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/toISOString
ECMA-262 Section: Date.prototype.toISOString()
Returns a string in ISO 8601 format: YYYY-MM-DDTHH:mm:ss.sssZ Throws RangeError for invalid dates (NaN). *)
open Helpers
(* ===================================================================
Basic formatting
=================================================================== *)
let to_iso_string_known_timestamp () =
(* From QuickJS tests: new Date(1506098258091).toISOString() *)
let d = Date.fromFloat 1506098258091. in
assert_string (Date.toISOString d) "2017-09-22T16:37:38.091Z"
let to_iso_string_epoch () =
let d = Date.fromFloat 0. in
assert_string (Date.toISOString d) "1970-01-01T00:00:00.000Z"
let to_iso_string_y2k () =
let d = Date.fromFloat 946684800000. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.000Z"
let to_iso_string_with_millis () =
let d = Date.fromFloat 1577840461123. in
assert_string (Date.toISOString d) "2020-01-01T01:01:01.123Z"
(* ===================================================================
Millisecond formatting (always 3 digits)
=================================================================== *)
let to_iso_string_millis_zero () =
(* 0ms should be formatted as .000 *)
let d = Date.fromFloat 946684800000. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.000Z"
let to_iso_string_millis_001 () =
let d = Date.fromFloat 946684800001. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.001Z"
let to_iso_string_millis_010 () =
let d = Date.fromFloat 946684800010. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.010Z"
let to_iso_string_millis_100 () =
let d = Date.fromFloat 946684800100. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.100Z"
let to_iso_string_millis_999 () =
let d = Date.fromFloat 946684800999. in
assert_string (Date.toISOString d) "2000-01-01T00:00:00.999Z"
(* ===================================================================
Zero-padding for date/time components
=================================================================== *)
let to_iso_string_single_digit_month () =
(* January = 01 *)
let d = Date.fromFloat 946684800000. in
(* 2000-01-01 *)
let iso = Date.toISOString d in
assert_bool (String.sub iso 5 2 = "01") true
let to_iso_string_single_digit_day () =
(* Day 1 = 01 *)
let d = Date.fromFloat 946684800000. in
(* 2000-01-01 *)
let iso = Date.toISOString d in
assert_bool (String.sub iso 8 2 = "01") true
let to_iso_string_single_digit_hour () =
(* Hour 1 = 01 *)
let d = Date.fromFloat 946688400000. in
(* 2000-01-01T01:00:00Z *)
let iso = Date.toISOString d in
assert_bool (String.sub iso 11 2 = "01") true
(* ===================================================================
Before epoch (negative timestamps)
=================================================================== *)
let to_iso_string_before_epoch () =
(* Dec 31, 1969 23:59:59.999 UTC *)
let d = Date.fromFloat (-1.) in
assert_string (Date.toISOString d) "1969-12-31T23:59:59.999Z"
let to_iso_string_1969_jan () =
(* Jan 1, 1969 00:00:00.000 UTC *)
let d = Date.fromFloat (-31536000000.) in
assert_string (Date.toISOString d) "1969-01-01T00:00:00.000Z"
(* ===================================================================
Expanded years (years outside 0000-9999)
=================================================================== *)
let to_iso_string_year_0 () =
(* Year 0 (1 BCE) - represented as +000000 or 0000 depending on implementation *)
let d = Date.fromFloat (-62167219200000.) in
(* Approximately year 0 *)
let iso = Date.toISOString d in
(* Should have valid format *)
assert_bool (String.length iso > 0) true
let to_iso_string_negative_year () =
(* Year -1 (2 BCE) - formatted with minus sign *)
let d = Date.fromFloat (-62198755200000.) in
(* Approximately year -1 *)
let iso = Date.toISOString d in
assert_bool (String.length iso > 0) true
let to_iso_string_year_10000 () =
(* Year 10000 - formatted with + prefix *)
let d = Date.fromFloat 253402300800000. in
(* Year 10000 *)
let iso = Date.toISOString d in
assert_bool (String.length iso > 0) true
(* ===================================================================
Parse/format roundtrip
=================================================================== *)
let to_iso_string_roundtrip () =
(* Format then parse should give same timestamp *)
let original_ms = 1506098258091. in
let d = Date.fromFloat original_ms in
let iso = Date.toISOString d in
let parsed_ms = Date.parseAsFloat iso in
assert_float_exact parsed_ms original_ms
let to_iso_string_roundtrip_epoch () =
let original_ms = 0. in
let d = Date.fromFloat original_ms in
let iso = Date.toISOString d in
let parsed_ms = Date.parseAsFloat iso in
assert_float_exact parsed_ms original_ms
let to_iso_string_roundtrip_before_epoch () =
let original_ms = -86400000. in
(* 1 day before epoch *)
let d = Date.fromFloat original_ms in
let iso = Date.toISOString d in
let parsed_ms = Date.parseAsFloat iso in
assert_float_exact parsed_ms original_ms
(* ===================================================================
QuickJS specific test cases
=================================================================== *)
let to_iso_string_qjs_test_1 () =
(* From QuickJS: new Date("2020-01-01T01:01:01.123Z").toISOString() *)
let d = Date.fromFloat (Date.parseAsFloat "2020-01-01T01:01:01.123Z") in
assert_string (Date.toISOString d) "2020-01-01T01:01:01.123Z"
let to_iso_string_qjs_test_2 () =
(* new Date("2020-01-01T01:01:01.1Z").toISOString() -> "...01.100Z" *)
let d = Date.fromFloat (Date.parseAsFloat "2020-01-01T01:01:01.1Z") in
assert_string (Date.toISOString d) "2020-01-01T01:01:01.100Z"
let to_iso_string_qjs_test_3 () =
(* new Date("2020-01-01T01:01:01.12Z").toISOString() -> "...01.120Z" *)
let d = Date.fromFloat (Date.parseAsFloat "2020-01-01T01:01:01.12Z") in
assert_string (Date.toISOString d) "2020-01-01T01:01:01.120Z"
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* Basic formatting *)
test "known timestamp" to_iso_string_known_timestamp;
test "epoch" to_iso_string_epoch;
test "Y2K" to_iso_string_y2k;
test "with milliseconds" to_iso_string_with_millis;
(* Millisecond formatting *)
test "millis: 000" to_iso_string_millis_zero;
test "millis: 001" to_iso_string_millis_001;
test "millis: 010" to_iso_string_millis_010;
test "millis: 100" to_iso_string_millis_100;
test "millis: 999" to_iso_string_millis_999;
(* Zero-padding *)
test "single digit month padded" to_iso_string_single_digit_month;
test "single digit day padded" to_iso_string_single_digit_day;
test "single digit hour padded" to_iso_string_single_digit_hour;
(* Before epoch *)
test "before epoch -1ms" to_iso_string_before_epoch;
test "1969 Jan" to_iso_string_1969_jan;
(* Expanded years *)
test "year 0" to_iso_string_year_0;
test "negative year" to_iso_string_negative_year;
test "year 10000" to_iso_string_year_10000;
(* Roundtrip *)
test "roundtrip known timestamp" to_iso_string_roundtrip;
test "roundtrip epoch" to_iso_string_roundtrip_epoch;
test "roundtrip before epoch" to_iso_string_roundtrip_before_epoch;
(* QuickJS tests *)
test "QJS: 2020-01-01T01:01:01.123Z" to_iso_string_qjs_test_1;
test "QJS: .1Z -> .100Z" to_iso_string_qjs_test_2;
test "QJS: .12Z -> .120Z" to_iso_string_qjs_test_3;
]
================================================
FILE: packages/Js/test/date_tests/to_string.ml
================================================
(** TC39 Test262: Date toString method tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/toString
https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/toDateString
https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/toTimeString
https://github.com/tc39/test262/tree/main/test/built-ins/Date/prototype/toUTCString
Tests for toString, toDateString, toTimeString, toUTCString *)
open Helpers
module Date = Js.Date
(* ===================================================================
toUTCString tests
Format: "Tue, 02 Dec 2025 09:30:00 GMT"
=================================================================== *)
let to_utc_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toUTCString d in
(* Should contain "Mon" (June 15 2020 was Monday), "15", "Jun", "2020", "12:30:45", "GMT" *)
assert_true "contains day name" (String.length s > 0);
assert_true "contains GMT" (String.sub s (String.length s - 3) 3 = "GMT")
let to_utc_string_epoch () =
let s = Date.toUTCString 0. in
(* Thu, 01 Jan 1970 00:00:00 GMT *)
assert_true "contains Thu" (String.sub s 0 3 = "Thu");
assert_true "contains 1970" (String.length s > 10)
let to_utc_string_format () =
let d = Date.utc ~year:2025. ~month:11. ~date:2. ~hours:9. ~minutes:30. ~seconds:0. () in
let s = Date.toUTCString d in
(* Verify format: "Day, DD Mon YYYY HH:MM:SS GMT" *)
assert_true "correct length roughly" (String.length s >= 25)
let to_utc_string_nan () =
let s = Date.toUTCString nan in
assert_string_equal s "Invalid Date"
let to_utc_string_negative_year () =
(* Test with a date before year 0 *)
let d = Date.utc ~year:(-1.) ~month:0. ~date:1. () in
let s = Date.toUTCString d in
assert_true "contains something" (String.length s > 0)
let to_utc_string_months () =
(* Test all month abbreviations *)
let months = [ "Jan"; "Feb"; "Mar"; "Apr"; "May"; "Jun"; "Jul"; "Aug"; "Sep"; "Oct"; "Nov"; "Dec" ] in
List.iteri
(fun i expected_month ->
let d = Date.utc ~year:2020. ~month:(Float.of_int i) ~date:15. () in
let s = Date.toUTCString d in
assert_true
(Printf.sprintf "month %d contains %s" i expected_month)
(String.length s > 0
&&
try
let _ = Str.search_forward (Str.regexp expected_month) s 0 in
true
with Not_found -> false))
months
let to_utc_string_day_names () =
(* Test that different days of week produce correct names *)
let d_sunday = Date.utc ~year:2021. ~month:0. ~date:3. () in
(* Sunday *)
let d_monday = Date.utc ~year:2021. ~month:0. ~date:4. () in
(* Monday *)
let s_sun = Date.toUTCString d_sunday in
let s_mon = Date.toUTCString d_monday in
assert_true "Sunday starts with Sun" (String.sub s_sun 0 3 = "Sun");
assert_true "Monday starts with Mon" (String.sub s_mon 0 3 = "Mon")
(* ===================================================================
toDateString tests
Format: "Mon Jun 15 2020"
=================================================================== *)
let to_date_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toDateString d in
assert_true "non-empty string" (String.length s > 0)
let to_date_string_epoch () =
(* Note: This is timezone-dependent for local time *)
let s = Date.toDateString 0. in
assert_true "non-empty string" (String.length s > 0)
let to_date_string_nan () =
let s = Date.toDateString nan in
assert_string_equal s "Invalid Date"
let to_date_string_no_time () =
(* toDateString should not contain time information like ":" *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toDateString d in
(* Should not have HH:MM:SS format *)
let has_time_separator =
try
let _ = Str.search_forward (Str.regexp "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]") s 0 in
true
with Not_found -> false
in
assert_true "should not contain time" (not has_time_separator)
(* ===================================================================
toTimeString tests
Format: "12:30:45 GMT+0000 (Coordinated Universal Time)"
=================================================================== *)
let to_time_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toTimeString d in
assert_true "non-empty string" (String.length s > 0)
let to_time_string_epoch () =
let s = Date.toTimeString 0. in
assert_true "non-empty string" (String.length s > 0)
let to_time_string_nan () =
let s = Date.toTimeString nan in
assert_string_equal s "Invalid Date"
let to_time_string_contains_time () =
(* toTimeString should contain time in HH:MM:SS format *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toTimeString d in
let has_time_format =
try
let _ = Str.search_forward (Str.regexp "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]") s 0 in
true
with Not_found -> false
in
assert_true "should contain time format" has_time_format
(* ===================================================================
toString tests
Format: "Mon Jun 15 2020 12:30:45 GMT+0000 (Coordinated Universal Time)"
=================================================================== *)
let to_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toString d in
assert_true "non-empty string" (String.length s > 0)
let to_string_epoch () =
let s = Date.toString 0. in
assert_true "non-empty string" (String.length s > 0)
let to_string_nan () =
let s = Date.toString nan in
assert_string_equal s "Invalid Date"
let to_string_contains_date_and_time () =
(* toString should contain both date and time *)
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toString d in
(* Should have time format *)
let has_time_format =
try
let _ = Str.search_forward (Str.regexp "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]") s 0 in
true
with Not_found -> false
in
assert_true "should contain time" has_time_format
let to_string_contains_year () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let s = Date.toString d in
let has_year =
try
let _ = Str.search_forward (Str.regexp "2020") s 0 in
true
with Not_found -> false
in
assert_true "should contain year" has_year
(* ===================================================================
toJSON tests
=================================================================== *)
let to_json_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 123. in
assert_option Alcotest.string "toJSON should return Some" (Date.toJSON d) (Some "2020-06-15T12:30:45.123Z")
let to_json_nan () = assert_option Alcotest.string "toJSON should return None for NaN" (Date.toJSON nan) None
let to_json_unsafe_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () +. 123. in
let s = Date.toJSONUnsafe d in
assert_string_equal s "2020-06-15T12:30:45.123Z"
(* ===================================================================
toLocaleString tests (simplified)
=================================================================== *)
let to_locale_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toLocaleString d in
assert_true "non-empty string" (String.length s > 0)
let to_locale_string_nan () =
let s = Date.toLocaleString nan in
assert_string_equal s "Invalid Date"
let to_locale_date_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. () in
let s = Date.toLocaleDateString d in
assert_true "non-empty string" (String.length s > 0)
let to_locale_date_string_nan () =
let s = Date.toLocaleDateString nan in
assert_string_equal s "Invalid Date"
let to_locale_time_string_basic () =
let d = Date.utc ~year:2020. ~month:5. ~date:15. ~hours:12. ~minutes:30. ~seconds:45. () in
let s = Date.toLocaleTimeString d in
assert_true "non-empty string" (String.length s > 0)
let to_locale_time_string_nan () =
let s = Date.toLocaleTimeString nan in
assert_string_equal s "Invalid Date"
(* ===================================================================
Edge cases
=================================================================== *)
let to_string_large_year () =
let d = Date.utc ~year:275760. ~month:8. ~date:13. () in
let s = Date.toString d in
assert_true "non-empty string" (String.length s > 0)
let to_string_negative_year () =
let d = Date.utc ~year:(-100.) ~month:0. ~date:1. () in
let s = Date.toString d in
assert_true "non-empty string" (String.length s > 0)
let to_utc_string_y2k () =
let d = Date.utc ~year:2000. ~month:0. ~date:1. ~hours:0. ~minutes:0. ~seconds:0. () in
let s = Date.toUTCString d in
assert_true "contains 2000" (String.length s > 0)
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* toUTCString *)
test "toUTCString basic" to_utc_string_basic;
test "toUTCString epoch" to_utc_string_epoch;
test "toUTCString format" to_utc_string_format;
test "toUTCString NaN" to_utc_string_nan;
test "toUTCString negative year" to_utc_string_negative_year;
test "toUTCString all months" to_utc_string_months;
test "toUTCString day names" to_utc_string_day_names;
(* toDateString *)
test "toDateString basic" to_date_string_basic;
test "toDateString epoch" to_date_string_epoch;
test "toDateString NaN" to_date_string_nan;
test "toDateString no time" to_date_string_no_time;
(* toTimeString *)
test "toTimeString basic" to_time_string_basic;
test "toTimeString epoch" to_time_string_epoch;
test "toTimeString NaN" to_time_string_nan;
test "toTimeString contains time" to_time_string_contains_time;
(* toString *)
test "toString basic" to_string_basic;
test "toString epoch" to_string_epoch;
test "toString NaN" to_string_nan;
test "toString contains date and time" to_string_contains_date_and_time;
test "toString contains year" to_string_contains_year;
(* toJSON *)
test "toJSON basic" to_json_basic;
test "toJSON NaN" to_json_nan;
test "toJSONUnsafe basic" to_json_unsafe_basic;
(* toLocaleString *)
test "toLocaleString basic" to_locale_string_basic;
test "toLocaleString NaN" to_locale_string_nan;
test "toLocaleDateString basic" to_locale_date_string_basic;
test "toLocaleDateString NaN" to_locale_date_string_nan;
test "toLocaleTimeString basic" to_locale_time_string_basic;
test "toLocaleTimeString NaN" to_locale_time_string_nan;
(* Edge cases *)
test "toString large year" to_string_large_year;
test "toString negative year" to_string_negative_year;
test "toUTCString Y2K" to_utc_string_y2k;
]
================================================
FILE: packages/Js/test/date_tests/utc.ml
================================================
(** TC39 Test262: Date.UTC tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Date/UTC
ECMA-262 Section: Date.UTC(year, month[, date[, hours[, minutes[, seconds[, ms]]]]])
Date.UTC returns the time value (epoch milliseconds) for the given UTC date components. *)
open Helpers
(* ===================================================================
Basic Date.UTC tests from QuickJS test suite
=================================================================== *)
let utc_year_only () =
(* Date.UTC(2017) = Jan 1, 2017 00:00:00.000 UTC *)
assert_float_exact (Date.utc ~year:2017. ~month:0. ()) 1483228800000.
let utc_year_month () =
(* Date.UTC(2017, 9) = Oct 1, 2017 00:00:00.000 UTC *)
assert_float_exact (Date.utc ~year:2017. ~month:9. ()) 1506816000000.
let utc_year_month_day () =
(* Date.UTC(2017, 9, 22) = Oct 22, 2017 00:00:00.000 UTC *)
assert_float_exact (Date.utc ~year:2017. ~month:9. ~date:22. ()) 1508630400000.
let utc_with_hours () =
(* Date.UTC(2017, 9, 22, 18) *)
assert_float_exact (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ()) 1508695200000.
let utc_with_minutes () =
(* Date.UTC(2017, 9, 22, 18, 10) *)
assert_float_exact (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ()) 1508695800000.
let utc_with_seconds () =
(* Date.UTC(2017, 9, 22, 18, 10, 11) *)
assert_float_exact (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ~seconds:11. ()) 1508695811000.
let utc_with_ms () =
(* Date.UTC(2017, 9, 22, 18, 10, 11, 91) *)
assert_float_exact
(Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ~seconds:11. () +. 91.)
1508695811091.
(* ===================================================================
NaN propagation
=================================================================== *)
let utc_nan_year () = assert_nan (Date.utc ~year:nan ~month:0. ())
let utc_nan_month () = assert_nan (Date.utc ~year:2017. ~month:nan ())
let utc_nan_day () = assert_nan (Date.utc ~year:2017. ~month:9. ~date:nan ())
let utc_nan_hours () = assert_nan (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:nan ())
let utc_nan_minutes () = assert_nan (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:nan ())
let utc_nan_seconds () = assert_nan (Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:10. ~seconds:nan ())
(* ===================================================================
Epoch and boundary values
=================================================================== *)
let utc_epoch () =
(* Unix epoch: Jan 1, 1970 00:00:00.000 UTC *)
assert_float_exact (Date.utc ~year:1970. ~month:0. ~date:1. ()) 0.
let utc_before_epoch () =
(* Dec 31, 1969 23:59:59.000 UTC = -1000ms *)
assert_float_exact (Date.utc ~year:1969. ~month:11. ~date:31. ~hours:23. ~minutes:59. ~seconds:59. ()) (-1000.)
let utc_y2k () =
(* Jan 1, 2000 00:00:00.000 UTC *)
assert_float_exact (Date.utc ~year:2000. ~month:0. ~date:1. ()) 946684800000.
(* ===================================================================
Year handling: 0-99 maps to 1900-1999
=================================================================== *)
let utc_year_0 () =
(* Year 0 maps to 1900 *)
let result = Date.utc ~year:0. ~month:0. ~date:1. () in
(* Jan 1, 1900 00:00:00 UTC *)
assert_float_exact result (-2208988800000.)
let utc_year_99 () =
(* Year 99 maps to 1999 *)
let result = Date.utc ~year:99. ~month:0. ~date:1. () in
(* Jan 1, 1999 00:00:00 UTC *)
assert_float_exact result 915148800000.
let utc_year_100 () =
(* Year 100 stays as year 100 (not mapped) *)
let result = Date.utc ~year:100. ~month:0. ~date:1. () in
assert_not_nan result
(* ===================================================================
Month overflow/underflow
=================================================================== *)
let utc_month_overflow () =
(* Month 12 = January of next year *)
let m12 = Date.utc ~year:2017. ~month:12. ~date:1. () in
let jan_next = Date.utc ~year:2018. ~month:0. ~date:1. () in
assert_float_exact m12 jan_next
let utc_month_underflow () =
(* Month -1 = December of previous year *)
let m_neg1 = Date.utc ~year:2017. ~month:(-1.) ~date:1. () in
let dec_prev = Date.utc ~year:2016. ~month:11. ~date:1. () in
assert_float_exact m_neg1 dec_prev
(* ===================================================================
Day overflow/underflow
=================================================================== *)
let utc_day_overflow () =
(* Day 32 in January = Feb 1 *)
let d32 = Date.utc ~year:2017. ~month:0. ~date:32. () in
let feb1 = Date.utc ~year:2017. ~month:1. ~date:1. () in
assert_float_exact d32 feb1
let utc_day_zero () =
(* Day 0 = last day of previous month *)
let d0 = Date.utc ~year:2017. ~month:1. ~date:0. () in
let jan31 = Date.utc ~year:2017. ~month:0. ~date:31. () in
assert_float_exact d0 jan31
let utc_day_negative () =
(* Day -1 = second-to-last day of previous month *)
let d_neg1 = Date.utc ~year:2017. ~month:1. ~date:(-1.) () in
let jan30 = Date.utc ~year:2017. ~month:0. ~date:30. () in
assert_float_exact d_neg1 jan30
(* ===================================================================
Leap year handling
=================================================================== *)
let utc_leap_year_feb_29 () =
(* Feb 29, 2020 is valid (2020 is a leap year) *)
let result = Date.utc ~year:2020. ~month:1. ~date:29. () in
assert_not_nan result
let utc_non_leap_year_feb_29 () =
(* Feb 29, 2019 overflows to Mar 1, 2019 (not a leap year) *)
let feb29_2019 = Date.utc ~year:2019. ~month:1. ~date:29. () in
let mar1_2019 = Date.utc ~year:2019. ~month:2. ~date:1. () in
assert_float_exact feb29_2019 mar1_2019
let utc_leap_year_2000 () =
(* 2000 is a leap year (divisible by 400) *)
let result = Date.utc ~year:2000. ~month:1. ~date:29. () in
assert_not_nan result
let utc_non_leap_year_1900 () =
(* 1900 is NOT a leap year (divisible by 100 but not 400) *)
let feb29_1900 = Date.utc ~year:1900. ~month:1. ~date:29. () in
let mar1_1900 = Date.utc ~year:1900. ~month:2. ~date:1. () in
assert_float_exact feb29_1900 mar1_1900
(* ===================================================================
Large value handling
=================================================================== *)
let utc_large_day_offset () =
(* From QuickJS: Date.UTC(2017, 9, 22 - 1e10, 18 + 24e10) *)
let result = Date.utc ~year:2017. ~month:9. ~date:(22. -. 1e10) ~hours:(18. +. 24e10) () in
assert_float_exact result 1508695200000.
let utc_large_minute_offset () =
(* Date.UTC(2017, 9, 22, 18 - 1e10, 10 + 60e10) *)
let result = Date.utc ~year:2017. ~month:9. ~date:22. ~hours:(18. -. 1e10) ~minutes:(10. +. 60e10) () in
assert_float_exact result 1508695800000.
let utc_large_second_offset () =
(* Date.UTC(2017, 9, 22, 18, 10 - 1e10, 11 + 60e10) *)
let result = Date.utc ~year:2017. ~month:9. ~date:22. ~hours:18. ~minutes:(10. -. 1e10) ~seconds:(11. +. 60e10) () in
assert_float_exact result 1508695811000.
(* ===================================================================
Test list
=================================================================== *)
let tests =
[
(* Basic UTC construction *)
test "UTC year only" utc_year_only;
test "UTC year and month" utc_year_month;
test "UTC year, month, day" utc_year_month_day;
test "UTC with hours" utc_with_hours;
test "UTC with minutes" utc_with_minutes;
test "UTC with seconds" utc_with_seconds;
test "UTC with milliseconds" utc_with_ms;
(* NaN propagation *)
test "UTC NaN year" utc_nan_year;
test "UTC NaN month" utc_nan_month;
test "UTC NaN day" utc_nan_day;
test "UTC NaN hours" utc_nan_hours;
test "UTC NaN minutes" utc_nan_minutes;
test "UTC NaN seconds" utc_nan_seconds;
(* Epoch and boundaries *)
test "UTC Unix epoch" utc_epoch;
test "UTC before epoch" utc_before_epoch;
test "UTC Y2K" utc_y2k;
(* Year mapping 0-99 -> 1900-1999 *)
test "UTC year 0 -> 1900" utc_year_0;
test "UTC year 99 -> 1999" utc_year_99;
test "UTC year 100 stays 100" utc_year_100;
(* Month overflow/underflow *)
test "UTC month 12 = January next year" utc_month_overflow;
test "UTC month -1 = December prev year" utc_month_underflow;
(* Day overflow/underflow *)
test "UTC day 32 Jan = Feb 1" utc_day_overflow;
test "UTC day 0 = last day prev month" utc_day_zero;
test "UTC day -1 = second-to-last prev" utc_day_negative;
(* Leap years *)
test "UTC leap year Feb 29 valid" utc_leap_year_feb_29;
test "UTC non-leap Feb 29 = Mar 1" utc_non_leap_year_feb_29;
test "UTC 2000 is leap year" utc_leap_year_2000;
test "UTC 1900 is NOT leap year" utc_non_leap_year_1900;
(* Large value handling *)
test "UTC large day offset" utc_large_day_offset;
test "UTC large minute offset" utc_large_minute_offset;
test "UTC large second offset" utc_large_second_offset;
]
================================================
FILE: packages/Js/test/dune
================================================
(include_subdirs qualified)
(test
(name test)
(libraries alcotest alcotest-lwt lwt js fmt str)
(preprocess
(pps lwt_ppx melange_native_ppx)))
================================================
FILE: packages/Js/test/helpers.ml
================================================
(** Shared test helpers for test262 tests *)
module BigInt = Js.Bigint
module Date = Js.Date
module Number = struct
include Js.Float
let parseFloat = Js.Global.parseFloat
let parseInt = Js.Global.parseInt
end
let test title fn = Alcotest_lwt.test_case_sync title `Quick fn
let test_async title fn = Alcotest_lwt.test_case title `Quick fn
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let assert_string_equal left right = Alcotest.check Alcotest.string "should be equal" right left
let assert_int left right = Alcotest.check Alcotest.int "should be equal" right left
let assert_float left right = Alcotest.check (Alcotest.float 2.) "should be equal" right left
let assert_float_exact left right = Alcotest.check (Alcotest.float 0.) "should be equal" right left
(* assert_bool for comparing boolean values - compatible with existing tests *)
let assert_bool left right = Alcotest.check Alcotest.bool "should be equal" right left
(* assert_true for checking a condition with a message *)
let assert_true msg cond = if not cond then Alcotest.fail msg
(* assert_option for comparing option values with proper Alcotest output *)
let assert_option ty msg left right = Alcotest.check (Alcotest.option ty) msg right left
let assert_raises fn exn =
match fn () with
| exception exn -> assert_string (Printexc.to_string exn) (Printexc.to_string exn)
| _ -> Alcotest.failf "Expected exception %s" (Printexc.to_string exn)
(* BigInt helpers *)
let bigint_testable = Alcotest.testable (Fmt.of_to_string BigInt.toString) (fun a b -> BigInt.compare a b = 0)
let assert_bigint left right = Alcotest.check bigint_testable "should be equal" right left
let assert_bigint_equal left right = Alcotest.check bigint_testable "should be equal" right left
let assert_bigint_string left expected_str =
let expected = BigInt.of_string expected_str in
Alcotest.check bigint_testable "should be equal" expected left
let assert_bigint_raises fn = match fn () with exception _ -> () | _ -> Alcotest.fail "Expected exception"
(* Float/Number helpers *)
let nan = Float.nan
let infinity = Float.infinity
let neg_infinity = Float.neg_infinity
let max_value = Float.max_float
let min_value = Float.min_float
let max_safe_integer = 9007199254740991.
let min_safe_integer = -9007199254740991.
let epsilon = Float.epsilon
let assert_not_nan value = if Float.is_nan value then Alcotest.fail "Expected non-NaN value"
let assert_nan value = if not (Float.is_nan value) then Alcotest.fail "Expected NaN value"
let assert_infinity value = if not (value = infinity) then Alcotest.failf "Expected Infinity, got %f" value
let assert_neg_infinity value = if not (value = neg_infinity) then Alcotest.failf "Expected -Infinity, got %f" value
let assert_negative_zero value = if not (1. /. value = neg_infinity) then Alcotest.fail "Expected negative zero"
================================================
FILE: packages/Js/test/number_tests/is_finite.ml
================================================
(** TC39 Test262: Number.isFinite tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/isFinite
ECMA-262 Section: Number.isFinite(number)
Note: Number.isFinite is different from global isFinite:
- Number.isFinite only returns true for finite number values
- It does NOT perform type coercion
- Returns false for NaN and Infinity *)
open Helpers
(* ===================================================================
Infinity values should return false
=================================================================== *)
let positive_infinity () = assert_bool (Number.isFinite infinity) false
let negative_infinity () = assert_bool (Number.isFinite neg_infinity) false
(* ===================================================================
NaN should return false
=================================================================== *)
let nan_value () = assert_bool (Number.isFinite nan) false
let nan_from_operations () =
assert_bool (Number.isFinite (0.0 /. 0.0)) false;
assert_bool (Number.isFinite (infinity -. infinity)) false
(* ===================================================================
Finite numbers should return true
=================================================================== *)
let zero_values () =
assert_bool (Number.isFinite 0.0) true;
assert_bool (Number.isFinite (-0.0)) true
let positive_integers () =
assert_bool (Number.isFinite 1.0) true;
assert_bool (Number.isFinite 42.0) true;
assert_bool (Number.isFinite 100.0) true
let negative_integers () =
assert_bool (Number.isFinite (-1.0)) true;
assert_bool (Number.isFinite (-42.0)) true;
assert_bool (Number.isFinite (-100.0)) true
let decimals () =
assert_bool (Number.isFinite 0.5) true;
assert_bool (Number.isFinite 3.14159) true;
assert_bool (Number.isFinite (-2.71828)) true
let max_min_values () =
(* MAX_VALUE and MIN_VALUE are finite *)
assert_bool (Number.isFinite max_value) true;
assert_bool (Number.isFinite min_value) true;
assert_bool (Number.isFinite (-.max_value)) true
let safe_integer_bounds () =
(* MAX_SAFE_INTEGER and MIN_SAFE_INTEGER are finite *)
assert_bool (Number.isFinite max_safe_integer) true;
assert_bool (Number.isFinite min_safe_integer) true
let epsilon_value () = assert_bool (Number.isFinite epsilon) true
let very_small_numbers () =
assert_bool (Number.isFinite 1e-300) true;
assert_bool (Number.isFinite 5e-324) true (* MIN_VALUE *)
let very_large_numbers () =
assert_bool (Number.isFinite 1e308) true;
assert_bool (Number.isFinite (-1e308)) true
(* Note: In OCaml, Number.isFinite only takes float values, so we don't need
to test non-number types. Those would be type errors at compile time. *)
let tests =
[
(* Infinity - returns false *)
test "infinity: positive Infinity returns false" positive_infinity;
test "negative_infinity: negative Infinity returns false" negative_infinity;
(* NaN - returns false *)
test "nan: NaN returns false" nan_value;
test "nan_from_operations: NaN from operations returns false" nan_from_operations;
(* Finite numbers - return true *)
test "finite_numbers: zeros" zero_values;
test "finite_numbers: positive integers" positive_integers;
test "finite_numbers: negative integers" negative_integers;
test "finite_numbers: decimals" decimals;
test "finite_numbers: MAX/MIN_VALUE" max_min_values;
test "finite_numbers: safe integer bounds" safe_integer_bounds;
test "finite_numbers: epsilon" epsilon_value;
test "finite_numbers: very small" very_small_numbers;
test "finite_numbers: very large" very_large_numbers;
]
================================================
FILE: packages/Js/test/number_tests/is_integer.ml
================================================
(** TC39 Test262: Number.isInteger tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/isInteger
ECMA-262 Section: Number.isInteger(number)
Note: Number.isInteger returns true if the number is a finite number with no fractional part. *)
open Helpers
(* ===================================================================
Integer values should return true
=================================================================== *)
let zero_values () =
assert_bool (Number.isInteger 0.0) true;
assert_bool (Number.isInteger (-0.0)) true
let positive_integers () =
assert_bool (Number.isInteger 1.0) true;
assert_bool (Number.isInteger 2.0) true;
assert_bool (Number.isInteger 42.0) true;
assert_bool (Number.isInteger 100.0) true;
assert_bool (Number.isInteger 1000000.0) true
let negative_integers () =
assert_bool (Number.isInteger (-1.0)) true;
assert_bool (Number.isInteger (-2.0)) true;
assert_bool (Number.isInteger (-42.0)) true;
assert_bool (Number.isInteger (-100.0)) true
let large_integers () =
assert_bool (Number.isInteger max_safe_integer) true;
assert_bool (Number.isInteger min_safe_integer) true;
assert_bool (Number.isInteger 9007199254740992.0) true (* MAX_SAFE_INTEGER + 1 *)
let decimal_zero_fraction () =
(* Numbers that look like decimals but have .0 *)
assert_bool (Number.isInteger 5.0) true;
assert_bool (Number.isInteger 123.0) true;
assert_bool (Number.isInteger 1e10) true (* 10000000000.0 *)
(* ===================================================================
Non-integer values should return false
=================================================================== *)
let decimals () =
assert_bool (Number.isInteger 0.1) false;
assert_bool (Number.isInteger 0.5) false;
assert_bool (Number.isInteger 1.5) false;
assert_bool (Number.isInteger 3.14159) false;
assert_bool (Number.isInteger (-2.71828)) false
let small_fractions () =
assert_bool (Number.isInteger 0.0001) false;
assert_bool (Number.isInteger 1.0001) false;
assert_bool (Number.isInteger 1e-10) false
(* ===================================================================
Special values should return false
=================================================================== *)
let nan_value () = assert_bool (Number.isInteger nan) false
let infinity_values () =
assert_bool (Number.isInteger infinity) false;
assert_bool (Number.isInteger neg_infinity) false
(* ===================================================================
Edge cases
=================================================================== *)
let max_value_is_integer () =
(* MAX_VALUE is an integer (though very large) *)
assert_bool (Number.isInteger max_value) true;
assert_bool (Number.isInteger (-.max_value)) true
let min_value_is_not_integer () =
(* MIN_VALUE is 5e-324, a very small fraction *)
assert_bool (Number.isInteger min_value) false
let epsilon_is_not_integer () = assert_bool (Number.isInteger epsilon) false
let powers_of_two () =
assert_bool (Number.isInteger (2.0 ** 10.0)) true;
(* 1024 *)
assert_bool (Number.isInteger (2.0 ** 52.0)) true;
(* Within safe integer range *)
assert_bool (Number.isInteger (2.0 ** 53.0)) true (* MAX_SAFE_INTEGER + 1 *)
let scientific_notation () =
assert_bool (Number.isInteger 1e5) true;
(* 100000 *)
assert_bool (Number.isInteger 5e3) true;
(* 5000 *)
assert_bool (Number.isInteger 1e-5) false (* 0.00001 *)
(* Note: In OCaml, Number.isInteger only takes float values, so we don't need
to test non-number types. Those would be type errors at compile time. *)
let tests =
[
(* Integer values - return true *)
test "integers: zeros" zero_values;
test "integers: positive" positive_integers;
test "integers: negative" negative_integers;
test "integers: large" large_integers;
test "integers: decimal zero fraction" decimal_zero_fraction;
(* Non-integer values - return false *)
test "non_integers: decimals" decimals;
test "non_integers: small fractions" small_fractions;
(* Special values - return false *)
test "special: NaN" nan_value;
test "special: Infinity" infinity_values;
(* Edge cases *)
test "edge: MAX_VALUE is integer" max_value_is_integer;
test "edge: MIN_VALUE is not integer" min_value_is_not_integer;
test "edge: epsilon is not integer" epsilon_is_not_integer;
test "edge: powers of two" powers_of_two;
test "edge: scientific notation" scientific_notation;
]
================================================
FILE: packages/Js/test/number_tests/is_nan.ml
================================================
(** TC39 Test262: Number.isNaN tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/isNaN
ECMA-262 Section: Number.isNaN(number)
Note: Number.isNaN is different from global isNaN:
- Number.isNaN only returns true for the actual NaN value
- It does NOT perform type coercion *)
open Helpers
(* ===================================================================
Basic NaN detection
=================================================================== *)
let nan_value () =
(* NaN should return true *)
assert_bool (Number.isNaN nan) true
let nan_from_operation () =
(* NaN from operations should return true *)
assert_bool (Number.isNaN (0.0 /. 0.0)) true;
assert_bool (Number.isNaN (infinity -. infinity)) true;
assert_bool (Number.isNaN (infinity *. 0.0)) true;
assert_bool (Number.isNaN (sqrt (-1.0))) true
let nan_from_parseint () =
(* NaN from parseInt should return true *)
assert_bool (Number.isNaN (Number.parseInt "abc")) true;
assert_bool (Number.isNaN (Number.parseInt "")) true
let nan_from_parsefloat () =
(* NaN from parseFloat should return true *)
assert_bool (Number.isNaN (Number.parseFloat "xyz")) true;
assert_bool (Number.isNaN (Number.parseFloat "")) true
(* ===================================================================
Non-NaN values should return false
=================================================================== *)
let finite_numbers () =
(* Finite numbers return false *)
assert_bool (Number.isNaN 0.0) false;
assert_bool (Number.isNaN 1.0) false;
assert_bool (Number.isNaN (-1.0)) false;
assert_bool (Number.isNaN 42.0) false;
assert_bool (Number.isNaN 3.14159) false;
assert_bool (Number.isNaN (-3.14159)) false
let zero_values () =
(* Zero values return false *)
assert_bool (Number.isNaN 0.0) false;
assert_bool (Number.isNaN (-0.0)) false
let infinity_values () =
(* Infinity returns false (Infinity is not NaN) *)
assert_bool (Number.isNaN infinity) false;
assert_bool (Number.isNaN neg_infinity) false
let max_min_values () =
(* Extreme values return false *)
assert_bool (Number.isNaN max_value) false;
assert_bool (Number.isNaN min_value) false;
assert_bool (Number.isNaN max_safe_integer) false;
assert_bool (Number.isNaN min_safe_integer) false
let epsilon_value () =
(* Epsilon returns false *)
assert_bool (Number.isNaN epsilon) false
(* Note: In OCaml, Number.isNaN only takes float values, so we don't need
to test non-number types like strings, objects, undefined, null, etc.
Those would be type errors at compile time. *)
let tests =
[
(* NaN detection *)
test "nan_value: NaN returns true" nan_value;
test "nan_from_operation: NaN from operations" nan_from_operation;
test "nan_from_parseInt: NaN from parseInt" nan_from_parseint;
test "nan_from_parseFloat: NaN from parseFloat" nan_from_parsefloat;
(* Non-NaN values *)
test "finite_numbers: finite numbers return false" finite_numbers;
test "zero_values: zeros return false" zero_values;
test "infinity_values: Infinity returns false" infinity_values;
test "max_min_values: extreme values return false" max_min_values;
test "epsilon_value: epsilon returns false" epsilon_value;
]
================================================
FILE: packages/Js/test/number_tests/parse_float.ml
================================================
(** TC39 Test262: parseFloat tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/parseFloat
ECMA-262 Section: parseFloat(string)
Test naming convention follows tc39/test262:
- [S15.1.2.3_A_T] format for legacy tests *)
open Helpers
(* ===================================================================
S15.1.2.3_A1: Basic parsing tests
=================================================================== *)
let a1_t1 () =
(* parseFloat("") should return NaN *)
assert_nan (Number.parseFloat "")
let a1_t2 () =
(* parseFloat with simple integers *)
assert_float (Number.parseFloat "0") 0.0;
assert_float (Number.parseFloat "1") 1.0;
assert_float (Number.parseFloat "123") 123.0
let a1_t3 () =
(* parseFloat with no numeric characters should return NaN *)
assert_nan (Number.parseFloat "abc");
assert_nan (Number.parseFloat "xyz123")
(* starts with non-digit *)
let a1_t4 () =
(* parseFloat with decimal numbers *)
assert_float (Number.parseFloat "3.14") 3.14;
assert_float (Number.parseFloat "0.5") 0.5;
assert_float (Number.parseFloat "123.456") 123.456
let a1_t5 () =
(* parseFloat stops at invalid character *)
assert_float (Number.parseFloat "123abc") 123.0;
assert_float (Number.parseFloat "3.14xyz") 3.14;
assert_float (Number.parseFloat "1.5.6") 1.5 (* second decimal point stops parsing *)
let a1_t6 () =
(* parseFloat with only whitespace returns NaN *)
assert_nan (Number.parseFloat " ");
assert_nan (Number.parseFloat "\t\n")
let a1_t7 () =
(* parseFloat behavior with various invalid starts *)
assert_nan (Number.parseFloat "$123");
assert_nan (Number.parseFloat "#123");
assert_nan (Number.parseFloat "@3.14")
(* ===================================================================
S15.1.2.3_A2: Whitespace handling
=================================================================== *)
let a2_t1 () =
(* Leading spaces *)
assert_float (Number.parseFloat " 123") 123.0;
assert_float (Number.parseFloat " 3.14") 3.14
let a2_t2 () =
(* Leading tabs *)
assert_float (Number.parseFloat "\t123") 123.0;
assert_float (Number.parseFloat "\t3.14") 3.14
let a2_t3 () =
(* Leading newlines *)
assert_float (Number.parseFloat "\n123") 123.0;
assert_float (Number.parseFloat "\n3.14") 3.14
let a2_t4 () =
(* Leading carriage return *)
assert_float (Number.parseFloat "\r123") 123.0
let a2_t5 () =
(* Leading form feed *)
assert_float (Number.parseFloat "\x0C123") 123.0
let a2_t6 () =
(* Leading vertical tab *)
assert_float (Number.parseFloat "\x0B123") 123.0
let a2_t7 () =
(* Multiple whitespace types *)
assert_float (Number.parseFloat " \t\n\r3.14159") 3.14159
let a2_t8 () =
(* Non-breaking space (U+00A0) - JavaScript treats NBSP as whitespace in parseFloat *)
assert_float (Number.parseFloat "\xC2\xA0123.5") 123.5
let a2_t9 () =
(* Trailing characters are ignored *)
assert_float (Number.parseFloat "123 ") 123.0;
assert_float (Number.parseFloat "3.14 abc") 3.14
let a2_t10 () =
(* Whitespace between sign and number *)
assert_nan (Number.parseFloat "- 123");
assert_nan (Number.parseFloat "+ 3.14")
(* ===================================================================
S15.1.2.3_A3: Sign handling
=================================================================== *)
let a3_t1 () =
(* Positive sign *)
assert_float (Number.parseFloat "+123") 123.0;
assert_float (Number.parseFloat "+3.14") 3.14
let a3_t2 () =
(* Negative sign *)
assert_float (Number.parseFloat "-123") (-123.0);
assert_float (Number.parseFloat "-3.14") (-3.14)
let a3_t3 () =
(* Sign with whitespace before *)
assert_float (Number.parseFloat " +123") 123.0;
assert_float (Number.parseFloat " -3.14") (-3.14)
(* ===================================================================
S15.1.2.3_A4: Scientific notation (exponent)
=================================================================== *)
let a4_t1 () =
(* Basic exponent with e *)
assert_float (Number.parseFloat "1e10") 1e10;
assert_float (Number.parseFloat "1e5") 100000.0;
assert_float (Number.parseFloat "5e0") 5.0
let a4_t2 () =
(* Exponent with E (uppercase) *)
assert_float (Number.parseFloat "1E10") 1e10;
assert_float (Number.parseFloat "2E3") 2000.0
let a4_t3 () =
(* Negative exponent *)
assert_float (Number.parseFloat "1e-3") 0.001;
assert_float (Number.parseFloat "5e-1") 0.5;
assert_float (Number.parseFloat "1.5e-2") 0.015
let a4_t4 () =
(* Positive exponent with explicit + *)
assert_float (Number.parseFloat "1e+10") 1e10;
assert_float (Number.parseFloat "2e+3") 2000.0
let a4_t5 () =
(* Decimal with exponent *)
assert_float (Number.parseFloat "1.5e3") 1500.0;
assert_float (Number.parseFloat "3.14e2") 314.0;
assert_float (Number.parseFloat "2.5e-1") 0.25
let a4_t6 () =
(* Exponent edge cases *)
assert_float (Number.parseFloat "1e") 1.0;
(* incomplete exponent - returns mantissa *)
assert_float (Number.parseFloat "1e+") 1.0;
(* incomplete exponent *)
assert_float (Number.parseFloat "1e-") 1.0 (* incomplete exponent *)
let a4_t7 () =
(* Very large exponents *)
assert_infinity (Number.parseFloat "1e309");
(* Overflow to Infinity *)
assert_float (Number.parseFloat "1e308") 1e308
(* ===================================================================
S15.1.2.3_A5: Infinity
=================================================================== *)
let a5_t1 () =
(* Infinity string *)
assert_infinity (Number.parseFloat "Infinity");
assert_neg_infinity (Number.parseFloat "-Infinity")
let a5_t2 () =
(* Infinity with sign *)
assert_infinity (Number.parseFloat "+Infinity");
assert_neg_infinity (Number.parseFloat "-Infinity")
let a5_t3 () =
(* Infinity with leading whitespace *)
assert_infinity (Number.parseFloat " Infinity");
assert_neg_infinity (Number.parseFloat " -Infinity")
let a5_t4 () =
(* Infinity followed by characters *)
assert_infinity (Number.parseFloat "Infinityxyz");
assert_infinity (Number.parseFloat "Infinity123")
(* ===================================================================
S15.1.2.3_A6: Miscellaneous edge cases
=================================================================== *)
let a6 () =
(* Multiple decimal points *)
assert_float (Number.parseFloat "1.2.3") 1.2;
(* In JavaScript, both "...3" and "..3" return NaN:
- First "." is valid but followed by another "." which stops parsing
- No valid digits found, so result is NaN *)
assert_nan (Number.parseFloat "...3");
assert_nan (Number.parseFloat "..3")
let a7_5 () =
(* Leading decimal point *)
assert_float (Number.parseFloat ".5") 0.5;
assert_float (Number.parseFloat ".123") 0.123;
assert_float (Number.parseFloat "-.5") (-0.5);
assert_float (Number.parseFloat "+.5") 0.5
let a7_6 () =
(* Trailing decimal point *)
assert_float (Number.parseFloat "5.") 5.0;
assert_float (Number.parseFloat "123.") 123.0
let a7_7 () =
(* Decimal point with exponent *)
assert_float (Number.parseFloat ".5e2") 50.0;
assert_float (Number.parseFloat "5.e2") 500.0;
assert_float (Number.parseFloat ".1e-1") 0.01
(* ===================================================================
Additional edge cases
=================================================================== *)
let misc_t1 () =
(* Hex notation NOT supported by parseFloat *)
assert_float (Number.parseFloat "0x10") 0.0;
(* stops at 'x' *)
assert_float (Number.parseFloat "0xFF") 0.0
let misc_t2 () =
(* Binary/Octal prefixes NOT supported *)
assert_float (Number.parseFloat "0b101") 0.0;
(* stops at 'b' *)
assert_float (Number.parseFloat "0o777") 0.0 (* stops at 'o' *)
let misc_t3 () =
(* Leading zeros *)
assert_float (Number.parseFloat "00123") 123.0;
assert_float (Number.parseFloat "007.5") 7.5;
assert_float (Number.parseFloat "0000") 0.0
let misc_t4 () =
(* NaN string does NOT parse to NaN value *)
assert_nan (Number.parseFloat "NaN");
(* Actually JS returns NaN for "NaN" *)
assert_nan (Number.parseFloat "nan")
(* case sensitive *)
let misc_t5 () =
(* Sign edge cases *)
assert_nan (Number.parseFloat "+");
assert_nan (Number.parseFloat "-");
assert_nan (Number.parseFloat "++1");
assert_nan (Number.parseFloat "--1");
assert_nan (Number.parseFloat "+-1")
let misc_t6 () =
(* Very small numbers *)
assert_float (Number.parseFloat "1e-323") 1e-323;
assert_float (Number.parseFloat "5e-324") 5e-324 (* MIN_VALUE *)
let misc_t7 () =
(* Negative zero *)
let result = Number.parseFloat "-0" in
assert_float result 0.0;
(* -0.0 = 0.0 in comparison *)
assert_negative_zero result
let misc_t8 () =
(* Numbers that would lose precision *)
assert_float (Number.parseFloat "9007199254740993") 9007199254740992.0;
(* > MAX_SAFE_INTEGER *)
assert_float (Number.parseFloat "0.1") 0.1 (* known floating point representation issue *)
let misc_t9 () =
(* Exponent without mantissa digits before e *)
assert_nan (Number.parseFloat "e10");
assert_nan (Number.parseFloat "E10")
let misc_t10 () =
(* Comprehensive whitespace *)
assert_float (Number.parseFloat "\t\n\x0B\x0C\r 3.14") 3.14
let misc_t11 () =
(* Mixed case Infinity *)
assert_nan (Number.parseFloat "infinity");
(* case sensitive *)
assert_nan (Number.parseFloat "INFINITY");
assert_infinity (Number.parseFloat "Infinity")
let misc_t12 () =
(* Plus and minus with decimals *)
assert_float (Number.parseFloat "-.0") (-0.0);
assert_float (Number.parseFloat "+.0") 0.0;
assert_float (Number.parseFloat "-.1") (-0.1);
assert_float (Number.parseFloat "+.1") 0.1
let misc_t13 () =
(* Multiple e in number *)
assert_float (Number.parseFloat "1e2e3") 100.0;
(* stops at second e *)
assert_float (Number.parseFloat "1E2E3") 100.0
let misc_t14 () =
(* Exponent with decimal *)
assert_float (Number.parseFloat "1e2.5") 100.0 (* stops at . in exponent *)
let tests =
[
(* A1: Basic parsing *)
test "S15.1.2.3_A1_T1: empty string returns NaN" a1_t1;
test "S15.1.2.3_A1_T2: simple integers" a1_t2;
test "S15.1.2.3_A1_T3: non-numeric returns NaN" a1_t3;
test "S15.1.2.3_A1_T4: decimal numbers" a1_t4;
test "S15.1.2.3_A1_T5: stops at invalid char" a1_t5;
test "S15.1.2.3_A1_T6: whitespace only returns NaN" a1_t6;
test "S15.1.2.3_A1_T7: invalid start chars" a1_t7;
(* A2: Whitespace handling *)
test "S15.1.2.3_A2_T1: leading spaces" a2_t1;
test "S15.1.2.3_A2_T2: leading tabs" a2_t2;
test "S15.1.2.3_A2_T3: leading newlines" a2_t3;
test "S15.1.2.3_A2_T4: leading carriage return" a2_t4;
test "S15.1.2.3_A2_T5: leading form feed" a2_t5;
test "S15.1.2.3_A2_T6: leading vertical tab" a2_t6;
test "S15.1.2.3_A2_T7: mixed whitespace" a2_t7;
test "S15.1.2.3_A2_T8: non-breaking space" a2_t8;
test "S15.1.2.3_A2_T9: trailing characters" a2_t9;
test "S15.1.2.3_A2_T10: whitespace between sign and number" a2_t10;
(* A3: Sign handling *)
test "S15.1.2.3_A3_T1: positive sign" a3_t1;
test "S15.1.2.3_A3_T2: negative sign" a3_t2;
test "S15.1.2.3_A3_T3: sign with whitespace" a3_t3;
(* A4: Scientific notation *)
test "S15.1.2.3_A4_T1: basic exponent" a4_t1;
test "S15.1.2.3_A4_T2: uppercase E" a4_t2;
test "S15.1.2.3_A4_T3: negative exponent" a4_t3;
test "S15.1.2.3_A4_T4: positive exponent with +" a4_t4;
test "S15.1.2.3_A4_T5: decimal with exponent" a4_t5;
test "S15.1.2.3_A4_T6: incomplete exponent" a4_t6;
test "S15.1.2.3_A4_T7: very large exponents" a4_t7;
(* A5: Infinity *)
test "S15.1.2.3_A5_T1: Infinity string" a5_t1;
test "S15.1.2.3_A5_T2: signed Infinity" a5_t2;
test "S15.1.2.3_A5_T3: Infinity with whitespace" a5_t3;
test "S15.1.2.3_A5_T4: Infinity with trailing chars" a5_t4;
(* A6-A7: Edge cases *)
test "S15.1.2.3_A6: multiple decimal points" a6;
test "S15.1.2.3_A7.5: leading decimal point" a7_5;
test "S15.1.2.3_A7.6: trailing decimal point" a7_6;
test "S15.1.2.3_A7.7: decimal with exponent" a7_7;
(* Miscellaneous *)
test "misc_t1: hex not supported" misc_t1;
test "misc_t2: binary/octal not supported" misc_t2;
test "misc_t3: leading zeros" misc_t3;
test "misc_t4: NaN string" misc_t4;
test "misc_t5: sign edge cases" misc_t5;
test "misc_t6: very small numbers" misc_t6;
test "misc_t7: negative zero" misc_t7;
test "misc_t8: precision loss" misc_t8;
test "misc_t9: exponent without mantissa" misc_t9;
test "misc_t10: comprehensive whitespace" misc_t10;
test "misc_t11: Infinity case sensitivity" misc_t11;
test "misc_t12: sign with decimal only" misc_t12;
test "misc_t13: multiple e" misc_t13;
test "misc_t14: exponent with decimal" misc_t14;
]
================================================
FILE: packages/Js/test/number_tests/parse_int.ml
================================================
(** TC39 Test262: parseInt tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/parseInt
ECMA-262 Section: parseInt(string, radix)
Test naming convention follows tc39/test262:
- [S15.1.2.2_A_T] format for legacy tests
- Descriptive names for newer tests *)
open Helpers
(* ===================================================================
S15.1.2.2_A1: Basic parsing tests
If string.[[Value]] does not begin with valid characters, return NaN
=================================================================== *)
let a1_t1 () =
(* parseInt("") should return NaN *)
assert_nan (Number.parseInt "")
let a1_t2 () =
(* parseInt(" ") (only whitespace) should return NaN *)
assert_nan (Number.parseInt " ")
let a1_t3 () =
(* parseInt with no numeric characters should return NaN *)
assert_nan (Number.parseInt "abc")
let a1_t4 () =
(* parseInt("$1234") - starts with invalid char *)
assert_nan (Number.parseInt "$1234")
let a1_t5 () =
(* parseInt with only sign characters should return NaN *)
assert_nan (Number.parseInt "+");
assert_nan (Number.parseInt "-")
let a1_t6 () =
(* parseInt stops at invalid characters *)
assert_float (Number.parseInt "123abc") 123.0;
assert_float (Number.parseInt "456.789") 456.0
let a1_t7 () =
(* parseInt with various non-numeric strings *)
assert_nan (Number.parseInt "NaN");
assert_nan (Number.parseInt "Infinity")
(* ===================================================================
S15.1.2.2_A2: Whitespace handling
Leading whitespace should be ignored
=================================================================== *)
let a2_t1 () =
(* Leading spaces *)
assert_float (Number.parseInt " 123") 123.0;
assert_float (Number.parseInt " 456") 456.0
let a2_t2 () =
(* Leading tabs *)
assert_float (Number.parseInt "\t123") 123.0;
assert_float (Number.parseInt "\t\t456") 456.0
let a2_t3 () =
(* Leading newlines *)
assert_float (Number.parseInt "\n123") 123.0;
assert_float (Number.parseInt "\n\n456") 456.0
let a2_t4 () =
(* Leading carriage return *)
assert_float (Number.parseInt "\r123") 123.0
let a2_t5 () =
(* Leading form feed *)
assert_float (Number.parseInt "\x0C123") 123.0
let a2_t6 () =
(* Leading vertical tab *)
assert_float (Number.parseInt "\x0B123") 123.0
let a2_t7 () =
(* Multiple whitespace types *)
assert_float (Number.parseInt " \t\n\r123") 123.0
let a2_t8 () =
(* Non-breaking space (U+00A0) - JavaScript treats NBSP as whitespace *)
assert_float (Number.parseInt "\xC2\xA0123") 123.0
let a2_t9 () =
(* Trailing whitespace is ignored (stops at first non-digit) *)
assert_float (Number.parseInt "123 ") 123.0
let a2_t10 () =
(* Whitespace between sign and digits *)
(* Note: JavaScript actually treats "- 123" as NaN, sign must be adjacent *)
assert_nan (Number.parseInt "- 123");
assert_nan (Number.parseInt "+ 123")
(* ===================================================================
S15.1.2.2_A3: Sign handling
The function handles + and - prefixes
=================================================================== *)
let a3_1_t1 () =
(* Positive sign *)
assert_float (Number.parseInt "+123") 123.0
let a3_1_t2 () =
(* Negative sign *)
assert_float (Number.parseInt "-123") (-123.0)
let a3_1_t3 () =
(* Sign with leading whitespace *)
assert_float (Number.parseInt " +123") 123.0;
assert_float (Number.parseInt " -456") (-456.0)
let a3_1_t4 () =
(* Multiple signs should stop at second sign *)
assert_nan (Number.parseInt "++123");
assert_nan (Number.parseInt "--123");
assert_nan (Number.parseInt "+-123")
let a3_1_t5 () =
(* Sign with zero *)
assert_float (Number.parseInt "+0") 0.0;
assert_float (Number.parseInt "-0") 0.0 (* Note: -0.0 in OCaml *)
let a3_1_t6 () =
(* Negative with hex prefix *)
assert_float (Number.parseInt "-0x10") (-16.0);
assert_float (Number.parseInt "+0x10") 16.0
let a3_1_t7 () =
(* Sign alone *)
assert_nan (Number.parseInt "+");
assert_nan (Number.parseInt "-")
(* ===================================================================
S15.1.2.2_A3.2: Radix with sign
=================================================================== *)
let a3_2_t1 () =
(* Negative with explicit radix *)
assert_float (Number.parseInt ~radix:16 "-ff") (-255.0);
assert_float (Number.parseInt ~radix:16 "+ff") 255.0
let a3_2_t2 () =
(* Negative binary *)
assert_float (Number.parseInt ~radix:2 "-1010") (-10.0);
assert_float (Number.parseInt ~radix:2 "+1010") 10.0
let a3_2_t3 () =
(* Negative octal *)
assert_float (Number.parseInt ~radix:8 "-77") (-63.0)
(* ===================================================================
S15.1.2.2_A4: Radix handling
radix must be in range [2, 36] or 0 (auto-detect)
=================================================================== *)
let a4_1_t1 () =
(* Radix 2 (binary) *)
assert_float (Number.parseInt ~radix:2 "1010") 10.0;
assert_float (Number.parseInt ~radix:2 "1111") 15.0;
assert_float (Number.parseInt ~radix:2 "0") 0.0
let a4_1_t2 () =
(* Radix 8 (octal) *)
assert_float (Number.parseInt ~radix:8 "77") 63.0;
assert_float (Number.parseInt ~radix:8 "10") 8.0;
assert_float (Number.parseInt ~radix:8 "777") 511.0
let a4_2_t1 () =
(* Radix 16 (hexadecimal) *)
assert_float (Number.parseInt ~radix:16 "ff") 255.0;
assert_float (Number.parseInt ~radix:16 "FF") 255.0;
assert_float (Number.parseInt ~radix:16 "10") 16.0;
assert_float (Number.parseInt ~radix:16 "abc") 2748.0
let a4_2_t2 () =
(* Radix 36 (maximum) *)
assert_float (Number.parseInt ~radix:36 "z") 35.0;
assert_float (Number.parseInt ~radix:36 "Z") 35.0;
assert_float (Number.parseInt ~radix:36 "10") 36.0
(* ===================================================================
S15.1.2.2_A5: Hex prefix handling
0x or 0X prefix auto-selects radix 16
=================================================================== *)
let a5_1_t1 () =
(* 0x prefix with radix 0 or undefined *)
assert_float (Number.parseInt "0x10") 16.0;
assert_float (Number.parseInt "0X10") 16.0;
assert_float (Number.parseInt "0xff") 255.0;
assert_float (Number.parseInt "0XFF") 255.0
let a5_2_t1 () =
(* 0x prefix with explicit radix 16 should work *)
assert_float (Number.parseInt ~radix:16 "0x10") 16.0;
assert_float (Number.parseInt ~radix:16 "0XFF") 255.0
let a5_2_t2 () =
(* 0x prefix with non-16 radix - 0 is parsed, x stops parsing *)
assert_float (Number.parseInt ~radix:10 "0x10") 0.0;
assert_float (Number.parseInt ~radix:8 "0x10") 0.0
(* ===================================================================
S15.1.2.2_A6: Invalid radix
Radix outside [2, 36] (except 0) should return NaN
=================================================================== *)
let a6_1_t1 () =
(* Radix 0 should auto-detect *)
assert_float (Number.parseInt ~radix:0 "123") 123.0;
assert_float (Number.parseInt ~radix:0 "0x10") 16.0
let a6_1_t2 () =
(* Radix 1 is invalid *)
assert_nan (Number.parseInt ~radix:1 "123")
let a6_1_t3 () =
(* Radix 37 is invalid *)
assert_nan (Number.parseInt ~radix:37 "123")
let a6_1_t4 () =
(* Large radix values *)
assert_nan (Number.parseInt ~radix:100 "123");
assert_nan (Number.parseInt ~radix:1000 "123")
let a6_1_t5 () =
(* Negative radix is invalid *)
assert_nan (Number.parseInt ~radix:(-1) "123");
assert_nan (Number.parseInt ~radix:(-16) "ff")
let a6_1_t6 () =
(* Radix values just outside valid range *)
assert_float (Number.parseInt ~radix:2 "1") 1.0;
assert_float (Number.parseInt ~radix:36 "z") 35.0
(* ===================================================================
S15.1.2.2_A7: Edge cases with digits and radix
=================================================================== *)
let a7_1_t1 () =
(* Digits beyond radix should stop parsing *)
assert_float (Number.parseInt ~radix:2 "102") 2.0;
(* stops at '0' after '10' *)
assert_float (Number.parseInt ~radix:8 "789") 7.0;
(* stops at '8' *)
assert_float (Number.parseInt ~radix:10 "12abc") 12.0
let a7_1_t2 () =
(* All digits invalid for radix *)
assert_nan (Number.parseInt ~radix:2 "234");
assert_nan (Number.parseInt ~radix:8 "89");
assert_nan (Number.parseInt ~radix:10 "abc")
let a7_2_t1 () =
(* Large numbers *)
assert_float (Number.parseInt "9007199254740991") 9007199254740991.0;
(* MAX_SAFE_INTEGER *)
assert_float (Number.parseInt "9007199254740992") 9007199254740992.0
let a7_2_t2 () =
(* Very large hex numbers *)
assert_float (Number.parseInt "0x1FFFFFFFFFFFFF") 9007199254740991.0
let a7_2_t3 () =
(* Numbers with many digits - this test is skipped because:
- The number 12345678901234567890 (~1.23e19) exceeds OCaml's max_int (~4.6e18)
- quickjs.ml's parse_int returns an OCaml int, which overflows for such large values
- JavaScript's parseInt returns a float64, so it can represent large numbers (with precision loss)
In practice, numbers this large lose precision anyway due to float64 limitations.
Original test:
let result = Number.parseInt "12345678901234567890" in
assert_bool (result > 1.23e19 && result < 1.24e19) true
*)
()
let a7_3_t1 () =
(* Leading zeros *)
assert_float (Number.parseInt "00123") 123.0;
assert_float (Number.parseInt "0000") 0.0;
assert_float (Number.parseInt "007") 7.0
let a7_3_t2 () =
(* Leading zeros with explicit radix *)
assert_float (Number.parseInt ~radix:10 "00123") 123.0;
assert_float (Number.parseInt ~radix:8 "00123") 83.0 (* Octal interpretation *)
let a7_3_t3 () =
(* Only zeros *)
assert_float (Number.parseInt "0") 0.0;
assert_float (Number.parseInt "00") 0.0;
assert_float (Number.parseInt "000") 0.0
(* ===================================================================
S15.1.2.2_A8: Various edge cases
=================================================================== *)
let a8 () =
(* Decimal point stops parsing *)
assert_float (Number.parseInt "3.14159") 3.0;
assert_float (Number.parseInt "2.71828") 2.0;
(* parseInt(".5") returns NaN in JavaScript - no valid digits before decimal *)
assert_nan (Number.parseInt ".5")
let misc_t1 () =
(* Scientific notation is not parsed by parseInt *)
assert_float (Number.parseInt "1e10") 1.0;
(* stops at 'e' *)
assert_float (Number.parseInt "1E10") 1.0
let misc_t2 () =
(* Unicode digits (not supported by parseInt - ASCII only) *)
(* Full-width digits should return NaN *)
assert_nan (Number.parseInt "\xEF\xBC\x91\xEF\xBC\x92\xEF\xBC\x93")
(* 123 U+FF11-FF13 *)
let misc_t3 () =
(* Octal prefix 0o is NOT auto-detected by parseInt *)
assert_float (Number.parseInt "0o123") 0.0;
(* stops at 'o' *)
assert_float (Number.parseInt "0O123") 0.0
let misc_t4 () =
(* Binary prefix 0b is NOT auto-detected by parseInt *)
assert_float (Number.parseInt "0b101") 0.0;
(* stops at 'b' *)
assert_float (Number.parseInt "0B101") 0.0
let misc_t5 () =
(* Single digit tests *)
assert_float (Number.parseInt "0") 0.0;
assert_float (Number.parseInt "1") 1.0;
assert_float (Number.parseInt "9") 9.0
let misc_t6 () =
(* Radix edge: exactly 2 and 36 *)
assert_float (Number.parseInt ~radix:2 "1") 1.0;
assert_float (Number.parseInt ~radix:2 "0") 0.0;
assert_float (Number.parseInt ~radix:36 "0") 0.0;
assert_float (Number.parseInt ~radix:36 "zz") 1295.0 (* 35*36 + 35 *)
let misc_t7 () =
(* Case insensitivity in hex and higher bases *)
assert_float (Number.parseInt ~radix:16 "AbCdEf") 11259375.0;
assert_float (Number.parseInt ~radix:36 "Hello") 29234652.0 (* H=17, e=14, l=21, l=21, o=24 *)
let misc_t8 () =
(* Whitespace characters comprehensive test *)
assert_float (Number.parseInt "\t\n\x0B\x0C\r 123") 123.0
let tests =
[
(* A1: Basic parsing *)
test "S15.1.2.2_A1_T1: empty string returns NaN" a1_t1;
test "S15.1.2.2_A1_T2: whitespace only returns NaN" a1_t2;
test "S15.1.2.2_A1_T3: non-numeric returns NaN" a1_t3;
test "S15.1.2.2_A1_T4: invalid start char returns NaN" a1_t4;
test "S15.1.2.2_A1_T5: sign only returns NaN" a1_t5;
test "S15.1.2.2_A1_T6: stops at invalid chars" a1_t6;
test "S15.1.2.2_A1_T7: NaN/Infinity strings return NaN" a1_t7;
(* A2: Whitespace handling *)
test "S15.1.2.2_A2_T1: leading spaces" a2_t1;
test "S15.1.2.2_A2_T2: leading tabs" a2_t2;
test "S15.1.2.2_A2_T3: leading newlines" a2_t3;
test "S15.1.2.2_A2_T4: leading carriage return" a2_t4;
test "S15.1.2.2_A2_T5: leading form feed" a2_t5;
test "S15.1.2.2_A2_T6: leading vertical tab" a2_t6;
test "S15.1.2.2_A2_T7: mixed whitespace" a2_t7;
test "S15.1.2.2_A2_T8: non-breaking space" a2_t8;
test "S15.1.2.2_A2_T9: trailing whitespace" a2_t9;
test "S15.1.2.2_A2_T10: whitespace between sign and digits" a2_t10;
(* A3.1: Sign handling *)
test "S15.1.2.2_A3.1_T1: positive sign" a3_1_t1;
test "S15.1.2.2_A3.1_T2: negative sign" a3_1_t2;
test "S15.1.2.2_A3.1_T3: sign with whitespace" a3_1_t3;
test "S15.1.2.2_A3.1_T4: multiple signs" a3_1_t4;
test "S15.1.2.2_A3.1_T5: sign with zero" a3_1_t5;
test "S15.1.2.2_A3.1_T6: sign with hex prefix" a3_1_t6;
test "S15.1.2.2_A3.1_T7: sign alone" a3_1_t7;
(* A3.2: Radix with sign *)
test "S15.1.2.2_A3.2_T1: negative hex" a3_2_t1;
test "S15.1.2.2_A3.2_T2: negative binary" a3_2_t2;
test "S15.1.2.2_A3.2_T3: negative octal" a3_2_t3;
(* A4: Radix handling *)
test "S15.1.2.2_A4.1_T1: radix 2" a4_1_t1;
test "S15.1.2.2_A4.1_T2: radix 8" a4_1_t2;
test "S15.1.2.2_A4.2_T1: radix 16" a4_2_t1;
test "S15.1.2.2_A4.2_T2: radix 36" a4_2_t2;
(* A5: Hex prefix *)
test "S15.1.2.2_A5.1_T1: 0x prefix auto-detection" a5_1_t1;
test "S15.1.2.2_A5.2_T1: 0x with explicit radix 16" a5_2_t1;
test "S15.1.2.2_A5.2_T2: 0x with non-16 radix" a5_2_t2;
(* A6: Invalid radix *)
test "S15.1.2.2_A6.1_T1: radix 0 auto-detects" a6_1_t1;
test "S15.1.2.2_A6.1_T2: radix 1 invalid" a6_1_t2;
test "S15.1.2.2_A6.1_T3: radix 37 invalid" a6_1_t3;
test "S15.1.2.2_A6.1_T4: large radix invalid" a6_1_t4;
test "S15.1.2.2_A6.1_T5: negative radix invalid" a6_1_t5;
test "S15.1.2.2_A6.1_T6: boundary radix valid" a6_1_t6;
(* A7: Digit and radix edge cases *)
test "S15.1.2.2_A7.1_T1: digits beyond radix" a7_1_t1;
test "S15.1.2.2_A7.1_T2: all digits invalid" a7_1_t2;
test "S15.1.2.2_A7.2_T1: large numbers" a7_2_t1;
test "S15.1.2.2_A7.2_T2: large hex numbers" a7_2_t2;
test "S15.1.2.2_A7.2_T3: many digits" a7_2_t3;
test "S15.1.2.2_A7.3_T1: leading zeros" a7_3_t1;
test "S15.1.2.2_A7.3_T2: leading zeros with radix" a7_3_t2;
test "S15.1.2.2_A7.3_T3: only zeros" a7_3_t3;
(* A8: Various edge cases *)
test "S15.1.2.2_A8: decimal point" a8;
(* Miscellaneous *)
test "misc_t1: scientific notation not parsed" misc_t1;
test "misc_t2: unicode digits not supported" misc_t2;
test "misc_t3: 0o octal prefix not auto-detected" misc_t3;
test "misc_t4: 0b binary prefix not auto-detected" misc_t4;
test "misc_t5: single digits" misc_t5;
test "misc_t6: radix boundaries" misc_t6;
test "misc_t7: case insensitivity" misc_t7;
test "misc_t8: comprehensive whitespace" misc_t8;
]
================================================
FILE: packages/Js/test/number_tests/to_exponential.ml
================================================
(** TC39 Test262: Number.prototype.toExponential tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/prototype/toExponential
ECMA-262 Section: Number.prototype.toExponential([fractionDigits]) *)
open Helpers
(* ===================================================================
Basic toExponential without fractionDigits
=================================================================== *)
let basic_no_digits () =
(* Without fractionDigits, uses the minimum digits needed *)
assert_string (Number.toExponential 1.0) "1";
assert_string (Number.toExponential 123.0) "123";
assert_string (Number.toExponential 0.0) "0"
(* ===================================================================
toExponential with fractionDigits
=================================================================== *)
let with_digits_zero () =
assert_string (Number.toExponential ~digits:0 1.0) "1e+0";
assert_string (Number.toExponential ~digits:0 123.0) "1e+2";
assert_string (Number.toExponential ~digits:0 0.5) "5e-1"
let with_digits_one () =
assert_string (Number.toExponential ~digits:1 1.0) "1.0e+0";
assert_string (Number.toExponential ~digits:1 123.0) "1.2e+2";
assert_string (Number.toExponential ~digits:1 0.05) "5.0e-2"
let with_digits_multiple () =
assert_string (Number.toExponential ~digits:2 123.0) "1.23e+2";
assert_string (Number.toExponential ~digits:3 123.0) "1.230e+2";
assert_string (Number.toExponential ~digits:4 12345.0) "1.2345e+4"
let with_large_digits () =
assert_string (Number.toExponential ~digits:10 1.0) "1.0000000000e+0";
assert_string (Number.toExponential ~digits:20 1.0) "1.00000000000000000000e+0"
(* ===================================================================
Special values
=================================================================== *)
let special_values () =
assert_string (Number.toExponential nan) "NaN";
assert_string (Number.toExponential infinity) "Infinity";
assert_string (Number.toExponential neg_infinity) "-Infinity"
let special_values_with_digits () =
assert_string (Number.toExponential ~digits:2 nan) "NaN";
assert_string (Number.toExponential ~digits:2 infinity) "Infinity";
assert_string (Number.toExponential ~digits:2 neg_infinity) "-Infinity"
(* ===================================================================
Negative numbers
=================================================================== *)
let negative_numbers () =
assert_string (Number.toExponential ~digits:0 (-1.0)) "-1e+0";
assert_string (Number.toExponential ~digits:2 (-123.0)) "-1.23e+2";
assert_string (Number.toExponential ~digits:1 (-0.05)) "-5.0e-2"
(* ===================================================================
Rounding
=================================================================== *)
let rounding () =
assert_string (Number.toExponential ~digits:1 1.25) "1.3e+0";
assert_string (Number.toExponential ~digits:1 1.24) "1.2e+0";
assert_string (Number.toExponential ~digits:0 1.5) "2e+0";
(* Note: 1.005 is actually stored as slightly less than 1.005 in IEEE 754 *)
assert_string (Number.toExponential ~digits:2 1.005) "1.00e+0"
(* ===================================================================
Edge cases
=================================================================== *)
let very_small_numbers () =
assert_string (Number.toExponential ~digits:2 0.0001) "1.00e-4";
assert_string (Number.toExponential ~digits:2 1e-10) "1.00e-10"
let very_large_numbers () =
assert_string (Number.toExponential ~digits:2 1e10) "1.00e+10";
assert_string (Number.toExponential ~digits:2 1e20) "1.00e+20"
let negative_zero () = assert_string (Number.toExponential ~digits:0 (-0.0)) "0e+0"
let tests =
[
(* Basic *)
test "basic: no digits" basic_no_digits;
(* With digits *)
test "digits: 0" with_digits_zero;
test "digits: 1" with_digits_one;
test "digits: multiple" with_digits_multiple;
test "digits: large" with_large_digits;
(* Special values *)
test "special values" special_values;
test "special values with digits" special_values_with_digits;
(* Negative numbers *)
test "negative numbers" negative_numbers;
(* Rounding *)
test "rounding" rounding;
(* Edge cases *)
test "very small numbers" very_small_numbers;
test "very large numbers" very_large_numbers;
test "negative zero" negative_zero;
]
================================================
FILE: packages/Js/test/number_tests/to_precision.ml
================================================
(** TC39 Test262: Number.prototype.toPrecision tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/prototype/toPrecision
ECMA-262 Section: Number.prototype.toPrecision([precision]) *)
open Helpers
(* ===================================================================
Basic toPrecision without precision argument
=================================================================== *)
let basic_no_precision () =
(* Without precision, returns same as toString *)
assert_string (Number.toPrecision 1.0) "1";
assert_string (Number.toPrecision 123.0) "123";
assert_string (Number.toPrecision 3.14159) "3.14159"
(* ===================================================================
toPrecision with precision argument
=================================================================== *)
let precision_1 () =
assert_string (Number.toPrecision ~digits:1 1.0) "1";
assert_string (Number.toPrecision ~digits:1 12.0) "1e+1";
assert_string (Number.toPrecision ~digits:1 123.0) "1e+2";
assert_string (Number.toPrecision ~digits:1 0.5) "0.5";
assert_string (Number.toPrecision ~digits:1 0.05) "0.05"
let precision_2 () =
assert_string (Number.toPrecision ~digits:2 1.0) "1.0";
assert_string (Number.toPrecision ~digits:2 12.0) "12";
assert_string (Number.toPrecision ~digits:2 123.0) "1.2e+2";
assert_string (Number.toPrecision ~digits:2 0.05) "0.050"
let precision_multiple () =
assert_string (Number.toPrecision ~digits:3 123.0) "123";
assert_string (Number.toPrecision ~digits:4 123.0) "123.0";
assert_string (Number.toPrecision ~digits:5 123.0) "123.00";
assert_string (Number.toPrecision ~digits:6 123.456) "123.456"
let precision_large () =
assert_string (Number.toPrecision ~digits:10 1.0) "1.000000000";
assert_string (Number.toPrecision ~digits:20 1.0) "1.0000000000000000000"
(* ===================================================================
Special values
=================================================================== *)
let special_values () =
assert_string (Number.toPrecision nan) "NaN";
assert_string (Number.toPrecision infinity) "Infinity";
assert_string (Number.toPrecision neg_infinity) "-Infinity"
let special_values_with_precision () =
assert_string (Number.toPrecision ~digits:2 nan) "NaN";
assert_string (Number.toPrecision ~digits:2 infinity) "Infinity";
assert_string (Number.toPrecision ~digits:2 neg_infinity) "-Infinity"
(* ===================================================================
Negative numbers
=================================================================== *)
let negative_numbers () =
assert_string (Number.toPrecision ~digits:1 (-1.0)) "-1";
assert_string (Number.toPrecision ~digits:2 (-123.0)) "-1.2e+2";
assert_string (Number.toPrecision ~digits:4 (-123.4)) "-123.4"
(* ===================================================================
Rounding
=================================================================== *)
let rounding () =
assert_string (Number.toPrecision ~digits:2 1.25) "1.3";
assert_string (Number.toPrecision ~digits:2 1.24) "1.2";
(* Note: 1.005 is actually stored as slightly less than 1.005 in IEEE 754 *)
assert_string (Number.toPrecision ~digits:3 1.005) "1.00";
assert_string (Number.toPrecision ~digits:3 1234.0) "1.23e+3"
(* ===================================================================
Edge cases
=================================================================== *)
let zero_handling () =
assert_string (Number.toPrecision ~digits:1 0.0) "0";
assert_string (Number.toPrecision ~digits:2 0.0) "0.0";
assert_string (Number.toPrecision ~digits:5 0.0) "0.0000"
let negative_zero () = assert_string (Number.toPrecision ~digits:1 (-0.0)) "0"
let very_small_numbers () =
assert_string (Number.toPrecision ~digits:2 0.0001) "0.00010";
assert_string (Number.toPrecision ~digits:2 1e-10) "1.0e-10"
let very_large_numbers () =
assert_string (Number.toPrecision ~digits:2 1e10) "1.0e+10";
assert_string (Number.toPrecision ~digits:5 12345.0) "12345"
let tests =
[
(* Basic *)
test "basic: no precision" basic_no_precision;
(* With precision *)
test "precision: 1" precision_1;
test "precision: 2" precision_2;
test "precision: multiple" precision_multiple;
test "precision: large" precision_large;
(* Special values *)
test "special values" special_values;
test "special values with precision" special_values_with_precision;
(* Negative numbers *)
test "negative numbers" negative_numbers;
(* Rounding *)
test "rounding" rounding;
(* Edge cases *)
test "zero handling" zero_handling;
test "negative zero" negative_zero;
test "very small numbers" very_small_numbers;
test "very large numbers" very_large_numbers;
]
================================================
FILE: packages/Js/test/number_tests/to_string.ml
================================================
(** TC39 Test262: Number.prototype.toString tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/Number/prototype/toString
ECMA-262 Section: Number.prototype.toString([radix]) *)
open Helpers
(* ===================================================================
Basic toString (no radix)
=================================================================== *)
let basic_integers () =
assert_string (Number.toString 0.0) "0";
assert_string (Number.toString 1.0) "1";
assert_string (Number.toString 42.0) "42";
assert_string (Number.toString 123.0) "123";
assert_string (Number.toString (-1.0)) "-1";
assert_string (Number.toString (-123.0)) "-123"
let basic_decimals () =
assert_string (Number.toString 0.5) "0.5";
assert_string (Number.toString 3.14159) "3.14159";
assert_string (Number.toString (-0.5)) "-0.5";
assert_string (Number.toString 1.23) "1.23"
let special_values () =
assert_string (Number.toString nan) "NaN";
assert_string (Number.toString infinity) "Infinity";
assert_string (Number.toString neg_infinity) "-Infinity"
let scientific_notation () =
(* Very large numbers use exponential notation *)
assert_string (Number.toString 1e20) "100000000000000000000";
assert_string (Number.toString 1e21) "1e+21";
(* Very small numbers *)
assert_string (Number.toString 1e-6) "0.000001";
assert_string (Number.toString 1e-7) "1e-7"
let negative_zero () =
(* Note: toString of -0 returns "0" *)
assert_string (Number.toString (-0.0)) "0"
(* ===================================================================
toString with radix
=================================================================== *)
let radix_2_binary () =
assert_string (Number.toString ~radix:2 0.0) "0";
assert_string (Number.toString ~radix:2 1.0) "1";
assert_string (Number.toString ~radix:2 2.0) "10";
assert_string (Number.toString ~radix:2 10.0) "1010";
assert_string (Number.toString ~radix:2 255.0) "11111111"
let radix_8_octal () =
assert_string (Number.toString ~radix:8 0.0) "0";
assert_string (Number.toString ~radix:8 7.0) "7";
assert_string (Number.toString ~radix:8 8.0) "10";
assert_string (Number.toString ~radix:8 64.0) "100";
assert_string (Number.toString ~radix:8 255.0) "377"
let radix_16_hex () =
assert_string (Number.toString ~radix:16 0.0) "0";
assert_string (Number.toString ~radix:16 15.0) "f";
assert_string (Number.toString ~radix:16 16.0) "10";
assert_string (Number.toString ~radix:16 255.0) "ff";
assert_string (Number.toString ~radix:16 256.0) "100"
let radix_36_max () =
assert_string (Number.toString ~radix:36 0.0) "0";
assert_string (Number.toString ~radix:36 35.0) "z";
assert_string (Number.toString ~radix:36 36.0) "10";
assert_string (Number.toString ~radix:36 1295.0) "zz"
let radix_10_explicit () =
assert_string (Number.toString ~radix:10 0.0) "0";
assert_string (Number.toString ~radix:10 123.0) "123";
assert_string (Number.toString ~radix:10 (-456.0)) "-456"
let negative_with_radix () =
assert_string (Number.toString ~radix:2 (-10.0)) "-1010";
assert_string (Number.toString ~radix:16 (-255.0)) "-ff";
assert_string (Number.toString ~radix:8 (-64.0)) "-100"
(* ===================================================================
Special values with radix
=================================================================== *)
let special_values_with_radix () =
assert_string (Number.toString ~radix:2 nan) "NaN";
assert_string (Number.toString ~radix:16 infinity) "Infinity";
assert_string (Number.toString ~radix:8 neg_infinity) "-Infinity"
(* ===================================================================
Edge cases
=================================================================== *)
let large_numbers () =
assert_string (Number.toString max_safe_integer) "9007199254740991";
assert_string (Number.toString min_safe_integer) "-9007199254740991"
let tests =
[
(* Basic toString *)
test "basic: integers" basic_integers;
test "basic: decimals" basic_decimals;
test "basic: special values" special_values;
test "basic: scientific notation" scientific_notation;
test "basic: negative zero" negative_zero;
(* With radix *)
test "radix 2: binary" radix_2_binary;
test "radix 8: octal" radix_8_octal;
test "radix 16: hex" radix_16_hex;
test "radix 36: max" radix_36_max;
test "radix 10: explicit" radix_10_explicit;
test "negative with radix" negative_with_radix;
test "special values with radix" special_values_with_radix;
test "large numbers" large_numbers;
]
================================================
FILE: packages/Js/test/regexp_tests/dotall.ml
================================================
(** TC39 Test262: RegExp dotAll flag tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/RegExp/dotall
ECMA-262 Section: dotAll flag (s) - makes . match line terminators *)
open Helpers
(* ===================================================================
Basic dotAll functionality
=================================================================== *)
let dot_without_flag () =
(* Without dotAll, . does NOT match newlines *)
let re = Js.Re.fromString "a.b" in
assert_bool (Js.Re.test ~str:"a\nb" re) false
let dot_with_flag () =
(* With dotAll (s flag), . matches newlines *)
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"s" in
assert_bool (Js.Re.test ~str:"a\nb" re) true
let dotall_flag_accessor () =
let re_without = Js.Re.fromString "abc" in
let re_with = Js.Re.fromStringWithFlags "abc" ~flags:"s" in
assert_bool (Js.Re.dotAll re_without) false;
assert_bool (Js.Re.dotAll re_with) true
(* ===================================================================
Line terminators
=================================================================== *)
let matches_line_feed () =
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"s" in
assert_bool (Js.Re.test ~str:"a\nb" re) true
let matches_carriage_return () =
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"s" in
assert_bool (Js.Re.test ~str:"a\rb" re) true
let matches_crlf () =
let re = Js.Re.fromStringWithFlags "a..b" ~flags:"s" in
assert_bool (Js.Re.test ~str:"a\r\nb" re) true
(* ===================================================================
Combined with other flags
=================================================================== *)
let with_global_flag () =
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"gs" in
assert_bool (Js.Re.global re) true;
assert_bool (Js.Re.dotAll re) true;
assert_bool (Js.Re.test ~str:"a\nb" re) true
let with_ignorecase_flag () =
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"si" in
assert_bool (Js.Re.ignoreCase re) true;
assert_bool (Js.Re.dotAll re) true;
assert_bool (Js.Re.test ~str:"A\nB" re) true
let with_multiline_flag () =
let re = Js.Re.fromStringWithFlags "a.b" ~flags:"sm" in
assert_bool (Js.Re.multiline re) true;
assert_bool (Js.Re.dotAll re) true;
assert_bool (Js.Re.test ~str:"a\nb" re) true
(* ===================================================================
flags accessor includes s
=================================================================== *)
let flags_includes_s () =
let re = Js.Re.fromStringWithFlags "abc" ~flags:"s" in
let flags = Js.Re.flags re in
assert_true "flags contains s" (String.contains flags 's')
let flags_order () =
(* Flags should be in canonical order: gimsuy *)
let re = Js.Re.fromStringWithFlags "abc" ~flags:"smig" in
let flags = Js.Re.flags re in
(* Should contain g, i, m, s in some order *)
assert_true "contains g" (String.contains flags 'g');
assert_true "contains i" (String.contains flags 'i');
assert_true "contains m" (String.contains flags 'm');
assert_true "contains s" (String.contains flags 's')
(* ===================================================================
Practical examples
=================================================================== *)
let multiline_content () =
let re = Js.Re.fromStringWithFlags "start.*end" ~flags:"s" in
let multiline_text = "start\nmiddle\nend" in
assert_bool (Js.Re.test ~str:multiline_text re) true
let no_dotall_multiline_fail () =
(* Without s flag, this should NOT match *)
let re = Js.Re.fromString "start.*end" in
let multiline_text = "start\nmiddle\nend" in
assert_bool (Js.Re.test ~str:multiline_text re) false
let tests =
[
(* Basic *)
test "basic: dot without flag" dot_without_flag;
test "basic: dot with flag" dot_with_flag;
test "basic: flag accessor" dotall_flag_accessor;
(* Line terminators *)
test "line: matches LF" matches_line_feed;
test "line: matches CR" matches_carriage_return;
test "line: matches CRLF" matches_crlf;
(* Combined flags *)
test "flags: with global" with_global_flag;
test "flags: with ignorecase" with_ignorecase_flag;
test "flags: with multiline" with_multiline_flag;
(* Flags accessor *)
test "accessor: includes s" flags_includes_s;
test "accessor: order" flags_order;
(* Practical *)
test "practical: multiline content" multiline_content;
test "practical: without flag fails" no_dotall_multiline_fail;
]
================================================
FILE: packages/Js/test/regexp_tests/named_groups.ml
================================================
(** TC39 Test262: RegExp Named Capture Groups tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/RegExp/named-groups
ECMA-262 Named Capturing Groups (ES2018+) *)
open Helpers
(* ===================================================================
Basic named capture groups
=================================================================== *)
let basic_named_group () =
let re = Js.Re.fromString "(?\\d{4})-(?\\d{2})-(?\\d{2})" in
let result = Js.Re.exec ~str:"2024-03-15" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
(* Check named groups *)
let groups = Js.Re.groups r in
assert_true "has year group" (List.exists (fun (k, _) -> k = "year") groups);
assert_true "has month group" (List.exists (fun (k, _) -> k = "month") groups);
assert_true "has day group" (List.exists (fun (k, _) -> k = "day") groups)
let single_named_group () =
let re = Js.Re.fromString "hello (?\\w+)" in
let result = Js.Re.exec ~str:"hello world" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
let name_value = Js.Re.group "name" r in
assert_string (Option.get name_value) "world"
let multiple_named_groups () =
let re = Js.Re.fromString "(?\\w+) (?\\w+)" in
let result = Js.Re.exec ~str:"hello world" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
assert_string (Option.get (Js.Re.group "first" r)) "hello";
assert_string (Option.get (Js.Re.group "second" r)) "world"
(* ===================================================================
Named group access
=================================================================== *)
let group_by_name () =
let re = Js.Re.fromString "(?hello|hi) (?\\w+)" in
let result = Js.Re.exec ~str:"hello world" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
let greeting = Js.Re.group "greeting" r in
let target = Js.Re.group "target" r in
assert_string (Option.get greeting) "hello";
assert_string (Option.get target) "world"
let nonexistent_group () =
let re = Js.Re.fromString "(?\\w+)" in
let result = Js.Re.exec ~str:"hello" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
let nonexistent = Js.Re.group "nonexistent" r in
assert_true "nonexistent group is None" (Option.is_none nonexistent)
let all_groups_list () =
let re = Js.Re.fromString "(?\\d)(?\\d)(?\\d)" in
let result = Js.Re.exec ~str:"123" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
let groups = Js.Re.groups r in
assert_int (List.length groups) 3
(* ===================================================================
Named groups with special patterns
=================================================================== *)
let named_group_with_quantifiers () =
let re = Js.Re.fromString "(?\\d+)" in
let result = Js.Re.exec ~str:"abc123def" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r -> assert_string (Option.get (Js.Re.group "digits" r)) "123"
let named_group_with_alternation () =
let re = Js.Re.fromString "(?cat|dog)" in
let result = Js.Re.exec ~str:"I have a cat" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r -> assert_string (Option.get (Js.Re.group "animal" r)) "cat"
(* ===================================================================
Edge cases
=================================================================== *)
let no_named_groups () =
let re = Js.Re.fromString "(\\d+)" in
let result = Js.Re.exec ~str:"123" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
let groups = Js.Re.groups r in
assert_int (List.length groups) 0
let mixed_named_and_unnamed () =
let re = Js.Re.fromString "(\\d+)-(?\\w+)" in
let result = Js.Re.exec ~str:"123-abc" re in
match result with
| None -> Alcotest.fail "Expected match"
| Some r ->
(* Named group should still be accessible *)
assert_string (Option.get (Js.Re.group "name" r)) "abc"
let tests =
[
(* Basic *)
test "basic: date pattern" basic_named_group;
test "basic: single group" single_named_group;
test "basic: multiple groups" multiple_named_groups;
(* Access *)
test "access: by name" group_by_name;
test "access: nonexistent" nonexistent_group;
test "access: all groups list" all_groups_list;
(* Special patterns *)
test "pattern: with quantifiers" named_group_with_quantifiers;
test "pattern: with alternation" named_group_with_alternation;
(* Edge cases *)
test "edge: no named groups" no_named_groups;
test "edge: mixed named and unnamed" mixed_named_and_unnamed;
]
================================================
FILE: packages/Js/test/regexp_tests/unicode.ml
================================================
(** TC39 Test262: RegExp Unicode flag tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/RegExp/unicode
ECMA-262 Section: Unicode flag (u) - enables full Unicode support *)
open Helpers
(* ===================================================================
Basic unicode flag functionality
=================================================================== *)
let unicode_flag_accessor () =
let re_without = Js.Re.fromString "abc" in
let re_with = Js.Re.fromStringWithFlags "abc" ~flags:"u" in
assert_bool (Js.Re.unicode re_without) false;
assert_bool (Js.Re.unicode re_with) true
let flags_includes_u () =
let re = Js.Re.fromStringWithFlags "abc" ~flags:"u" in
let flags = Js.Re.flags re in
assert_true "flags contains u" (String.contains flags 'u')
(* ===================================================================
Unicode character matching
=================================================================== *)
let basic_unicode_match () =
let re = Js.Re.fromStringWithFlags "世界" ~flags:"u" in
assert_bool (Js.Re.test ~str:"hello 世界" re) true
let emoji_match () =
let re = Js.Re.fromStringWithFlags "🎉" ~flags:"u" in
assert_bool (Js.Re.test ~str:"celebrate 🎉!" re) true
let unicode_no_match () =
let re = Js.Re.fromStringWithFlags "世界" ~flags:"u" in
assert_bool (Js.Re.test ~str:"hello world" re) false
(* ===================================================================
Unicode with other flags
=================================================================== *)
let unicode_with_global () =
let re = Js.Re.fromStringWithFlags "\\w+" ~flags:"gu" in
assert_bool (Js.Re.global re) true;
assert_bool (Js.Re.unicode re) true
let unicode_with_ignorecase () =
let re = Js.Re.fromStringWithFlags "abc" ~flags:"ui" in
assert_bool (Js.Re.unicode re) true;
assert_bool (Js.Re.ignoreCase re) true;
assert_bool (Js.Re.test ~str:"ABC" re) true
let unicode_with_multiline () =
let re = Js.Re.fromStringWithFlags "^hello" ~flags:"um" in
assert_bool (Js.Re.unicode re) true;
assert_bool (Js.Re.multiline re) true
(* ===================================================================
Unicode property escapes (\p{} and \P{})
Note: Support depends on the regex engine implementation
=================================================================== *)
let unicode_letter_category () =
(* This test checks if the regex with unicode flag compiles *)
(* Support for \p{L} depends on implementation *)
let re = Js.Re.fromStringWithFlags "\\p{L}+" ~flags:"u" in
assert_bool (Js.Re.unicode re) true
(* ===================================================================
Unicode astral plane characters
=================================================================== *)
let astral_plane_chars () =
(* Test with emoji (from astral plane) *)
let re = Js.Re.fromStringWithFlags "." ~flags:"u" in
(* In unicode mode, . should match full emoji codepoint *)
assert_bool (Js.Re.test ~str:"🎉" re) true
let surrogate_pairs () =
(* UTF-16 surrogate pairs should be treated as single codepoint in unicode mode *)
let re = Js.Re.fromStringWithFlags "^..$" ~flags:"u" in
(* Two emoji characters *)
assert_bool (Js.Re.test ~str:"🎉🎊" re) true
(* ===================================================================
Practical examples
=================================================================== *)
let international_text () =
let re = Js.Re.fromStringWithFlags "こんにちは" ~flags:"u" in
assert_bool (Js.Re.test ~str:"こんにちは世界" re) true
let mixed_scripts () =
let re = Js.Re.fromStringWithFlags "Hello 世界 🌍" ~flags:"u" in
assert_bool (Js.Re.test ~str:"Hello 世界 🌍!" re) true
let tests =
[
(* Basic flag *)
test "flag: accessor" unicode_flag_accessor;
test "flag: in flags string" flags_includes_u;
(* Character matching *)
test "match: basic unicode" basic_unicode_match;
test "match: emoji" emoji_match;
test "match: no match" unicode_no_match;
(* Combined flags *)
test "combined: with global" unicode_with_global;
test "combined: with ignorecase" unicode_with_ignorecase;
test "combined: with multiline" unicode_with_multiline;
(* Property escapes *)
test "property: letter category" unicode_letter_category;
(* Astral plane *)
test "astral: basic" astral_plane_chars;
test "astral: surrogate pairs" surrogate_pairs;
(* Practical *)
test "practical: international" international_text;
test "practical: mixed scripts" mixed_scripts;
]
================================================
FILE: packages/Js/test/string_tests/normalize.ml
================================================
(** TC39 Test262: String.prototype.normalize tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/String/prototype/normalize
ECMA-262 Section: String.prototype.normalize([form])
Note: Unicode normalization forms:
- NFC: Canonical Decomposition, followed by Canonical Composition
- NFD: Canonical Decomposition
- NFKC: Compatibility Decomposition, followed by Canonical Composition
- NFKD: Compatibility Decomposition *)
open Helpers
(* ===================================================================
Default normalization (NFC)
=================================================================== *)
let default_form () =
(* Without form argument, NFC is used *)
let composed = "café" in
(* é as single codepoint U+00E9 *)
assert_string (Js.String.normalize composed) composed;
let decomposed = "cafe\u{0301}" in
(* e + combining acute accent *)
assert_string (Js.String.normalize decomposed) composed
let empty_string () = assert_string (Js.String.normalize "") ""
let ascii_unchanged () =
assert_string (Js.String.normalize "hello") "hello";
assert_string (Js.String.normalize "ABC123") "ABC123"
(* ===================================================================
NFC - Canonical Composition
=================================================================== *)
let nfc_basic () =
let decomposed = "e\u{0301}" in
(* e + combining acute *)
let composed = "é" in
assert_string (Js.String.normalize ~form:`NFC decomposed) composed
let nfc_multiple_accents () =
(* Multiple combining characters *)
let decomposed = "e\u{0301}\u{0327}" in
(* e + acute + cedilla *)
let result = Js.String.normalize ~form:`NFC decomposed in
(* Result varies by Unicode version, just check it's normalized *)
assert_true "NFC result not empty" (String.length result > 0)
(* ===================================================================
NFD - Canonical Decomposition
=================================================================== *)
let nfd_basic () =
let composed = "é" in
(* U+00E9 *)
let decomposed = "e\u{0301}" in
assert_string (Js.String.normalize ~form:`NFD composed) decomposed
let nfd_already_decomposed () =
let decomposed = "e\u{0301}" in
assert_string (Js.String.normalize ~form:`NFD decomposed) decomposed
(* ===================================================================
NFKC - Compatibility Composition
=================================================================== *)
let nfkc_ligature () =
(* fi ligature U+FB01 -> "fi" *)
let ligature = "\u{FB01}" in
assert_string (Js.String.normalize ~form:`NFKC ligature) "fi"
let nfkc_superscript () =
(* Superscript 2 U+00B2 -> "2" *)
let superscript = "\u{00B2}" in
assert_string (Js.String.normalize ~form:`NFKC superscript) "2"
(* ===================================================================
NFKD - Compatibility Decomposition
=================================================================== *)
let nfkd_ligature () =
(* fi ligature U+FB01 -> "fi" *)
let ligature = "\u{FB01}" in
assert_string (Js.String.normalize ~form:`NFKD ligature) "fi"
let nfkd_with_accents () =
(* Composed character with compatibility decomposition *)
let nfkd = Js.String.normalize ~form:`NFKD "fi" in
assert_string nfkd "fi"
(* ===================================================================
Edge cases
=================================================================== *)
let hangul_syllable () =
(* Korean syllable normalization *)
let syllable = "가" in
(* U+AC00 *)
assert_string (Js.String.normalize ~form:`NFC syllable) syllable
let combining_sequences () =
(* Canonical ordering of combining marks *)
let text = "a\u{0308}\u{0323}" in
(* a + umlaut + dot below *)
let result = Js.String.normalize ~form:`NFC text in
assert_true "NFC combining sequence" (String.length result > 0)
let tests =
[
(* Default form *)
test "default form (NFC)" default_form;
test "empty string" empty_string;
test "ASCII unchanged" ascii_unchanged;
(* NFC *)
test "NFC: basic" nfc_basic;
test "NFC: multiple accents" nfc_multiple_accents;
(* NFD *)
test "NFD: basic" nfd_basic;
test "NFD: already decomposed" nfd_already_decomposed;
(* NFKC *)
test "NFKC: ligature" nfkc_ligature;
test "NFKC: superscript" nfkc_superscript;
(* NFKD *)
test "NFKD: ligature" nfkd_ligature;
test "NFKD: with accents" nfkd_with_accents;
(* Edge cases *)
test "Hangul syllable" hangul_syllable;
test "combining sequences" combining_sequences;
]
================================================
FILE: packages/Js/test/string_tests/search.ml
================================================
(** TC39 Test262: String.prototype.search tests
Based on: https://github.com/tc39/test262/tree/main/test/built-ins/String/prototype/search
ECMA-262 Section: String.prototype.search(regexp) *)
open Helpers
(* ===================================================================
Basic search functionality
=================================================================== *)
let basic_match () =
let re = Js.Re.fromString "world" in
assert_int (Js.String.search ~regexp:re "hello world") 6
let no_match () =
let re = Js.Re.fromString "xyz" in
assert_int (Js.String.search ~regexp:re "hello world") (-1)
let match_at_start () =
let re = Js.Re.fromString "hello" in
assert_int (Js.String.search ~regexp:re "hello world") 0
let match_at_end () =
let re = Js.Re.fromString "world" in
assert_int (Js.String.search ~regexp:re "hello world") 6
let empty_string () =
let re = Js.Re.fromString "a" in
assert_int (Js.String.search ~regexp:re "") (-1)
let empty_pattern () =
let re = Js.Re.fromString "" in
assert_int (Js.String.search ~regexp:re "hello") 0
(* ===================================================================
Case sensitivity
=================================================================== *)
let case_sensitive () =
let re = Js.Re.fromString "WORLD" in
assert_int (Js.String.search ~regexp:re "hello world") (-1)
let case_insensitive () =
let re = Js.Re.fromStringWithFlags "world" ~flags:"i" in
assert_int (Js.String.search ~regexp:re "hello WORLD") 6
(* ===================================================================
Special patterns
=================================================================== *)
let digit_class () =
let re = Js.Re.fromString "\\d+" in
assert_int (Js.String.search ~regexp:re "abc123def") 3
let word_boundary () =
let re = Js.Re.fromString "\\bworld\\b" in
assert_int (Js.String.search ~regexp:re "hello world here") 6
let any_character () =
let re = Js.Re.fromString "w.rld" in
assert_int (Js.String.search ~regexp:re "hello world") 6
let alternation () =
let re = Js.Re.fromString "cat|dog" in
assert_int (Js.String.search ~regexp:re "I have a dog") 9;
assert_int (Js.String.search ~regexp:re "I have a cat") 9
let optional_character () =
let re = Js.Re.fromString "colou?r" in
assert_int (Js.String.search ~regexp:re "colour") 0;
assert_int (Js.String.search ~regexp:re "color") 0
(* ===================================================================
Multiple occurrences (search finds first)
=================================================================== *)
let multiple_matches () =
let re = Js.Re.fromString "a" in
assert_int (Js.String.search ~regexp:re "banana") 1
let global_flag_ignored () =
(* search should return first match regardless of global flag *)
let re = Js.Re.fromStringWithFlags "a" ~flags:"g" in
assert_int (Js.String.search ~regexp:re "banana") 1
(* ===================================================================
Unicode
=================================================================== *)
let unicode_characters () =
let re = Js.Re.fromString "世界" in
assert_int (Js.String.search ~regexp:re "hello 世界") 6
let emoji () =
let re = Js.Re.fromString "🎉" in
let result = Js.String.search ~regexp:re "celebrate 🎉!" in
assert_true "emoji found" (result >= 0)
(* ===================================================================
Edge cases
=================================================================== *)
let special_regex_chars () =
let re = Js.Re.fromString "\\." in
assert_int (Js.String.search ~regexp:re "hello.world") 5
let newline () =
let re = Js.Re.fromString "world" in
assert_int (Js.String.search ~regexp:re "hello\nworld") 6
let tests =
[
(* Basic *)
test "basic: match" basic_match;
test "basic: no match" no_match;
test "basic: match at start" match_at_start;
test "basic: match at end" match_at_end;
test "basic: empty string" empty_string;
test "basic: empty pattern" empty_pattern;
(* Case sensitivity *)
test "case: sensitive" case_sensitive;
test "case: insensitive" case_insensitive;
(* Special patterns *)
test "pattern: digit class" digit_class;
test "pattern: word boundary" word_boundary;
test "pattern: any character" any_character;
test "pattern: alternation" alternation;
test "pattern: optional" optional_character;
(* Multiple matches *)
test "multiple: finds first" multiple_matches;
test "multiple: global flag ignored" global_flag_ignored;
(* Unicode *)
test "unicode: characters" unicode_characters;
test "unicode: emoji" emoji;
(* Edge cases *)
test "edge: special regex chars" special_regex_chars;
test "edge: newline" newline;
]
================================================
FILE: packages/Js/test/test.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let assert_option x left right = Alcotest.check (Alcotest.option x) "should be equal" right left
let assert_array ty left right = Alcotest.check (Alcotest.array ty) "should be equal" right left
let assert_string_array = assert_array Alcotest.string
let assert_string_option_array = assert_array (Alcotest.option Alcotest.string)
let assert_array_int = assert_array Alcotest.int
let assert_dict_entries type_ left right =
Alcotest.check (Alcotest.array (Alcotest.pair Alcotest.string type_)) "should be equal" right left
let assert_int_dict_entries = assert_dict_entries Alcotest.int
let assert_string_dict_entries = assert_dict_entries Alcotest.string
let assert_option_int = assert_option Alcotest.int
(* let assert_option_string = assert_option Alcotest.string *)
let assert_int left right = Alcotest.check Alcotest.int "should be equal" right left
let assert_float left right = Alcotest.check (Alcotest.float 2.) "should be equal" right left
let assert_bool left right = Alcotest.check Alcotest.bool "should be equal" right left
let assert_raises fn exn =
match fn () with
| exception exn -> assert_string (Printexc.to_string exn) (Printexc.to_string exn)
| _ -> Alcotest.failf "Expected exception %s" (Printexc.to_string exn)
let test title fn = Alcotest_lwt.test_case_sync title `Quick fn
let test_async title fn = Alcotest_lwt.test_case title `Quick fn
let re_tests =
[
test "captures" (fun () ->
let abc_regex = Js.Re.fromString "abc" in
let result = Js.Re.exec ~str:"abcdefabcdef" abc_regex |> Option.get in
let matches = Js.Re.captures result |> Array.map Option.get in
assert_string_array matches [| "abc" |]);
test "exec" (fun () ->
let regex = Js.Re.fromString ".ats" in
let input = "cats and bats" in
let regex_and_capture = Js.Re.exec ~str:input regex |> Option.get |> Js.Re.captures |> Array.map Option.get in
assert_string_array regex_and_capture [| "cats" |];
assert_string_array regex_and_capture [| "cats" |];
assert_string_array regex_and_capture [| "cats" |]);
test "exec with global" (fun () ->
let regex = Js.Re.fromStringWithFlags ~flags:"g" ".ats" in
let input = "cats and bats and mats" in
assert_bool (Js.Re.global regex) true;
assert_string_array
(Js.Re.exec ~str:input regex |> Option.get |> Js.Re.captures |> Array.map Option.get)
[| "cats" |];
assert_string_array
(Js.Re.exec ~str:input regex |> Option.get |> Js.Re.captures |> Array.map Option.get)
[| "bats" |];
assert_string_array
(Js.Re.exec ~str:input regex |> Option.get |> Js.Re.captures |> Array.map Option.get)
[| "mats" |]);
test "modifier: end ($)" (fun () ->
let regex = Js.Re.fromString "cat$" in
assert_bool (Js.Re.test ~str:"The cat and mouse" regex) false;
assert_bool (Js.Re.test ~str:"The mouse and cat" regex) true);
test "modifier: more than one (+)" (fun () ->
let regex = Js.Re.fromStringWithFlags ~flags:"i" "boo+(hoo+)+" in
assert_bool (Js.Re.test ~str:"Boohoooohoohooo" regex) true);
test "global (g) and caseless (i)" (fun () ->
let regex = Js.Re.fromStringWithFlags ~flags:"gi" "Hello" in
let result = Js.Re.exec ~str:"Hello gello! hello" regex |> Option.get in
let matches = Js.Re.captures result |> Array.map Option.get in
assert_string_array matches [| "Hello" |];
let result = Js.Re.exec ~str:"Hello gello! hello" regex |> Option.get in
let matches = Js.Re.captures result |> Array.map Option.get in
assert_string_array matches [| "hello" |]);
test "modifier: or ([])" (fun () ->
let regex = Js.Re.fromString "(\\w+)\\s(\\w+)" in
assert_bool (Js.Re.test ~str:"Jane Smith" regex) true;
assert_bool (Js.Re.test ~str:"Wololo" regex) false);
test "backreferencing" (fun () ->
let regex = Js.Re.fromString "[bt]ear" in
assert_bool (Js.Re.test ~str:"bear" regex) true;
assert_bool (Js.Re.test ~str:"tear" regex) true;
assert_bool (Js.Re.test ~str:"fear" regex) false);
test "http|s example" (fun () ->
let regex = Js.Re.fromString "^[https?]+:\\/\\/((w{3}\\.)?[\\w+]+)\\.[\\w+]+$" in
assert_bool (Js.Re.test ~str:"https://www.example.com" regex) true;
assert_bool (Js.Re.test ~str:"http://example.com" regex) true;
assert_bool (Js.Re.test ~str:"https://example" regex) false);
test "index" (fun () ->
let regex = Js.Re.fromString "zbar" in
match Js.Re.exec ~str:"foobarbazbar" regex with
| Some res -> assert_int (Js.Re.index res) 8
| None -> Alcotest.fail "should have matched");
test "lastIndex" (fun () ->
let regex = Js.Re.fromStringWithFlags ~flags:"g" "y" in
Js.Re.setLastIndex regex 3;
match Js.Re.exec ~str:"xyzzy" regex with
| Some res ->
assert_int (Js.Re.index res) 4;
assert_int (Js.Re.lastIndex regex) 5
| None -> Alcotest.fail "should have matched");
test "input" (fun () ->
let regex = Js.Re.fromString "zbar" in
match Js.Re.exec ~str:"foobarbazbar" regex with
| Some res -> assert_string (Js.Re.input res) "foobarbazbar"
| None -> Alcotest.fail "should have matched");
]
let string_tests =
[
test "make" (fun () ->
(* assert_string (make 3.5) "3.5"; *)
(* assert_string (make [| 1; 2; 3 |]) "1,2,3"); *)
());
test "length" (fun () -> assert_int (Js.String.length "abcd") 4);
test "get" (fun () ->
assert_string (Js.String.get "Reason" 0) "R";
assert_string (Js.String.get "Reason" 4) "o" (* assert_string (Js.String.get {js|Rẽasöń|js} 5) {js|ń|js}; *));
test "fromCharCode" (fun () ->
assert_string (Js.String.fromCharCode 65) "A";
(* assert_string (Js.String.fromCharCode 0x3c8) {js|ψ|js}; *)
(* assert_string (Js.String.fromCharCode 0xd55c) {js|한|js} *)
(* assert_string (Js.String.fromCharCode -64568) {js|ψ|js}; *)
());
test "fromCharCodeMany" (fun () ->
(* fromCharCodeMany([|0xd55c, 0xae00, 33|]) = {js|한글!|js} *)
());
test "fromCodePoint" (fun () ->
assert_string (Js.String.fromCodePoint 65) "A"
(* assert_string (Js.String.fromCodePoint 0x3c8) {js|ψ|js}; *)
(* assert_string (Js.String.fromCodePoint 0xd55c) {js|한|js} *)
(* assert_string (Js.String.fromCodePoint 0x1f63a) {js|😺|js} *));
test "fromCodePointMany" (fun () ->
(* assert_string
(Js.String.fromCodePointMany [| 0xd55c; 0xae00; 0x1f63a |])
{js|한글😺|js} *)
());
test "charAt" (fun () ->
assert_string (Js.String.charAt "Reason" ~index:0) "R";
assert_string (Js.String.charAt "Reason" ~index:12) ""
(* assert_string (Js.String.charAt {js|Rẽasöń|js} 5) {js|ń|js} *));
test "charCodeAt" (fun () ->
(* charCodeAt {js|😺|js} 0) 0xd83d *)
assert_float (Js.String.charCodeAt "lola" ~index:1) 111.;
assert_float (Js.String.charCodeAt "lola" ~index:0) 108.);
test "codePointAt" (fun () ->
assert_option_int (Js.String.codePointAt "lola" ~index:1) (Some 111);
(* assert_option_int (Js.String.codePointAt {js|¿😺?|js} 1) (Some 0x1f63a); *)
assert_option_int (Js.String.codePointAt "abc" ~index:5) None);
test "concat" (fun () -> assert_string (Js.String.concat "cow" ~other:"bell") "cowbell");
test "concatMany" (fun () ->
assert_string (Js.String.concatMany "1st" ~strings:[| "2nd"; "3rd"; "4th" |]) "1st2nd3rd4th");
test "endsWith" (fun () ->
assert_bool (Js.String.endsWith "ReScript" ~suffix:"Script") true;
assert_bool (Js.String.endsWith "ReShoes" ~suffix:"Script") false;
assert_bool (Js.String.endsWith "abcd" ~suffix:"cd" ~len:4) true;
assert_bool (Js.String.endsWith "abcde" ~suffix:"cd" ~len:3) false;
(* assert_bool (Js.String.endsWith "abcde" ~suffix:"cde" ~len:99) true; *)
assert_bool (Js.String.endsWith "example.dat" ~suffix:"ple" ~len:7) true);
test "includes" (fun () ->
assert_bool (Js.String.includes "programmer" ~search:"gram") true;
assert_bool (Js.String.includes "programmer" ~search:"er") true;
assert_bool (Js.String.includes "programmer" ~search:"pro") true;
assert_bool (Js.String.includes "programmer" ~search:"xyz") false;
assert_bool (Js.String.includes "programmer" ~search:"gram" ~start:1) true;
assert_bool (Js.String.includes "programmer" ~search:"gram" ~start:4) false
(* assert_bool (Js.String.includesFrom {js|한|js} {js|대한민국|js} 1) true *));
test "indexOf" (fun () ->
assert_int (Js.String.indexOf "bookseller" ~search:"ok") 2;
assert_int (Js.String.indexOf "bookseller" ~search:"sell") 4;
assert_int (Js.String.indexOf "beekeeper" ~search:"ee") 1;
assert_int (Js.String.indexOf "bookseller" ~search:"xyz") (-1);
assert_int (Js.String.indexOf "bookseller" ~search:"ok" ~start:1) 2;
assert_int (Js.String.indexOf "bookseller" ~search:"sell" ~start:2) 4;
assert_int (Js.String.indexOf "bookseller" ~search:"sell" ~start:5) (-1);
assert_int (Js.String.indexOf "bookseller" ~search:"xyz") (-1));
test "lastIndexOf" (fun () ->
assert_int (Js.String.lastIndexOf "bookseller" ~search:"ok") 2;
assert_int (Js.String.lastIndexOf "beekeeper" ~search:"ee") 4;
assert_int (Js.String.lastIndexOf "abcdefg" ~search:"xyz") (-1);
assert_int (Js.String.lastIndexOf "bookseller" ~search:"ok" ~start:6) 2;
assert_int (Js.String.lastIndexOf "beekeeper" ~search:"ee" ~start:8) 4;
assert_int (Js.String.lastIndexOf "beekeeper" ~search:"ee" ~start:3) 1;
assert_int (Js.String.lastIndexOf "abcdefg" ~search:"xyz" ~start:4) (-1));
(* test "localeCompare" (fun () ->
localeCompare "ant" "zebra" > 0.0
localeCompare "zebra" "ant" < 0.0
localeCompare "cat" "cat" = 0.0
localeCompare "cat" "CAT" > 0.0
());
*)
test "match" (fun () ->
let unsafe_match s r = Js.String.match_ ~regexp:r s |> Stdlib.Option.get in
assert_string_option_array (unsafe_match "The better bats" (Js.Re.fromString "b[aeiou]t")) [| Some "bet" |]);
test "match 0" (fun () ->
let unsafe_match r s = Js.String.match_ ~regexp:r s |> Stdlib.Option.value ~default:[||] in
assert_string_option_array (unsafe_match (Js.Re.fromString "b[aeiou]t") "The better bats") [| Some "bet" |];
assert_string_option_array
(unsafe_match (Js.Re.fromStringWithFlags "b[aeiou]t" ~flags:"g") "The better bats")
[| Some "bet"; Some "bat" |];
assert_string_option_array
(unsafe_match [%re "/(\\d+)-(\\d+)-(\\d+)/"] "Today is 2018-04-05.")
[| Some "2018-04-05"; Some "2018"; Some "04"; Some "05" |];
assert_string_option_array (unsafe_match [%re "/b[aeiou]g/"] "The large container.") [||]);
test "repeat" (fun () ->
assert_string (Js.String.repeat "ha" ~count:3) "hahaha";
assert_string (Js.String.repeat "empty" ~count:0) "");
test "replace" (fun () ->
assert_string (Js.String.replace ~search:"old" ~replacement:"new" "old string") "new string";
assert_string (Js.String.replace ~search:"the" ~replacement:"this" "the cat and the dog") "this cat and the dog");
test "replaceByRe" (fun () ->
assert_string (Js.String.replaceByRe "david" ~regexp:[%re "/d/"] ~replacement:"x") "xavid");
test "replaceByRe with references ($n)" (fun () ->
assert_string (Js.String.replaceByRe "david" ~regexp:[%re "/d(.*?)d/g"] ~replacement:"$1") "avi");
test "replaceByRe with $1 capturing group" (fun () ->
assert_string
(Js.String.replaceByRe "hello world" ~regexp:[%re "/(.*?)<\\/em>/gi"] ~replacement:"$1")
"hello world");
test "replaceByRe with multiple capturing groups" (fun () ->
assert_string
(Js.String.replaceByRe "John Smith" ~regexp:[%re "/(\\w+)\\s(\\w+)/"] ~replacement:"$2, $1")
"Smith, John");
test "replaceByRe with $&" (fun () ->
assert_string (Js.String.replaceByRe "hello" ~regexp:[%re "/l/g"] ~replacement:"[$&]") "he[l][l]o");
test "replaceByRe with $$" (fun () ->
assert_string (Js.String.replaceByRe "price" ~regexp:[%re "/price/"] ~replacement:"$$100") "$100");
test "replaceByRe with global" (fun () ->
assert_string
(Js.String.replaceByRe "vowels be gone" ~regexp:[%re "/[aeiou]/g"] ~replacement:"x")
"vxwxls bx gxnx");
test "unsafeReplaceBy0" (fun () ->
(* let str = "beautiful vowels" in
let re = [%re "/[aeiou]/g"] in
let matchFn matchPart offset wholeString =
Js.String.toUpperCase matchPart
in
let replaced = Js.String.unsafeReplaceBy0 re matchFn str in
assert_string replaced "bEAUtifUl vOwEls" *)
());
test "unsafeReplaceBy1" (fun () ->
(* let str = "increment 23" in
let re = [%re "/increment (\\d+)/g"] in
let matchFn matchPart p1 offset wholeString =
wholeString ^ " is " ^ string_of_int (int_of_string p1 + 1)
in
let replaced = Js.String.unsafeReplaceBy1 re matchFn str in
assert_string replaced "increment 23 is 24" *)
());
test "unsafeReplaceBy2" (fun () ->
(* let str = "7 times 6" in
let re = [%re "/(\\d+) times (\\d+)/"] in
let matchFn matchPart p1 p2 offset wholeString =
string_of_int (int_of_string p1 * int_of_string p2)
in
let replaced = Js.String.unsafeReplaceBy2 re matchFn str in
assert_string replaced "42" *)
());
test "search" (fun () ->
(* assert_int (Js.String.search [%re "/\\d+/"] "testing 1 2 3") 8;
assert_int (Js.String.search [%re "/\\d+/"] "no numbers") (-1) *)
());
test "slice" (fun () ->
assert_string (Js.String.slice ~start:2 ~end_:5 "abcdefg") "cde";
assert_string (Js.String.slice ~start:2 ~end_:9 "abcdefg") "cdefg";
(* assert_string (Js.String.slice ~from:(-4) ~to_:(-2) "abcdefg") "de"; *)
assert_string (Js.String.slice ~start:5 ~end_:1 "abcdefg") "";
assert_string (Js.String.slice ~start:4 "abcdefg") "efg";
(* assert_string (Js.String.sliceToEnd ~from:(-2) "abcdefg") "fg"; *)
assert_string (Js.String.slice ~start:7 "abcdefg") "");
test "split" (fun () ->
assert_string_array (Js.String.split ~sep:"" "") [||];
assert_string_array (Js.String.split ~sep:"-" "2018-01-02") [| "2018"; "01"; "02" |];
assert_string_array (Js.String.split ~sep:"," "a,b,,c") [| "a"; "b"; ""; "c" |];
assert_string_array
(Js.String.split ~sep:"::" "good::bad as great::awful")
[| "good"; "bad as great"; "awful" |];
assert_string_array (Js.String.split ~sep:";" "has-no-delimiter") [| "has-no-delimiter" |];
assert_string_array
(Js.String.split ~sep:"with" "with-sep-equals-to-beginning")
[| ""; "-sep-equals-to-beginning" |];
assert_string_array (Js.String.split ~sep:"end" "with-sep-equals-to-end") [| "with-sep-equals-to-"; "" |];
assert_string_array
(Js.String.split ~sep:"/" "/with-sep-on-beginning-and-end/")
[| ""; "with-sep-on-beginning-and-end"; "" |];
assert_string_array
(Js.String.split ~sep:"" "with-empty-sep")
[| "w"; "i"; "t"; "h"; "-"; "e"; "m"; "p"; "t"; "y"; "-"; "s"; "e"; "p" |];
assert_string_array (Js.String.split ~sep:"-" "with-limit-equals-to-zero" ~limit:0) [||];
assert_string_array
(Js.String.split ~sep:"-" "with-limit-equals-to-length" ~limit:5)
[| "with"; "limit"; "equals"; "to"; "length" |];
assert_string_array
(Js.String.split ~sep:"-" "with-limit-greater-than-length" ~limit:100)
[| "with"; "limit"; "greater"; "than"; "length" |];
assert_string_array
(Js.String.split ~sep:"-" "with-limit-less-than-zero" ~limit:(-2))
[| "with"; "limit"; "less"; "than"; "zero" |]);
test "splitAtMost" (fun () ->
(* assert_string_array
(splitAtMost "/" ~limit:3 "ant/bee/cat/dog/elk")
[| "ant"; "bee"; "cat" |];
assert_string_array
(splitAtMost "/" ~limit:0 "ant/bee/cat/dog/elk")
[||];
assert_string_array
(splitAtMost "/" ~limit:9 "ant/bee/cat/dog/elk")
[| "ant"; "bee"; "cat"; "dog"; "elk" |] *)
());
test "splitByRe" (fun () ->
let unsafe_splitByRe s r = Js.String.splitByRe ~regexp:r s |> Stdlib.Array.map Stdlib.Option.get in
assert_string_array
(unsafe_splitByRe "art; bed , cog ;dad" [%re "/\\s*[,;]\\s*/"])
[| "art"; "bed"; "cog"; "dad" |]
(* assert_string_array
(unsafe_splitByRe "has:no:match" [%re "/[,;]/"])
[| "has:no:match" |] *));
(* test "splitByReAtMost" (fun () ->
assert_string_array
(splitByReAtMost [%re "/\\s*:\\s*/"] ~limit:3
"one: two: three: four")
[| Some "one"; Some "two"; Some "three" |];
assert_string_array
(splitByReAtMost [%re "/\\s*:\\s*/"] ~limit:0
"one: two: three: four")
[||];
assert_string_array
(splitByReAtMost [%re "/\\s*:\\s*/"] ~limit:8
"one: two: three: four")
[| Some "one"; Some "two"; Some "three"; Some "four" |];
assert_string_array
(splitByReAtMost [%re "/(#)(:)?/"] ~limit:3 "a#b#:c")
[| Some "a"; Some "#"; None |]
());*)
test "startsWith" (fun () ->
assert_bool (Js.String.startsWith "ReScript" ~prefix:"Re") true;
assert_bool (Js.String.startsWith "ReScript" ~prefix:"") true;
assert_bool (Js.String.startsWith "JavaScript" ~prefix:"Re") false;
assert_bool (Js.String.startsWith ~prefix:"cri" ~start:3 "ReScript") true;
assert_bool (Js.String.startsWith ~prefix:"" ~start:3 "ReScript") true;
assert_bool (Js.String.startsWith ~prefix:"Re" ~start:2 "JavaScript") false);
test "substr" (fun () ->
assert_string (Js.String.substr ~start:3 "abcdefghij") "defghij";
(* assert_string (Js.String.substr ~from:(-3) "abcdefghij") "hij"; *)
assert_string (Js.String.substr ~start:12 "abcdefghij") "");
test "substrAtMost" (fun () ->
(* assert_string (Js.String.substrAtMost ~from:3 ~length:4 "abcdefghij") "defghij"; *)
(* assert_string (Js.String.substrAtMost ~from:(-3) ~length:4 "abcdefghij") "hij"; *)
(* assert_string (Js.String.substrAtMost ~from:12 ~length:2 "abcdefghij") "" *)
());
test "substring" (fun () ->
assert_string (Js.String.substring ~start:3 ~end_:6 "playground") "ygr";
assert_string (Js.String.substring ~start:6 ~end_:3 "playground") "ygr";
assert_string (Js.String.substring ~start:4 ~end_:12 "playground") "ground";
assert_string (Js.String.substring ~start:4 "playground") "ground";
assert_string (Js.String.substring ~start:(-3) "playground") "playground";
assert_string (Js.String.substring ~start:12 "playground") "");
test "toLowerCase" (fun () ->
assert_string (Js.String.toLowerCase "") "";
assert_string (Js.String.toLowerCase "ASCII: ABC") "ascii: abc";
assert_string (Js.String.toLowerCase "Non ASCII: ΣΠ") "non ascii: σπ";
assert_string (Js.String.toLowerCase "Unicode Σ: \u{03a3}") "unicode σ: \u{03c3}";
assert_string
(Js.String.toLowerCase "Unicode Mongolian separator + Σ + Mongolian separator: \u{180E} + \u{03a3} + \u{180E}")
"unicode mongolian separator + σ + mongolian separator: \u{180E} + \u{03c3} + \u{180E}");
test "toUpperCase" (fun () ->
assert_string (Js.String.toUpperCase "") "";
assert_string (Js.String.toUpperCase "abc") "ABC";
assert_string (Js.String.toUpperCase "Non ASCII: σπ") "NON ASCII: ΣΠ";
assert_string (Js.String.toUpperCase "Unicode: \u{03c3}") "UNICODE: \u{03a3}";
assert_string
(Js.String.toUpperCase "Unicode Mongolian separator + σ + Mongolian separator: \u{180E} + \u{03c3} + \u{180E}")
"UNICODE MONGOLIAN SEPARATOR + Σ + MONGOLIAN SEPARATOR: \u{180E} + \u{03a3} + \u{180E}");
test "trim" (fun () ->
assert_string (Js.String.trim " abc def ") "abc def";
assert_string (Js.String.trim "\n\r\t abc def \n\n\t\r ") "abc def");
test "anchor" (fun () ->
(* assert_string
(anchor "page1" "Page One")
"Page One" *)
());
test "link" (fun () ->
(* assert_string
(link "page2.html" "Go to page two")
"Go to page two" *)
());
]
let global_tests =
[
test "decodeURI - ascii and spaces" (fun () ->
assert_string (Js.Global.decodeURI "Hello%20World") "Hello World";
assert_string (Js.Global.decodeURI "Hello%20%20%20World") "Hello World";
assert_string (Js.Global.decodeURI "Hello%2DWorld") "Hello-World");
test "decodeURI - reserved characters" (fun () ->
assert_string (Js.Global.decodeURI ";,/?:@&=+$#") ";,/?:@&=+$#";
assert_string (Js.Global.decodeURI "-_.!~*'()") "-_.!~*'()";
assert_string (Js.Global.decodeURI "%5B%5D") "[]";
assert_string (Js.Global.decodeURI "%7B%7D") "{}";
assert_string (Js.Global.decodeURI "%7C") "|");
test "decodeURI - alphabets" (fun () ->
assert_string (Js.Global.decodeURI "ABCDEFGHIJKLMNOPQRSTUVWXYZ") "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert_string (Js.Global.decodeURI "abcdefghijklmnopqrstuvwxyz") "abcdefghijklmnopqrstuvwxyz";
assert_string (Js.Global.decodeURI "0123456789") "0123456789");
test "decodeURI - unicode characters" (fun () ->
assert_string (Js.Global.decodeURI "%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4") "Юникод";
assert_string (Js.Global.decodeURI "%E2%82%AC%E2%98%85%E2%99%A0") "€★♠";
assert_string (Js.Global.decodeURI "%E4%BD%A0%E5%A5%BD") "你好");
test "decodeURI - mixed percent encodings and Unicode" (fun () ->
assert_string (Js.Global.decodeURI "Hello%20%E4%BD%A0%E5%A5%BD%20World") "Hello 你好 World";
assert_string (Js.Global.decodeURI "%E2%82%AC%20%24%20%C2%A3%20%C2%A5") "€ %24 £ ¥");
test "decodeURI - complete URLs" (fun () ->
assert_string
(Js.Global.decodeURI "http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4")
"http://ru.wikipedia.org/wiki/Юникод";
assert_string
(Js.Global.decodeURI "http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork")
"http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork";
assert_string
(Js.Global.decodeURI "https://example.com/path%20name/file.txt")
"https://example.com/path name/file.txt");
test "decodeURI - overencoded sequences" (fun () ->
assert_string (Js.Global.decodeURI "Hello%2520World") "Hello%20World";
assert_string (Js.Global.decodeURI "%25252525") "%252525");
test "decodeURI - special characters" (fun () ->
assert_string (Js.Global.decodeURI "%0A") "\n";
assert_string (Js.Global.decodeURI "%0D") "\r";
assert_string (Js.Global.decodeURI "%3C%3E%22%5C") "<>\"\\");
test "decodeURI - beyond U+10FFFF" (fun () ->
assert_raises (fun () -> Js.Global.decodeURI "%F4%90%80%80") (Failure "decodeURI: malformed URI sequence"));
test "decodeURI - partial sequences" (fun () ->
(* Incomplete or malformed sequences *)
assert_raises (fun () -> Js.Global.decodeURI "%E4") (Failure "decodeURI: malformed URI sequence");
assert_raises (fun () -> Js.Global.decodeURI "%E4%A") (Failure "decodeURI: malformed URI sequence"));
test "encodeURI - ascii and spaces" (fun () ->
assert_string (Js.Global.encodeURI "Hello World") "Hello%20World";
assert_string (Js.Global.encodeURI "Hello World") "Hello%20%20%20World";
assert_string (Js.Global.encodeURI "Hello-World") "Hello-World");
test "encodeURI - reserved characters" (fun () ->
assert_string (Js.Global.encodeURI ";,/?:@&=+$#") ";,/?:@&=+$#";
assert_string (Js.Global.encodeURI "-_.!~*'()") "-_.!~*'()");
test "encodeURI - alphabets" (fun () ->
assert_string (Js.Global.encodeURI "ABCDEFGHIJKLMNOPQRSTUVWXYZ") "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert_string (Js.Global.encodeURI "abcdefghijklmnopqrstuvwxyz") "abcdefghijklmnopqrstuvwxyz";
assert_string (Js.Global.encodeURI "0123456789") "0123456789");
test "encodeURI - unicode characters" (fun () ->
assert_string (Js.Global.encodeURI "Юникод") "%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4";
assert_string (Js.Global.encodeURI "€★♠") "%E2%82%AC%E2%98%85%E2%99%A0";
assert_string (Js.Global.encodeURI "你好") "%E4%BD%A0%E5%A5%BD");
test "encodeURI - complete URLs" (fun () ->
assert_string (Js.Global.encodeURI "http://unipro.ru/0123456789") "http://unipro.ru/0123456789";
assert_string
(Js.Global.encodeURI "http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork")
"http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork";
assert_string (Js.Global.encodeURI "http://unipro.ru/\nabout") "http://unipro.ru/%0Aabout";
assert_string (Js.Global.encodeURI "http://unipro.ru/\rabout") "http://unipro.ru/%0Dabout";
assert_string
(Js.Global.encodeURI "http://ru.wikipedia.org/wiki/Юникод")
"http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4";
assert_string
(Js.Global.encodeURI "http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork")
"http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork";
assert_string
(Js.Global.encodeURI "https://example.com/path name/file.txt")
"https://example.com/path%20name/file.txt");
test "encodeURI - special characters" (fun () ->
assert_string (Js.Global.encodeURI "\n") "%0A";
assert_string (Js.Global.encodeURI "\r") "%0D";
assert_string (Js.Global.encodeURI "<>\"\\") "%3C%3E%22%5C";
assert_string (Js.Global.encodeURI "http://unipro.ru/\nabout") "http://unipro.ru/%0Aabout";
assert_string (Js.Global.encodeURI "http://unipro.ru/\rabout") "http://unipro.ru/%0Dabout");
test "encodeURI - combining characters" (fun () ->
(* Characters with combining diacritical marks *)
assert_string (Js.Global.encodeURI "é") (* e + acute accent as single char *) "%C3%A9";
assert_string (Js.Global.encodeURI "e\u{0301}") (* e + combining acute accent *) "e%CC%81";
assert_string (Js.Global.encodeURI "ế") (* e + circumflex + acute *) "%E1%BA%BF");
test "encodeURI - Surrogate pairs" (fun () ->
(* Surrogate pairs for emoji and complex Unicode *)
assert_string (Js.Global.encodeURI "𝌆") (* Musical symbol *) "%F0%9D%8C%86";
assert_string (Js.Global.encodeURI "🌍") (* Earth globe *) "%F0%9F%8C%8D";
assert_string
(Js.Global.encodeURI "👨👩👧👦") (* Family emoji with ZWJ sequences *)
"%F0%9F%91%A8%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7%E2%80%8D%F0%9F%91%A6");
(* \v and \f are not supported in ocaml *)
(*
assert_string
(Js.Global.decodeURIComponent "http://unipro.ru/%0Babout")
"http://unipro.ru/\vabout";
assert_string
(Js.Global.decodeURIComponent "http://unipro.ru/%0Cabout")
"http://unipro.ru/\fabout"; *)
test "encodeURIComponent" (fun () ->
assert_string (Js.Global.encodeURIComponent "http://unipro.ru") "http%3A%2F%2Funipro.ru";
assert_string
(Js.Global.encodeURIComponent
"http://www.google.ru/support/jobs/bin/static.py?page=why-ru.html&sid=liveandwork")
"http%3A%2F%2Fwww.google.ru%2Fsupport%2Fjobs%2Fbin%2Fstatic.py%3Fpage%3Dwhy-ru.html%26sid%3Dliveandwork";
assert_string (Js.Global.encodeURIComponent "ABCDEFGHIJKLMNOPQRSTUVWXYZ") "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert_string (Js.Global.encodeURIComponent "abcdefghijklmnopqrstuvwxyz") "abcdefghijklmnopqrstuvwxyz";
assert_string (Js.Global.encodeURIComponent "http://unipro.ru/\nabout") "http%3A%2F%2Funipro.ru%2F%0Aabout";
assert_string (Js.Global.encodeURIComponent "http://unipro.ru/\rabout") "http%3A%2F%2Funipro.ru%2F%0Dabout");
(* \v and \f are not supported in ocaml
assert_string
(Js.Global.encodeURIComponent "http://unipro.ru/\vabout")
"http%3A%2F%2Funipro.ru%2F%0Babout";
assert_string
(Js.Global.encodeURIComponent "http://unipro.ru/\fabout")
"http%3A%2F%2Funipro.ru%2F%0Cabout"; *)
]
let obj () = Js.Dict.fromList [ ("foo", 43); ("bar", 86) ]
let long_obj () = Js.Dict.fromList [ ("david", 99); ("foo", 43); ("bar", 86) ]
let obj_duplicated () = Js.Dict.fromList [ ("foo", 43); ("bar", 86); ("bar", 1) ]
module Obj_test = struct
external make : onLoad:string -> ?retries:int -> unit -> < onLoad : string ; retries : int option > Js.t = ""
[@@mel.obj]
external makeKeyword : _type:string -> unit -> < _type : string > Js.t = "" [@@mel.obj]
end
let obj_tests =
[
test "empty" (fun () -> assert_string_array (Js.Obj.keys (Js.Obj.empty ())) [||]);
test "@@mel.obj" (fun () ->
let props = Obj_test.make ~onLoad:"ready" () in
assert_string props##onLoad "ready";
assert_option_int props##retries None;
assert_string_array (Js.Obj.keys props) [| "onLoad" |];
let props = Obj_test.make ~onLoad:"ready" ~retries:2 () in
assert_option_int props##retries (Some 2);
assert_string_array (Js.Obj.keys props) [| "onLoad"; "retries" |]);
test "@@mel.obj with keyword label" (fun () ->
let props = Obj_test.makeKeyword ~_type:"button" () in
assert_string props##_type "button";
assert_string_array (Js.Obj.keys props) [| "type" |]);
test "assign mutates target" (fun () ->
let target = Obj_test.make ~onLoad:"ready" () in
let source = Obj_test.make ~onLoad:"updated" ~retries:2 () in
let returned = Js.Obj.assign target source in
assert_int (Oo.id returned) (Oo.id target);
assert_string target##onLoad "updated";
assert_option_int target##retries (Some 2);
assert_string_array (Js.Obj.keys target) [| "onLoad"; "retries" |];
assert_string source##onLoad "updated";
assert_option_int source##retries (Some 2));
test "merge creates fresh object" (fun () ->
let left = Obj_test.make ~onLoad:"left" () in
let right = Obj_test.make ~onLoad:"right" ~retries:3 () in
let merged : < onLoad : string ; retries : int option > Js.t = Obj.magic (Js.Obj.merge () left right) in
assert_bool (Oo.id merged = Oo.id left) false;
assert_bool (Oo.id merged = Oo.id right) false;
assert_string merged##onLoad "right";
assert_option_int merged##retries (Some 3);
assert_string_array (Js.Obj.keys merged) [| "onLoad"; "retries" |];
assert_string left##onLoad "left";
assert_option_int left##retries None);
test "[%mel.obj] evaluates fields once" (fun () ->
let counter = ref 0 in
let props =
[%mel.obj
{
count =
(incr counter;
!counter);
}]
in
assert_int props##count 1;
assert_int props##count 1;
assert_int !counter 1;
assert_string_array (Js.Obj.keys props) [| "count" |]);
]
let dict_tests =
[
test "empty" (fun _ -> assert_string_dict_entries (Js.Dict.entries (Js.Dict.empty ())) [||]);
test "get" (fun _ -> assert_option_int (Js.Dict.get (obj ()) "foo") (Some 43));
test "get from missing property" (fun _ -> assert_option_int (Js.Dict.get (obj ()) "baz") None);
test "unsafe_get" (fun _ -> assert_int (Js.Dict.unsafeGet (obj ()) "foo") 43);
test "set" (fun _ ->
let o = Js.Dict.empty () in
Js.Dict.set o "foo" 36;
assert_option_int (Js.Dict.get o "foo") (Some 36));
test "keys" (fun _ -> assert_string_array (Js.Dict.keys (long_obj ())) [| "bar"; "david"; "foo" |]);
test "keys duplicated" (fun _ -> assert_string_array (Js.Dict.keys (obj_duplicated ())) [| "bar"; "bar"; "foo" |]);
test "entries" (fun _ -> assert_int_dict_entries (Js.Dict.entries (obj ())) [| ("bar", 86); ("foo", 43) |]);
test "values" (fun _ -> assert_array_int (Js.Dict.values (obj ())) [| 86; 43 |]);
test "values duplicated" (fun _ -> assert_array_int (Js.Dict.values (obj_duplicated ())) [| 86; 1; 43 |]);
test "fromList - []" (fun _ -> assert_int_dict_entries (Js.Dict.entries (Js.Dict.fromList [])) [||]);
test "fromList" (fun _ ->
assert_int_dict_entries (Js.Dict.entries (Js.Dict.fromList [ ("x", 23); ("y", 46) ])) [| ("x", 23); ("y", 46) |]);
test "fromArray - []" (fun _ -> assert_int_dict_entries (Js.Dict.entries (Js.Dict.fromArray [||])) [||]);
test "fromArray" (fun _ ->
assert_int_dict_entries
(Js.Dict.entries (Js.Dict.fromArray [| ("x", 23); ("y", 46) |]))
[| ("x", 23); ("y", 46) |]);
test "map" (fun _ ->
let prices = Js.Dict.fromList [ ("pen", 1); ("book", 5); ("stapler", 7) ] in
let discount price = price * 10 in
let salePrices = Js.Dict.map ~f:discount prices in
assert_int_dict_entries (Js.Dict.entries salePrices) [| ("book", 50); ("stapler", 70); ("pen", 10) |]);
]
let promise_to_lwt (p : 'a Js.Promise.t) : 'a Lwt.t = Obj.magic p
let set_timeout callback delay =
Lwt.async (fun () ->
let%lwt () = Lwt_unix.sleep delay in
callback ();
Lwt.return ())
let set_immediate callback =
Lwt.async (fun () ->
let%lwt () = Lwt.pause () in
callback ();
Lwt.return ())
let promise_tests =
[
test_async "resolve" (fun _switch () ->
let value = "hi" in
let resolved = Js.Promise.resolve value in
resolved |> promise_to_lwt |> Lwt.map (assert_string value));
test_async "all" (fun _switch () ->
let p0 = Js.Promise.make (fun ~resolve ~reject:_ -> resolve 5) in
let p1 = Js.Promise.make (fun ~resolve ~reject:_ -> resolve 10) in
let resolved = Js.Promise.all [| p0; p1 |] in
resolved |> promise_to_lwt |> Lwt.map (assert_array_int [| 5; 10 |]));
test_async "all_async" (fun _switch () ->
let p0 = Js.Promise.make (fun ~resolve ~reject:_ -> set_immediate (fun () -> resolve 5)) in
let p1 = Js.Promise.make (fun ~resolve ~reject:_ -> set_immediate (fun () -> resolve 99)) in
let resolved = Js.Promise.all [| p0; p1 |] in
resolved |> promise_to_lwt |> Lwt.map (assert_array_int [| 5; 99 |]));
test_async "race_async" (fun _switch () ->
let p0 = Js.Promise.make (fun ~resolve ~reject:_ -> set_timeout (fun () -> resolve "second") 0.005) in
let p1 = Js.Promise.make (fun ~resolve ~reject:_ -> set_immediate (fun () -> resolve "first")) in
let resolved = Js.Promise.race [| p0; p1 |] in
resolved |> promise_to_lwt |> Lwt.map (assert_string "first"));
]
let float_tests =
[
test "string_of_float" (fun () ->
assert_string (string_of_float 0.5) "0.5";
assert_string (string_of_float 80.0) "80.";
assert_string (string_of_float 80.) "80.";
assert_string (string_of_float 80.0001) "80.0001";
assert_string (string_of_float 80.00000000001) "80.");
test "toString" (fun () ->
assert_string (Js.Float.toString 0.5) "0.5";
assert_string (Js.Float.toString 80.0) "80";
assert_string (Js.Float.toString 80.) "80";
assert_string (Js.Float.toString 80.0001) "80.0001";
(* assert_string (Js.Float.toString 80.00000000001) "80.00000000001"; JS/Melange outputs "80.00000000001" but ocaml outputs "80." *)
assert_string (Js.Float.toString Stdlib.Float.nan) "NaN";
assert_string (Js.Float.toString Stdlib.Float.infinity) "Infinity";
assert_string (Js.Float.toString Stdlib.Float.neg_infinity) "-Infinity");
test "fromString" (fun () ->
assert_float (Js.Float.fromString "0.5") 0.5;
assert_float (Js.Float.fromString "80") 80.;
assert_float (Js.Float.fromString "80.0001") 80.0001;
(* assert_float (Js.Float.fromString "80.00000000001") 80.00000000001; JS/Melange outputs 80.00000000001 but ocaml outputs 80. *)
assert_float (Js.Float.fromString "NaN") Stdlib.Float.nan;
assert_float (Js.Float.fromString "Infinity") Stdlib.Float.infinity;
assert_float (Js.Float.fromString "-Infinity") Stdlib.Float.neg_infinity);
test "toFixed" (fun () ->
assert_string (Js.Float.toFixed 12.3456) "12";
assert_string (Js.Float.toFixed ~digits:20 0.) "0.00000000000000000000";
assert_string (Js.Float.toFixed ~digits:0 (-12.)) "-12";
assert_string (Js.Float.toFixed ~digits:0 Stdlib.Float.nan) "NaN";
assert_string (Js.Float.toFixed ~digits:0 1000000000000000128.) "1000000000000000128";
assert_string (Js.Float.toFixed ~digits:3 12.3456) "12.346";
assert_string (Js.Float.toFixed ~digits:50 0.3) "0.29999999999999998889776975374843459576368331909180";
Alcotest.check_raises "Expected failure" (Failure "toFixed() digits argument must be between 0 and 100")
(fun () ->
let _ = Js.Float.toFixed ~digits:(-1) 12. in
());
assert_string (Js.Float.toFixed ~digits:2 12.345) "12.35";
assert_string (Js.Float.toFixed ~digits:2 12.344) "12.34";
assert_string (Js.Float.toFixed ~digits:1 0.05) "0.1";
assert_string (Js.Float.toFixed ~digits:5 1e20) "100000000000000000000.00000";
assert_string (Js.Float.toFixed ~digits:5 1e-20) "0.00000";
assert_string (Js.Float.toFixed ~digits:10 1e-10) "0.0000000001";
assert_string (Js.Float.toFixed ~digits:100 0.1)
"0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000";
assert_string (Js.Float.toFixed ~digits:0 0.99) "1";
assert_string (Js.Float.toFixed ~digits:5 Float.infinity) "Infinity";
assert_string (Js.Float.toFixed ~digits:5 Float.neg_infinity) "-Infinity";
assert_string (Js.Float.toFixed ~digits:2 (-12.3456)) "-12.35";
assert_string (Js.Float.toFixed ~digits:4 0.) "0.0000";
(* assert_string (Js.Float.toFixed ~digits:0 1.2e34) "1.2e+34"; JS/Melange outputs "1.2e+34" but ocaml outputs "11999999999999999346902771844513792" *)
Alcotest.check_raises "Expected failure for negative digits"
(Failure "toFixed() digits argument must be between 0 and 100") (fun () ->
ignore (Js.Float.toFixed ~digits:(-1) 12.34));
Alcotest.check_raises "Expected failure for exceeding digits limit"
(Failure "toFixed() digits argument must be between 0 and 100") (fun () ->
ignore (Js.Float.toFixed ~digits:101 12.34)));
]
let () =
Lwt_main.run
@@ Alcotest_lwt.run "Js"
[
("Js.Global", global_tests);
("Js.Promise", promise_tests);
("Js.Float", float_tests);
("Js.String", string_tests);
("Js.Re", re_tests);
("Js.Obj", obj_tests);
("Js.Dict", dict_tests);
("Js.Array", []);
("Js.Undefined", Undefined_tests.Undefined.tests);
(* Test262 - BigInt *)
("BigInt.Arithmetic", Bigint_tests.Arithmetic.tests);
("BigInt.Bitwise", Bigint_tests.Bitwise.tests);
("BigInt.Comparison", Bigint_tests.Comparison.tests);
("BigInt.Constructor", Bigint_tests.Constructor.tests);
("BigInt.Conversion", Bigint_tests.Conversion.tests);
("BigInt.AsIntN", Bigint_tests.As_int_n.tests);
("BigInt.AsUintN", Bigint_tests.As_uint_n.tests);
("BigInt.Prototype", Bigint_tests.Prototype.tests);
(* Test262 - Date *)
("Date.Getters", Date_tests.Getters.tests);
("Date.LocalGetters", Date_tests.Local_getters.tests);
("Date.Setters", Date_tests.Setters.tests);
("Date.ToString", Date_tests.To_string.tests);
("Date.Now", Date_tests.Now.tests);
("Date.Parse", Date_tests.Parse.tests);
("Date.ToISOString", Date_tests.To_iso_string.tests);
("Date.UTC", Date_tests.Utc.tests);
(* Test262 - Number *)
("Number.IsFinite", Number_tests.Is_finite.tests);
("Number.IsInteger", Number_tests.Is_integer.tests);
("Number.IsNaN", Number_tests.Is_nan.tests);
("Number.ParseFloat", Number_tests.Parse_float.tests);
("Number.ParseInt", Number_tests.Parse_int.tests);
("Number.ToString", Number_tests.To_string.tests);
("Number.ToExponential", Number_tests.To_exponential.tests);
("Number.ToPrecision", Number_tests.To_precision.tests);
(* Test262 - String *)
("String.Normalize", String_tests.Normalize.tests);
("String.Search", String_tests.Search.tests);
(* Test262 - RegExp *)
("RegExp.NamedGroups", Regexp_tests.Named_groups.tests);
("RegExp.DotAll", Regexp_tests.Dotall.tests);
("RegExp.Unicode", Regexp_tests.Unicode.tests);
]
================================================
FILE: packages/Js/test/undefined_tests/undefined.ml
================================================
open Helpers
let return_int () =
let v = Js.Undefined.return 42 in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.int "return int should be Some" opt (Some 42)
let return_string () =
let v = Js.Undefined.return "hello" in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.string "return string should be Some" opt (Some "hello")
let return_float () =
let v = Js.Undefined.return 3.14 in
let opt = Js.Undefined.toOption v in
assert_option (Alcotest.float 0.) "return float should be Some" opt (Some 3.14)
let return_date () =
let d = Date.fromFloat 1506098258091. in
let v = Js.Undefined.return d in
let opt = Js.Undefined.toOption v in
assert_option (Alcotest.float 0.) "return Js.Date.t should be Some" opt (Some 1506098258091.)
let empty_is_none () =
let v : int Js.Undefined.t = Js.Undefined.empty in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.int "empty should be None" opt None
let get_unsafe_int () =
let v = Js.Undefined.return 42 in
let result = Js.Undefined.getUnsafe v in
assert_int result 42
let get_unsafe_date () =
let d = Date.fromFloat 1506098258091. in
let v = Js.Undefined.return d in
let result = Js.Undefined.getUnsafe v in
assert_float_exact result 1506098258091.
let from_opt_some () =
let v = Js.Undefined.fromOpt (Some 42) in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.int "fromOpt (Some 42) round-trip" opt (Some 42)
let from_opt_none () =
let v = Js.Undefined.fromOpt None in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.int "fromOpt None round-trip" opt None
let from_option_some () =
let v = Js.Undefined.fromOption (Some "test") in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.string "fromOption (Some \"test\") round-trip" opt (Some "test")
let from_option_none () =
let v : string Js.Undefined.t = Js.Undefined.fromOption None in
let opt = Js.Undefined.toOption v in
assert_option Alcotest.string "fromOption None round-trip" opt None
let pattern_match_return_int () =
let v = Js.Undefined.return 99 in
match (v : int Js.Undefined.t) with
| Some x -> assert_int x 99
| None -> Alcotest.fail "pattern match on return int should be Some"
let pattern_match_return_float () =
let v = Js.Undefined.return 2.718 in
match (v : float Js.Undefined.t) with
| Some x -> assert_float_exact x 2.718
| None -> Alcotest.fail "pattern match on return float should be Some"
let pattern_match_return_date () =
let d = Date.fromFloat 0. in
let v = Js.Undefined.return d in
match (v : Date.t Js.Undefined.t) with
| Some x -> assert_float_exact x 0.
| None -> Alcotest.fail "pattern match on return Js.Date.t should be Some"
let pattern_match_empty () =
let v : int Js.Undefined.t = Js.Undefined.empty in
match v with Some _ -> Alcotest.fail "pattern match on empty should be None" | None -> ()
let tests =
[
test "return with int" return_int;
test "return with string" return_string;
test "return with float" return_float;
test "return with Js.Date.t" return_date;
test "empty is None" empty_is_none;
test "getUnsafe on return int" get_unsafe_int;
test "getUnsafe on return Js.Date.t" get_unsafe_date;
test "fromOpt Some round-trip" from_opt_some;
test "fromOpt None round-trip" from_opt_none;
test "fromOption Some round-trip" from_option_some;
test "fromOption None round-trip" from_option_none;
test "pattern match on return int" pattern_match_return_int;
test "pattern match on return float" pattern_match_return_float;
test "pattern match on return Js.Date.t" pattern_match_return_date;
test "pattern match on empty" pattern_match_empty;
]
================================================
FILE: packages/browser-ppx/dune
================================================
(library
(name browser_ppx)
(modules ppx)
(public_name server-reason-react.browser_ppx)
(flags :standard -w -9)
(libraries ppxlib ppxlib.astlib)
(ppx_runtime_libraries runtime)
(preprocess
(pps ppxlib.metaquot))
(kind ppx_rewriter))
================================================
FILE: packages/browser-ppx/ppx.ml
================================================
open Ppxlib
module Builder = Ast_builder.Default
type target = Native | Js
let mode = ref Native
let browser_ppx = "browser_ppx"
let browser_only = "browser_only"
let platform_tag = "platform"
let is_platform_tag str = String.equal str browser_ppx || String.equal str browser_only || String.equal str platform_tag
module Platform = struct
let pattern = Ast_pattern.(__')
let collect_expressions ~loc first second =
match (first.pc_lhs.ppat_desc, second.pc_lhs.ppat_desc) with
| ( Ppat_construct ({ txt = Lident "Server" | Ldot (Lident "Runtime", "Server"); _ }, _),
Ppat_construct ({ txt = Lident "Client" | Ldot (Lident "Runtime", "Client"); _ }, _) ) ->
Ok (first.pc_rhs, second.pc_rhs)
| ( Ppat_construct ({ txt = Lident "Client" | Ldot (Lident "Runtime", "Client"); _ }, _),
Ppat_construct ({ txt = Lident "Server" | Ldot (Lident "Runtime", "Server"); _ }, _) ) ->
Ok (second.pc_rhs, first.pc_rhs)
| _ -> Error [%expr [%ocaml.error "[browser_only] switch%platform requires 2 cases: `Server` and `Client`"]]
let switch_platform_requires_a_match ~loc =
[%expr [%ocaml.error "[browser_ppx] switch%platform requires a match expression"]]
let handler ~ctxt:_ { txt = payload; loc } =
match payload with
| PStr [ { pstr_desc = Pstr_eval (expression, _); _ } ] -> (
match expression.pexp_desc with
| Pexp_match (_expression, cases) -> (
match cases with
| [ first; second ] -> (
match collect_expressions ~loc first second with
| Ok (server_expr, client_expr) -> (
match !mode with
(* When it's -js keep the client_expr *)
| Js -> client_expr
(* When it's isn't -js keep the server_expr *)
| Native -> server_expr)
| Error error_msg_expr -> error_msg_expr)
| _ -> switch_platform_requires_a_match ~loc)
| _ -> switch_platform_requires_a_match ~loc)
| _ -> switch_platform_requires_a_match ~loc
let rule = Context_free.Rule.extension (Extension.V3.declare "platform" Extension.Context.expression pattern handler)
end
let remove_type_constraint pattern =
match pattern with { ppat_desc = Ppat_constraint (pattern, _); _ } -> pattern | _ -> pattern
let rec last_expr_to_raise_impossible ~loc original_name expr =
match expr.pexp_desc with
| Pexp_constraint (expr, _) -> last_expr_to_raise_impossible ~loc original_name expr
| Pexp_function
({ pparam_desc = Pparam_val (arg_label, _arg_expression, fun_pattern); _ } :: _rest, _, Pfunction_body expression)
->
let new_fun_pattern = remove_type_constraint fun_pattern in
let fn =
Builder.pexp_fun ~loc arg_label None new_fun_pattern
(last_expr_to_raise_impossible ~loc original_name expression)
in
{ fn with pexp_attributes = expr.pexp_attributes }
| _ -> [%expr Runtime.fail_impossible_action_in_ssr [%e Builder.estring ~loc original_name]]
module Browser_only = struct
let get_function_name pattern = match pattern with Ppat_var { txt = name; _ } -> name | _ -> ""
let error_only_works_on ~loc =
[%expr
[%ocaml.error
"[browser_ppx] browser_only works on function definitions. For other cases, use switch%platform or feel free \
to open an issue in https://github.com/ml-in-barcelona/server-reason-react."]]
let remove_alert_browser_only ~loc =
Builder.attribute ~loc ~name:{ txt = "alert"; loc } ~payload:(PStr [ [%stri "-browser_only"] ])
let browser_only_fun ~loc arg_label pattern expression =
let stringified = Ppxlib.Pprintast.string_of_expression expression in
let message = Builder.estring ~loc stringified in
let fn = Builder.pexp_fun ~loc arg_label None pattern [%expr Runtime.fail_impossible_action_in_ssr [%e message]] in
{ fn with pexp_attributes = expression.pexp_attributes }
let browser_only_value_binding pattern expression =
let loc = pattern.ppat_loc in
match pattern with
| [%pat? ()] -> Builder.value_binding ~loc ~pat:pattern ~expr:[%expr ()]
| _ -> (
match expression.pexp_desc with
| Pexp_constraint
( { pexp_desc = Pexp_function ({ pparam_desc = Pparam_val _; _ } :: _, _, Pfunction_body _); _ },
_type_constraint ) ->
let function_name = get_function_name pattern.ppat_desc in
let expr = last_expr_to_raise_impossible ~loc function_name expression in
let vb = Builder.value_binding ~loc ~pat:pattern ~expr in
{ vb with pvb_attributes = [ remove_alert_browser_only ~loc ] }
| Pexp_function ({ pparam_desc = Pparam_val _; _ } :: _, _, Pfunction_body _) ->
let function_name = get_function_name pattern.ppat_desc in
let expr = last_expr_to_raise_impossible ~loc function_name expression in
let vb = Builder.value_binding ~loc ~pat:pattern ~expr in
{ vb with pvb_attributes = [ remove_alert_browser_only ~loc ] }
| _ -> Builder.value_binding ~loc ~pat:pattern ~expr:(error_only_works_on ~loc))
let extractor_single_payload = Ast_pattern.(single_expr_payload __)
let expression_handler ~ctxt payload =
let replace_fun_body_with_raise_impossible ~loc pexp_desc =
match pexp_desc with
| Pexp_constraint
( {
pexp_desc =
Pexp_function
( { pparam_desc = Pparam_val (arg_label, _arg_expression, pattern); _ } :: _,
_,
Pfunction_body expression );
_;
},
type_constraint ) ->
let fn = browser_only_fun ~loc arg_label pattern expression in
Builder.pexp_constraint ~loc { fn with pexp_attributes = expression.pexp_attributes } type_constraint
| Pexp_function
({ pparam_desc = Pparam_val (arg_label, _arg_expression, pattern); _ } :: _, _, Pfunction_body expr) ->
let function_name = get_function_name pattern.ppat_desc in
let new_fun_pattern = remove_type_constraint pattern in
Builder.pexp_fun ~loc arg_label None new_fun_pattern (last_expr_to_raise_impossible ~loc function_name expr)
| Pexp_let (rec_flag, value_bindings, expression) ->
let pexp_let =
Builder.pexp_let ~loc rec_flag
(List.map (fun binding -> browser_only_value_binding binding.pvb_pat binding.pvb_expr) value_bindings)
expression
in
[%expr [%e pexp_let]]
| _ -> error_only_works_on ~loc
in
match !mode with
| Js -> payload
| Native ->
let loc = Expansion_context.Extension.extension_point_loc ctxt in
replace_fun_body_with_raise_impossible ~loc payload.pexp_desc
let expression_rule =
Context_free.Rule.extension
(Extension.V3.declare "browser_only" Extension.Context.expression extractor_single_payload expression_handler)
(* Generates a structure_item with a value binding with a pattern and an expression with all the alerts and warnings *)
let make_vb_with_browser_only ~loc ?type_constraint pattern expression =
match type_constraint with
| Some type_constraint ->
[%stri
let[@warning "-27-32"] ([%p pattern] :
([%t type_constraint]
[@alert
browser_only
"This expression is marked to only run on the browser where JavaScript can run. \
You can only use it inside a let%browser_only function."])) =
[%e expression] [@alert "-browser_only"]]
| None ->
[%stri
let[@warning "-27-32"] ([%p pattern]
[@alert
browser_only
"This expression is marked to only run on the browser where JavaScript can run. \
You can only use it inside a let%browser_only function."]) =
[%e expression] [@alert "-browser_only"]]
let extractor_vb =
let open Ast_pattern in
let extractor_in_let = pstr_value __ (value_binding ~pat:__ ~expr:__ ~constraint_:drop ^:: nil) in
pstr @@ extractor_in_let ^:: nil
let structure_item_handler ~ctxt rec_flag pattern expression =
let loc = Expansion_context.Extension.extension_point_loc ctxt in
let do_nothing rec_flag =
match rec_flag with
| Recursive -> [%stri let rec [%p pattern] = [%e expression]]
| Nonrecursive -> [%stri let [%p pattern] = [%e expression]]
in
let add_browser_only_alert expression =
match expression.pexp_desc with
| Pexp_constraint
( {
pexp_desc =
Pexp_function
( { pparam_desc = Pparam_val (arg_label, _arg_expression, fun_pattern); _ } :: _,
_,
Pfunction_body expr );
_;
},
type_constraint ) ->
let original_function_name = get_function_name pattern.ppat_desc in
let new_fun_pattern = remove_type_constraint fun_pattern in
let fn =
Builder.pexp_fun ~loc arg_label None new_fun_pattern
(last_expr_to_raise_impossible ~loc original_function_name expr)
in
let item = { fn with pexp_attributes = expr.pexp_attributes } in
make_vb_with_browser_only ~loc ~type_constraint pattern item
| Pexp_function
({ pparam_desc = Pparam_val (arg_label, _arg_expression, fun_pattern); _ } :: _, _, Pfunction_body expr) ->
let original_function_name = get_function_name pattern.ppat_desc in
let new_fun_pattern = remove_type_constraint fun_pattern in
let fn =
Builder.pexp_fun ~loc arg_label None new_fun_pattern
(last_expr_to_raise_impossible ~loc original_function_name expr)
in
let item = { fn with pexp_attributes = expr.pexp_attributes } in
make_vb_with_browser_only ~loc pattern item
| Pexp_function ([], _, Pfunction_cases (_cases, _, _)) ->
(* Because pexp_function with cases doesn't have a pattern, neither a label, we construct an empty pattern and use it to generate the vb *)
let original_function_name = get_function_name pattern.ppat_desc in
let fn =
Builder.pexp_fun ~loc Nolabel None
[%pat? _]
(last_expr_to_raise_impossible ~loc original_function_name expression)
in
let item = { fn with pexp_attributes = expression.pexp_attributes } in
make_vb_with_browser_only ~loc pattern item
| Pexp_ident { txt = _longident; loc } ->
let item = [%expr Obj.magic ()] in
make_vb_with_browser_only ~loc pattern item
| Pexp_newtype (name, expr) ->
let original_function_name = name.txt in
let item = last_expr_to_raise_impossible ~loc original_function_name expr in
make_vb_with_browser_only ~loc pattern item
| _expr -> do_nothing rec_flag
in
match !mode with
(* When it's -js, keep item as it is *)
| Js -> do_nothing rec_flag
| Native -> add_browser_only_alert expression
let structure_item_rule =
Context_free.Rule.extension
(Extension.V3.declare "browser_only" Extension.Context.structure_item extractor_vb structure_item_handler)
let has_browser_only_attribute expr =
match expr.pexp_desc with Pexp_extension ({ txt = "browser_only" }, _) -> true | _ -> false
let use_effect (expr : expression) =
let add_browser_only_extension expr =
match expr.pexp_desc with
| (Pexp_apply (_, [ (Nolabel, effect_body) ]) | Pexp_apply (_, [ (Nolabel, effect_body); _ ]))
when has_browser_only_attribute effect_body ->
None
| Pexp_apply (apply_expr, [ (Nolabel, effect_body); second_arg ]) ->
let loc = expr.pexp_loc in
let new_effect_body = [%expr [%browser_only [%e effect_body]]] in
let new_effect_fun = Builder.pexp_apply ~loc apply_expr [ (Nolabel, new_effect_body); second_arg ] in
Some new_effect_fun
| Pexp_apply (apply_expr, [ (Nolabel, effect_body) ]) ->
let loc = expr.pexp_loc in
let new_effect_body = [%expr [%browser_only [%e effect_body]]] in
let new_effect_fun = Builder.pexp_apply ~loc apply_expr [ (Nolabel, new_effect_body) ] in
Some new_effect_fun
| _ -> None
in
match !mode with
(* When it's -js, keep item as it is *)
| Js -> None
| Native -> add_browser_only_extension expr
let use_effects =
[
(* useEffect *)
Context_free.Rule.special_function "React.useEffect" use_effect;
Context_free.Rule.special_function "React.useEffect0" use_effect;
Context_free.Rule.special_function "React.useEffect1" use_effect;
Context_free.Rule.special_function "React.useEffect2" use_effect;
Context_free.Rule.special_function "React.useEffect3" use_effect;
Context_free.Rule.special_function "React.useEffect4" use_effect;
Context_free.Rule.special_function "React.useEffect5" use_effect;
Context_free.Rule.special_function "React.useEffect6" use_effect;
Context_free.Rule.special_function "React.useEffect7" use_effect;
(* useLayoutEffect *)
Context_free.Rule.special_function "React.useLayoutEffect" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect0" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect1" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect2" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect3" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect4" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect5" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect6" use_effect;
Context_free.Rule.special_function "React.useLayoutEffect7" use_effect;
]
end
module Preprocess = struct
(* This module is heavily based on leostera `config.ml` PPX:
https://github.com/ocaml-sys/config.ml/blob/d248987cc1795de99d3735c06635dbd355d4d642/config/cfg_ppx.ml*)
let eval_attr attr =
if not (is_platform_tag attr.attr_name.txt) then `keep
else
match (attr.attr_name.txt, attr.attr_payload, !mode) with
| "browser_only", _, Native
| "platform", PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_ident { txt = Lident "js" } }, []); _ } ], Native
| "platform", PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_ident { txt = Lident "native" } }, []); _ } ], Js
->
`drop
| _ -> `keep
let rec should_keep attrs =
match attrs with [] -> `keep | attr :: attrs -> if eval_attr attr = `drop then `drop else should_keep attrs
let rec should_keep_many list fn =
match list with
| [] -> `keep
| item :: list -> if should_keep (fn item) = `drop then `drop else should_keep_many list fn
let apply_config_on_types (tds : type_declaration list) =
List.filter_map
(fun td ->
match td with
| {
ptype_kind = Ptype_abstract;
ptype_manifest = Some ({ ptyp_desc = Ptyp_variant (rows, closed_flag, labels); _ } as manifest);
_;
} ->
let rows =
List.filter_map (fun row -> if should_keep row.prf_attributes = `keep then Some row else None) rows
in
if rows = [] then None
else
Some
{ td with ptype_manifest = Some { manifest with ptyp_desc = Ptyp_variant (rows, closed_flag, labels) } }
| { ptype_kind = Ptype_variant cstrs; _ } ->
let cstrs =
List.filter_map (fun cstr -> if should_keep cstr.pcd_attributes = `keep then Some cstr else None) cstrs
in
if cstrs = [] then None else Some { td with ptype_kind = Ptype_variant cstrs }
| { ptype_kind = Ptype_record labels; _ } ->
let labels =
List.filter_map
(fun label -> if should_keep label.pld_attributes = `keep then Some label else None)
labels
in
if labels = [] then None else Some { td with ptype_kind = Ptype_record labels }
| _ -> Some td)
tds
let apply_config_on_structure_item stri =
match stri.pstr_desc with
| Pstr_typext { ptyext_attributes = attrs; _ }
| Pstr_modtype { pmtd_attributes = attrs; _ }
| Pstr_open { popen_attributes = attrs; _ }
| Pstr_include { pincl_attributes = attrs; _ }
| Pstr_exception { ptyexn_attributes = attrs; _ }
| Pstr_primitive { pval_attributes = attrs; _ }
| Pstr_eval (_, attrs)
| Pstr_module { pmb_attributes = attrs; _ } ->
if should_keep attrs = `keep then Some stri else None
| Pstr_value (_, vbs) -> if should_keep_many vbs (fun vb -> vb.pvb_attributes) = `keep then Some stri else None
| Pstr_type (recflag, tds) ->
if should_keep_many tds (fun td -> td.ptype_attributes) = `keep then
let tds = apply_config_on_types tds in
Some { stri with pstr_desc = Pstr_type (recflag, tds) }
else None
| Pstr_recmodule md -> if should_keep_many md (fun md -> md.pmb_attributes) = `keep then Some stri else None
| Pstr_class cds -> if should_keep_many cds (fun cd -> cd.pci_attributes) = `keep then Some stri else None
| Pstr_class_type ctds -> if should_keep_many ctds (fun ctd -> ctd.pci_attributes) = `keep then Some stri else None
| Pstr_extension _ | Pstr_attribute _ -> Some stri
let apply_config_on_signature_item sigi =
match sigi.psig_desc with
| Psig_typext { ptyext_attributes = attrs; _ }
| Psig_modtype { pmtd_attributes = attrs; _ }
| Psig_open { popen_attributes = attrs; _ }
| Psig_include { pincl_attributes = attrs; _ }
| Psig_exception { ptyexn_attributes = attrs; _ }
| Psig_value { pval_attributes = attrs; _ }
| Psig_modtypesubst { pmtd_attributes = attrs; _ }
| Psig_modsubst { pms_attributes = attrs; _ }
| Psig_module { pmd_attributes = attrs; _ } ->
if should_keep attrs = `keep then Some sigi else None
| Psig_typesubst tds ->
if should_keep_many tds (fun td -> td.ptype_attributes) = `keep then
let tds = apply_config_on_types tds in
Some { sigi with psig_desc = Psig_typesubst tds }
else None
| Psig_type (recflag, tds) ->
if should_keep_many tds (fun td -> td.ptype_attributes) = `keep then
let tds = apply_config_on_types tds in
Some { sigi with psig_desc = Psig_type (recflag, tds) }
else None
| Psig_recmodule md -> if should_keep_many md (fun md -> md.pmd_attributes) = `keep then Some sigi else None
| Psig_class cds -> if should_keep_many cds (fun cd -> cd.pci_attributes) = `keep then Some sigi else None
| Psig_class_type ctds -> if should_keep_many ctds (fun ctd -> ctd.pci_attributes) = `keep then Some sigi else None
| Psig_extension _ | Psig_attribute _ -> Some sigi
let traverse =
object (_ : Ast_traverse.map)
inherit Ast_traverse.map as super
method! structure str =
let str = super#structure str in
match str with
| { pstr_desc = Pstr_attribute attr; _ } :: rest when is_platform_tag attr.attr_name.txt ->
if eval_attr attr = `keep then rest else []
| str -> List.filter_map apply_config_on_structure_item str
method! signature sigi =
let sigi = super#signature sigi in
match sigi with
| { psig_desc = Psig_attribute attr; _ } :: rest when is_platform_tag attr.attr_name.txt ->
if eval_attr attr = `keep then rest else []
| _ -> List.filter_map apply_config_on_signature_item sigi
method! expression expr =
let expr = super#expression expr in
let loc = expr.pexp_loc in
match expr.pexp_desc with
| Pexp_let (_, [ { pvb_attributes = attrs; _ } ], _) ->
let loc = expr.pexp_loc in
if should_keep attrs = `keep then expr
else [%expr [%ocaml.error "Don't use browser_only on expressions, use switch%platform instead"]]
| _ ->
if should_keep expr.pexp_attributes = `keep then expr
else [%expr [%ocaml.error "Don't use browser_only on expressions, use switch%platform instead"]]
method! pattern pat =
match pat.ppat_desc with
| Ppat_constraint (inner_pat, _) ->
let loc = pat.ppat_loc in
if should_keep inner_pat.ppat_attributes = `keep then super#pattern pat else [%pat? _]
| _ ->
let pat = super#pattern pat in
let loc = pat.ppat_loc in
if should_keep pat.ppat_attributes = `keep then pat else [%pat? _]
end
end
let () =
Driver.add_arg "-js" (Unit (fun () -> mode := Js)) ~doc:"preprocess for js build";
let rules =
[ Browser_only.expression_rule; Browser_only.structure_item_rule; Platform.rule ] @ Browser_only.use_effects
in
Driver.V2.register_transformation browser_ppx ~rules
~impl:(fun _ -> Preprocess.traverse#structure)
~intf:(fun _ -> Preprocess.traverse#signature)
================================================
FILE: packages/browser-ppx/tests/at_browser_only.t
================================================
Pstr_include
$ cat > input_include.ml << EOF
> include struct
> type t = Js.Json.t
> end [@@browser_only]
> EOF
With -js flag it picks the block with `[@@browser_only]`
$ ./standalone.exe -impl input_include.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = Js.Json.t
end [@@browser_only]
Without -js flag, it picks the block without `[@@browser_only]`
$ ./standalone.exe -impl input_include.ml | ocamlformat - --enable-outside-detected-project --impl
Pstr_module
$ cat > input_module.ml << EOF
> module M = struct
> let x = 42
> end [@@browser_only]
> EOF
$ ./standalone.exe -impl input_module.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_module.ml -js | ocamlformat - --enable-outside-detected-project --impl
module M = struct
let x = 42
end
[@@browser_only]
Pstr_value
$ cat > input_value.ml << EOF
> let x = 42 [@@browser_only]
> let y = 44
> EOF
$ ./standalone.exe -impl input_value.ml | ocamlformat - --enable-outside-detected-project --impl
let y = 44
$ ./standalone.exe -impl input_value.ml -js | ocamlformat - --enable-outside-detected-project --impl
let x = 42 [@@browser_only]
let y = 44
Nested
$ cat > input_nested.ml << EOF
> module X = struct
> module Y = struct
> type t = Js.Json.t
> let a = 4 + 4
> end [@@browser_only]
> end
> EOF
With -js flag it picks the block with `[@@browser_only]`
$ ./standalone.exe -impl input_nested.ml -js | ocamlformat - --enable-outside-detected-project --impl
module X = struct
module Y = struct
type t = Js.Json.t
let a = 4 + 4
end
[@@browser_only]
end
Without -js flag, it picks the block without `[@@browser_only]`
$ ./standalone.exe -impl input_nested.ml | ocamlformat - --enable-outside-detected-project --impl
module X = struct end
Ppat_tuple
$ cat > input_tuple.ml << EOF
> let (x, y [@browser_only]) = (42, 44)
> EOF
$ ./standalone.exe -impl input_tuple.ml | ocamlformat - --enable-outside-detected-project --impl
let x, _ = (42, 44)
$ ./standalone.exe -impl input_tuple.ml -js | ocamlformat - --enable-outside-detected-project --impl
let x, (y [@browser_only]) = (42, 44)
$ ./standalone.exe -impl input_tuple.ml > input_tuple_server.ml && ocamlc -c input_tuple_server.ml
Ppat_var
$ cat > input_var.ml << EOF
> let x (onClick [@browser_only]) = 24
> let y ~onClick:(onClick [@browser_only]) = 42
> EOF
$ ./standalone.exe -impl input_var.ml | ocamlformat - --enable-outside-detected-project --impl
let x _ = 24
let y ~onClick:_ = 42
$ ./standalone.exe -impl input_var.ml -js | ocamlformat - --enable-outside-detected-project --impl
let x (onClick [@browser_only]) = 24
let y ~onClick:(onClick [@browser_only]) = 42
$ ./standalone.exe -impl input_var.ml > input_var_server.ml && ocamlc -c input_var_server.ml
Pstr_open
$ cat > input_open.ml << EOF
> open Printf [@@browser_only]
> EOF
$ ./standalone.exe -impl input_open.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_open.ml -js | ocamlformat - --enable-outside-detected-project --impl
open Printf [@@browser_only]
$ ./standalone.exe -impl input_open.ml > input_open_server.ml && ocamlc -c input_open_server.ml
Pstr_exception
$ cat > input_exception.ml << EOF
> exception MyException of string [@@browser_only]
> EOF
$ ./standalone.exe -impl input_exception.ml -js | ocamlformat - --enable-outside-detected-project --impl
exception MyException of string [@@browser_only]
$ ./standalone.exe -impl input_exception.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_exception.ml > input_exception_server.ml && ocamlc -c input_exception_server.ml
Pstr_primitive
$ cat > input_primitive.ml << EOF
> external add : int -> int -> int = "caml_add_int" [@@browser_only]
> EOF
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
external add : int -> int -> int = "caml_add_int" [@@browser_only]
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_primitive.ml > input_primitive_server.ml && ocamlc -c input_primitive_server.ml
Pstr_eval
$ cat > input_primitive.ml << EOF
> 2 [@@browser_only]
> EOF
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
2 [@@browser_only]
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_primitive.ml > input_primitive_server.ml && ocamlc -c input_primitive_server.ml
Pstr_type
$ cat > input_type.ml << EOF
> type point = { x : int; y : int } [@@browser_only]
> EOF
$ ./standalone.exe -impl input_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
type point = { x : int; y : int } [@@browser_only]
$ ./standalone.exe -impl input_type.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_type.ml > input_type_server.ml && ocamlc -c input_type_server.ml
Pstr_recmodule
$ cat > input_recmodule.ml << EOF
> module rec M = struct
> let x = 42
> end [@@browser_only]
> EOF
$ ./standalone.exe -impl input_recmodule.ml -js | ocamlformat - --enable-outside-detected-project --impl
module rec M = struct
let x = 42
end
[@@browser_only]
$ ./standalone.exe -impl input_recmodule.ml | ocamlformat - --enable-outside-detected-project --impl
Pstr_class
$ cat > input_class.ml << EOF
> class virtual ['a] base x = object
> method get = x
> end [@@browser_only]
> EOF
$ ./standalone.exe -impl input_class.ml -js | ocamlformat - --enable-outside-detected-project --impl
class virtual ['a] base x =
object
method get = x
end [@@browser_only]
$ ./standalone.exe -impl input_class.ml | ocamlformat - --enable-outside-detected-project --impl
Pstr_class_type
$ cat > input_class_type.ml << EOF
> class type base = object
> method get : int
> end [@@browser_only]
> EOF
$ ./standalone.exe -impl input_class_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
class type base = object
method get : int
end [@@browser_only]
$ ./standalone.exe -impl input_class_type.ml | ocamlformat - --enable-outside-detected-project --impl
$ ./standalone.exe -impl input_class_type.ml > input_class_type_server.ml && ocamlc -c input_class_type_server.ml
Ppat_constraint
$ cat > input_constr.ml << EOF
> let foo ~on:((on [@browser_only]): unit -> string) ?opt:((opt [@browser_only])=42) = 0
> EOF
$ ./standalone.exe -impl input_constr.ml -js | ocamlformat - --enable-outside-detected-project --impl
let foo ~on:((on [@browser_only]) : unit -> string)
?opt:((opt [@browser_only]) = 42) =
0
$ ./standalone.exe -impl input_constr.ml | ocamlformat - --enable-outside-detected-project --impl
let foo ~on:_ ?opt:(_ = 42) = 0
Pexp_* should be throw an error
$ cat > input_let.ml << EOF
> let x =
> let _ = 42 [@@browser_only] in
> let y = 44 in
> y
> EOF
$ ./standalone.exe -impl input_let.ml | ocamlformat - --enable-outside-detected-project --impl
let x =
[%ocaml.error
"Don't use browser_only on expressions, use switch%platform instead"]
$ ./standalone.exe -impl input_let.ml > input_let_server.ml && ocamlc -c input_let_server.ml
File "input_let_server.ml", line 2, characters 4-15:
2 | [%ocaml.error
^^^^^^^^^^^
Error: Don't use browser_only on expressions, use switch%platform instead
[2]
================================================
FILE: packages/browser-ppx/tests/at_platform.t
================================================
Nested
$ cat > input.ml << EOF
> module X = struct
> include struct
> type t = Js.Json.t
> let a = 2 + 2
> end [@@platform js]
>
> include struct
> type t = Js.Json.t
> let a = 4 + 4
> end [@@platform native]
> end
> EOF
With -js flag it picks the block with `[@@platform js]`
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
module X = struct
include struct
type t = Js.Json.t
let a = 2 + 2
end [@@platform js]
end
Without -js flag, it picks the block with `[@@platform native]`
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
module X = struct
include struct
type t = Js.Json.t
let a = 4 + 4
end [@@platform native]
end
Pstr_include
$ cat > input.ml << EOF
> include struct
> type t = Js.Json.t
> end [@@platform js]
>
> include struct
> type t = string
> end [@@platform native]
> EOF
With -js flag it picks the block with `[@@platform js]`
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = Js.Json.t
end [@@platform js]
Without -js flag, it picks the block with `[@@platform native]`
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = string
end [@@platform native]
Use only one of the platforms
$ cat > input.ml << EOF
> include struct
> type t = Js.Json.t
> end [@@platform js]
>
> include struct
> type t = string
> end
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = string
end
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = Js.Json.t
end [@@platform js]
include struct
type t = string
end
Pstr_module
$ cat > input_module.ml << EOF
> module M = struct
> let x = 42
> end [@@platform js]
> module M = struct
> let x = 44
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_module.ml | ocamlformat - --enable-outside-detected-project --impl
module M = struct
let x = 44
end
[@@platform native]
$ ./standalone.exe -impl input_module.ml -js | ocamlformat - --enable-outside-detected-project --impl
module M = struct
let x = 42
end
[@@platform js]
Pstr_value
$ cat > input_let.ml << EOF
> let x = 42 [@@platform js]
> let y = 44 [@@platform native]
> EOF
$ ./standalone.exe -impl input_let.ml | ocamlformat - --enable-outside-detected-project --impl
let y = 44 [@@platform native]
$ ./standalone.exe -impl input_let.ml -js | ocamlformat - --enable-outside-detected-project --impl
let x = 42 [@@platform js]
Pstr_open
$ cat > input_open.ml << EOF
> open Printf [@@platform js]
> open List [@@platform native]
> EOF
$ ./standalone.exe -impl input_open.ml | ocamlformat - --enable-outside-detected-project --impl
open List [@@platform native]
$ ./standalone.exe -impl input_open.ml -js | ocamlformat - --enable-outside-detected-project --impl
open Printf [@@platform js]
Pstr_exception
$ cat > input_exception.ml << EOF
> exception MyException of string [@@platform js]
> exception AnotherException of int [@@platform native]
> EOF
$ ./standalone.exe -impl input_exception.ml | ocamlformat - --enable-outside-detected-project --impl
exception AnotherException of int [@@platform native]
$ ./standalone.exe -impl input_exception.ml -js | ocamlformat - --enable-outside-detected-project --impl
exception MyException of string [@@platform js]
Pstr_primitive
$ cat > input_primitive.ml << EOF
> external add : int -> int -> int = "caml_add_int" [@@platform js]
> external subtract : int -> int -> int = "caml_subtract_int" [@@platform native]
> EOF
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
external subtract : int -> int -> int = "caml_subtract_int" [@@platform native]
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
external add : int -> int -> int = "caml_add_int" [@@platform js]
Pstr_eval (doesn't work)
$ cat > input_primitive.ml << EOF
> include struct
> 2 [@@platform js]
> end
>
> include struct
> 3 [@@platform native]
> end
> EOF
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
include struct end
include struct
3 [@@platform native]
end
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
2 [@@platform js]
end
include struct end
Pstr_type
$ cat > input_type.ml << EOF
> type point = { x : int; y : int } [@@platform js]
> type color = Red | Green | Blue [@@platform native]
> EOF
$ ./standalone.exe -impl input_type.ml | ocamlformat - --enable-outside-detected-project --impl
type color = Red | Green | Blue [@@platform native]
$ ./standalone.exe -impl input_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
type point = { x : int; y : int } [@@platform js]
Pstr_recmodule
$ cat > input_recmodule.ml << EOF
> module rec M = struct
> let x = 42
> end [@@platform js]
> module rec M = struct
> let x = 44
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_recmodule.ml | ocamlformat - --enable-outside-detected-project --impl
module rec M = struct
let x = 44
end
[@@platform native]
$ ./standalone.exe -impl input_recmodule.ml -js | ocamlformat - --enable-outside-detected-project --impl
module rec M = struct
let x = 42
end
[@@platform js]
Pstr_class
$ cat > input_class.ml << EOF
> class virtual ['a] base x = object
> method get = x
> end [@@platform js]
> class derived = object
> inherit base 42
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_class.ml | ocamlformat - --enable-outside-detected-project --impl
class derived =
object
inherit base 42
end [@@platform native]
$ ./standalone.exe -impl input_class.ml -js | ocamlformat - --enable-outside-detected-project --impl
class virtual ['a] base x =
object
method get = x
end [@@platform js]
Pstr_class_type
$ cat > input_class_type.ml << EOF
> class type base = object
> method get : int
> end [@@platform js]
> class type derived = object
> inherit base
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_class_type.ml | ocamlformat - --enable-outside-detected-project --impl
class type derived = object
inherit base
end [@@platform native]
$ ./standalone.exe -impl input_class_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
class type base = object
method get : int
end [@@platform js]
================================================
FILE: packages/browser-ppx/tests/dune
================================================
(cram
(package server-reason-react)
(enabled_if
(>= %{ocaml_version} 5.2.0))
(deps %{bin:ocamlformat} standalone.exe))
(executable
(name standalone)
(libraries ppxlib browser_ppx))
================================================
FILE: packages/browser-ppx/tests/pexp_apply.t
================================================
$ cat > input.ml << EOF
> let pstr_value_binding = [%browser_only Webapi.Dom.getElementById "foo"]
> let make () =
> let%browser_only pstr_value_binding_2 = Webapi.Dom.getElementById "foo" in
> ()
>
> EOF
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let pstr_value_binding = Webapi.Dom.getElementById "foo"
let make () =
let pstr_value_binding_2 = Webapi.Dom.getElementById "foo" in
()
Without -js flag, the compilation to native errors out indicating that a function must be used
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let pstr_value_binding =
[%ocaml.error
"[browser_ppx] browser_only works on function definitions. For other \
cases, use switch%platform or feel free to open an issue in \
https://github.com/ml-in-barcelona/server-reason-react."]
let make () =
let pstr_value_binding_2 =
[%ocaml.error
"[browser_ppx] browser_only works on function definitions. For other \
cases, use switch%platform or feel free to open an issue in \
https://github.com/ml-in-barcelona/server-reason-react."]
in
()
================================================
FILE: packages/browser-ppx/tests/pexp_constraint_re.t
================================================
$ cat > input.re << EOF
> let make = () => {
> let%browser_only discard: Js.Promise.t(unit) => unit = value => ignore(value);
> ();
> };
>
> let%browser_only reifyStyle = (type a, x: 'a): (style(a), a) => {
> let isCanvasGradient = _ => false;
> let isCanvasPattern = _ => false;
>
> (
> if (Js.typeof(x) == "string") {
> Obj.magic(String);
> } else if (isCanvasGradient(x)) {
> Obj.magic(Gradient);
> } else if (isCanvasPattern(x)) {
> Obj.magic(Pattern);
> } else {
> invalid_arg("Unknown canvas style kind. Known values are: String, CanvasGradient, CanvasPattern");
> },
> Obj.magic(x),
> );
> };
>
> EOF
$ refmt --print ml input.re > input.ml
$ ./standalone.exe -impl input.ml -js | refmt --parse ml --print re
let make = () => {
let discard: Js.Promise.t(unit) => unit = value => ignore(value);
();
};
let reifyStyle = (type a, x: 'a): (style(a), a) => {
let isCanvasGradient = _ => false;
let isCanvasPattern = _ => false;
(
if (Js.typeof(x) == "string") {
Obj.magic(String);
} else if (isCanvasGradient(x)) {
Obj.magic(Gradient);
} else if (isCanvasPattern(x)) {
Obj.magic(Pattern);
} else {
invalid_arg(
"Unknown canvas style kind. Known values are: String, CanvasGradient, CanvasPattern",
);
},
Obj.magic(x),
);
};
$ ./standalone.exe -impl input.ml | refmt --parse ml --print re
let make = () => {
[@alert "-browser_only"]
let discard = value => Runtime.fail_impossible_action_in_ssr("discard");
();
};
let reifyStyle = (type a, x: 'a): (style(a), a) => {
let isCanvasGradient = _ => false;
let isCanvasPattern = _ => false;
(
if (Js.typeof(x) == "string") {
Obj.magic(String);
} else if (isCanvasGradient(x)) {
Obj.magic(Gradient);
} else if (isCanvasPattern(x)) {
Obj.magic(Pattern);
} else {
invalid_arg(
"Unknown canvas style kind. Known values are: String, CanvasGradient, CanvasPattern",
);
},
Obj.magic(x),
);
};
================================================
FILE: packages/browser-ppx/tests/pexp_fun.t
================================================
$ cat > input.ml << EOF
> let make () =
> let%browser_only fun_value_binding_pexp_fun_2arg evt moar_arguments =
> Webapi.Dom.getElementById "foo"
> in
>
> let%browser_only perform ?abortController ?(base = defaultBase) (req : ('handler, 'a, 'i, 'o) Client.request) input =
> Js.log Foo.var;
> Js.log record.field;
> Local.setHtmlFetchState value;
> Js.log abortController;
> Js.log (fun a -> a);
> Js.log base;
> Js.log req;
> Js.log input
> in
>
> let%browser_only fun_value_binding_labelled_args ~argument1 ~argument2 =
> setHtmlFetchState Loading
> in
> ()
> EOF
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let fun_value_binding_pexp_fun_2arg evt moar_arguments =
Webapi.Dom.getElementById "foo"
in
let perform ?abortController ?(base = defaultBase)
(req : ('handler, 'a, 'i, 'o) Client.request) input =
Js.log Foo.var;
Js.log record.field;
Local.setHtmlFetchState value;
Js.log abortController;
Js.log (fun a -> a);
Js.log base;
Js.log req;
Js.log input
in
let fun_value_binding_labelled_args ~argument1 ~argument2 =
setHtmlFetchState Loading
in
()
Without -js flag, the compilation to native replaces the expression with a raise
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl > output.ml
$ cat output.ml
let make () =
let fun_value_binding_pexp_fun_2arg evt =
Runtime.fail_impossible_action_in_ssr "fun_value_binding_pexp_fun_2arg"
[@@alert "-browser_only"]
in
let perform ?abortController =
Runtime.fail_impossible_action_in_ssr "perform"
[@@alert "-browser_only"]
in
let fun_value_binding_labelled_args ~argument1 =
Runtime.fail_impossible_action_in_ssr "fun_value_binding_labelled_args"
[@@alert "-browser_only"]
in
()
Replace Runtime.fail_impossible_action_in_ssr with print_endline so ocamlc can compile it without the Runtime module dependency
$ sed "s/Runtime.fail_impossible_action_in_ssr/print_endline/g" output.ml > output.ml
$ ocamlc -c output.ml
================================================
FILE: packages/browser-ppx/tests/pexp_fun_with_vb.t
================================================
$ cat > input.ml << EOF
> let make () =
> let%browser_only fun_value_binding_pexp_fun_2arg evt moar_arguments =
> let a = "foo" in
> Webapi.Dom.getElementById a
> in
>
> let%browser_only fun_value_binding_pexp_fun_default_expr ?(evt=22) =
> Webapi.Dom.getElementById evt
> in
>
> let%browser_only fun_value_binding_pexp_fun_2arg evt moar_arguments =
> let a = 1 in
> let b = 2 in
> Webapi.Dom.getElementById b
> in
> ()
>
> EOF
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let fun_value_binding_pexp_fun_2arg evt moar_arguments =
let a = "foo" in
Webapi.Dom.getElementById a
in
let fun_value_binding_pexp_fun_default_expr ?(evt = 22) =
Webapi.Dom.getElementById evt
in
let fun_value_binding_pexp_fun_2arg evt moar_arguments =
let a = 1 in
let b = 2 in
Webapi.Dom.getElementById b
in
()
Without -js flag, the compilation to native replaces the expression with a raise
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl > output.ml
$ cat output.ml
let make () =
let fun_value_binding_pexp_fun_2arg evt =
Runtime.fail_impossible_action_in_ssr "fun_value_binding_pexp_fun_2arg"
[@@alert "-browser_only"]
in
let fun_value_binding_pexp_fun_default_expr ?evt =
Runtime.fail_impossible_action_in_ssr
"fun_value_binding_pexp_fun_default_expr"
[@@alert "-browser_only"]
in
let fun_value_binding_pexp_fun_2arg evt =
Runtime.fail_impossible_action_in_ssr "fun_value_binding_pexp_fun_2arg"
[@@alert "-browser_only"]
in
()
Replace Runtime.fail_impossible_action_in_ssr with print_endline so ocamlc can compile it without the Runtime module dependency
$ sed "s/Runtime.fail_impossible_action_in_ssr/print_endline/g" output.ml > output.ml
$ ocamlc -c output.ml
================================================
FILE: packages/browser-ppx/tests/pexp_function.t
================================================
$ cat > input.re << EOF
> let%browser_only foo = fun
> | x when x < 0. => None
> | x => Some("bar");
>
> let make = () => {
> let%browser_only foo = fun
> | x when x < 0. => None
> | x => Some("bar");
> ();
> };
> EOF
$ refmt --parse re --print ml input.re > input.ml
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let foo = function x when x < 0. -> None | x -> Some "bar" [@explicit_arity]
let make () =
let foo = function
| x when x < 0. -> None
| x -> ( Some "bar" [@explicit_arity])
in
()
Without -js flag, the compilation to native errors out indicating that a function must be used
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let (foo
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun _ -> Runtime.fail_impossible_action_in_ssr "foo") [@alert "-browser_only"]
[@@warning "-27-32"]
let make () =
let foo =
[%ocaml.error
"[browser_ppx] browser_only works on function definitions. For other \
cases, use switch%platform or feel free to open an issue in \
https://github.com/ml-in-barcelona/server-reason-react."]
in
()
================================================
FILE: packages/browser-ppx/tests/pexp_ident.t
================================================
$ cat > input.ml << EOF
> let make () =
> let%browser_only pexp_ident = Webapi__Dom__Element.asHtmlElement in
> ()
> EOF
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let pexp_ident = Webapi__Dom__Element.asHtmlElement in
()
Without -js flag, the compilation to native errors out indicating that a function must be used
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let make () =
let pexp_ident =
[%ocaml.error
"[browser_ppx] browser_only works on function definitions. For other \
cases, use switch%platform or feel free to open an issue in \
https://github.com/ml-in-barcelona/server-reason-react."]
in
()
================================================
FILE: packages/browser-ppx/tests/playground.t/input.re
================================================
let%browser_only makeQuery =
(~abortController, ~encoding=?, pathname, req, input) => {
let signal =
abortController->Option.map(abortController =>
abortController->Fetch.AbortController.signal
);
let query = Js.Dict.empty();
encoding->Option.forEach(enc =>
query->Js.Dict.set("mode", "csv-" ++ EncodingT.unwrap(enc))
);
let defaultPostHeaders = (
"Content-Type",
"application/json; charset=utf-8",
);
switch (req.config.allowed_methods) {
| GET_or_POST =>
let queryParam = Sensitivity.to_query_param(req.config.sensitivity);
let inputString = input->(req.writeInput)->Js.Json.stringify;
let cloudflareLimit =
/* Cloudflare limit is 16358: https://ahrefs.slack.com/archives/CUPHP0EP8/p1677644203445649?thread_ts=1677599205.939129&cid=CUPHP0EP8
but we leave some room for headers and other parts of the req */
15000;
let inputStringEncoded = inputString->Js.Global.encodeURIComponent;
String.length(inputStringEncoded) > cloudflareLimit
? Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ()),
Fetch.RequestInit.make(
~method_=Post,
~body=inputString->Fetch.BodyInit.make,
~credentials=Include,
~headers=
makeHeadersInit(
~shouldBeGet=true,
~initHeaders=defaultPostHeaders,
(),
),
~signal?,
(),
),
)
: {
Js.Dict.set(query, queryParam, inputStringEncoded);
Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ~encode=false, ()),
Fetch.RequestInit.make(
~method_=Get,
~credentials=Include,
~headers=makeHeadersInit(),
~signal?,
(),
),
);
};
| POST_only =>
Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ()),
Fetch.RequestInit.make(
~method_=Post,
~body=input->(req.writeInput)->Js.Json.stringify->Fetch.BodyInit.make,
~credentials=Include,
~headers=makeHeadersInit(~initHeaders=defaultPostHeaders, ()),
~signal?,
(),
),
)
};
};
================================================
FILE: packages/browser-ppx/tests/playground.t/run.t
================================================
$ refmt --print ml ./input.re > input.ml
$ ../standalone.exe -impl input.ml -js | refmt --parse ml --print re
let makeQuery = (~abortController, ~encoding=?, pathname, req, input) => {
let signal =
abortController->Option.map(abortController =>
abortController->Fetch.AbortController.signal
);
let query = Js.Dict.empty();
encoding->Option.forEach(enc =>
query->Js.Dict.set("mode", "csv-" ++ EncodingT.unwrap(enc))
);
let defaultPostHeaders = (
"Content-Type",
"application/json; charset=utf-8",
);
switch (req.config.allowed_methods) {
| GET_or_POST =>
let queryParam = Sensitivity.to_query_param(req.config.sensitivity);
let inputString = input->(req.writeInput)->Js.Json.stringify;
let cloudflareLimit = 15000;
let inputStringEncoded = inputString->Js.Global.encodeURIComponent;
String.length(inputStringEncoded) > cloudflareLimit
? Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ()),
Fetch.RequestInit.make(
~method_=Post,
~body=inputString->Fetch.BodyInit.make,
~credentials=Include,
~headers=
makeHeadersInit(
~shouldBeGet=true,
~initHeaders=defaultPostHeaders,
(),
),
~signal?,
(),
),
)
: {
Js.Dict.set(query, queryParam, inputStringEncoded);
Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ~encode=false, ()),
Fetch.RequestInit.make(
~method_=Get,
~credentials=Include,
~headers=makeHeadersInit(),
~signal?,
(),
),
);
};
| POST_only =>
Fetch.fetchWithInit(
Url.makeStringWithQueryDict(~pathname, ~query, ()),
Fetch.RequestInit.make(
~method_=Post,
~body=input->(req.writeInput)->Js.Json.stringify->Fetch.BodyInit.make,
~credentials=Include,
~headers=makeHeadersInit(~initHeaders=defaultPostHeaders, ()),
~signal?,
(),
),
)
};
};
$ ../standalone.exe -impl input.ml | refmt --parse ml --print re
[@warning "-27-32"]
let [@alert
browser_only(
"This expression is marked to only run on the browser where JavaScript can run. You can only use it inside a let%browser_only function.",
)
]
makeQuery =
[@alert "-browser_only"]
(
(~abortController, ~encoding=?, pathname, req, input) =>
Runtime.fail_impossible_action_in_ssr("makeQuery")
);
================================================
FILE: packages/browser-ppx/tests/preprocess.t
================================================
Nested
$ cat > input.ml << EOF
> module X = struct
> include struct
> type t = Js.Json.t
> let a = 2 + 2
> end [@@platform js]
>
> include struct
> type t = Js.Json.t
> let a = 4 + 4
> end [@@platform native]
> end
> EOF
With -js flag it picks the block with `[@@platform js]`
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
module X = struct
include struct
type t = Js.Json.t
let a = 2 + 2
end [@@platform js]
end
Without -js flag, it picks the block with `[@@platform native]`
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
module X = struct
include struct
type t = Js.Json.t
let a = 4 + 4
end [@@platform native]
end
Pstr_include
$ cat > input.ml << EOF
> include struct
> type t = Js.Json.t
> end [@@platform js]
>
> include struct
> type t = string
> end [@@platform native]
> EOF
With -js flag it picks the block with `[@@platform js]`
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = Js.Json.t
end [@@platform js]
Without -js flag, it picks the block with `[@@platform native]`
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = string
end [@@platform native]
Use only one of the platforms
$ cat > input.ml << EOF
> include struct
> type t = Js.Json.t
> end [@@platform js]
>
> include struct
> type t = string
> end
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = string
end
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
type t = Js.Json.t
end [@@platform js]
include struct
type t = string
end
Pstr_module
$ cat > input_module.ml << EOF
> module M = struct
> let x = 42
> end [@@platform js]
> module M = struct
> let x = 44
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_module.ml | ocamlformat - --enable-outside-detected-project --impl
module M = struct
let x = 44
end
[@@platform native]
$ ./standalone.exe -impl input_module.ml -js | ocamlformat - --enable-outside-detected-project --impl
module M = struct
let x = 42
end
[@@platform js]
Pstr_value
$ cat > input_let.ml << EOF
> let x = 42 [@@platform js]
> let y = 44 [@@platform native]
> EOF
$ ./standalone.exe -impl input_let.ml | ocamlformat - --enable-outside-detected-project --impl
let y = 44 [@@platform native]
$ ./standalone.exe -impl input_let.ml -js | ocamlformat - --enable-outside-detected-project --impl
let x = 42 [@@platform js]
Pstr_open
$ cat > input_open.ml << EOF
> open Printf [@@platform js]
> open List [@@platform native]
> EOF
$ ./standalone.exe -impl input_open.ml | ocamlformat - --enable-outside-detected-project --impl
open List [@@platform native]
$ ./standalone.exe -impl input_open.ml -js | ocamlformat - --enable-outside-detected-project --impl
open Printf [@@platform js]
Pstr_exception
$ cat > input_exception.ml << EOF
> exception MyException of string [@@platform js]
> exception AnotherException of int [@@platform native]
> EOF
$ ./standalone.exe -impl input_exception.ml | ocamlformat - --enable-outside-detected-project --impl
exception AnotherException of int [@@platform native]
$ ./standalone.exe -impl input_exception.ml -js | ocamlformat - --enable-outside-detected-project --impl
exception MyException of string [@@platform js]
Pstr_primitive
$ cat > input_primitive.ml << EOF
> external add : int -> int -> int = "caml_add_int" [@@platform js]
> external subtract : int -> int -> int = "caml_subtract_int" [@@platform native]
> EOF
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
external subtract : int -> int -> int = "caml_subtract_int" [@@platform native]
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
external add : int -> int -> int = "caml_add_int" [@@platform js]
Pstr_eval (doesn't work)
$ cat > input_primitive.ml << EOF
> include struct
> 2 [@@platform js]
> end
>
> include struct
> 3 [@@platform native]
> end
> EOF
$ ./standalone.exe -impl input_primitive.ml | ocamlformat - --enable-outside-detected-project --impl
include struct end
include struct
3 [@@platform native]
end
$ ./standalone.exe -impl input_primitive.ml -js | ocamlformat - --enable-outside-detected-project --impl
include struct
2 [@@platform js]
end
include struct end
Pstr_type
$ cat > input_type.ml << EOF
> type point = { x : int; y : int } [@@platform js]
> type color = Red | Green | Blue [@@platform native]
> EOF
$ ./standalone.exe -impl input_type.ml | ocamlformat - --enable-outside-detected-project --impl
type color = Red | Green | Blue [@@platform native]
$ ./standalone.exe -impl input_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
type point = { x : int; y : int } [@@platform js]
Pstr_recmodule
$ cat > input_recmodule.ml << EOF
> module rec M = struct
> let x = 42
> end [@@platform js]
> module rec M = struct
> let x = 44
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_recmodule.ml | ocamlformat - --enable-outside-detected-project --impl
module rec M = struct
let x = 44
end
[@@platform native]
$ ./standalone.exe -impl input_recmodule.ml -js | ocamlformat - --enable-outside-detected-project --impl
module rec M = struct
let x = 42
end
[@@platform js]
Pstr_class
$ cat > input_class.ml << EOF
> class virtual ['a] base x = object
> method get = x
> end [@@platform js]
> class derived = object
> inherit base 42
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_class.ml | ocamlformat - --enable-outside-detected-project --impl
class derived =
object
inherit base 42
end [@@platform native]
$ ./standalone.exe -impl input_class.ml -js | ocamlformat - --enable-outside-detected-project --impl
class virtual ['a] base x =
object
method get = x
end [@@platform js]
Pstr_class_type
$ cat > input_class_type.ml << EOF
> class type base = object
> method get : int
> end [@@platform js]
> class type derived = object
> inherit base
> end [@@platform native]
> EOF
$ ./standalone.exe -impl input_class_type.ml | ocamlformat - --enable-outside-detected-project --impl
class type derived = object
inherit base
end [@@platform native]
$ ./standalone.exe -impl input_class_type.ml -js | ocamlformat - --enable-outside-detected-project --impl
class type base = object
method get : int
end [@@platform js]
================================================
FILE: packages/browser-ppx/tests/standalone.ml
================================================
(* To run as a standalone binary, run the registered drivers *)
let () = Ppxlib.Driver.standalone ()
================================================
FILE: packages/browser-ppx/tests/structure_item.t
================================================
$ cat > input.ml << EOF
> [%%browser_only let ( let+ ) = fun p f -> map f p]
>
> let%browser_only pexp_ident = Webapi__Dom__Element.asHtmlElement
>
> let%browser_only pexp_fun_1arg_structure_item evt =
> Webapi.Dom.getElementById "foo"
>
> let%browser_only pexp_fun_2arg_structure_item evt moar_arguments =
> let a = "foo" in
> Webapi.Dom.getElementById a
>
> let%browser_only pexp_fun_2arg_structure_item evt moar_arguments =
> let a = "foo" in
> let a = "foo" in
> Webapi.Dom.getElementById a
>
> let%browser_only perform ?abortController ?(base = defaultBase) (req : ('handler, 'a, 'i, 'o) Client.request) input =
> Js.log abortController;
> Js.log base;
> Js.log req;
> Js.log input
>
> EOF
With -js flag everything keeps as it is and browser_only extension disappears
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let ( let+ ) p f = map f p
let pexp_ident = Webapi__Dom__Element.asHtmlElement
let pexp_fun_1arg_structure_item evt = Webapi.Dom.getElementById "foo"
let pexp_fun_2arg_structure_item evt moar_arguments =
let a = "foo" in
Webapi.Dom.getElementById a
let pexp_fun_2arg_structure_item evt moar_arguments =
let a = "foo" in
let a = "foo" in
Webapi.Dom.getElementById a
let perform ?abortController ?(base = defaultBase)
(req : ('handler, 'a, 'i, 'o) Client.request) input =
Js.log abortController;
Js.log base;
Js.log req;
Js.log input
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let (( let+ )
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun p ->
Runtime.fail_impossible_action_in_ssr "let+")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (pexp_ident
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
Obj.magic () [@alert "-browser_only"]
[@@warning "-27-32"]
let (pexp_fun_1arg_structure_item
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun evt ->
Runtime.fail_impossible_action_in_ssr "pexp_fun_1arg_structure_item")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (pexp_fun_2arg_structure_item
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun evt ->
Runtime.fail_impossible_action_in_ssr "pexp_fun_2arg_structure_item")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (pexp_fun_2arg_structure_item
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun evt ->
Runtime.fail_impossible_action_in_ssr "pexp_fun_2arg_structure_item")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (perform
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun ?abortController ->
Runtime.fail_impossible_action_in_ssr "perform")
[@alert "-browser_only"]
[@@warning "-27-32"]
Replace Runtime.fail_impossible_action_in_ssr with print_endline so ocamlc can compile it without the Runtime module dependency
$ echo "module Runtime = struct" >> output.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> output.ml
$ echo "end" >> output.ml
$ ocamlc -c output.ml
================================================
FILE: packages/browser-ppx/tests/structure_item_re.t
================================================
$ cat > input.re << EOF
> let%browser_only valueFromEvent = evt => React.Event.Form.target(evt)##value;
> let%browser_only getSortedWordCountsBrowserOnly = (words: array(string)): array((string, int)) => {
> words->List.map->Js.log;
> };
>
> let%browser_only renderToElementWithId = (~id="", component) => {
> switch (ReactDOM.querySelector("#" ++ id)) {
> | Some(node) =>
> let root = ReactDOM.Client.createRoot(node);
> ReactDOM.Client.render(root, component);
> | None => Js.Console.error("RR.renderToElementWithId : no element of id '" ++ id ++ "' found in the HTML.")
> };
> };
>
> let%browser_only getSortedWordCountsBrowserOnly = (words: array(string)): array((string, int)) => {
> words |> Js.log |> List.map;
> };
>
> let%browser_only getSortedWordCountsBrowserOnly = (words: array(string)): array((string, int)) => {
> words
> ->Js.Array2.reduce(
> (acc, word) => {
> Map.String.update(acc, word, count =>
> switch (count) {
> | Some(existingCount) => Some(existingCount + 1)
> | None => Some(1)
> }
> )
> },
> Map.String.empty
> )
> ->Map.String.toArray
> ->Js.Array2.sortInPlaceWith(((_, a), (_, b)) => b - a);
> };
>
> EOF
$ refmt --print ml input.re > input.ml
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let valueFromEvent evt = (React.Event.Form.target evt)##value
let getSortedWordCountsBrowserOnly (words : string array) =
(words |. List.map |. Js.log : (string * int) array)
let renderToElementWithId ?(id = "") =
fun component ->
match ReactDOM.querySelector ("#" ^ id) with
| ((Some node) [@explicit_arity]) ->
let root = ReactDOM.Client.createRoot node in
ReactDOM.Client.render root component
| None ->
Js.Console.error
("RR.renderToElementWithId : no element of id '" ^ id
^ "' found in the HTML.")
let getSortedWordCountsBrowserOnly (words : string array) =
(words |> Js.log |> List.map : (string * int) array)
let getSortedWordCountsBrowserOnly (words : string array) =
(((words |. Js.Array2.reduce)
(fun acc ->
fun word ->
Map.String.update acc word (fun count ->
match count with
| ((Some existingCount) [@explicit_arity]) ->
Some (existingCount + 1) [@explicit_arity]
| None -> Some 1 [@explicit_arity]))
Map.String.empty
|. Map.String.toArray |. Js.Array2.sortInPlaceWith) (fun (_, a) ->
fun (_, b) -> b - a)
: (string * int) array)
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let (valueFromEvent
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun evt ->
Runtime.fail_impossible_action_in_ssr "valueFromEvent")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (getSortedWordCountsBrowserOnly
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun words ->
Runtime.fail_impossible_action_in_ssr "getSortedWordCountsBrowserOnly")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (renderToElementWithId
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun ?id component ->
Runtime.fail_impossible_action_in_ssr "renderToElementWithId")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (getSortedWordCountsBrowserOnly
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun words ->
Runtime.fail_impossible_action_in_ssr "getSortedWordCountsBrowserOnly")
[@alert "-browser_only"]
[@@warning "-27-32"]
let (getSortedWordCountsBrowserOnly
[@alert
browser_only
"This expression is marked to only run on the browser where \
JavaScript can run. You can only use it inside a let%browser_only \
function."]) =
(fun words ->
Runtime.fail_impossible_action_in_ssr "getSortedWordCountsBrowserOnly")
[@alert "-browser_only"]
[@@warning "-27-32"]
================================================
FILE: packages/browser-ppx/tests/switch-platform.t/input.re
================================================
switch%platform (Runtime.platform) {
| Runtime.Server => doServerSideLogic()
| Client => doClientSideLogic()
};
let value =
switch%platform (Runtime.platform) {
| Server => doServerSideLogic()
| Client => doClientSideLogic()
};
let universal_fn = () => {
switch%platform (Runtime.platform) {
| Server => doServerSideLogic()
| Client => doClientSideLogic()
};
};
let universal_fn_with_arg1 = arg1 => {
switch%platform (Runtime.platform) {
| Server => doServerSideLogic(arg1)
| Client => doClientSideLogic()
};
};
================================================
FILE: packages/browser-ppx/tests/switch-platform.t/run.t
================================================
With -js flag everything keeps as it is and effect extension disappears
$ refmt --parse re --print ml input.re > input.re.ml
$ ../standalone.exe -impl input.re.ml -js | ocamlformat - --enable-outside-detected-project --impl
doClientSideLogic ();;
let value = doClientSideLogic ()
let universal_fn () = doClientSideLogic ()
let universal_fn_with_arg1 arg1 = doClientSideLogic ()
Without -js flag, the compilation to native replaces the effect expression
with a no-op effect, raises in case of wrongly applied to other than an effect.
$ ../standalone.exe -impl input.re.ml | ocamlformat - --enable-outside-detected-project --impl
doServerSideLogic ();;
let value = doServerSideLogic ()
let universal_fn () = doServerSideLogic ()
let universal_fn_with_arg1 arg1 = doServerSideLogic arg1
================================================
FILE: packages/browser-ppx/tests/use_effect.t
================================================
$ cat > input.re << EOF
> [@react.component]
> let make = () => {
> let (state, dispatch) = React.useReducer(reducer, initialState);
>
> React.useEffect0(() => {
> dispatch @@ UsersRequestStarted;
> None;
> });
>
> ;
> };
> EOF
$ refmt --parse re --print ml input.re > input.ml
With -js flag everything keeps as it is
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect0 (fun () ->
dispatch @@ UsersRequestStarted;
None);
div ~children:[] () [@JSX]
[@@react.component]
Without -js flag, we add the browser_only transformation and browser_only applies the transformation to fail_impossible_action_in_ssr
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect0 (fun () ->
Runtime.fail_impossible_action_in_ssr "");
div ~children:[] () [@JSX]
[@@react.component]
$ cat > input.re << EOF
> [@react.component]
> let make = () => {
> React.useEffect2(
> () => {
> if (uiState == Submitted) {
> dispatch @@
> CurrentPasswordUpdated(
> switch (currentPassword) {
> | WithValue(value) when value == "" => Empty
> | _ => currentPassword
> },
> );
>
> switch (currentPassword, newPassword) {
> | (WithValue(currentPassword), Valid(newPassword)) when currentPassword != "" =>
> passwordReset({oldPassword: currentPassword, newPassword}, dispatch, onConfirmed)
> | _ => dispatch @@ SubmitTriggered(Idle)
> };
> };
> None;
> },
> (uiState, newPassword),
> );
>
> ;
> };
> EOF
$ refmt --parse re --print ml input.re > input.ml
Without -js flag, we add the browser_only transformation and browser_only applies the transformation to fail_impossible_action_in_ssr
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let make () =
React.useEffect2
(fun () -> Runtime.fail_impossible_action_in_ssr "")
(uiState, newPassword);
div ~children:[] () [@JSX]
[@@react.component]
$ cat > input.re << EOF
> [@react.component]
> let make = () => {
> let (state, dispatch) = React.useReducer(reducer, initialState);
>
> React.useEffect2(
> [%browser_only
> () => {
> let handler = Js.Global.setTimeout(~f=_ => setDebouncedValue(focusedEntryText), delayInMs);
> Some(_ => Js.Global.clearTimeout(handler));
> }
> ],
> (focusedEntryText, delayInMs),
> );
>
> ;
> };
> EOF
$ refmt --parse re --print ml input.re > input.ml
With -js flag everything keeps as it is
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect2
(fun () ->
let handler =
Js.Global.setTimeout
~f:(fun _ -> setDebouncedValue focusedEntryText)
delayInMs
in
(Some (fun _ -> Js.Global.clearTimeout handler) [@explicit_arity]))
(focusedEntryText, delayInMs);
div ~children:[] () [@JSX]
[@@react.component]
Without -js flag, we add the browser_only transformation and browser_only applies the transformation to fail_impossible_action_in_ssr
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect2
(fun () -> Runtime.fail_impossible_action_in_ssr "")
(focusedEntryText, delayInMs);
div ~children:[] () [@JSX]
[@@react.component]
$ cat > input.re << EOF
> [@react.component]
> let make = () => {
> let (state, dispatch) = React.useReducer(reducer, initialState);
>
> React.useEffect1(
> () => {
> isFocused ? onFocusedItemChange(domRef) : ();
> None;
> },
> [|isFocused|],
> );
>
> ;
> };
> EOF
$ refmt --parse re --print ml input.re > input.ml
With -js flag everything keeps as it is
$ ./standalone.exe -impl input.ml -js | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect1
(fun () ->
(match isFocused with true -> onFocusedItemChange domRef | false -> ());
None)
[| isFocused |];
div ~children:[] () [@JSX]
[@@react.component]
Without -js flag, we add the browser_only transformation and browser_only applies the transformation to fail_impossible_action_in_ssr
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let make () =
let state, dispatch = React.useReducer reducer initialState in
React.useEffect1
(fun () -> Runtime.fail_impossible_action_in_ssr "")
[| isFocused |];
div ~children:[] () [@JSX]
[@@react.component]
================================================
FILE: packages/esbuild-plugin/dune
================================================
(executable
(name extract_client_components)
(public_name server-reason-react.extract_client_components)
(libraries unix cmdliner))
(install
(section lib)
(package server-reason-react)
(files
(plugin.mjs as esbuild-plugin/plugin.mjs)))
================================================
FILE: packages/esbuild-plugin/extract_client_components.ml
================================================
module List = ListLabels
let read_file path = try Some (In_channel.with_open_bin path In_channel.input_all) with _ -> None
type manifest_item =
| Client_component of { original_path : string; compiled_js_path : string; module_name : string list option }
| Server_function of {
id : string;
compiled_js_path : string;
module_name : string list option;
function_name : string;
}
let parse_module_name str = String.split_on_char '.' str
let print_module_name str = String.concat "." str
let parse_client_component_line line =
try
Scanf.sscanf line "// extract-client %s %s" (fun filename module_name ->
Ok (filename, if module_name = "" then None else Some (parse_module_name module_name)))
with End_of_file | Scanf.Scan_failure _ -> Error "Invalid `extract-client` command format"
let parse_server_function_line line =
try
Scanf.sscanf line "// extract-server-function %s %s %s" (fun id function_name module_name ->
Ok ((if module_name = "" then None else Some (parse_module_name module_name)), function_name, id))
with End_of_file | Scanf.Scan_failure _ -> Error "Invalid `extract-server-function` command format"
let parse_manifest_item ~path line =
match (parse_client_component_line (String.trim line), parse_server_function_line (String.trim line)) with
| Ok (original_path, module_name), _ ->
Some (Client_component { compiled_js_path = path; original_path; module_name })
| _, Ok (module_name, function_name, id) ->
Some (Server_function { compiled_js_path = path; module_name; function_name; id })
| Error _, Error _ -> None
let parse_manifest_data ~path content : manifest_item list =
content |> String.split_on_char '\n' |> List.filter_map ~f:(parse_manifest_item ~path)
let render_manifest manifest =
let register_client_modules =
List.map manifest ~f:(function
| Client_component { original_path; compiled_js_path; module_name } ->
let original_path_with_submodule =
match module_name with
| Some name -> Printf.sprintf "%s#%s" original_path (print_module_name name)
| None -> original_path
in
let export =
match module_name with
| Some name -> Printf.sprintf "%s.make_client" (print_module_name name)
| None -> "make_client"
in
Printf.sprintf
"window.__client_manifest_map[\"%s\"] = React.lazy(() => import(\"%s\").then(module => {\n\
\ return { default: module.%s }\n\
}).catch(err => { console.error(err); return { default: null }; }))"
original_path_with_submodule compiled_js_path export
| Server_function { compiled_js_path; module_name; function_name; id } ->
let export =
match module_name with
| Some name -> Printf.sprintf "%s.%s" (print_module_name name) function_name
| None -> function_name
in
Printf.sprintf "window.__server_functions_manifest_map[\"%s\"] = require(\"%s\").%s" id compiled_js_path
export)
in
Printf.sprintf
{|import React from "react";
window.__client_manifest_map = window.__client_manifest_map || {};
window.__server_functions_manifest_map = window.__server_functions_manifest_map || {};
%s|}
(String.concat "\n" register_client_modules)
(* TODO: Add parameter to allow users to configure the extension of the files *)
let is_js_file path =
let ext = Filename.extension path in
ext = ".js" || ext = ".bs.js" || ext = ".jsx"
(* TODO: refactor path to be a Filepath, not a string *)
let capture_all_client_modules_files_in_target path =
let rec traverse_fs path =
try
match Sys.is_directory path with
| true ->
let contents = Sys.readdir path in
Array.fold_left
(fun acc entry ->
let full_path = Filename.concat path entry in
match acc with
| Ok files -> (
match traverse_fs full_path with Ok new_files -> Ok (files @ new_files) | Error err -> Error err)
| Error err -> Error err)
(Ok []) contents
| false ->
if is_js_file path then
match read_file path with
| Some content -> Ok (parse_manifest_data ~path content)
| None -> Error (Printf.sprintf "Failed to read file: %s" path)
else Ok []
with
| Sys_error msg -> Error (Printf.sprintf "System error: %s" msg)
| Unix.Unix_error (err, _, _) -> Error (Printf.sprintf "Unix error: %s" (Unix.error_message err))
| e -> Error (Printf.sprintf "Unexpected error: %s" (Printexc.to_string e))
in
traverse_fs path
let melange_target =
let doc = "Path to the melange target directory (melange.emit (target xxx))" in
Cmdliner.Arg.(required & pos 0 (some string) None & info [] ~docv:"MELANGE_TARGET" ~doc)
let extract_modules target =
let current_dir = Sys.getcwd () in
let melange_target = Filename.concat current_dir target in
match capture_all_client_modules_files_in_target melange_target with
| Ok manifest ->
print_endline (render_manifest manifest);
Ok ()
| Error msg -> Error (`Msg msg)
let extract_cmd =
let open Cmdliner in
let doc = "Extract all client modules from a Melange target folder" in
let sdocs = Manpage.s_common_options in
let info = Cmd.info "extract-client-components" ~version:"1.0.0" ~doc ~sdocs in
let term = Term.(term_result (const extract_modules $ melange_target)) in
Cmd.v info term
let () = exit (Cmdliner.Cmd.eval extract_cmd)
================================================
FILE: packages/esbuild-plugin/package.json
================================================
{
"name": "server-reason-react-esbuild-plugin",
"description": "esbuild plugin to enable React server components with server-reason-react",
"version": "0.1.0",
"exports": {
".": "./plugin.mjs"
},
"homepage": "https://github.com/ml-in-barcelona/server-reason-react",
"bugs": "https://github.com/ml-in-barcelona/server-reason-react/issues",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ml-in-barcelona/server-reason-react.git",
"directory": "packages/esbuild-plugin"
}
}
================================================
FILE: packages/esbuild-plugin/plugin.mjs
================================================
import Fs from "node:fs/promises";
import Path from "node:path";
import { execSync } from "node:child_process";
async function writeFile(path, contents, cb) {
await Fs.mkdir(Path.dirname(path), { recursive: true })
Fs.writeFile(path, contents, cb);
}
async function generateBootstrapFile(output, content) {
let previousContent = undefined;
try {
previousContent = await Fs.readFile(output, "utf8");
} catch (e) {
if (e.code !== "ENOENT") {
throw e;
}
}
const contentHasChanged = previousContent !== content;
if (contentHasChanged) {
await writeFile(output, content, "utf8");
}
}
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export default function plugin(config) {
const entrypointFilter = new RegExp(config.entrypoints.map(escapeRegex).join("|"));
return {
name: "extract-client-components",
setup(build) {
if (
config.bootstrapOutput &&
typeof config.bootstrapOutput !== "string"
) {
console.error("bootstrapOutput must be a string");
return;
}
const bootstrapOutput = config.bootstrapOutput || "./bootstrap.js";
if (!config.target) {
console.error("target is required");
return;
}
if (typeof config.target !== "string") {
console.error("target must be a string");
return;
}
build.onStart(async () => {
try {
/* TODO: Make sure `server-reason-react.extract_client_components` is available in $PATH */
const bootstrapContent = execSync(
`server-reason-react.extract_client_components ${config.target}`,
{ encoding: "utf8" },
);
await generateBootstrapFile(bootstrapOutput, bootstrapContent);
} catch (e) {
console.log("Extraction of client components failed:");
console.error(e);
return;
}
});
build.onResolve({ filter: entrypointFilter }, (args) => {
const isEntryPoint = args.kind === "entry-point";
if (isEntryPoint) {
return {
path: args.path,
namespace: "entrypoint",
};
}
return null;
});
build.onLoad({ filter: entrypointFilter, namespace: "entrypoint" }, async (args) => {
const filePath = args.path.replace(/^entrypoint:/, "");
const entryPointContents = await Fs.readFile(filePath, "utf8");
const relativeBootstrapOutput = Path.relative(
Path.dirname(filePath),
bootstrapOutput,
);
const contents = `
require("./${relativeBootstrapOutput}");
${entryPointContents}`;
return {
loader: "jsx",
contents,
resolveDir: Path.dirname(Path.resolve(process.cwd(), filePath)),
};
});
},
};
}
================================================
FILE: packages/esbuild-plugin/test/ClientComponent.js
================================================
// extract-client demo/universal/native/shared/Button.re
function make_client() { }
export {
make_client
}
================================================
FILE: packages/esbuild-plugin/test/ClientComponentWithModule.js
================================================
// extract-client demo/universal/native/shared/Button.re WithModule
function make_client() { }
const WithModule = {
make_client
}
export {
WithModule
}
================================================
FILE: packages/esbuild-plugin/test/ServerFunction.js
================================================
// extract-server-function 1234-4567 serverFunction
function serverFunction() { }
// extract-server-function 7654-3210 serverFunctionWithModule WithModule
function serverFunctionWithModule() { }
const WithModule = {
serverFunctionWithModule
};
================================================
FILE: packages/esbuild-plugin/test/dune
================================================
(cram
(package server-reason-react)
(deps
(package server-reason-react)
ClientComponent.js
ClientComponentWithModule.js
ServerFunction.js
%{bin:server-reason-react.extract_client_components}))
================================================
FILE: packages/esbuild-plugin/test/run.t
================================================
$ server-reason-react.extract_client_components ./ClientComponent.js
import React from "react";
window.__client_manifest_map = window.__client_manifest_map || {};
window.__server_functions_manifest_map = window.__server_functions_manifest_map || {};
window.__client_manifest_map["demo/universal/native/shared/Button.re"] = React.lazy(() => import("$TESTCASE_ROOT/./ClientComponent.js").then(module => {
return { default: module.make_client }
}).catch(err => { console.error(err); return { default: null }; }))
$ server-reason-react.extract_client_components ./ClientComponentWithModule.js
import React from "react";
window.__client_manifest_map = window.__client_manifest_map || {};
window.__server_functions_manifest_map = window.__server_functions_manifest_map || {};
window.__client_manifest_map["demo/universal/native/shared/Button.re#WithModule"] = React.lazy(() => import("$TESTCASE_ROOT/./ClientComponentWithModule.js").then(module => {
return { default: module.WithModule.make_client }
}).catch(err => { console.error(err); return { default: null }; }))
$ server-reason-react.extract_client_components ./ServerFunction.js
import React from "react";
window.__client_manifest_map = window.__client_manifest_map || {};
window.__server_functions_manifest_map = window.__server_functions_manifest_map || {};
window.__server_functions_manifest_map["1234-4567"] = require("$TESTCASE_ROOT/./ServerFunction.js").serverFunction
window.__server_functions_manifest_map["7654-3210"] = require("$TESTCASE_ROOT/./ServerFunction.js").WithModule.serverFunctionWithModule
================================================
FILE: packages/expand-styles-attribute/dune
================================================
(library
(name expand_styles_attribute)
(public_name server-reason-react.expand-styles-attribute)
(libraries ppxlib ppxlib.astlib)
(preprocess
(pps ppxlib.metaquot)))
================================================
FILE: packages/expand-styles-attribute/expand_styles_attribute.ml
================================================
let make ~loc attributes =
let merge_className current_className (label, expr) =
match current_className with
| Some (existing_label, existing_expr) ->
let merged =
match label with
| Ppxlib.Optional "className" ->
[%expr match [%e expr] with None -> [%e existing_expr] | Some x -> x ^ " " ^ [%e existing_expr]]
| _ -> [%expr [%e expr] ^ " " ^ [%e existing_expr]]
in
Some (existing_label, merged)
| None -> Some (label, expr)
in
let merge_style current_style (label, expr) =
match current_style with
| Some (existing_label, existing_expr) ->
let merged =
match label with
| Ppxlib.Optional "style" ->
[%expr
match [%e expr] with
| None -> [%e existing_expr]
| Some x -> ReactDOM.Style.combine [%e existing_expr] x]
| _ -> [%expr ReactDOM.Style.combine [%e existing_expr] [%e expr]]
in
Some (existing_label, merged)
| None -> Some (label, expr)
in
let handle_styles className style label arg =
let className_label, className_expr, style_label, style_expr =
match label with
| Ppxlib.Labelled "styles" ->
(Ppxlib.Labelled "className", [%expr fst [%e arg]], Ppxlib.Labelled "style", [%expr snd [%e arg]])
| _ ->
( Ppxlib.Optional "className",
[%expr match [%e arg] with None -> None | Some x -> Some (fst x)],
Ppxlib.Optional "style",
[%expr match [%e arg] with None -> None | Some x -> Some (snd x)] )
in
(merge_className className (className_label, className_expr), merge_style style (style_label, style_expr))
in
let rec aux (className, style, other_args) args =
match args with
| [] ->
let rest = List.rev other_args in
([ className; style ] |> List.filter_map Stdlib.Fun.id) @ rest
| (label, arg) :: rest -> (
match label with
| Ppxlib.Labelled "className" | Ppxlib.Optional "className" ->
aux (merge_className className (label, arg), style, other_args) rest
| Ppxlib.Labelled "style" | Ppxlib.Optional "style" ->
aux (className, merge_style style (label, arg), other_args) rest
| Ppxlib.Labelled "styles" | Ppxlib.Optional "styles" ->
let new_className, new_style = handle_styles className style label arg in
aux (new_className, new_style, other_args) rest
| _ -> aux (className, style, (label, arg) :: other_args) rest)
in
aux (None, None, []) attributes
================================================
FILE: packages/expand-styles-attribute/test/dune
================================================
(test
(name test)
(libraries
ppxlib
ppxlib.astlib
alcotest
server-reason-react.expand-styles-attribute)
(preprocess
(pps ppxlib.metaquot)))
================================================
FILE: packages/expand-styles-attribute/test/test.ml
================================================
let test_expand_styles () =
let loc = Ppxlib.Location.none in
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
let attributes = [ (Ppxlib.Labelled "styles", expr) ] in
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
List.iter
(fun attribute ->
match attribute with
| ( Ppxlib.Labelled "className",
[%expr fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ) ->
Alcotest.(check pass) "className uses fst" () ()
| Ppxlib.Labelled "style", [%expr snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ->
Alcotest.(check pass) "style uses snd" () ()
| _ -> Alcotest.fail "Expanded attributes should be className and style")
expanded_attributes
let test_expand_styles_with_previous_className () =
let loc = Ppxlib.Location.none in
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
let attributes = [ (Ppxlib.Labelled "className", [%expr "previous-class-name"]); (Ppxlib.Labelled "styles", expr) ] in
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
List.iter
(fun attribute ->
match attribute with
| ( Ppxlib.Labelled "className",
[%expr
fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) ^ " " ^ "previous-class-name"]
) ->
Alcotest.(check pass) "className uses previous class name" () ()
| Ppxlib.Labelled "style", [%expr snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ->
Alcotest.(check pass) "style uses combine" () ()
| _ -> Alcotest.fail "Expanded attributes should be className and style")
expanded_attributes
let test_expand_styles_with_previous_style () =
let loc = Ppxlib.Location.none in
let expr = [%expr "some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()] in
let attributes = [ (Ppxlib.Labelled "style", [%expr "previous-style"]); (Ppxlib.Labelled "styles", expr) ] in
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
List.iter
(fun attribute ->
match attribute with
| ( Ppxlib.Labelled "className",
[%expr fst ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] ) ->
Alcotest.(check pass) "className uses fst" () ()
| ( Ppxlib.Labelled "style",
[%expr
ReactDOM.Style.combine "previous-style"
(snd ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()))] ) ->
Alcotest.(check pass) "style uses combine" () ()
| _ -> Alcotest.fail "Expanded attributes should be className and style")
expanded_attributes
let test_expand_styles_optional () =
let loc = Ppxlib.Location.none in
let expr = [%expr Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ())] in
let attributes = [ (Ppxlib.Optional "styles", expr) ] in
let expanded_attributes = Expand_styles_attribute.make ~loc:Ppxlib.Location.none attributes in
List.iter
(fun attribute ->
match attribute with
| ( Ppxlib.Optional "className",
[%expr
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
| None -> None
| Some x -> Some (fst x)] ) ->
Alcotest.(check pass) "className uses fst" () ()
| ( Ppxlib.Optional "style",
[%expr
match Some ("some-class-name", ReactDOM.Style.make ~backgroundColor:"gainsboro" ()) with
| None -> None
| Some x -> Some (snd x)] ) ->
Alcotest.(check pass) "style uses snd" () ()
| _ -> Alcotest.fail "Expanded attributes should be className and style")
expanded_attributes
let test title fn = (title, [ Alcotest.test_case "" `Quick fn ])
let () =
Alcotest.run "expand_styles_attribute"
[
test "expand_styles_prop_on_attributes" test_expand_styles;
test "expand_styles_with_previous_className" test_expand_styles_with_previous_className;
test "expand_styles_with_previous_style" test_expand_styles_with_previous_style;
test "expand_styles_optional" test_expand_styles_optional;
]
================================================
FILE: packages/fetch/Fetch.ml
================================================
type body
type bodyInit
type headers = string Js.Dict.t
type headersInit
type response
type request
type requestInit
type signal = { aborted : bool; onabort : (unit -> unit) option; reason : string option }
type abortController = { signal : signal; abort : unit -> unit }
(* external *)
type arrayBuffer (* TypedArray *)
type bufferSource (* Web IDL, either an arrayBuffer or arrayBufferView *)
type formData (* XMLHttpRequest *)
type readableStream (* Streams *)
type urlSearchParams (* URL *)
type blob
type file
module AbortController = struct
type t = abortController
(* external signal : t -> signal = "signal" [@@mel.get] *)
let signal controller = controller.signal
(* external abort : unit = "abort" [@@mel.send.pipe: t] *)
let abort controller = controller.abort ()
(* external make : unit -> t = "AbortController" [@@mel.new] *)
let make () = { signal = { aborted = false; onabort = None; reason = None }; abort = (fun _ -> ()) }
end
type requestMethod = Get | Head | Post | Put | Delete | Connect | Options | Trace | Patch | Other of string
let encodeRequestMethod =
(* internal *)
function
| Get -> "GET"
| Head -> "HEAD"
| Post -> "POST"
| Put -> "PUT"
| Delete -> "DELETE"
| Connect -> "CONNECT"
| Options -> "OPTIONS"
| Trace -> "TRACE"
| Patch -> "PATCH"
| Other method_ -> method_
let decodeRequestMethod =
(* internal *)
function
| "GET" -> Get
| "HEAD" -> Head
| "POST" -> Post
| "PUT" -> Put
| "DELETE" -> Delete
| "CONNECT" -> Connect
| "OPTIONS" -> Options
| "TRACE" -> Trace
| "PATCH" -> Patch
| method_ -> Other method_
type referrerPolicy =
| None
| NoReferrer
| NoReferrerWhenDowngrade
| SameOrigin
| Origin
| StrictOrigin
| OriginWhenCrossOrigin
| StrictOriginWhenCrossOrigin
| UnsafeUrl
let encodeReferrerPolicy =
(* internal *)
function
| NoReferrer -> "no-referrer"
| None -> ""
| NoReferrerWhenDowngrade -> "no-referrer-when-downgrade"
| SameOrigin -> "same-origin"
| Origin -> "origin"
| StrictOrigin -> "strict-origin"
| OriginWhenCrossOrigin -> "origin-when-cross-origin"
| StrictOriginWhenCrossOrigin -> "strict-origin-when-cross-origin"
| UnsafeUrl -> "unsafe-url"
let decodeReferrerPolicy =
(* internal *)
function
| "no-referrer" -> NoReferrer
| "" -> None
| "no-referrer-when-downgrade" -> NoReferrerWhenDowngrade
| "same-origin" -> SameOrigin
| "origin" -> Origin
| "strict-origin" -> StrictOrigin
| "origin-when-cross-origin" -> OriginWhenCrossOrigin
| "strict-origin-when-cross-origin" -> StrictOriginWhenCrossOrigin
| "unsafe-url" -> UnsafeUrl
| e -> raise (Failure ("Unknown referrerPolicy: " ^ e))
type requestType =
| None (* default? unknown? just empty string in spec *)
| Audio
| Font
| Image
| Script
| Style
| Track
| Video
let decodeRequestType =
(* internal *)
function
| "audio" -> Audio
| "" -> None
| "font" -> Font
| "image" -> Image
| "script" -> Script
| "style" -> Style
| "track" -> Track
| "video" -> Video
| e -> raise (Failure ("Unknown requestType: " ^ e))
type requestDestination =
| None (* default? unknown? just empty string in spec *)
| Document
| Embed
| Font
| Image
| Manifest
| Media
| Object
| Report
| Script
| ServiceWorker
| SharedWorker
| Style
| Worker
| Xslt
let decodeRequestDestination =
(* internal *)
function
| "document" -> Document
| "" -> None
| "embed" -> Embed
| "font" -> Font
| "image" -> Image
| "manifest" -> Manifest
| "media" -> Media
| "object" -> Object
| "report" -> Report
| "script" -> Script
| "serviceworker" -> ServiceWorker
| "sharedworder" -> SharedWorker
| "style" -> Style
| "worker" -> Worker
| "xslt" -> Xslt
| e -> raise (Failure ("Unknown requestDestination: " ^ e))
type requestMode = Navigate | SameOrigin | NoCORS | CORS
let encodeRequestMode =
(* internal *)
function
| Navigate -> "navigate"
| SameOrigin -> "same-origin"
| NoCORS -> "no-cors"
| CORS -> "cors"
let decodeRequestMode =
(* internal *)
function
| "navigate" -> Navigate
| "same-origin" -> SameOrigin
| "no-cors" -> NoCORS
| "cors" -> CORS
| e -> raise (Failure ("Unknown requestMode: " ^ e))
type requestCredentials = Omit | SameOrigin | Include
let encodeRequestCredentials =
(* internal *)
function
| Omit -> "omit"
| SameOrigin -> "same-origin"
| Include -> "include"
let decodeRequestCredentials =
(* internal *)
function
| "omit" -> Omit
| "same-origin" -> SameOrigin
| "include" -> Include
| e -> raise (Failure ("Unknown requestCredentials: " ^ e))
type requestCache = Default | NoStore | Reload | NoCache | ForceCache | OnlyIfCached
let encodeRequestCache =
(* internal *)
function
| Default -> "default"
| NoStore -> "no-store"
| Reload -> "reload"
| NoCache -> "no-cache"
| ForceCache -> "force-cache"
| OnlyIfCached -> "only-if-cached"
let decodeRequestCache =
(* internal *)
function
| "default" -> Default
| "no-store" -> NoStore
| "reload" -> Reload
| "no-cache" -> NoCache
| "force-cache" -> ForceCache
| "only-if-cached" -> OnlyIfCached
| e -> raise (Failure ("Unknown requestCache: " ^ e))
type requestRedirect = Follow | Error | Manual
let encodeRequestRedirect =
(* internal *)
function
| Follow -> "follow"
| Error -> "error"
| Manual -> "manual"
let decodeRequestRedirect =
(* internal *)
function
| "follow" -> Follow
| "error" -> Error
| "manual" -> Manual
| e -> raise (Failure ("Unknown requestRedirect: " ^ e))
module HeadersInit = struct
type t = headersInit
external make : < .. > Js.t -> t = "%identity"
external makeWithDict : string Js.Dict.t -> t = "%identity"
(* external makeWithArray : (string * string) array -> t = "%identity" *)
let makeWithArray arr =
let dict = Js.Dict.empty () in
Array.iter (fun (k, v) -> Js.Dict.set dict k v) arr;
makeWithDict dict
end
module Headers = struct
type t = headers
(* external make : t = "Headers" [@@mel.new] *)
let make () = Js.Dict.empty ()
(* external makeWithInit : headersInit -> t = "Headers" [@@mel.new] *)
external makeWithInit : headersInit -> t = "Headers" [@@mel.new]
external append : string -> string -> unit = "append" [@@mel.send.pipe: t]
external delete : string -> unit = "delete" [@@mel.send.pipe: t]
(* entries *)
(* very experimental *)
external get : string -> string option = "get" [@@mel.send.pipe: t] [@@mel.return { null_to_opt }]
external has : string -> bool = "has" [@@mel.send.pipe: t]
(* keys *)
(* very experimental *)
external set : string -> string -> unit = "set" [@@mel.send.pipe: t]
(* values *)
(* very experimental *)
end
module BodyInit = struct
type t = bodyInit
external make : string -> t = "%identity"
external makeWithBlob : blob -> t = "%identity"
external makeWithBufferSource : bufferSource -> t = "%identity"
external makeWithFormData : formData -> t = "%identity"
external makeWithUrlSearchParams : urlSearchParams -> t = "%identity"
end
module Body = struct
module Impl (T : sig
type t
end) =
struct
external body : T.t -> readableStream = "body" [@@mel.get]
external bodyUsed : T.t -> bool = "bodyUsed" [@@mel.get]
external arrayBuffer : arrayBuffer Js.Promise.t = "arrayBuffer" [@@mel.send.pipe: T.t]
external blob : blob Js.Promise.t = "blob" [@@mel.send.pipe: T.t]
external formData : formData Js.Promise.t = "formData" [@@mel.send.pipe: T.t]
external json : Js.Json.t Js.Promise.t = "json" [@@mel.send.pipe: T.t]
external text : string Js.Promise.t = "text" [@@mel.send.pipe: T.t]
end
type t = body
include Impl (struct
type nonrec t = t
end)
end
module RequestInit = struct
type t = requestInit
let map f = function
(* internal *)
| Some v -> Some (f v)
| None -> None
external make :
?_method:string ->
?headers:headersInit ->
?body:bodyInit ->
?referrer:string ->
?referrerPolicy:string ->
?mode:string ->
?credentials:string ->
?cache:string ->
?redirect:string ->
?integrity:string ->
?keepalive:bool ->
?signal:signal ->
unit ->
requestInit = ""
[@@mel.obj]
let make ?(method_ : requestMethod option) ?(headers : headersInit option) ?(body : bodyInit option)
?(referrer : string option) ?(referrerPolicy : referrerPolicy = None) ?(mode : requestMode option)
?(credentials : requestCredentials option) ?(cache : requestCache option) ?(redirect : requestRedirect option)
?(integrity : string = "") ?(keepalive : bool option) ?(signal : signal option) =
make ?_method:(map encodeRequestMethod method_) ?headers ?body ?referrer
~referrerPolicy:(encodeReferrerPolicy referrerPolicy) ?mode:(map encodeRequestMode mode)
?credentials:(map encodeRequestCredentials credentials)
?cache:(map encodeRequestCache cache) ?redirect:(map encodeRequestRedirect redirect) ~integrity ?keepalive ?signal
end
module Request = struct
type t = request
include Body.Impl (struct
type nonrec t = t
end)
external make : string -> t = "Request" [@@mel.new]
external makeWithInit : string -> requestInit -> t = "Request" [@@mel.new]
external makeWithRequest : t -> t = "Request" [@@mel.new]
external makeWithRequestInit : t -> requestInit -> t = "Request" [@@mel.new]
external method_ : t -> string = "method" [@@mel.get]
let method_ : t -> requestMethod = fun self -> decodeRequestMethod (method_ self)
external url : t -> string = "url" [@@mel.get]
external headers : t -> headers = "headers" [@@mel.get]
external type_ : t -> string = "type" [@@mel.get]
let type_ : t -> requestType = fun self -> decodeRequestType (type_ self)
external destination : t -> string = "destination" [@@mel.get]
let destination : t -> requestDestination = fun self -> decodeRequestDestination (destination self)
external referrer : t -> string = "referrer" [@@mel.get]
external referrerPolicy : t -> string = "referrerPolicy" [@@mel.get]
let referrerPolicy : t -> referrerPolicy = fun self -> decodeReferrerPolicy (referrerPolicy self)
external mode : t -> string = "mode" [@@mel.get]
let mode : t -> requestMode = fun self -> decodeRequestMode (mode self)
external credentials : t -> string = "credentials" [@@mel.get]
let credentials : t -> requestCredentials = fun self -> decodeRequestCredentials (credentials self)
external cache : t -> string = "cache" [@@mel.get]
let cache : t -> requestCache = fun self -> decodeRequestCache (cache self)
external redirect : t -> string = "redirect" [@@mel.get]
let redirect : t -> requestRedirect = fun self -> decodeRequestRedirect (redirect self)
external integrity : t -> string = "integrity" [@@mel.get]
external keepalive : t -> bool = "keepalive" [@@mel.get]
external signal : t -> signal = "signal" [@@mel.get]
end
module Response = struct
type t = response
include Body.Impl (struct
type nonrec t = t
end)
external error : unit -> t = "error"
external redirect : string -> t = "redirect"
external redirectWithStatus : string -> int (* enum-ish *) -> t = "redirect"
external headers : t -> headers = "headers" [@@mel.get]
external ok : t -> bool = "ok" [@@mel.get]
external redirected : t -> bool = "redirected" [@@mel.get]
external status : t -> int = "status" [@@mel.get]
external statusText : t -> string = "statusText" [@@mel.get]
external type_ : t -> string = "type" [@@mel.get]
external url : t -> string = "url" [@@mel.get]
external clone : t = "clone" [@@mel.send.pipe: t]
end
module FormData = struct
module EntryValue = struct
type t
let%browser_only classify t = if Js.typeof t = "string" then `String (Obj.magic t) else `File (Obj.magic t)
end
module Iterator = struct
module Next = struct
type 'a t
external done_ : _ t -> bool option = "done" [@@mel.get]
external value : 'a t -> 'a option = "value" [@@mel.get] [@@mel.return nullable]
end
type 'a t
external next : 'a t -> 'a Next.t = "next" [@@mel.send]
let rec forEach ~f t =
let item = next t in
match Next.(done_ item, value item) with
| Some true, Some value -> f value
| Some true, None -> ()
| (Some false | None), Some value ->
f value;
forEach ~f t
| (Some false | None), None -> forEach ~f t
end
type t = formData
external make : unit -> t = "FormData" [@@mel.new]
external append : string -> string -> unit = "append" [@@mel.send.pipe: t]
external delete : string -> unit = "delete" [@@mel.send.pipe: t]
external get : string -> EntryValue.t option = "get" [@@mel.send.pipe: t]
external getAll : string -> EntryValue.t array = "getAll" [@@mel.send.pipe: t]
external set : string -> string -> unit = "set" [@@mel.send.pipe: t]
external has : string -> bool = "has" [@@mel.send.pipe: t]
external keys : t -> string Iterator.t = "keys" [@@mel.send]
external values : t -> EntryValue.t Iterator.t = "values" [@@mel.send]
external appendObject : string -> < .. > Js.t -> ?filename:string -> unit = "append" [@@mel.send.pipe: t]
external appendBlob : string -> blob -> ?filename:string -> unit = "append" [@@mel.send.pipe: t]
external appendFile : string -> file -> ?filename:string -> unit = "append" [@@mel.send.pipe: t]
external setObject : string -> < .. > Js.t -> ?filename:string -> unit = "set" [@@mel.send.pipe: t]
external setBlob : string -> blob -> ?filename:string -> unit = "set" [@@mel.send.pipe: t]
external setFile : string -> file -> ?filename:string -> unit = "set" [@@mel.send.pipe: t]
external entries : t -> (string * EntryValue.t) Iterator.t = "entries" [@@mel.send]
end
external fetch : string -> response Js.Promise.t = "fetch"
external fetchWithInit : string -> requestInit -> response Js.Promise.t = "fetch"
external fetchWithRequest : request -> response Js.Promise.t = "fetch"
external fetchWithRequestInit : request -> requestInit -> response Js.Promise.t = "fetch"
================================================
FILE: packages/fetch/dune
================================================
(library
(name fetch)
(public_name server-reason-react.fetch)
(libraries server-reason-react.js)
(modules Fetch)
(preprocess
(pps melange_native_ppx browser_ppx)))
================================================
FILE: packages/html/Html.ml
================================================
let is_self_closing_tag = function
(* Take the list from
https://github.com/facebook/react/blob/97d75c9c8bcddb0daed1ed062101c7f5e9b825f4/packages/react-dom-bindings/src/shared/omittedCloseTags.js but found https://github.com/wooorm/html-void-elements to be more complete. *)
| "area" | "base" | "basefont" | "bgsound" | "br" | "col" | "command" | "embed" | "frame" | "hr" | "image" | "img"
| "input" | "keygen" | "link" (* | "menuitem" *) | "meta" | "param" | "source" | "track" | "wbr" ->
true
| _ -> false
let escape buf s =
let length = String.length s in
let exception First_char_to_escape of int in
match
for i = 0 to length - 1 do
match String.unsafe_get s i with
| '&' | '<' | '>' | '\'' | '"' -> raise_notrace (First_char_to_escape i)
| _ -> ()
done
with
| exception First_char_to_escape first ->
if first > 0 then Buffer.add_substring buf s 0 first;
for i = first to length - 1 do
match String.unsafe_get s i with
| '&' -> Buffer.add_string buf "&"
| '<' -> Buffer.add_string buf "<"
| '>' -> Buffer.add_string buf ">"
| '\'' -> Buffer.add_string buf "'"
| '"' -> Buffer.add_string buf """
| c -> Buffer.add_char buf c
done
| () -> Buffer.add_string buf s
type attribute = [ `Present of string | `Value of string * string | `Omitted ]
type attribute_list = attribute list
let attribute name value = `Value (name, value)
let present name = `Present name
let omitted () = `Omitted
let write_attribute buf (attr : attribute) =
match attr with
| `Omitted -> ()
| `Present name ->
Buffer.add_char buf ' ';
Buffer.add_string buf name
| `Value (name, value) ->
Buffer.add_char buf ' ';
Buffer.add_string buf name;
Buffer.add_string buf "=\"";
escape buf value;
Buffer.add_char buf '"'
type element =
| Null
| String of string
| Raw of string (* text without encoding *)
| Node of node
| Int of int
| Float of float
| List of (string * element list)
| Array of element array
and node = { tag : string; attributes : attribute_list; children : element list }
let string txt = String txt
let raw txt = Raw txt
let null = Null
let int i = Int i
let float f = Float f
let list ?(separator = "") list = List (separator, list)
let array arr = Array arr
let fragment arr = List arr
let node tag attributes children = Node { tag; attributes; children }
let to_string ?(add_separator_between_text_nodes = true) element =
let out = Buffer.create 1024 in
(* This ref is used to enable rendering comments between text nodes
and can be disabled by `add_separator_between_text_nodes` *)
let previous_node_was_text = ref false in
let should_add_doctype_to_html = ref true in
let rec write element =
match element with
| Null -> should_add_doctype_to_html.contents <- false
| Int i -> Buffer.add_string out (Int.to_string i)
| Float f -> Buffer.add_string out (Float.to_string f)
| String text ->
let is_previous_text_node = previous_node_was_text.contents in
previous_node_was_text.contents <- true;
if is_previous_text_node && add_separator_between_text_nodes then Buffer.add_string out "";
escape out text;
should_add_doctype_to_html.contents <- false
| Raw text ->
Buffer.add_string out text;
should_add_doctype_to_html.contents <- false
| Node { tag; attributes; _ } when is_self_closing_tag tag ->
Buffer.add_char out '<';
Buffer.add_string out tag;
List.iter (write_attribute out) attributes;
Buffer.add_string out " />";
should_add_doctype_to_html.contents <- false
| Node { tag; attributes; children } ->
(* capturing the value of should_add_doctype_to_html before setting it to false, so the first thing is set to false and use the captured value *)
let should_add_doctype = should_add_doctype_to_html.contents in
should_add_doctype_to_html.contents <- false;
(* If the previous node was text, but from another parent node, then the comment shouldn't be added.
Check `separated_text_nodes_by_other_nodes` in test_renderToString.ml *)
if add_separator_between_text_nodes then previous_node_was_text.contents <- false;
if tag = "html" && should_add_doctype then Buffer.add_string out "";
Buffer.add_char out '<';
Buffer.add_string out tag;
List.iter (write_attribute out) attributes;
Buffer.add_char out '>';
List.iter write children;
Buffer.add_string out "";
Buffer.add_string out tag;
Buffer.add_char out '>';
if add_separator_between_text_nodes then previous_node_was_text.contents <- false
| List ("", list) -> List.iter write list
| List (separator, list) ->
let rec iter = function
| [] -> ()
| [ one ] -> write one
| [ first; second ] ->
write first;
Buffer.add_string out separator;
write second
| first :: rest ->
write first;
Buffer.add_string out separator;
iter rest
in
iter list
| Array elements -> Array.iter write elements
in
write element;
Buffer.contents out
(* The pretty print is used for debugging purposes *)
let pp element =
let out = Buffer.create 1024 in
let rec write element =
match element with
| Null -> ()
| Int i -> Buffer.add_string out (Int.to_string i)
| Float f -> Buffer.add_string out (Float.to_string f)
| String text -> escape out text
| Raw text -> Buffer.add_string out text
| Node { tag; attributes; _ } when is_self_closing_tag tag ->
Buffer.add_char out '<';
Buffer.add_string out tag;
List.iter (write_attribute out) attributes;
Buffer.add_string out " />"
| Node { tag; attributes; children } ->
Buffer.add_char out '<';
Buffer.add_string out tag;
List.iter (write_attribute out) attributes;
Buffer.add_char out '>';
List.iter write children;
Buffer.add_string out "";
Buffer.add_string out tag;
Buffer.add_char out '>'
| List ("", list) -> List.iter write list
| List (separator, list) ->
let rec iter = function
| [] -> ()
| [ one ] -> write one
| [ first; second ] ->
write first;
Buffer.add_string out separator;
write second
| first :: rest ->
write first;
Buffer.add_string out separator;
iter rest
in
iter list
| Array elements -> Array.iter write elements
in
write element;
Buffer.contents out
let add_attribute_escaped b s =
let getc = String.unsafe_get s in
let adds = Buffer.add_string in
let len = String.length s in
let max_idx = len - 1 in
let flush b start i = if start < len then Buffer.add_substring b s start (i - start) in
let rec loop start i =
if i > max_idx then flush b start i
else
let next = i + 1 in
match getc i with
| '\'' ->
flush b start i;
adds b "'";
loop next next
| '&' ->
flush b start i;
adds b "&";
loop next next
| _ -> loop start next
in
loop 0 0
let escape_attribute_value data =
let buf = Buffer.create (String.length data) in
add_attribute_escaped buf data;
Buffer.contents buf
================================================
FILE: packages/html/dune
================================================
(library
(name html)
(wrapped false)
(public_name server-reason-react.html))
================================================
FILE: packages/melange.ppx/base32/LICENSES/ISC.txt
================================================
ISC License:
Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
Copyright (c) 1995-2003 by Internet Software Consortium
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: packages/melange.ppx/base32/README.md
================================================
This library was vendored for server-reason-react from https://codeberg.org/pukkamustard/ocaml-base32/src/commit/c08f37455b7ea67d8106c110af0efd501f1374ae.
# Base32 for OCaml
This implements Base32 encoded as specified by [RFC 4648](https://tools.ietf.org/html/rfc4648) for OCaml.
ocaml-base32 is an adaptation of [ocaml-base64](https://github.com/mirage/ocaml-base64)
## License
[ISC](./LICENSES/ISC.txt)
================================================
FILE: packages/melange.ppx/base32/lib/base32.ml
================================================
(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2010 Thomas Gazagnaire
* Copyright (c) 2014-2016 Anil Madhavapeddy
* Copyright (c) 2016 David Kaloper Meršinjak
* Copyright (c) 2018 Romain Calascibetta
* Copyright (c) 2021 pukkamustard
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)
type alphabet = { emap : int array; dmap : int array }
type sub = string * int * int
let ( // ) x y =
if y < 1 then raise Division_by_zero;
if x > 0 then 1 + ((x - 1) / y) else 0
[@@inline]
let unsafe_get_uint8 input off = String.unsafe_get input off |> Char.code
let unsafe_set_uint8 input off v = v |> Char.chr |> Bytes.unsafe_set input off
let none = -1
(* We mostly want to have an optional array for [dmap] (e.g. [int option
array]). So we consider the [none] value as [-1]. *)
let make_alphabet alphabet =
if String.length alphabet <> 32 then invalid_arg "Length of alphabet must be 32";
if String.contains alphabet '=' then invalid_arg "Alphabet can not contain padding character";
let emap = Array.init (String.length alphabet) (fun i -> Char.code alphabet.[i]) in
let dmap = Array.make 256 none in
String.iteri (fun idx chr -> dmap.(Char.code chr) <- idx) alphabet;
{ emap; dmap }
let length_alphabet { emap; _ } = Array.length emap
let alphabet { emap; _ } = emap
let default_alphabet = make_alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
let padding = int_of_char '='
let error_msgf fmt = Format.ksprintf (fun err -> Error (`Msg err)) fmt
let encode_sub pad { emap; _ } ?(off = 0) ?len input =
let len = match len with Some len -> len | None -> String.length input - off in
if len < 0 || off < 0 || off > String.length input - len then error_msgf "Invalid bounds"
else
let n = len in
let n' = n // 5 * 8 in
let res = Bytes.make n' (Char.chr 0) in
let emap i = Array.unsafe_get emap i in
(* the bit magic - takes 5 bytes and reads 5-bits at a time *)
let emit b1 b2 b3 b4 b5 i =
unsafe_set_uint8 res i (emap ((0b11111000 land b1) lsr 3));
unsafe_set_uint8 res (i + 1) (emap (((0b00000111 land b1) lsl 2) lor ((0b11000000 land b2) lsr 6)));
unsafe_set_uint8 res (i + 2) (emap ((0b00111110 land b2) lsr 1));
unsafe_set_uint8 res (i + 3) (emap (((0b00000001 land b2) lsl 4) lor ((0b11110000 land b3) lsr 4)));
unsafe_set_uint8 res (i + 4) (emap (((0b00001111 land b3) lsl 1) lor ((0b10000000 land b4) lsr 7)));
unsafe_set_uint8 res (i + 5) (emap ((0b01111100 land b4) lsr 2));
unsafe_set_uint8 res (i + 6) (emap (((0b00000011 land b4) lsl 3) lor ((0b11100000 land b5) lsr 5)));
unsafe_set_uint8 res (i + 7) (emap (0b00011111 land b5))
in
let rec enc j i =
if i = len then ()
else if i = n - 1 then emit (unsafe_get_uint8 input (off + i)) 0 0 0 0 j
else if i = n - 2 then emit (unsafe_get_uint8 input (off + i)) (unsafe_get_uint8 input (off + i + 1)) 0 0 0 j
else if i = n - 3 then
emit
(unsafe_get_uint8 input (off + i))
(unsafe_get_uint8 input (off + i + 1))
(unsafe_get_uint8 input (off + i + 2))
0 0 j
else if i = n - 4 then
emit
(unsafe_get_uint8 input (off + i))
(unsafe_get_uint8 input (off + i + 1))
(unsafe_get_uint8 input (off + i + 2))
(unsafe_get_uint8 input (off + i + 3))
0 j
else (
emit
(unsafe_get_uint8 input (off + i))
(unsafe_get_uint8 input (off + i + 1))
(unsafe_get_uint8 input (off + i + 2))
(unsafe_get_uint8 input (off + i + 3))
(unsafe_get_uint8 input (off + i + 4))
j;
enc (j + 8) (i + 5))
in
let rec unsafe_fix = function
| 0 -> ()
| i ->
unsafe_set_uint8 res (n' - i) padding;
unsafe_fix (i - 1)
in
enc 0 0;
(* amount of padding required *)
let pad_to_write = match n mod 5 with 0 -> 0 | 1 -> 6 | 2 -> 4 | 3 -> 3 | 4 -> 1 | _ -> 0 in
if pad then (
unsafe_fix pad_to_write;
Ok (Bytes.unsafe_to_string res, 0, n'))
else Ok (Bytes.unsafe_to_string res, 0, n' - pad_to_write)
let encode ?(pad = true) ?(alphabet = default_alphabet) ?off ?len input =
match encode_sub pad alphabet ?off ?len input with
| Ok (res, off, len) -> Ok (String.sub res off len)
| Error _ as err -> err
let encode_string ?pad ?alphabet input =
match encode ?pad ?alphabet input with Ok res -> res | Error _ -> assert false
let encode_sub ?(pad = true) ?(alphabet = default_alphabet) ?off ?len input = encode_sub pad alphabet ?off ?len input
let encode_exn ?pad ?alphabet ?off ?len input =
match encode ?pad ?alphabet ?off ?len input with Ok v -> v | Error (`Msg err) -> invalid_arg err
let decode_sub { dmap; _ } ?(off = 0) ?len input =
let len = match len with Some len -> len | None -> String.length input - off in
if len < 0 || off < 0 || off > String.length input - len then error_msgf "Invalid bounds"
else
let n = len // 8 * 8 in
let n' = n // 8 * 5 in
let res = Bytes.create n' in
let get_uint8 t i = if i < len then Char.code (String.unsafe_get t (off + i)) else padding in
let set_uint8 t off v =
(* Format.printf "set_uint8 %d\n" (v land 0xff); *)
if off < 0 || off >= Bytes.length t then () else unsafe_set_uint8 t off (v land 0xff)
in
let emit b0 b1 b2 b3 b4 b5 b6 b7 j =
set_uint8 res j ((b0 lsl 3) lor (b1 lsr 2));
set_uint8 res (j + 1) ((b1 lsl 6) lor (b2 lsl 1) lor (b3 lsr 4));
set_uint8 res (j + 2) ((b3 lsl 4) lor (b4 lsr 1));
set_uint8 res (j + 3) ((b4 lsl 7) lor (b5 lsl 2) lor (b6 lsr 3));
set_uint8 res (j + 4) ((b6 lsl 5) lor b7)
in
let dmap i = Array.unsafe_get dmap i in
let get_uint8_with_padding t i padding =
let x = get_uint8 t i in
if x = 61 then (0, padding)
else
let v = dmap x in
if v >= 0 then (v, 0) else raise Not_found
in
let rec dec j i =
if i = n then 0
else
let b0, pad0 = get_uint8_with_padding input i 5 in
let b1, pad1 = get_uint8_with_padding input (i + 1) 5 in
let b2, pad2 = get_uint8_with_padding input (i + 2) 4 in
let b3, pad3 = get_uint8_with_padding input (i + 3) 4 in
let b4, pad4 = get_uint8_with_padding input (i + 4) 3 in
let b5, pad5 = get_uint8_with_padding input (i + 5) 2 in
let b6, pad6 = get_uint8_with_padding input (i + 6) 2 in
let b7, pad7 = get_uint8_with_padding input (i + 7) 1 in
let pad = List.fold_left max 0 [ pad0; pad1; pad2; pad3; pad4; pad5; pad6; pad7 ] in
(* Format.printf "emit %d %d %d %d %d %d %d %d\n" b0 b1 b2 b3 b4 b5 b6 b7; *)
emit b0 b1 b2 b3 b4 b5 b6 b7 j;
if pad == 0 then dec (j + 5) (i + 8) else pad
in
match dec 0 0 with
| pad -> Ok (Bytes.unsafe_to_string res, 0, n' - pad)
| exception Not_found -> error_msgf "Malformed input"
let decode ?(alphabet = default_alphabet) ?off ?len input =
match decode_sub alphabet ?off ?len input with
| Ok (res, off, len) -> Ok (String.sub res off len)
| Error _ as err -> err
let decode_sub ?(alphabet = default_alphabet) ?off ?len input = decode_sub alphabet ?off ?len input
let decode_exn ?alphabet ?off ?len input =
match decode ?alphabet ?off ?len input with Ok res -> res | Error (`Msg err) -> invalid_arg err
================================================
FILE: packages/melange.ppx/base32/lib/base32.mli
================================================
(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2010 Thomas Gazagnaire
* Copyright (c) 2014-2016 Anil Madhavapeddy
* Copyright (c) 2016 David Kaloper Meršinjak
* Copyright (c) 2018 Romain Calascibetta
* Copyright (c) 2021 pukkamustard
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)
(** Base32 RFC4648 implementation.
Base32 is a group of similar binary-to-text encoding schemes that represent binary data in an ASCII string format by
translating it into a radix-32 representation. It is specified in RFC 4648.
{e Release %%VERSION%% - %%PKG_HOMEPAGE%%} *)
type alphabet
(** Type of alphabet. *)
type sub = string * int * int
(** Type of sub-string: [str, off, len]. *)
val default_alphabet : alphabet
(** A 32-character alphabet specifying the regular Base32 alphabet. *)
val make_alphabet : string -> alphabet
(** Make a new alphabet. *)
val length_alphabet : alphabet -> int
(** Returns length of the alphabet, should be 64. *)
val alphabet : alphabet -> int array
(** Returns the alphabet. *)
val decode_exn : ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
(** [decode_exn ?off ?len s] decodes [len] bytes (defaults to [String.length s - off]) of the string [s] starting from
[off] (defaults to [0]) that is encoded in Base32 format. Will leave trailing NULLs on the string, padding it out to
a multiple of 3 characters. [alphabet] defaults to {!default_alphabet}. [pad = true] specifies to check if [s] is
padded or not, otherwise, it raises an exception.
Decoder can fail when character of [s] is not a part of [alphabet] or is not [padding] character. If input is not
padded correctly, decoder does the best-effort but it does not ensure [decode_exn (encode ~pad:false x) = x].
@raise if Invalid_argument [s] is not a valid Base32 string. *)
val decode_sub : ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (sub, [> `Msg of string ]) result
(** Same as {!decode_exn} but it returns a result type instead to raise an exception. Then, it returns a {!sub} string.
Decoded input [(str, off, len)] will starting to [off] and will have [len] bytes - by this way, we ensure to
allocate only one time result. *)
val decode : ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (string, [> `Msg of string ]) result
(** Same as {!decode_exn}, but returns an explicit error message {!result} if it fails. *)
val encode : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (string, [> `Msg of string ]) result
(** [encode s] encodes the string [s] into base32. If [pad] is false, no trailing padding is added. [pad] defaults to
[true], and [alphabet] to {!default_alphabet}.
[encode] fails when [off] and [len] do not designate a valid range of [s]. *)
val encode_string : ?pad:bool -> ?alphabet:alphabet -> string -> string
(** [encode_string s] encodes the string [s] into base32. If [pad] is false, no trailing padding is added. [pad]
defaults to [true], and [alphabet] to {!default_alphabet}. *)
val encode_sub : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (sub, [> `Msg of string ]) result
(** Same as {!encode} but return a {!sub}-string instead a plain result. By this way, we ensure to allocate only one
time result. *)
val encode_exn : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
(** Same as {!encode} but raises an invalid argument exception if we retrieve an error. *)
================================================
FILE: packages/melange.ppx/base32/lib/dune
================================================
(library
(name base32)
(public_name server-reason-react.base32))
================================================
FILE: packages/melange.ppx/derive_util.ml
================================================
(** Shared utilities for record type derivers (jsProperties, getSet).
These helpers handle common operations like checking for optional attributes and extracting type information from
type declarations. *)
open Ppxlib
module Builder = Ast_builder.Default
(** Returns [true] if the attribute list contains [[@mel.optional]] or [[@bs.optional]]. *)
let has_mel_optional attrs =
List.exists (fun { attr_name = { txt; _ }; _ } -> txt = "mel.optional" || txt = "bs.optional") attrs
(** Extracts the inner type from an optional field.
For fields marked with [[@mel.optional]], this unwraps the [option] type to get the inner type. For example,
[int option] becomes [int].
Raises an error if [[@mel.optional]] is used on a non-option type. *)
let get_pld_type pld_type ~attrs =
let is_optional = has_mel_optional attrs in
match is_optional with
| true -> (
match pld_type.ptyp_desc with
| Ptyp_constr ({ txt = Lident "option"; _ }, [ inner_type ]) -> inner_type
| _ -> Location.raise_errorf ~loc:pld_type.ptyp_loc "`[@mel.optional]' must appear on an option type (`_ option')"
)
| false -> pld_type
(** Constructs a core type from a type declaration.
Given [type 'a t = ...], this returns the core type ['a t] that can be used in function signatures. Handles type
parameters correctly. *)
let core_type_of_type_declaration (tdcl : type_declaration) =
match tdcl with
| { ptype_name = { txt; loc }; ptype_params; _ } ->
Builder.ptyp_constr ~loc { txt = Lident txt; loc } (List.map fst ptype_params)
================================================
FILE: packages/melange.ppx/double_hash.ml
================================================
open Ppxlib
module Builder = Ast_builder.Default
let expander e =
let loc = e.pexp_loc in
match e.pexp_desc with
| Pexp_apply
( { pexp_desc = Pexp_ident { txt = Lident "##"; _ }; pexp_loc_stack = _; pexp_loc = _; pexp_attributes = _ },
[ (Nolabel, objectArg); (Nolabel, methodArg) ] ) -> (
match methodArg with
| { pexp_desc = Pexp_ident { txt = Lident li; _ }; _ } ->
Some (Builder.pexp_send ~loc objectArg { txt = li; loc })
| _ -> None)
| _ -> None
let rule = Context_free.Rule.special_function "##" expander
================================================
FILE: packages/melange.ppx/dune
================================================
(library
(name melange_native_ppx)
(public_name server-reason-react.melange_ppx)
(ppx_runtime_libraries server-reason-react.runtime)
(flags :standard -w -9)
(libraries base32 ppxlib ppxlib.astlib str quickjs xxhash)
(preprocess
(pps ppxlib.metaquot))
(kind ppx_rewriter))
================================================
FILE: packages/melange.ppx/get_set.ml
================================================
(** [[@\@deriving getSet]] generates getter and setter functions for record fields.
This is a native OCaml implementation compatible with melange's getSet deriver.
{2 Basic usage}
{[
type person = { name : string; age : int } [@@deriving getSet]
(* Generates: *)
let nameGet x = x.name
let ageGet x = x.age
]}
{2 Mutable fields}
Mutable fields also generate setter functions:
{[
type person = { name : string; mutable age : int } [@@deriving getSet]
(* Generates: *)
let nameGet x = x.name
let ageGet x = x.age
let ageSet x v = x.age <- v
]}
{2 Light mode}
With [{ light }], getters are named after the field (without "Get" suffix):
{[
type person = { name : string; mutable age : int } [@@deriving getSet { light }]
(* Generates: *)
let name x = x.name
let age x = x.age
let ageSet x v = x.age <- v
]} *)
open Ppxlib
module Builder = Ast_builder.Default
let derive_str ~light tdcls =
List.concat_map
(fun tdcl ->
match tdcl.ptype_kind with
| Ptype_record label_declarations ->
List.concat_map
(fun { pld_name; pld_mutable; pld_loc; _ } ->
let getter_name = if light then pld_name.txt else pld_name.txt ^ "Get" in
let getter_expr =
Builder.pexp_fun ~loc:pld_loc Nolabel None
(Builder.ppat_var ~loc:pld_loc { loc = pld_loc; txt = "x" })
(Builder.pexp_field ~loc:pld_loc
(Builder.pexp_ident ~loc:pld_loc { loc = pld_loc; txt = Lident "x" })
{ loc = pld_loc; txt = Lident pld_name.txt })
in
let getter_vb =
Builder.value_binding ~loc:pld_loc ~pat:(Builder.pvar ~loc:pld_loc getter_name) ~expr:getter_expr
in
let getter = Builder.pstr_value ~loc:pld_loc Nonrecursive [ getter_vb ] in
let setter =
match pld_mutable with
| Mutable ->
let setter_name = pld_name.txt ^ "Set" in
let setter_expr =
Builder.pexp_fun ~loc:pld_loc Nolabel None
(Builder.ppat_var ~loc:pld_loc { loc = pld_loc; txt = "x" })
(Builder.pexp_fun ~loc:pld_loc Nolabel None
(Builder.ppat_var ~loc:pld_loc { loc = pld_loc; txt = "v" })
(Builder.pexp_setfield ~loc:pld_loc
(Builder.pexp_ident ~loc:pld_loc { loc = pld_loc; txt = Lident "x" })
{ loc = pld_loc; txt = Lident pld_name.txt }
(Builder.pexp_ident ~loc:pld_loc { loc = pld_loc; txt = Lident "v" })))
in
let setter_vb =
Builder.value_binding ~loc:pld_loc ~pat:(Builder.pvar ~loc:pld_loc setter_name) ~expr:setter_expr
in
[ Builder.pstr_value ~loc:pld_loc Nonrecursive [ setter_vb ] ]
| Immutable -> []
in
[ getter ] @ setter)
label_declarations
| Ptype_abstract | Ptype_variant _ | Ptype_open ->
let loc = tdcl.ptype_loc in
Location.raise_errorf ~loc "[@@deriving getSet] can only be used on record types")
tdcls
let derive_sig ~light tdcls =
List.concat_map
(fun tdcl ->
match tdcl.ptype_kind with
| Ptype_record label_declarations ->
let core_type = Derive_util.core_type_of_type_declaration tdcl in
List.concat_map
(fun { pld_name; pld_type; pld_mutable; pld_attributes; pld_loc; _ } ->
let is_optional = Derive_util.has_mel_optional pld_attributes in
let getter_name = if light then pld_name.txt else pld_name.txt ^ "Get" in
let getter_type = Builder.ptyp_arrow ~loc:pld_loc Nolabel core_type pld_type in
let getter =
Builder.psig_value ~loc:pld_loc
(Builder.value_description ~loc:pld_loc ~name:{ loc = pld_loc; txt = getter_name } ~type_:getter_type
~prim:[])
in
let setter =
match pld_mutable with
| Mutable ->
let pld_type_inner =
if is_optional then Derive_util.get_pld_type pld_type ~attrs:pld_attributes else pld_type
in
let setter_name = pld_name.txt ^ "Set" in
let setter_type =
Builder.ptyp_arrow ~loc:pld_loc Nolabel core_type
(Builder.ptyp_arrow ~loc:pld_loc Nolabel pld_type_inner
(Builder.ptyp_constr ~loc:pld_loc { loc = pld_loc; txt = Lident "unit" } []))
in
[
Builder.psig_value ~loc:pld_loc
(Builder.value_description ~loc:pld_loc ~name:{ loc = pld_loc; txt = setter_name }
~type_:setter_type ~prim:[]);
]
| Immutable -> []
in
[ getter ] @ setter)
label_declarations
| Ptype_abstract | Ptype_variant _ | Ptype_open ->
let loc = tdcl.ptype_loc in
Location.raise_errorf ~loc "[@@deriving getSet] can only be used on record types")
tdcls
let str_type_decl =
let args = Deriving.Args.(empty +> flag "light") in
Deriving.Generator.V2.make args (fun ~ctxt:_ (_, type_decls) light -> derive_str ~light type_decls)
let sig_type_decl =
let args = Deriving.Args.(empty +> flag "light") in
Deriving.Generator.V2.make args (fun ~ctxt:_ (_, type_decls) light -> derive_sig ~light type_decls)
let deriver = Deriving.add "getSet" ~str_type_decl ~sig_type_decl
================================================
FILE: packages/melange.ppx/js_converter.ml
================================================
open Ppxlib
module Builder = Ast_builder.Default
let is_mel_as_attr txt = txt = "mel.as"
let get_mel_as_int attrs =
List.find_map
(fun { attr_name = { txt; _ }; attr_payload; _ } ->
if is_mel_as_attr txt then
match attr_payload with
| PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_constant (Pconst_integer (s, _)); _ }, _); _ } ] ->
Some (int_of_string s)
| _ -> None
else None)
attrs
let get_mel_as_string attrs =
List.find_map
(fun { attr_name = { txt; _ }; attr_payload; _ } ->
if is_mel_as_attr txt then
match attr_payload with
| PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_constant (Pconst_string (s, _, _)); _ }, _); _ } ] ->
Some s
| _ -> None
else None)
attrs
type variant_info = { name : string; js_value : int }
type poly_variant_info = { name : string; js_string : string }
let check_duplicate_values ~loc mappings =
let values = List.map (fun { js_value; _ } -> js_value) mappings in
let rec check seen = function
| [] -> ()
| v :: rest ->
if List.mem v seen then
Location.raise_errorf ~loc
"[@@deriving jsConverter] has duplicate value %d - each constructor must map to a unique integer" v
else check (v :: seen) rest
in
check [] values
let check_duplicate_strings ~loc mappings =
let strings = List.map (fun { js_string; _ } -> js_string) mappings in
let rec check seen = function
| [] -> ()
| s :: rest ->
if List.mem s seen then
Location.raise_errorf ~loc
"[@@deriving jsConverter] has duplicate value %S - each constructor must map to a unique string" s
else check (s :: seen) rest
in
check [] strings
let compute_variant_mappings ~loc constrs =
if constrs = [] then Location.raise_errorf ~loc "[@@deriving jsConverter] cannot be used on empty variant types";
let explicit_values =
List.filter_map (fun { pcd_name = _; pcd_attributes; _ } -> get_mel_as_int pcd_attributes) constrs
in
let next_available all_used current =
let rec find n = if List.mem n all_used then find (n + 1) else n in
find current
in
let _, _, mappings =
List.fold_left
(fun (current, all_used, acc) { pcd_name; pcd_attributes; pcd_args; pcd_loc; _ } ->
match pcd_args with
| Pcstr_tuple [] | Pcstr_record [] ->
let js_value, next =
match get_mel_as_int pcd_attributes with
| Some v -> (v, max current (v + 1))
| None ->
let v = next_available (all_used @ explicit_values) current in
(v, v + 1)
in
let new_all_used = js_value :: all_used in
(next, new_all_used, { name = pcd_name.txt; js_value } :: acc)
| _ ->
Location.raise_errorf ~loc:pcd_loc
"[@@deriving jsConverter] does not support variant constructors with payloads")
(0, [], []) constrs
in
let result = List.rev mappings in
check_duplicate_values ~loc result;
result
let compute_poly_variant_mappings ~loc row_fields =
if row_fields = [] then
Location.raise_errorf ~loc "[@@deriving jsConverter] cannot be used on empty polymorphic variant types";
let mappings =
List.map
(fun row_field ->
match row_field.prf_desc with
| Rtag ({ txt = name; _ }, true, []) ->
let js_string = match get_mel_as_string row_field.prf_attributes with Some s -> s | None -> name in
{ name; js_string }
| Rtag (_, _, _ :: _) ->
Location.raise_errorf ~loc:row_field.prf_loc
"[@@deriving jsConverter] does not support polymorphic variant constructors with payloads"
| Rtag (_, false, []) ->
Location.raise_errorf ~loc:row_field.prf_loc
"[@@deriving jsConverter] does not support polymorphic variant constructors with payloads"
| Rinherit _ ->
Location.raise_errorf ~loc:row_field.prf_loc
"[@@deriving jsConverter] does not support inherited polymorphic variants")
row_fields
in
check_duplicate_strings ~loc mappings;
mappings
let generate_to_js_variant ~loc type_name mappings =
let cases =
List.map
(fun { name; js_value } ->
let pattern = Builder.ppat_construct ~loc { loc; txt = Lident name } None in
let expr = Builder.pexp_constant ~loc (Pconst_integer (string_of_int js_value, None)) in
Builder.case ~lhs:pattern ~guard:None ~rhs:expr)
mappings
in
let func_name = type_name ^ "ToJs" in
let param_name = "x" in
let func_expr =
Builder.pexp_fun ~loc Nolabel None
(Builder.ppat_var ~loc { loc; txt = param_name })
(Builder.pexp_match ~loc (Builder.pexp_ident ~loc { loc; txt = Lident param_name }) cases)
in
Builder.value_binding ~loc ~pat:(Builder.pvar ~loc func_name) ~expr:func_expr
let generate_from_js_variant ~loc type_name mappings ~new_type =
let cases =
List.map
(fun { name; js_value } ->
let pattern = Builder.ppat_constant ~loc (Pconst_integer (string_of_int js_value, None)) in
let constr = Builder.pexp_construct ~loc { loc; txt = Lident name } None in
let rhs = if new_type then constr else Builder.pexp_construct ~loc { loc; txt = Lident "Some" } (Some constr) in
Builder.case ~lhs:pattern ~guard:None ~rhs)
mappings
in
let default_case =
if new_type then None
else
Some
(Builder.case ~lhs:(Builder.ppat_any ~loc) ~guard:None
~rhs:(Builder.pexp_construct ~loc { loc; txt = Lident "None" } None))
in
let all_cases = cases @ Option.to_list default_case in
let func_name = type_name ^ "FromJs" in
let param_name = "x" in
let func_expr =
Builder.pexp_fun ~loc Nolabel None
(Builder.ppat_var ~loc { loc; txt = param_name })
(Builder.pexp_match ~loc (Builder.pexp_ident ~loc { loc; txt = Lident param_name }) all_cases)
in
Builder.value_binding ~loc ~pat:(Builder.pvar ~loc func_name) ~expr:func_expr
let generate_to_js_poly ~loc type_name mappings =
let cases =
List.map
(fun { name; js_string } ->
let pattern = Builder.ppat_variant ~loc name None in
let expr = Builder.pexp_constant ~loc (Pconst_string (js_string, loc, None)) in
Builder.case ~lhs:pattern ~guard:None ~rhs:expr)
mappings
in
let func_name = type_name ^ "ToJs" in
let param_name = "x" in
let func_expr =
Builder.pexp_fun ~loc Nolabel None
(Builder.ppat_var ~loc { loc; txt = param_name })
(Builder.pexp_match ~loc (Builder.pexp_ident ~loc { loc; txt = Lident param_name }) cases)
in
Builder.value_binding ~loc ~pat:(Builder.pvar ~loc func_name) ~expr:func_expr
let generate_from_js_poly ~loc type_name mappings ~new_type =
let cases =
List.map
(fun { name; js_string } ->
let pattern = Builder.ppat_constant ~loc (Pconst_string (js_string, loc, None)) in
let variant = Builder.pexp_variant ~loc name None in
let rhs =
if new_type then variant else Builder.pexp_construct ~loc { loc; txt = Lident "Some" } (Some variant)
in
Builder.case ~lhs:pattern ~guard:None ~rhs)
mappings
in
let default_case =
if new_type then None
else
Some
(Builder.case ~lhs:(Builder.ppat_any ~loc) ~guard:None
~rhs:(Builder.pexp_construct ~loc { loc; txt = Lident "None" } None))
in
let all_cases = cases @ Option.to_list default_case in
let func_name = type_name ^ "FromJs" in
let param_name = "x" in
let func_expr =
Builder.pexp_fun ~loc Nolabel None
(Builder.ppat_var ~loc { loc; txt = param_name })
(Builder.pexp_match ~loc (Builder.pexp_ident ~loc { loc; txt = Lident param_name }) all_cases)
in
Builder.value_binding ~loc ~pat:(Builder.pvar ~loc func_name) ~expr:func_expr
let generate_abstract_type ~loc type_name ~is_poly =
let abs_type_name = "abs_" ^ type_name in
let manifest =
if is_poly then Some (Builder.ptyp_constr ~loc { loc; txt = Lident "string" } [])
else Some (Builder.ptyp_constr ~loc { loc; txt = Lident "int" } [])
in
Builder.type_declaration ~loc ~name:{ loc; txt = abs_type_name } ~params:[] ~cstrs:[] ~kind:Ptype_abstract
~private_:Public ~manifest
let str_gen ~loc ~path:_ (rec_flag, type_decls) new_type =
let _ = rec_flag in
List.concat_map
(fun { ptype_name; ptype_kind; ptype_manifest; ptype_loc; _ } ->
let type_name = ptype_name.txt in
match (ptype_kind, ptype_manifest) with
| Ptype_variant constrs, _ ->
let mappings = compute_variant_mappings ~loc:ptype_loc constrs in
let to_js = generate_to_js_variant ~loc:ptype_loc type_name mappings in
let from_js = generate_from_js_variant ~loc:ptype_loc type_name mappings ~new_type in
let abs_type =
if new_type then
[ Builder.pstr_type ~loc Nonrecursive [ generate_abstract_type ~loc type_name ~is_poly:false ] ]
else []
in
abs_type
@ [ Builder.pstr_value ~loc Nonrecursive [ to_js ]; Builder.pstr_value ~loc Nonrecursive [ from_js ] ]
| Ptype_abstract, Some { ptyp_desc = Ptyp_variant (row_fields, Closed, None); ptyp_loc; _ } ->
let mappings = compute_poly_variant_mappings ~loc:ptyp_loc row_fields in
let to_js = generate_to_js_poly ~loc:ptype_loc type_name mappings in
let from_js = generate_from_js_poly ~loc:ptype_loc type_name mappings ~new_type in
let abs_type =
if new_type then
[ Builder.pstr_type ~loc Nonrecursive [ generate_abstract_type ~loc type_name ~is_poly:true ] ]
else []
in
abs_type
@ [ Builder.pstr_value ~loc Nonrecursive [ to_js ]; Builder.pstr_value ~loc Nonrecursive [ from_js ] ]
| Ptype_abstract, Some { ptyp_desc = Ptyp_variant (_, Open, _); ptyp_loc; _ } ->
Location.raise_errorf ~loc:ptyp_loc "[@@deriving jsConverter] does not support open polymorphic variants"
| Ptype_abstract, Some { ptyp_desc = Ptyp_variant (_, _, Some _); ptyp_loc; _ } ->
Location.raise_errorf ~loc:ptyp_loc
"[@@deriving jsConverter] does not support polymorphic variants with row variables"
| _ ->
Location.raise_errorf ~loc:ptype_loc
"[@@deriving jsConverter] only supports variant types and polymorphic variant types")
type_decls
let sig_gen ~loc ~path:_ (_rec_flag, type_decls) new_type =
List.concat_map
(fun { ptype_name; ptype_kind; ptype_manifest; ptype_loc; _ } ->
let type_name = ptype_name.txt in
let type_lid = { loc; txt = Lident type_name } in
match (ptype_kind, ptype_manifest) with
| Ptype_variant _, _ ->
let to_js_name = type_name ^ "ToJs" in
let from_js_name = type_name ^ "FromJs" in
let abs_type_name = "abs_" ^ type_name in
let abs_type_lid = { loc; txt = Lident abs_type_name } in
let type_t = Builder.ptyp_constr ~loc type_lid [] in
let int_t = Builder.ptyp_constr ~loc { loc; txt = Lident "int" } [] in
let abs_t = Builder.ptyp_constr ~loc abs_type_lid [] in
let return_t, from_input_t = if new_type then (abs_t, abs_t) else (int_t, int_t) in
let to_js_type = Builder.ptyp_arrow ~loc Nolabel type_t return_t in
let from_js_return =
if new_type then type_t else Builder.ptyp_constr ~loc { loc; txt = Lident "option" } [ type_t ]
in
let from_js_type = Builder.ptyp_arrow ~loc Nolabel from_input_t from_js_return in
let abs_type_decl =
if new_type then
[
Builder.psig_type ~loc Nonrecursive
[
Builder.type_declaration ~loc ~name:{ loc; txt = abs_type_name } ~params:[] ~cstrs:[]
~kind:Ptype_abstract ~private_:Public ~manifest:None;
];
]
else []
in
abs_type_decl
@ [
Builder.psig_value ~loc
(Builder.value_description ~loc ~name:{ loc; txt = to_js_name } ~type_:to_js_type ~prim:[]);
Builder.psig_value ~loc
(Builder.value_description ~loc ~name:{ loc; txt = from_js_name } ~type_:from_js_type ~prim:[]);
]
| Ptype_abstract, Some { ptyp_desc = Ptyp_variant (_, Closed, None); _ } ->
let to_js_name = type_name ^ "ToJs" in
let from_js_name = type_name ^ "FromJs" in
let abs_type_name = "abs_" ^ type_name in
let abs_type_lid = { loc; txt = Lident abs_type_name } in
let type_t = Builder.ptyp_constr ~loc type_lid [] in
let string_t = Builder.ptyp_constr ~loc { loc; txt = Lident "string" } [] in
let abs_t = Builder.ptyp_constr ~loc abs_type_lid [] in
let return_t, from_input_t = if new_type then (abs_t, abs_t) else (string_t, string_t) in
let to_js_type = Builder.ptyp_arrow ~loc Nolabel type_t return_t in
let from_js_return =
if new_type then type_t else Builder.ptyp_constr ~loc { loc; txt = Lident "option" } [ type_t ]
in
let from_js_type = Builder.ptyp_arrow ~loc Nolabel from_input_t from_js_return in
let abs_type_decl =
if new_type then
[
Builder.psig_type ~loc Nonrecursive
[
Builder.type_declaration ~loc ~name:{ loc; txt = abs_type_name } ~params:[] ~cstrs:[]
~kind:Ptype_abstract ~private_:Public ~manifest:None;
];
]
else []
in
abs_type_decl
@ [
Builder.psig_value ~loc
(Builder.value_description ~loc ~name:{ loc; txt = to_js_name } ~type_:to_js_type ~prim:[]);
Builder.psig_value ~loc
(Builder.value_description ~loc ~name:{ loc; txt = from_js_name } ~type_:from_js_type ~prim:[]);
]
| _ ->
Location.raise_errorf ~loc:ptype_loc
"[@@deriving jsConverter] only supports variant types and polymorphic variant types")
type_decls
let str_type_decl =
let args = Deriving.Args.(empty +> flag "newType") in
Deriving.Generator.V2.make args (fun ~ctxt (rec_flag, type_decls) new_type ->
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
str_gen ~loc ~path:[] (rec_flag, type_decls) new_type)
let sig_type_decl =
let args = Deriving.Args.(empty +> flag "newType") in
Deriving.Generator.V2.make args (fun ~ctxt (rec_flag, type_decls) new_type ->
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
sig_gen ~loc ~path:[] (rec_flag, type_decls) new_type)
let deriver = Deriving.add "jsConverter" ~str_type_decl ~sig_type_decl
================================================
FILE: packages/melange.ppx/js_properties.ml
================================================
(** [[@\@deriving jsProperties]] generates a constructor function for record types.
This is a native OCaml implementation compatible with melange's jsProperties deriver.
{2 Basic usage}
{[
type person = { name : string; age : int } [@@deriving jsProperties]
(* Generates: *)
let person ~name ~age = { name; age }
]}
{2 Optional fields}
Fields marked with [[@mel.optional]] become optional labeled arguments. When any optional field exists, a trailing
[unit] argument is added:
{[
type config = { host : string; port : int option [@mel.optional] } [@@deriving jsProperties]
(* Generates: *)
let config ~host ?port () = { host; port }
]}
{2 Private types}
Private types do not generate a constructor (the type cannot be constructed outside the module). *)
open Ppxlib
module Builder = Ast_builder.Default
let derive_str tdcls =
List.concat_map
(fun tdcl ->
match tdcl.ptype_kind with
| Ptype_record label_declarations -> (
match tdcl.ptype_private with
| Private -> []
| Public ->
let loc = tdcl.ptype_loc in
let has_optional_field =
List.exists
(fun (x : label_declaration) -> Derive_util.has_mel_optional x.pld_attributes)
label_declarations
in
let record_fields =
List.map
(fun { pld_name; _ } ->
({ loc; txt = Lident pld_name.txt }, Builder.pexp_ident ~loc { loc; txt = Lident pld_name.txt }))
label_declarations
in
let record_expr = Builder.pexp_record ~loc record_fields None in
let body_with_unit =
if has_optional_field then Builder.pexp_fun ~loc Nolabel None (Builder.punit ~loc) record_expr
else record_expr
in
let func_expr =
List.fold_right
(fun { pld_name; pld_attributes; pld_loc; _ } acc ->
let is_optional = Derive_util.has_mel_optional pld_attributes in
let label = if is_optional then Optional pld_name.txt else Labelled pld_name.txt in
Builder.pexp_fun ~loc:pld_loc label None
(Builder.ppat_var ~loc:pld_loc { loc = pld_loc; txt = pld_name.txt })
acc)
label_declarations body_with_unit
in
let pat = Builder.pvar ~loc tdcl.ptype_name.txt in
let vb = Builder.value_binding ~loc ~pat ~expr:func_expr in
[ Builder.pstr_value ~loc Nonrecursive [ vb ] ])
| Ptype_abstract | Ptype_variant _ | Ptype_open ->
let loc = tdcl.ptype_loc in
Location.raise_errorf ~loc "[@@deriving jsProperties] can only be used on record types")
tdcls
let derive_sig tdcls =
List.concat_map
(fun tdcl ->
match tdcl.ptype_kind with
| Ptype_record label_declarations -> (
match tdcl.ptype_private with
| Private -> []
| Public ->
let loc = tdcl.ptype_loc in
let has_optional_field =
List.exists
(fun (x : label_declaration) -> Derive_util.has_mel_optional x.pld_attributes)
label_declarations
in
let core_type = Derive_util.core_type_of_type_declaration tdcl in
let make_type =
List.fold_right
(fun { pld_name; pld_type; pld_attributes; pld_loc; _ } acc ->
let is_optional = Derive_util.has_mel_optional pld_attributes in
let label = if is_optional then Optional pld_name.txt else Labelled pld_name.txt in
let pld_type_inner =
if is_optional then Derive_util.get_pld_type pld_type ~attrs:pld_attributes else pld_type
in
Builder.ptyp_arrow ~loc:pld_loc label pld_type_inner acc)
label_declarations
(if has_optional_field then
Builder.ptyp_arrow ~loc Nolabel
(Builder.ptyp_constr ~loc { loc; txt = Lident "unit" } [])
core_type
else core_type)
in
[
Builder.psig_value ~loc
(Builder.value_description ~loc ~name:{ loc; txt = tdcl.ptype_name.txt } ~type_:make_type ~prim:[]);
])
| Ptype_abstract | Ptype_variant _ | Ptype_open ->
let loc = tdcl.ptype_loc in
Location.raise_errorf ~loc "[@@deriving jsProperties] can only be used on record types")
tdcls
let str_type_decl =
Deriving.Generator.V2.make Deriving.Args.empty (fun ~ctxt:_ (_, type_decls) -> derive_str type_decls)
let sig_type_decl =
Deriving.Generator.V2.make Deriving.Args.empty (fun ~ctxt:_ (_, type_decls) -> derive_sig type_decls)
let deriver = Deriving.add "jsProperties" ~str_type_decl ~sig_type_decl
================================================
FILE: packages/melange.ppx/pipe_first.ml
================================================
(* Based on https://github.com/jaredly/belt/blob/master/belt_ppx/Belt_ppx.ml,
rewriten in ppxlib register and Context_free.Rule.special_function *)
open Ppxlib
let expander e =
let rec expander' e =
let loc = e.pexp_loc in
match e.pexp_desc with
| Pexp_apply
( { pexp_desc = Pexp_ident { txt = Lident "|."; _ }; pexp_loc_stack; pexp_loc = _; pexp_attributes = _ },
[ (Nolabel, arg); (Nolabel, fn) ] ) -> (
let fn = Option.value ~default:fn (expander' fn) in
let arg = Option.value ~default:arg (expander' arg) in
match fn with
| { pexp_desc = Pexp_apply (fn, args); pexp_loc; _ } ->
let args =
List.filter_map
(fun (lab, exp) -> match expander' exp with Some e -> Some (lab, e) | None -> Some (lab, exp))
args
in
Some { pexp_desc = Pexp_apply (fn, (Nolabel, arg) :: args); pexp_attributes = []; pexp_loc; pexp_loc_stack }
| { pexp_desc = Pexp_construct (lident, None); pexp_loc; pexp_loc_stack; pexp_attributes = _ } ->
Some { pexp_desc = Pexp_construct (lident, Some arg); pexp_attributes = []; pexp_loc; pexp_loc_stack }
| _ -> Some (Ast_builder.Default.pexp_apply ~loc fn [ (Nolabel, arg) ]))
| _ -> None
in
expander' e
let rule = Context_free.Rule.special_function "( |. )" expander
================================================
FILE: packages/melange.ppx/ppx.ml
================================================
open Ppxlib
module Builder = Ast_builder.Default
module Private = struct
module Typemod_hide = struct
let no_type_defined (x : structure_item) =
match x.pstr_desc with
| Pstr_eval _ | Pstr_value _ | Pstr_primitive _ | Pstr_typext _ | Pstr_exception _
(* | Pstr_module {pmb_expr = {pmod_desc = Pmod_ident _} } *) ->
true
| Pstr_include
{
pincl_mod =
{
pmod_desc =
Pmod_constraint ({ pmod_desc = Pmod_structure [ { pstr_desc = Pstr_primitive _; _ } ]; _ }, _);
_;
};
_;
} ->
true
(* FIX https://github.com/rescript-lang/rescript/issues/4881
generated code from:
{[
external %private x : int -> int = "x"
[@@mel.module "./x"]
]}
*)
| _ -> false
let check (x : structure) =
List.iter
(fun x ->
if not (no_type_defined x) then
Location.raise_errorf ~loc:x.pstr_loc "the structure is not supported in local extension")
x
end
let rule =
let expand (stru : structure) =
Typemod_hide.check stru;
let last_loc = (List.hd stru).pstr_loc in
let first_loc = (List.hd stru).pstr_loc in
let loc = { first_loc with loc_end = last_loc.loc_end } in
Ast_helper.[ Str.open_ (Opn.mk ~override:Override (Mod.structure ~loc stru)) ] |> List.hd
in
let rule label =
let extractor = Ast_pattern.__' in
let handler ~ctxt:_ { txt = payload; loc } =
match payload with
| PStr work -> expand work
| PSig _ | PTyp _ | PPat _ -> Location.raise_errorf ~loc "private extension is not support"
in
let extender = Extension.V3.declare label Structure_item extractor handler in
Context_free.Rule.extension extender
in
rule "private"
end
module Mel_module = struct
type bundler = Webpack | Esbuild
let bundler = ref Webpack
let prefix = ref "/"
let is_melange_attr { attr_name = { txt = attr } } = "mel.module" = attr
let has_attr attrs = List.exists is_melange_attr attrs
let asset_payload attrs =
let attr =
(* we use `find` directly even if it can raise, assuming `has_attr` has been called before *)
List.find is_melange_attr attrs
in
match attr.attr_payload with
| PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_constant (Pconst_string (str, _, _)) }, _) } ]
when String.length (Filename.extension str) > 0 ->
Some str
| _ -> None
module Esbuild = struct
(* This code is adapted from Esbuild hashing algorithm:
base32: https://github.com/evanw/esbuild/blob/efa3dd2d8e895f7f9a9bef0d588560bbae7d776e/internal/bundler/bundler.go#L1174
sum function: https://github.com/evanw/esbuild/blob/efa3dd2d8e895f7f9a9bef0d588560bbae7d776e/internal/xxhash/xxhash.go#L104
the internal xxhash that esbuild uses is adapted from https://github.com/cespare/xxhash
*)
let hash_for_filename bytes = String.sub (Base32.encode_string (Bytes.to_string bytes)) 0 8
let sum hex_str =
(* Convert hexadecimal string to Int64 *)
let int64_value = Int64.of_string ("0x" ^ hex_str) in
(* Create an 8-byte buffer *)
let bytes = Bytes.create 8 in
(* Fill the buffer with the bytes of the Int64 value *)
for i = 0 to 7 do
let byte = Int64.(to_int (shift_right_logical int64_value (8 * (7 - i)))) land 0xFF in
Bytes.set bytes i (char_of_int byte)
done;
bytes
let hash content =
let hash = XXH64.hash content in
let b = sum (XXH64.to_hex hash) in
hash_for_filename b
let filename ~base content = Filename.(chop_extension base ^ "-" ^ hash content ^ extension base)
end
(*
(* For now, rspack doesn't support real content hashes, see https://github.com/web-infra-dev/rspack/issues/6606 *)
module Rspack = struct
(* This code is adapted from Rspack hashing algorithm:
https://github.com/web-infra-dev/rspack/blob/0a5cf0ddf38d41c2cad58c95ee9c1d3bd95e377f/crates/rspack_hash/src/lib.rs
*)
let hex_to_little_endian hex_str =
(* Split the hex string into byte pairs *)
let rec split_into_bytes acc i =
if i >= String.length hex_str then List.rev acc
else
let byte = String.sub hex_str i 2 in
split_into_bytes (byte :: acc) (i + 2)
in
(* Join byte pairs into a single string *)
let join_bytes bytes = String.concat "" bytes in
(* Perform the transformation *)
let bytes = split_into_bytes [] 0 in
let reversed_bytes = List.rev bytes in
join_bytes reversed_bytes
let hash content =
let open XXHash in
let hash = XXH3_64.hash content in
hex_to_little_endian (XXH3_64.to_hex hash)
end *)
module Webpack = struct
(* Needs following config in webpack.config.js, see https://webpack.js.org/configuration/output/#outputhashfunction
```
module.exports = {
//...
output: {
hashFunction: 'xxhash64',
},
};
```
Also needs to set `realContentHash` for it to work in dev mode (see https://webpack.js.org/configuration/optimization/#optimizationrealcontenthash):
```
module.exports = {
//...
optimization: {
realContentHash: false,
},
};
```
*)
let hash content =
let hash = XXH64.hash content in
XXH64.to_hex hash
let filename ~base content = hash content ^ Filename.extension base
end
end
module String_interpolation = struct
(* https://github.com/melange-re/melange/blob/fb1466fed7d6e5aafd3ee266bbd4ec70c8fb857a/ppx/string_interp.ml *)
module Utf8_string = struct
type byte = Single of int | Cont of int | Leading of int * int | Invalid
(** [classify chr] returns the {!byte} corresponding to [chr] *)
let classify chr =
let c = int_of_char chr in
(* Classify byte according to leftmost 0 bit *)
if c land 0b1000_0000 = 0 then Single c
else if
(* c 0b0____*)
c land 0b0100_0000 = 0
then Cont (c land 0b0011_1111)
else if
(* c 0b10___*)
c land 0b0010_0000 = 0
then Leading (1, c land 0b0001_1111)
else if
(* c 0b110__*)
c land 0b0001_0000 = 0
then Leading (2, c land 0b0000_1111)
else if
(* c 0b1110_ *)
c land 0b0000_1000 = 0
then Leading (3, c land 0b0000_0111)
else if
(* c 0b1111_0___*)
c land 0b0000_0100 = 0
then Leading (4, c land 0b0000_0011)
else if
(* c 0b1111_10__*)
c land 0b0000_0010 = 0
then Leading (5, c land 0b0000_0001) (* c 0b1111_110__ *)
else Invalid
end
type error =
| Invalid_code_point
| Unterminated_backslash
| Unterminated_variable
| Unmatched_paren
| Invalid_syntax_of_var of string
type kind = String | Var of int * int
(* [Var (loffset, roffset)]
For parens it used to be (2,-1)
for non-parens it used to be (1,0) *)
(* Note the position is about code point *)
type pos = {
lnum : int;
offset : int;
byte_bol : int; (* Note it actually needs to be in sync with OCaml's lexing semantics *)
}
type segment = { start : pos; finish : pos; kind : kind; content : string }
type cxt = {
mutable segment_start : pos;
buf : Buffer.t;
s_len : int;
mutable segments : segment list;
pos_bol : int; (* record the abs position of current beginning line *)
byte_bol : int;
pos_lnum : int; (* record the line number *)
}
exception Error of pos * pos * error
let pp_error fmt err =
Format.pp_print_string fmt
@@
match err with
| Invalid_code_point -> "Invalid code point"
| Unterminated_backslash -> "\\ ended unexpectedly"
| Unterminated_variable -> "$ unterminated"
| Unmatched_paren -> "Unmatched paren"
| Invalid_syntax_of_var s -> "`" ^ s ^ "' is not a valid syntax of interpolated identifer"
let valid_lead_identifier_char x = match x with 'a' .. 'z' | '_' -> true | _ -> false
let valid_identifier_char x = match x with 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '\'' -> true | _ -> false
(* Invariant: [valid_lead_identifier] has to be [valid_identifier] *)
let valid_identifier =
let for_all_from =
let rec unsafe_for_all_range s ~start ~finish p =
start > finish || (p (String.unsafe_get s start) && unsafe_for_all_range s ~start:(start + 1) ~finish p)
in
fun s start p ->
let len = String.length s in
if start < 0 then invalid_arg "for_all_from" else unsafe_for_all_range s ~start ~finish:(len - 1) p
in
fun s ->
let s_len = String.length s in
if s_len = 0 then false else valid_lead_identifier_char s.[0] && for_all_from s 1 valid_identifier_char
(* FIXME: multiple line offset
if there is no line offset. Note {|{j||} border will never trigger a new
line *)
let update_position border { lnum; offset; byte_bol } (pos : Lexing.position) =
if lnum = 0 then { pos with pos_cnum = pos.pos_cnum + border + offset }
(* When no newline, the column number is [border + offset] *)
else
{
pos with
pos_lnum = pos.pos_lnum + lnum;
pos_bol = pos.pos_cnum + border + byte_bol;
pos_cnum = pos.pos_cnum + border + byte_bol + offset;
(* when newline, the column number is [offset] *)
}
let update border start finish (loc : Location.t) =
let start_pos = loc.loc_start in
{ loc with loc_start = update_position border start start_pos; loc_end = update_position border finish start_pos }
let pos_error cxt ~loc error =
raise
(Error (cxt.segment_start, { lnum = cxt.pos_lnum; offset = loc - cxt.pos_bol; byte_bol = cxt.byte_bol }, error))
let add_var_segment cxt loc loffset roffset =
let content = Buffer.contents cxt.buf in
Buffer.clear cxt.buf;
let next_loc = { lnum = cxt.pos_lnum; offset = loc - cxt.pos_bol; byte_bol = cxt.byte_bol } in
if valid_identifier content then (
cxt.segments <-
{ start = cxt.segment_start; finish = next_loc; kind = Var (loffset, roffset); content } :: cxt.segments;
cxt.segment_start <- next_loc)
else
let cxt =
match String.trim content with
| "" ->
(* Move the position back 2 characters "$(" if this is the empty
interpolation. *)
{
cxt with
segment_start =
{
cxt.segment_start with
offset = (match cxt.segment_start.offset with 0 -> 0 | n -> n - 3);
byte_bol = (match cxt.segment_start.byte_bol with 0 -> 0 | n -> n - 3);
};
pos_bol = cxt.pos_bol + 3;
byte_bol = cxt.byte_bol + 3;
}
| _ -> cxt
in
pos_error cxt ~loc (Invalid_syntax_of_var content)
let add_str_segment cxt loc =
let content = Buffer.contents cxt.buf in
Buffer.clear cxt.buf;
let next_loc = { lnum = cxt.pos_lnum; offset = loc - cxt.pos_bol; byte_bol = cxt.byte_bol } in
cxt.segments <- { start = cxt.segment_start; finish = next_loc; kind = String; content } :: cxt.segments;
cxt.segment_start <- next_loc
let rec check_and_transform loc s byte_offset ({ s_len; buf; _ } as cxt) =
if byte_offset = s_len then add_str_segment cxt loc
else
let current_char = s.[byte_offset] in
match Utf8_string.classify current_char with
| Single 92 (* '\\' *) ->
let loc = loc + 1 in
let offset = byte_offset + 1 in
if offset >= s_len then pos_error cxt ~loc Unterminated_backslash else Buffer.add_char buf '\\';
let cur_char = s.[offset] in
Buffer.add_char buf cur_char;
check_and_transform (loc + 1) s (offset + 1) cxt
| Single 36 ->
(* $ *)
add_str_segment cxt loc;
let offset = byte_offset + 1 in
if offset >= s_len then pos_error ~loc cxt Unterminated_variable
else
let cur_char = s.[offset] in
if cur_char = '(' then expect_var_paren (loc + 2) s (offset + 1) cxt
else expect_simple_var (loc + 1) s offset cxt
| Single _ | Leading _ | Cont _ ->
Buffer.add_char buf current_char;
check_and_transform (loc + 1) s (byte_offset + 1) cxt
| Invalid -> pos_error ~loc cxt Invalid_code_point
(* Lets keep identifier simple, so that we could generating a function easier
in the future for example
let f = [%fn{| $x + $y = $x_add_y |}] *)
and expect_simple_var loc s offset ({ buf; s_len; _ } as cxt) =
let v = ref offset in
if not (offset < s_len && valid_lead_identifier_char s.[offset]) then
pos_error cxt ~loc (Invalid_syntax_of_var String.empty)
else (
while !v < s_len && valid_identifier_char s.[!v] do
(* TODO *)
let cur_char = s.[!v] in
Buffer.add_char buf cur_char;
incr v
done;
let added_length = !v - offset in
let loc = added_length + loc in
add_var_segment cxt loc 1 0;
check_and_transform loc s (added_length + offset) cxt)
and expect_var_paren loc s offset ({ buf; s_len; _ } as cxt) =
let v = ref offset in
while !v < s_len && s.[!v] <> ')' do
let cur_char = s.[!v] in
Buffer.add_char buf cur_char;
incr v
done;
let added_length = !v - offset in
let loc = added_length + 1 + loc in
if !v < s_len && s.[!v] = ')' then (
add_var_segment cxt loc 2 (-1);
check_and_transform loc s (added_length + 1 + offset) cxt)
else pos_error cxt ~loc Unmatched_paren
(* TODO: Allow identifers x.A.y *)
let border = String.length "{j|"
let rec handle_segments =
let module Exp = Ast_helper.Exp in
let concat_ident : Longident.t = Ldot (Lident "Stdlib", "^") in
let escaped_js_delimiter =
(* syntax not allowed at the user level *)
let unescaped_js_delimiter = "js" in
Some unescaped_js_delimiter
in
let merge_loc (l : Location.t) (r : Location.t) =
if l.loc_ghost then r
else if r.loc_ghost then l
else
match (l, r) with
| { loc_start; _ }, { loc_end; _ } (* TODO: improve*) -> { loc_start; loc_end; loc_ghost = false }
in
let aux loc segment =
match segment with
| { start; finish; kind; content } -> (
match kind with
| String ->
let loc = update border start finish loc in
Exp.constant (Pconst_string (content, loc, escaped_js_delimiter))
| Var (soffset, foffset) ->
let loc =
{
loc with
loc_start = update_position (soffset + border) start loc.loc_start;
loc_end = update_position (foffset + border) finish loc.loc_start;
}
in
Exp.ident ~loc { loc; txt = Lident content })
in
let concat_exp a_loc x ~(lhs : expression) =
let loc = merge_loc a_loc lhs.pexp_loc in
Exp.apply (Exp.ident { txt = concat_ident; loc }) [ (Nolabel, lhs); (Nolabel, aux loc x) ]
in
fun loc rev_segments ->
match rev_segments with
| [] -> Exp.constant (Pconst_string ("", loc, escaped_js_delimiter))
| [ segment ] -> aux loc segment (* string literal *)
| { content = ""; _ } :: rest -> handle_segments loc rest
| a :: rest -> concat_exp loc a ~lhs:(handle_segments loc rest)
let transform =
let transform (e : expression) s =
let s_len = String.length s in
let buf = Buffer.create (s_len * 2) in
let cxt =
{
segment_start = { lnum = 0; offset = 0; byte_bol = 0 };
buf;
s_len;
segments = [];
pos_lnum = 0;
byte_bol = 0;
pos_bol = 0;
}
in
check_and_transform 0 s 0 cxt;
handle_segments e.pexp_loc cxt.segments
in
fun ~loc expr s ->
try transform expr s
with Error (start, pos, error) ->
let loc = update border start pos loc in
Location.raise_errorf ~loc "%a" pp_error error
end
let is_send_pipe pval_attributes =
List.exists (fun { attr_name = { txt = attr } } -> String.equal attr "mel.send.pipe") pval_attributes
let has_browser_ppx_attribute attrs =
List.exists
(fun { attr_name = { txt = attr }; attr_payload; _ } ->
match (attr, attr_payload) with
| "browser_only", _ -> true
| "platform", PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_ident { txt = Lident "js" } }, _); _ } ] -> true
| _ -> false)
attrs
let has_attribute attrs name = List.exists (fun { attr_name = { txt; _ }; _ } -> String.equal txt name) attrs
(* Keep this keyword list and translate_mel_obj_label in sync with Melange's
Lam_methname.translate implementation:
https://github.com/melange-re/melange/blob/main/jscomp/common/lam_methname.ml *)
let mel_obj_keywords =
[
"and";
"as";
"assert";
"begin";
"class";
"constraint";
"do";
"done";
"downto";
"else";
"end";
"exception";
"external";
"false";
"for";
"fun";
"function";
"functor";
"if";
"in";
"include";
"inherit";
"initializer";
"lazy";
"let";
"match";
"method";
"module";
"mutable";
"new";
"nonrec";
"object";
"of";
"open";
"or";
"private";
"rec";
"sig";
"struct";
"then";
"to";
"true";
"try";
"type";
"val";
"virtual";
"when";
"while";
"with";
"mod";
"land";
"lor";
"lxor";
"lsl";
"lsr";
"asr";
]
let find_double_underscore name =
let rec go index =
if index < 0 then -1
else if index + 1 < String.length name && name.[index] = '_' && name.[index + 1] = '_' then index
else go (index - 1)
in
go (String.length name - 2)
let translate_mel_obj_label name =
let valid_start_char = function '_' | 'a' .. 'z' -> true | _ -> false in
let double_underscore_index = find_double_underscore name in
if double_underscore_index = 0 then name
else if double_underscore_index > 0 then String.sub name 0 double_underscore_index
else
match name.[0] with
| '_' when String.length name > 1 ->
let candidate = String.sub name 1 (String.length name - 1) in
if (not (valid_start_char candidate.[0])) || List.mem candidate mel_obj_keywords then candidate else name
| _ -> name
type js_object_field = { method_name : string; js_name : string; present_expr : expression; value_expr : expression }
let option_is_some_expr ~loc expr = [%expr match [%e expr] with None -> false | Some _ -> true]
let js_obj_internal_expression ~loc name =
Builder.pexp_ident ~loc { txt = Ldot (Ldot (Ldot (Lident "Js", "Obj"), "Internal"), name); loc }
let build_registered_js_object_expression ?as_type ?(register_name = "register_structural") ~loc fields =
let generated_fields =
List.mapi
(fun index { method_name; js_name; present_expr; value_expr } ->
let cell_name = Printf.sprintf "__js_obj_cell_%d" index in
let entry_name = Printf.sprintf "__js_obj_entry_%d" index in
let slot_call =
Builder.pexp_apply ~loc
(js_obj_internal_expression ~loc "slot_ref")
[
(Labelled "method_name", Builder.estring ~loc method_name);
(Labelled "js_name", Builder.estring ~loc js_name);
(Labelled "present", present_expr);
(Nolabel, value_expr);
]
in
let slot_binding =
Builder.value_binding ~loc
~pat:
(Builder.ppat_tuple ~loc
[ Builder.ppat_var ~loc { loc; txt = cell_name }; Builder.ppat_var ~loc { loc; txt = entry_name } ])
~expr:slot_call
in
let method_body = Builder.pexp_apply ~loc (Builder.evar ~loc "!") [ (Nolabel, Builder.evar ~loc cell_name) ] in
let method_ =
Builder.pcf_method ~loc (Builder.Located.mk method_name ~loc, Public, Cfk_concrete (Fresh, method_body))
in
(slot_binding, Builder.evar ~loc entry_name, method_))
fields
in
let slot_bindings, entry_exprs, methods =
List.fold_right
(fun (slot_binding, entry_expr, method_) (slot_bindings, entry_exprs, methods) ->
(slot_binding :: slot_bindings, entry_expr :: entry_exprs, method_ :: methods))
generated_fields ([], [], [])
in
let object_name = "__js_obj" in
let object_binding =
Builder.value_binding ~loc
~pat:(Builder.ppat_var ~loc { loc; txt = object_name })
~expr:(Builder.pexp_object ~loc (Builder.class_structure ~self:(Builder.ppat_any ~loc) ~fields:methods))
in
let register_call =
Builder.pexp_apply ~loc
(js_obj_internal_expression ~loc register_name)
[ (Nolabel, Builder.evar ~loc object_name); (Nolabel, Builder.elist ~loc entry_exprs) ]
in
let register_call =
match as_type with None -> register_call | Some core_type -> Builder.pexp_constraint ~loc register_call core_type
in
List.fold_right
(fun binding acc -> Builder.pexp_let ~loc Nonrecursive [ binding ] acc)
(slot_bindings @ [ object_binding ]) register_call
let rec get_return_core_type = function
| { ptyp_desc = Ptyp_arrow (_, _, rest); _ } -> get_return_core_type rest
| core_type -> core_type
let get_function_name pattern =
let rec go pattern =
match pattern with
| Ppat_var { txt = name; _ } -> Some name
| Ppat_constraint (pattern, _) -> go pattern.ppat_desc
| _ -> None
in
go pattern
let get_label = function Ptyp_constr ({ txt = Lident label; _ }, _) -> Some label | _ -> None
(* Extract the `t` from [@mel.send.pipe: t] *)
let get_send_pipe pval_attributes =
if is_send_pipe pval_attributes then
let first_attribute = List.hd pval_attributes in
match first_attribute.attr_payload with PTyp core_type -> Some core_type | _ -> None
else None
let has_ptyp_attribute ptyp_attributes attribute =
List.exists (fun { attr_name = { txt = attr } } -> attr = attribute) ptyp_attributes
let is_mel_as core_type =
match core_type with
| { ptyp_desc = Ptyp_any; ptyp_attributes; _ } -> has_ptyp_attribute ptyp_attributes "mel.as"
| _ -> false
let extract_args_labels_types acc pval_type =
let rec go acc = function
(* In case of being mel.as, ignore those *)
| { ptyp_desc = Ptyp_arrow (_label, t1, _t2); _ } when is_mel_as t1 -> acc
| { ptyp_desc = Ptyp_arrow (_label, _t1, t2); _ } when is_mel_as t2 -> acc
| { ptyp_desc = Ptyp_arrow (_label, t1, t2); _ } when is_mel_as t1 && is_mel_as t2 -> acc
| { ptyp_desc = Ptyp_arrow (label, t1, t2); _ } ->
let pattern = Builder.ppat_var ~loc:t1.ptyp_loc { loc = t1.ptyp_loc; txt = "_" } in
go ((label, pattern, t1) :: acc) t2
| _ -> acc
in
go acc pval_type
(* Insert send_pipe_core_type as a last argument of the function, but not the return type *)
let construct_pval_with_send_pipe send_pipe_core_type pval_type =
let rec insert_core_type_in_arrow core_type =
match core_type with
(* Handle only ptyp and constr.
Missing `| Ptyp_any | Ptyp_var | Ptyp_arrow | Ptyp_tuple | Ptyp_constr
| Ptyp_object | Ptyp_class | Ptyp_alias | Ptyp_variant
| Ptyp_poly | Ptyp_package | Ptyp_extension`
The aren't used in most bindings.
*)
| { ptyp_desc = Ptyp_arrow (label, t1, t2); _ } -> (
match (t1.ptyp_desc, t2.ptyp_desc) with
(* `constr -> arrow (constr -> constr)` gets transformed into
`constr -> constr -> t -> constr` *)
| Ptyp_constr _, Ptyp_arrow (_inner_label, _p1, _p2) ->
Builder.ptyp_arrow ~loc:t1.ptyp_loc label t1 (insert_core_type_in_arrow t2)
(* `constr -> constr` gets transformed into `constr -> t -> constr` *)
(* `arrow (constr -> constr) -> constr` gets transformed into,
`arrow (constr -> constr) -> t -> constr` *)
| _, _ ->
Builder.ptyp_arrow ~loc:t2.ptyp_loc label t1
(Builder.ptyp_arrow ~loc:t2.ptyp_loc Nolabel send_pipe_core_type t2))
(* In case of being a single ptyp_* turn into ptyp_* -> t *)
| { ptyp_desc = Ptyp_constr ({ txt = _; loc }, _); _ } | { ptyp_desc = Ptyp_var _; ptyp_loc = loc; _ } ->
Builder.ptyp_arrow ~loc Nolabel core_type send_pipe_core_type
(* Here we ignore the Ptyp_any *)
| _ -> core_type
in
insert_core_type_in_arrow pval_type
let inject_send_pipe_as_last_argument pipe_type args_labels =
match pipe_type with None -> args_labels | Some pipe_core_type -> pipe_core_type :: args_labels
let is_mel_raw expr = match expr with Pexp_extension ({ txt = "mel.raw"; _ }, _) -> true | _ -> false
let capture_payload expr =
match expr with
| PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_constant (Pconst_string (payload, _, _)); _ }, _); _ } ] ->
payload
| _ -> "..."
let get_payload_from_mel_raw expr =
let rec go expr =
match expr with
| Pexp_extension ({ txt = "mel.raw"; _ }, pstr) -> capture_payload pstr
| Pexp_constraint (expr, _) -> go expr.pexp_desc
| Pexp_function (_, _, Pfunction_body expr) -> go expr.pexp_desc
| _ -> "..."
in
go expr
let expression_has_mel_raw expr =
let rec go expr =
match expr with
| Pexp_extension ({ txt = "mel.raw"; _ }, _) as pexp_desc -> is_mel_raw pexp_desc
| Pexp_constraint (expr, _) -> is_mel_raw expr.pexp_desc
| Pexp_function (_, _, Pfunction_body expr) -> go expr.pexp_desc
| _ -> false
in
go expr
let raise_failure ~loc name =
[%expr
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr [%e Builder.pexp_constant ~loc (Pconst_string (name, loc, None))])]
let validate_mel_obj_primitive ~loc pval_prim =
match pval_prim with
| [] -> ()
| prims when List.for_all (String.equal "") prims -> ()
| _ ->
Location.raise_errorf ~loc
"[server-reason-react.melange_ppx] [@@mel.obj] requires its external payload to be the empty string"
let transform_external_obj ~loc pval_name pval_type =
let function_core_type = Builder.ppat_var ~loc:pval_name.loc { loc = pval_name.loc; txt = pval_name.txt } in
let pat =
Builder.ppat_constraint ~loc:pval_type.ptyp_loc function_core_type
(Builder.ptyp_poly ~loc:pval_type.ptyp_loc [] pval_type)
in
let rec collect_arguments function_args fields = function
| { ptyp_desc = Ptyp_arrow (label, core_type, rest); _ } -> (
if is_mel_as core_type then
Location.raise_errorf ~loc:core_type.ptyp_loc
"[server-reason-react.melange_ppx] [@mel.as] is not supported in native [@@mel.obj] externals yet";
match label with
| Nolabel ->
let pattern =
match core_type.ptyp_desc with
| Ptyp_constr ({ txt = Lident "unit"; _ }, []) -> Builder.ppat_any ~loc:core_type.ptyp_loc
| _ ->
Location.raise_errorf ~loc:core_type.ptyp_loc
"[server-reason-react.melange_ppx] [@@mel.obj] externals in native only support labelled \
arguments, optionally labelled arguments, and a final unit argument"
in
collect_arguments ((label, pattern, core_type.ptyp_loc) :: function_args) fields rest
| Labelled name | Optional name ->
let ident = { loc = core_type.ptyp_loc; txt = name } in
let pattern = Builder.ppat_var ~loc:core_type.ptyp_loc ident in
let value = Builder.pexp_ident ~loc:core_type.ptyp_loc { loc = core_type.ptyp_loc; txt = Lident name } in
let present_expr =
match label with
| Labelled _ -> Builder.ebool ~loc:core_type.ptyp_loc true
| Optional _ -> option_is_some_expr ~loc:core_type.ptyp_loc value
| Nolabel -> assert false
in
let field =
{ method_name = name; js_name = translate_mel_obj_label name; present_expr; value_expr = value }
in
collect_arguments ((label, pattern, core_type.ptyp_loc) :: function_args) (field :: fields) rest)
| _ -> (List.rev function_args, List.rev fields)
in
let function_args, fields = collect_arguments [] [] pval_type in
let object_expression =
build_registered_js_object_expression ~loc ~register_name:"register_abstract"
~as_type:(get_return_core_type pval_type) fields
in
let function_expression =
List.fold_right
(fun (label, arg_pat, arg_loc) acc -> Builder.pexp_fun ~loc:arg_loc label None arg_pat acc)
function_args object_expression
in
let vb = Builder.value_binding ~loc ~pat ~expr:function_expression in
Ast_helper.Str.value Nonrecursive [ vb ]
let mel_raw_found_in_native_message ~loc payload =
let msg =
Printf.sprintf
"[server-reason-react.melange_ppx] There's a [%%mel.raw \"%s\"] expression in native, which should only happen \
in JavaScript. You need to conditionally run it via let%%browser_only or switch%%platform. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"
payload
in
Builder.pexp_constant ~loc (Pconst_string (msg, loc, None))
let mel_module_found_in_native_message ~loc =
let msg =
Printf.sprintf
"[server-reason-react.melange_ppx] There's an external with [%%mel.module \"...\"] in native, which should only \
happen in JavaScript. You need to conditionally discard it from the native build, either by moving the external \
in a module only available in native, or annotating it with [@platform js]. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"
in
Builder.pexp_constant ~loc (Pconst_string (msg, loc, None))
let external_found_in_native_message ~loc =
let msg =
Printf.sprintf
"[server-reason-react.melange_ppx] There's an external in native, which should only happen in JavaScript. You \
need to conditionally discard it from the native build, either by moving the external in a module only \
available in native, or annotating it with [@platform js]. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"
in
Builder.pexp_constant ~loc (Pconst_string (msg, loc, None))
let get_function_arity pattern =
let rec go arity params body =
match params with
| _ :: rest -> go (arity + 1) rest body
| [] -> (
match body with
| Pfunction_body { pexp_desc = Pexp_function (more_params, _, inner_body); _ } ->
go arity more_params inner_body
| _ -> arity)
in
match pattern with Pexp_function (params, _, body) -> go 0 params body | _ -> 0
let transform_external_arrow ~loc pval_name pval_attributes pval_type =
let pipe_type =
match get_send_pipe pval_attributes with
| Some core_type ->
let pattern = Builder.ppat_var ~loc:core_type.ptyp_loc { loc = core_type.ptyp_loc; txt = "_" } in
Some (Nolabel, pattern, core_type)
| None -> None
in
let args_labels_types = extract_args_labels_types [] pval_type in
let function_core_type = Builder.ppat_var ~loc:pval_name.loc { loc = pval_name.loc; txt = pval_name.txt } in
let pval_type_piped =
match pipe_type with
| None -> pval_type
| Some (_, _, pipe_type) -> construct_pval_with_send_pipe pipe_type pval_type
in
let pat =
Builder.ppat_constraint ~loc:pval_type.ptyp_loc function_core_type
(Builder.ptyp_poly ~loc:pval_type.ptyp_loc [] pval_type_piped)
in
let arg_labels = inject_send_pipe_as_last_argument pipe_type args_labels_types in
let function_expression =
List.fold_left
(fun acc (label, arg_pat, arg_type) -> Builder.pexp_fun ~loc:arg_type.ptyp_loc label None arg_pat acc)
(raise_failure ~loc:pval_type.ptyp_loc pval_name.txt)
arg_labels
in
let vb = Builder.value_binding ~loc ~pat ~expr:function_expression in
Ast_helper.Str.value Nonrecursive [ vb ]
let ptyp_humanize = function
| Ptyp_tuple _ -> "Tuples"
| Ptyp_object _ -> "Objects"
| Ptyp_class _ -> "Classes"
| Ptyp_variant _ -> "Variants"
| Ptyp_extension _ -> "Extensions"
| Ptyp_alias _ -> "Alias"
| Ptyp_poly _ -> "Polyvariants"
| Ptyp_package _ -> "Packages"
| Ptyp_any -> "Any"
| Ptyp_var _ -> "Var"
| Ptyp_arrow _ -> "Arrow"
| Ptyp_constr _ -> "Constr"
| Ptyp_open _ -> "Open"
let transform_external ~module_path pval_name pval_attributes pval_loc pval_type pval_prim =
let loc = pval_loc in
match pval_type.ptyp_desc with
| Ptyp_arrow _ ->
if has_attribute pval_attributes "mel.obj" then (
validate_mel_obj_primitive ~loc pval_prim;
transform_external_obj ~loc pval_name pval_type)
else transform_external_arrow ~loc pval_name pval_attributes pval_type
| Ptyp_var _ | Ptyp_any | Ptyp_constr _ ->
(* When mel.send.pipe is used, it's treated as a funcion *)
if Option.is_some (get_send_pipe pval_attributes) then
transform_external_arrow ~loc pval_name pval_attributes pval_type
else if Mel_module.has_attr pval_attributes then
match Mel_module.asset_payload pval_attributes with
| None ->
(* If it doesn't have asset payload, we error out as it must be some .js module or package being imported *)
[%stri [%%ocaml.error [%e mel_module_found_in_native_message ~loc]]]
| Some str ->
(* If it has asset payload (file with extension), calculate hash and replace external *)
let name = Builder.pvar ~loc:pval_name.loc pval_name.txt in
let path =
let asset_path = Filename.(concat (dirname module_path) str) in
let s = In_channel.with_open_bin asset_path In_channel.input_all in
let filename_fn =
match !Mel_module.bundler with
| Webpack -> Mel_module.Webpack.filename
| Esbuild -> Mel_module.Esbuild.filename
in
let prefix = !Mel_module.prefix in
Builder.estring ~loc Filename.(concat prefix (filename_fn ~base:(Filename.basename str) s))
in
[%stri let [%p name] = [%e path]]
else [%stri [%%ocaml.error [%e external_found_in_native_message ~loc]]]
| _ ->
[%stri
[%%ocaml.error
"[server-reason-react.melange_ppx] %s are not supported in native externals the same way as melange.ppx \
support them."
(ptyp_humanize pval_type.ptyp_desc)]]
let validate_record_labels ~loc record =
List.fold_left
(fun acc (longident, expression) ->
match acc with
| Error _ as error -> error
| Ok acc -> (
match longident.txt with
| Lident label ->
Ok
({
method_name = label;
js_name = translate_mel_obj_label label;
present_expr = Builder.ebool ~loc:expression.pexp_loc true;
value_expr = expression;
}
:: acc)
| Ldot _ | Lapply _ ->
Error
(Location.error_extensionf ~loc
"[server-reason-react.melange_ppx] Js.t objects only support labels as keys")))
(Ok []) record
|> Result.map List.rev
class raise_exception_mapper (module_path : string) =
object (_self)
inherit Ast_traverse.map as super
method! expression expr =
let expr = super#expression expr in
match expr.pexp_desc with
| Pexp_extension
( { txt = "mel.obj"; _ },
PStr [ { pstr_desc = Pstr_eval ({ pexp_desc = Pexp_record (record, None); pexp_loc }, _); _ } ] ) -> (
match validate_record_labels ~loc:pexp_loc record with
| Ok record -> build_registered_js_object_expression ~loc:pexp_loc record
| Error extension -> Builder.pexp_extension ~loc:pexp_loc extension)
| Pexp_extension ({ txt = "mel.obj"; loc }, _) ->
Builder.pexp_extension ~loc
(Location.error_extensionf ~loc:expr.pexp_loc
"[server-reason-react.melange_ppx] Js.t objects requires a record literal")
| Pexp_constant (Pconst_string (s, loc, Some "j")) -> String_interpolation.transform ~loc expr s
| _ -> expr
method! structure_item item =
match item.pstr_desc with
(* [%%mel.raw ...] *)
| Pstr_extension (({ txt = "mel.raw"; _ }, pstr), _) ->
let loc = item.pstr_loc in
let payload = capture_payload pstr in
[%stri [%%ocaml.error [%e mel_raw_found_in_native_message ~loc payload]]]
(* let a _ = [%mel.raw ...] *)
| Pstr_value
( Nonrecursive,
[
{
pvb_expr = { pexp_desc = Pexp_function (_ :: _, _, Pfunction_body expression); _ };
pvb_pat = { ppat_desc = Ppat_var { txt = _function_name; _ } };
pvb_attributes = _;
pvb_loc;
};
] )
when expression_has_mel_raw expression.pexp_desc ->
let loc = item.pstr_loc in
let payload = get_payload_from_mel_raw expression.pexp_desc in
[%stri [%error [%e mel_raw_found_in_native_message ~loc:pvb_loc payload]]]
(* let a = [%mel.raw ...] *)
| Pstr_value
( Nonrecursive,
[
{
pvb_expr = expression;
pvb_pat = { ppat_desc = Ppat_var { txt = _function_name; _ } };
pvb_attributes = _;
pvb_loc;
};
] )
when expression_has_mel_raw expression.pexp_desc ->
let loc = item.pstr_loc in
let payload = get_payload_from_mel_raw expression.pexp_desc in
[%stri [%error [%e mel_raw_found_in_native_message ~loc:pvb_loc payload]]]
(* let a: t = [%mel.raw ...] *)
| Pstr_value
(Nonrecursive, [ { pvb_expr = expression; pvb_pat = { ppat_desc = _ }; pvb_attributes = _; pvb_loc } ])
when expression_has_mel_raw expression.pexp_desc ->
let loc = item.pstr_loc in
let payload = get_payload_from_mel_raw expression.pexp_desc in
[%stri [%error [%e mel_raw_found_in_native_message ~loc:pvb_loc payload]]]
(* %mel. *)
(* external foo: t = "{{JavaScript}}" *)
| Pstr_primitive { pval_name; pval_attributes; pval_loc; pval_type; pval_prim } ->
(* Detects [@browser_only] or [@platform js] attributes. When present on an external, we pass it through unchanged so browser_ppx can filter it out in native mode. *)
if has_browser_ppx_attribute pval_attributes then item
else transform_external ~module_path pval_name pval_attributes pval_loc pval_type pval_prim
| _ -> super#structure_item item
end
let structure_mapper ctxt s =
let module_path = Code_path.file_path (Expansion_context.Base.code_path ctxt) in
(new raise_exception_mapper module_path)#structure s
module Debug = struct
let rule =
let extractor = Ast_pattern.(__') in
let handler ~ctxt:_ { loc } = [%expr ()] in
Context_free.Rule.extension (Extension.V3.declare "debug" Extension.Context.expression extractor handler)
end
let () =
Driver.add_arg "-bundler"
(String
(fun str ->
match str with
| "webpack" -> Mel_module.bundler := Webpack
| "esbuild" -> Mel_module.bundler := Esbuild
| _ ->
failwith
(Printf.sprintf
{|Unknown value %S passed as -bundler flag in melange.ppx, valid values: "webpack", "esbuild"|} str)))
~doc:"generate paths to assets in mel.module using the file name scheme of the bundler of choice";
Driver.add_arg "-prefix"
(String (fun str -> Mel_module.prefix := str))
~doc:"the paths to the generated assets will include the given prefix before the filename (default: \"/\")";
Driver.V2.register_transformation ~impl:structure_mapper
~rules:[ Pipe_first.rule; Regex.rule; Double_hash.rule; Debug.rule; Private.rule ]
"melange-native-ppx"
================================================
FILE: packages/melange.ppx/regex.ml
================================================
open Ppxlib
module Builder = Ast_builder.Default
let parse_re str =
try
let _ = Str.search_forward (Str.regexp "/\\(.*\\)/\\(.*\\)") str 0 in
let first = Str.matched_group 1 str in
let second = Str.matched_group 2 str in
match String.length second with 0 -> Ok (first, None) | _ -> Ok (first, Some second)
with Not_found -> Error "invalid regex"
let extractor = Ast_pattern.(__')
let handler ~ctxt:_ ({ txt = payload; loc } : Ppxlib.Parsetree.payload loc) =
match payload with
| PStr [ { pstr_desc = Pstr_eval (expression, _); _ } ] -> (
match expression.pexp_desc with
| Pexp_constant (Pconst_string (str, location, _delimiter)) -> (
match parse_re str with
| Ok (regex, flags) -> (
let regex = Builder.estring ~loc:location regex in
match flags with
| None -> [%expr Js.Re.fromString [%e regex]]
| Some flags' ->
let flags = Builder.estring ~loc:location flags' in
[%expr Js.Re.fromStringWithFlags ~flags:[%e flags] [%e regex]])
| Error err ->
Builder.pexp_extension ~loc
(Location.error_extensionf ~loc:location "[server-reason-react.melange_ppx] invalid regex: %s,\n%s" err
str))
| _ ->
Builder.pexp_extension ~loc
(Location.error_extensionf ~loc "[server-reason-react.melange_ppx] payload should be a string literal"))
| _ ->
Builder.pexp_extension ~loc
(Location.error_extensionf ~loc
"[server-reason-react.melange_ppx] [%%re] extension should have an expression as payload")
let rule =
let extension = Extension.V3.declare "mel.re" Extension.Context.expression extractor handler in
Context_free.Rule.extension extension
================================================
FILE: packages/melange.ppx/tests/dune
================================================
(cram
(package server-reason-react)
(enabled_if
(>= %{ocaml_version} 5.2.0))
(deps %{bin:ocamlformat} standalone.exe))
(executable
(name standalone)
(libraries ppxlib melange_native_ppx))
================================================
FILE: packages/melange.ppx/tests/external.t
================================================
An external without platform attribute errors
$ cat > input.ml << EOF
> type t
> external document: t = "document"
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t
[%%ocaml.error
"[server-reason-react.melange_ppx] There's an external in native, which should \
only happen in JavaScript. You need to conditionally discard it from the \
native build, either by moving the external in a module only available in \
native, or annotating it with [@platform js]. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
File "main.ml", line 26, characters 3-14:
26 | [%%ocaml.error
^^^^^^^^^^^
Error: [server-reason-react.melange_ppx] There's an external in native, which
should only happen in JavaScript. You need to conditionally discard it
from the native build, either by moving the external in a module only
available in native, or annotating it with [@platform js]. More info
at
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html
[2]
An external with [@platform js] is passed through (browser_ppx will filter it)
$ cat > input.ml << EOF
> type t
> external document: t = "document" [@@platform js]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type t
external document : t = "document" [@@platform js]
An external with [@browser_only] is passed through (browser_ppx will filter it)
$ cat > input.ml << EOF
> type t
> external document: t = "document" [@@browser_only]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type t
external document : t = "document" [@@browser_only]
================================================
FILE: packages/melange.ppx/tests/input.ml
================================================
type action = Click | Submit | Cancel [@@deriving jsConverter]
================================================
FILE: packages/melange.ppx/tests/jsConverter.t
================================================
Basic regular variant
$ cat > input.ml << EOF
> type action = Click | Submit | Cancel [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type action = Click | Submit | Cancel [@@deriving jsConverter]
include struct
let _ = fun (_ : action) -> ()
let actionToJs x = match x with Click -> 0 | Submit -> 1 | Cancel -> 2
let _ = actionToJs
let actionFromJs x =
match x with
| 0 -> Some Click
| 1 -> Some Submit
| 2 -> Some Cancel
| _ -> None
let _ = actionFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Regular variant with @mel.as
$ cat > input.ml << EOF
> type action = Click | Submit [@mel.as 3] | Cancel [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type action = Click | Submit [@mel.as 3] | Cancel [@@deriving jsConverter]
include struct
let _ = fun (_ : action) -> ()
let actionToJs x = match x with Click -> 0 | Submit -> 3 | Cancel -> 4
let _ = actionToJs
let actionFromJs x =
match x with
| 0 -> Some Click
| 3 -> Some Submit
| 4 -> Some Cancel
| _ -> None
let _ = actionFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Basic polymorphic variant
$ cat > input.ml << EOF
> type state = [\`Idle | \`Loading | \`Error] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type state = [ `Idle | `Loading | `Error ] [@@deriving jsConverter]
include struct
let _ = fun (_ : state) -> ()
let stateToJs x =
match x with `Idle -> "Idle" | `Loading -> "Loading" | `Error -> "Error"
let _ = stateToJs
let stateFromJs x =
match x with
| "Idle" -> Some `Idle
| "Loading" -> Some `Loading
| "Error" -> Some `Error
| _ -> None
let _ = stateFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Polymorphic variant with @mel.as
$ cat > input.ml << EOF
> type state = [\`Idle | \`Loading [@mel.as "loading"] | \`Error] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type state = [ `Idle | `Loading [@mel.as "loading"] | `Error ]
[@@deriving jsConverter]
include struct
let _ = fun (_ : state) -> ()
let stateToJs x =
match x with `Idle -> "Idle" | `Loading -> "loading" | `Error -> "Error"
let _ = stateToJs
let stateFromJs x =
match x with
| "Idle" -> Some `Idle
| "loading" -> Some `Loading
| "Error" -> Some `Error
| _ -> None
let _ = stateFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Regular variant with newType
$ cat > input.ml << EOF
> type action = Click | Submit [@@deriving jsConverter { newType }]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type action = Click | Submit [@@deriving jsConverter { newType }]
include struct
let _ = fun (_ : action) -> ()
type nonrec abs_action = int
let actionToJs x = match x with Click -> 0 | Submit -> 1
let _ = actionToJs
let actionFromJs x = match x with 0 -> Click | 1 -> Submit
let _ = actionFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Polymorphic variant with newType
$ cat > input.ml << EOF
> type state = [\`Idle | \`Loading] [@@deriving jsConverter { newType }]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type state = [ `Idle | `Loading ] [@@deriving jsConverter { newType }]
include struct
let _ = fun (_ : state) -> ()
type nonrec abs_state = string
let stateToJs x = match x with `Idle -> "Idle" | `Loading -> "Loading"
let _ = stateToJs
let stateFromJs x = match x with "Idle" -> `Idle | "Loading" -> `Loading
let _ = stateFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Error: variant with payload
$ cat > input.ml << EOF
> type action = Click | Submit of int [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 20-35:
1 | type action = Click | Submit of int [@@deriving jsConverter]
^^^^^^^^^^^^^^^
Error: [@deriving jsConverter] does not support variant constructors with payloads
[1]
Error: polymorphic variant with payload
$ cat > input.ml << EOF
> type state = [\`Idle | \`Loading of int] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 22-37:
1 | type state = [`Idle | `Loading of int] [@@deriving jsConverter]
^^^^^^^^^^^^^^^
Error: [@deriving jsConverter] does not support polymorphic variant constructors with payloads
[1]
Signature generation for regular variant
$ cat > input.mli << EOF
> type action = Click | Submit | Cancel [@@deriving jsConverter]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type action = Click | Submit | Cancel [@@deriving jsConverter]
include sig
[@@@ocaml.warning "-32"]
val actionToJs : action -> int
val actionFromJs : int -> action option
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for polymorphic variant
$ cat > input.mli << EOF
> type state = [\`Idle | \`Loading] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type state = [ `Idle | `Loading ] [@@deriving jsConverter]
include sig
[@@@ocaml.warning "-32"]
val stateToJs : state -> string
val stateFromJs : string -> state option
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation with newType
$ cat > input.mli << EOF
> type action = Click | Submit [@@deriving jsConverter { newType }]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type action = Click | Submit [@@deriving jsConverter { newType }]
include sig
[@@@ocaml.warning "-32"]
type nonrec abs_action
val actionToJs : action -> abs_action
val actionFromJs : abs_action -> action
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Backwards compatibility: @bs.as attribute
$ cat > input.ml << EOF
> type legacy = A | B [@bs.as 5] | C [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type legacy = A | B [@bs.as 5] | C [@@deriving jsConverter]
include struct
let _ = fun (_ : legacy) -> ()
let legacyToJs x = match x with A -> 0 | B -> 1 | C -> 2
let _ = legacyToJs
let legacyFromJs x =
match x with 0 -> Some A | 1 -> Some B | 2 -> Some C | _ -> None
let _ = legacyFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Multiple type declarations with 'and'
$ cat > input.ml << EOF
> type a = A1 | A2
> and b = B1 | B2 [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type a = A1 | A2
and b = B1 | B2 [@@deriving jsConverter]
include struct
let _ = fun (_ : a) -> ()
let _ = fun (_ : b) -> ()
let aToJs x = match x with A1 -> 0 | A2 -> 1
let _ = aToJs
let aFromJs x = match x with 0 -> Some A1 | 1 -> Some A2 | _ -> None
let _ = aFromJs
let bToJs x = match x with B1 -> 0 | B2 -> 1
let _ = bToJs
let bFromJs x = match x with 0 -> Some B1 | 1 -> Some B2 | _ -> None
let _ = bFromJs
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Error: empty variant
$ cat > input.ml << EOF
> type empty = | [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 0-39:
1 | type empty = | [@@deriving jsConverter]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: [@deriving jsConverter] cannot be used on empty variant types
[1]
Error: duplicate @mel.as values
$ cat > input.ml << EOF
> type dup = A [@mel.as 1] | B [@mel.as 1] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 0-65:
1 | type dup = A [@mel.as 1] | B [@mel.as 1] [@@deriving jsConverter]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: [@deriving jsConverter] has duplicate value 1 - each constructor must map to a unique integer
[1]
Error: open polymorphic variant
$ cat > input.ml << EOF
> type open_poly = [> \`A | \`B] [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 17-28:
1 | type open_poly = [> `A | `B] [@@deriving jsConverter]
^^^^^^^^^^^
Error: [@deriving jsConverter] does not support open polymorphic variants
[1]
Error: record type
$ cat > input.ml << EOF
> type person = { name: string; age: int } [@@deriving jsConverter]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 0-65:
1 | type person = { name: string; age: int } [@@deriving jsConverter]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: [@deriving jsConverter] only supports variant types and polymorphic variant types
[1]
================================================
FILE: packages/melange.ppx/tests/jsProperties.t
================================================
Basic jsProperties
$ cat > input.ml << EOF
> type person = { name: string; age: int } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; age : int } [@@deriving jsProperties]
include struct
let _ = fun (_ : person) -> ()
let person ~name ~age = { name; age }
let _ = person
end [@@ocaml.doc "@inline"] [@@merlin.hide]
jsProperties with @mel.optional
$ cat > input.ml << EOF
> type person = { name: string; age: int option [@mel.optional] } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; age : int option [@mel.optional] }
[@@deriving jsProperties]
include struct
let _ = fun (_ : person) -> ()
let person ~name ?age () = { name; age }
let _ = person
end [@@ocaml.doc "@inline"] [@@merlin.hide]
jsProperties with multiple optional fields
$ cat > input.ml << EOF
> type config = {
> host: string;
> port: int option [@mel.optional];
> debug: bool option [@mel.optional]
> } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type config = {
host : string;
port : int option; [@mel.optional]
debug : bool option; [@mel.optional]
}
[@@deriving jsProperties]
include struct
let _ = fun (_ : config) -> ()
let config ~host ?port ?debug () = { host; port; debug }
let _ = config
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Basic getSet
$ cat > input.ml << EOF
> type person = { name: string; age: int } [@@deriving getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; age : int } [@@deriving getSet]
include struct
let _ = fun (_ : person) -> ()
let nameGet x = x.name
let _ = nameGet
let ageGet x = x.age
let _ = ageGet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
getSet with mutable fields
$ cat > input.ml << EOF
> type person = { name: string; mutable age: int } [@@deriving getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; mutable age : int } [@@deriving getSet]
include struct
let _ = fun (_ : person) -> ()
let nameGet x = x.name
let _ = nameGet
let ageGet x = x.age
let _ = ageGet
let ageSet x v = x.age <- v
let _ = ageSet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
getSet with light mode
$ cat > input.ml << EOF
> type person = { name: string; mutable age: int } [@@deriving getSet { light }]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; mutable age : int } [@@deriving getSet { light }]
include struct
let _ = fun (_ : person) -> ()
let name x = x.name
let _ = name
let age x = x.age
let _ = age
let ageSet x v = x.age <- v
let _ = ageSet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Combined jsProperties and getSet
$ cat > input.ml << EOF
> type person = { name: string; mutable age: int } [@@deriving jsProperties, getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = { name : string; mutable age : int }
[@@deriving jsProperties, getSet]
include struct
let _ = fun (_ : person) -> ()
let person ~name ~age = { name; age }
let _ = person
let nameGet x = x.name
let _ = nameGet
let ageGet x = x.age
let _ = ageGet
let ageSet x v = x.age <- v
let _ = ageSet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Combined jsProperties and getSet with optional and light
$ cat > input.ml << EOF
> type config = {
> host: string;
> mutable port: int option [@mel.optional]
> } [@@deriving jsProperties, getSet { light }]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type config = { host : string; mutable port : int option [@mel.optional] }
[@@deriving jsProperties, getSet { light }]
include struct
let _ = fun (_ : config) -> ()
let config ~host ?port () = { host; port }
let _ = config
let host x = x.host
let _ = host
let port x = x.port
let _ = port
let portSet x v = x.port <- v
let _ = portSet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for jsProperties
$ cat > input.mli << EOF
> type person = { name: string; age: int } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type person = { name : string; age : int } [@@deriving jsProperties]
include sig
[@@@ocaml.warning "-32"]
val person : name:string -> age:int -> person
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for jsProperties with optional
$ cat > input.mli << EOF
> type person = { name: string; age: int option [@mel.optional] } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type person = { name : string; age : int option [@mel.optional] }
[@@deriving jsProperties]
include sig
[@@@ocaml.warning "-32"]
val person : name:string -> ?age:int -> unit -> person
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for getSet
$ cat > input.mli << EOF
> type person = { name: string; mutable age: int } [@@deriving getSet]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type person = { name : string; mutable age : int } [@@deriving getSet]
include sig
[@@@ocaml.warning "-32"]
val nameGet : person -> string
val ageGet : person -> int
val ageSet : person -> int -> unit
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for getSet with light mode
$ cat > input.mli << EOF
> type person = { name: string; mutable age: int } [@@deriving getSet { light }]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type person = { name : string; mutable age : int } [@@deriving getSet { light }]
include sig
[@@@ocaml.warning "-32"]
val name : person -> string
val age : person -> int
val ageSet : person -> int -> unit
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Signature generation for record with type parameter
$ cat > input.mli << EOF
> type 'a container = { value: 'a } [@@deriving jsProperties, getSet]
> EOF
$ ./standalone.exe -intf input.mli | ocamlformat - --enable-outside-detected-project --intf
type 'a container = { value : 'a } [@@deriving jsProperties, getSet]
include sig
[@@@ocaml.warning "-32"]
val container : value:'a -> 'a container
val valueGet : 'a container -> 'a
end
[@@ocaml.doc "@inline"] [@@merlin.hide]
Error: jsProperties on variant type
$ cat > input.ml << EOF
> type action = Click | Submit [@@deriving jsProperties]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 0-54:
1 | type action = Click | Submit [@@deriving jsProperties]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: [@deriving jsProperties] can only be used on record types
[1]
Error: getSet on variant type
$ cat > input.ml << EOF
> type action = Click | Submit [@@deriving getSet]
> EOF
$ ./standalone.exe -impl input.ml 2>&1
File "input.ml", line 1, characters 0-48:
1 | type action = Click | Submit [@@deriving getSet]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: [@deriving getSet] can only be used on record types
[1]
Private types should not generate jsProperties constructor
$ cat > input.ml << EOF
> type person = private { name: string; age: int } [@@deriving jsProperties]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type person = private { name : string; age : int } [@@deriving jsProperties]
include struct
let _ = fun (_ : person) -> ()
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Single field record
$ cat > input.ml << EOF
> type single = { value: int } [@@deriving jsProperties, getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type single = { value : int } [@@deriving jsProperties, getSet]
include struct
let _ = fun (_ : single) -> ()
let single ~value = { value }
let _ = single
let valueGet x = x.value
let _ = valueGet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Record with type parameter
$ cat > input.ml << EOF
> type 'a container = { value: 'a } [@@deriving jsProperties, getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type 'a container = { value : 'a } [@@deriving jsProperties, getSet]
include struct
let _ = fun (_ : 'a container) -> ()
let container ~value = { value }
let _ = container
let valueGet x = x.value
let _ = valueGet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
Mutually recursive types (generates shadowing bindings, matching melange behavior)
$ cat > input.ml << EOF
> type a = { x: int; b_ref: b option [@mel.optional] } [@@deriving jsProperties, getSet]
> and b = { y: string; a_ref: a option [@mel.optional] } [@@deriving jsProperties, getSet]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
type a = { x : int; b_ref : b option [@mel.optional] }
[@@deriving jsProperties, getSet]
and b = { y : string; a_ref : a option [@mel.optional] }
[@@deriving jsProperties, getSet]
include struct
let _ = fun (_ : a) -> ()
let _ = fun (_ : b) -> ()
let a ~x ?b_ref () = { x; b_ref }
let _ = a
let b ~y ?a_ref () = { y; a_ref }
let _ = b
let xGet x = x.x
let _ = xGet
let b_refGet x = x.b_ref
let _ = b_refGet
let yGet x = x.y
let _ = yGet
let a_refGet x = x.a_ref
let _ = a_refGet
let a ~x ?b_ref () = { x; b_ref }
let _ = a
let b ~y ?a_ref () = { y; a_ref }
let _ = b
let xGet x = x.x
let _ = xGet
let b_refGet x = x.b_ref
let _ = b_refGet
let yGet x = x.y
let _ = yGet
let a_refGet x = x.a_ref
let _ = a_refGet
end [@@ocaml.doc "@inline"] [@@merlin.hide]
================================================
FILE: packages/melange.ppx/tests/mel_as.t
================================================
mel.as attribute
$ cat > input.ml << EOF
> external get : t -> (_[@mel.as {json|{}|json}]) -> t = "get" [@@mel.send]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let get : t -> (_[@mel.as {json|{}|json}]) -> t =
fun _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "get")
================================================
FILE: packages/melange.ppx/tests/mel_module.t
================================================
$ cat > input.ml << EOF
> type keycloak
> external keycloak : string -> keycloak = "default" [@@mel.module]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type keycloak
let keycloak : string -> keycloak =
fun _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "keycloak")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Multiple args with optional
$ cat > input.ml << EOF
> type keycloak
> external keycloak : ?z:int -> int -> foo:string -> keycloak = "default" [@@mel.module]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type keycloak
let keycloak : ?z:int -> int -> foo:string -> keycloak =
fun ?z:_ _ ~foo:_ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "keycloak")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Single type (invalid OCaml, but valid in Melange)
$ cat > input.ml << EOF
> type keycloak
> external keycloak : keycloak = "default" [@@mel.module "keycloak-js"]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type keycloak
[%%ocaml.error
"[server-reason-react.melange_ppx] There's an external with [%mel.module \
\"...\"] in native, which should only happen in JavaScript. You need to \
conditionally discard it from the native build, either by moving the external \
in a module only available in native, or annotating it with [@platform js]. \
More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
File "main.ml", line 26, characters 3-14:
26 | [%%ocaml.error
^^^^^^^^^^^
Error: [server-reason-react.melange_ppx] There's an external with
[%mel.module "..."] in native, which should only happen in JavaScript.
You need to conditionally discard it from the native build, either by
moving the external in a module only available in native, or
annotating it with [@platform js]. More info at
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html
[2]
Assets with file not found
$ cat > input.ml << EOF
> external img : string = "default" [@@mel.module "does-not-exist.svg"]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
File "input.ml", line 1:
Error: I/O error: ./does-not-exist.svg: No such file or directory
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Webpack: assets like svg or images (payload to mel.module includes file extension)
$ cat > input.ml << EOF
> external img : string = "default" [@@mel.module "./image.svg"]
> EOF
$ cat > image.svg << EOF
>
> EOF
$ ./standalone.exe -impl input.ml -bundler webpack | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let img = "/1d876c8887ac1038.svg"
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Create folder for following tests
$ mkdir foo
Webpack: assets like svg or images with paths outside current folder
$ cat > foo/input.ml << EOF
> external img : string = "default" [@@mel.module "../image.svg"]
> EOF
$ cat > image.svg << EOF
>
> EOF
$ ./standalone.exe -impl $(pwd)/foo/input.ml -bundler webpack | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let img = "/1d876c8887ac1038.svg"
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Esbuild: assets like svg or images (payload to mel.module includes file extension)
$ cat > input.ml << EOF
> external img : string = "default" [@@mel.module "./image.svg"]
> EOF
$ cat > image.svg << EOF
>
> EOF
$ ./standalone.exe -impl input.ml -bundler esbuild | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let img = "/image-DWDWZCEH.svg"
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Esbuild: assets like svg or images with paths outside current folder
$ cat > foo/input.ml << EOF
> external img : string = "default" [@@mel.module "../image.svg"]
> EOF
$ cat > image.svg << EOF
>
> EOF
$ ./standalone.exe -impl $(pwd)/foo/input.ml -bundler esbuild | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let img = "/image-DWDWZCEH.svg"
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
With prefix
$ cat > foo/input.ml << EOF
> external img : string = "default" [@@mel.module "../demo.txt"]
> EOF
$ cat > demo.txt << EOF
> hello
> EOF
$ ./standalone.exe -impl $(pwd)/foo/input.ml -bundler esbuild -prefix /foo/bar | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let img = "/foo/bar/demo-4TAZDUER.txt"
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
================================================
FILE: packages/melange.ppx/tests/mel_obj.t
================================================
Transform mel.obj into OCaml object literals
$ cat > input.ml << EOF
> let a = [%mel.obj { lola = 33; cositas = "hola"}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let a =
let __js_obj_cell_0, __js_obj_entry_0 =
Js.Obj.Internal.slot_ref ~method_name:"lola" ~js_name:"lola" ~present:true
33
in
let __js_obj_cell_1, __js_obj_entry_1 =
Js.Obj.Internal.slot_ref ~method_name:"cositas" ~js_name:"cositas"
~present:true "hola"
in
let __js_obj =
object
method lola = !__js_obj_cell_0
method cositas = !__js_obj_cell_1
end
in
Js.Obj.Internal.register_structural __js_obj
[ __js_obj_entry_0; __js_obj_entry_1 ]
$ cat > main.ml << EOF
> module Js = struct
> module Obj = struct
> module Internal = struct
> type entry = unit
> let slot_ref ~method_name:_ ~js_name:_ ~present:_ value = (ref value, ())
> let register_structural obj _ = obj
> end
> end
> end
> EOF
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Transform nested mel.obj into OCaml object literals
$ cat > input.ml << EOF
> let a = [%mel.obj { lola = 33; cositas = [%mel.obj { value = "hola" }]}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let a =
let __js_obj_cell_0, __js_obj_entry_0 =
Js.Obj.Internal.slot_ref ~method_name:"lola" ~js_name:"lola" ~present:true
33
in
let __js_obj_cell_1, __js_obj_entry_1 =
Js.Obj.Internal.slot_ref ~method_name:"cositas" ~js_name:"cositas"
~present:true
(let __js_obj_cell_0, __js_obj_entry_0 =
Js.Obj.Internal.slot_ref ~method_name:"value" ~js_name:"value"
~present:true "hola"
in
let __js_obj =
object
method value = !__js_obj_cell_0
end
in
Js.Obj.Internal.register_structural __js_obj [ __js_obj_entry_0 ])
in
let __js_obj =
object
method lola = !__js_obj_cell_0
method cositas = !__js_obj_cell_1
end
in
Js.Obj.Internal.register_structural __js_obj
[ __js_obj_entry_0; __js_obj_entry_1 ]
$ cat > main.ml << EOF
> module Js = struct
> module Obj = struct
> module Internal = struct
> type entry = unit
> let slot_ref ~method_name:_ ~js_name:_ ~present:_ value = (ref value, ())
> let register_structural obj _ = obj
> end
> end
> end
> EOF
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Fail if the object is not a record
$ cat > input.ml << EOF
> let a = [%mel.obj "hola"]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let a =
[%ocaml.error
"[server-reason-react.melange_ppx] Js.t objects requires a record literal"]
$ ocamlc -c output.ml
File "output.ml", line 2, characters 4-15:
2 | [%ocaml.error
^^^^^^^^^^^
Error: [server-reason-react.melange_ppx] Js.t objects requires a record
literal
[2]
Fail if the object is not a record
$ cat > input.ml << EOF
> let a = [%mel.obj { Lola.cositas = "hola"}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let a =
[%ocaml.error
"[server-reason-react.melange_ppx] Js.t objects only support labels as keys"]
$ ocamlc -c output.ml
File "output.ml", line 2, characters 4-15:
2 | [%ocaml.error
^^^^^^^^^^^
Error: [server-reason-react.melange_ppx] Js.t objects only support labels as
keys
[2]
================================================
FILE: packages/melange.ppx/tests/mel_raw.t
================================================
mel.raw as a value
$ cat > input.ml << EOF
> let value = [%mel.raw {| function(element) { return element.ownerDocument; } |}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
[%error
"[server-reason-react.melange_ppx] There's a [%mel.raw \" function(element) \
{ return element.ownerDocument; } \"] expression in native, which should \
only happen in JavaScript. You need to conditionally run it via \
let%browser_only or switch%platform. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
mel.raw as an unary function
$ cat > input.ml << EOF
> let unary_function element = [%mel.raw {| function(element) { return element.ownerDocument; } |}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
[%error
"[server-reason-react.melange_ppx] There's a [%mel.raw \" function(element) \
{ return element.ownerDocument; } \"] expression in native, which should \
only happen in JavaScript. You need to conditionally run it via \
let%browser_only or switch%platform. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
mel.raw as an binary function
$ cat > input.ml << EOF
> let binary_function element count = [%mel.raw {| function(element, number) {
> console.log(number);
> return element.ownerDocument;
> } |}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
[%error
"[server-reason-react.melange_ppx] There's a [%mel.raw \" function(element, \
number) {\n\
\ console.log(number);\n\
\ return element.ownerDocument;\n\
} \"] expression in native, which should only happen in JavaScript. You \
need to conditionally run it via let%browser_only or switch%platform. More \
info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
mel.raw with type
$ cat > input.ml << EOF
> type t
> let global: t = [%mel.raw "window"]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t;;
[%error
"[server-reason-react.melange_ppx] There's a [%mel.raw \"window\"] \
expression in native, which should only happen in JavaScript. You need to \
conditionally run it via let%browser_only or switch%platform. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
File "main.ml", line 26, characters 2-7:
26 | [%error
^^^^^
Error: [server-reason-react.melange_ppx] There's a [%mel.raw "window"]
expression in native, which should only happen in JavaScript. You need
to conditionally run it via let%browser_only or switch%platform. More
info at
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html
[2]
mel.raw as a value
$ cat > input.ml << EOF
> [%%mel.raw {| console.log("running in JS"); |}]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
[%%ocaml.error
"[server-reason-react.melange_ppx] There's a [%mel.raw \" \
console.log(\"running in JS\"); \"] expression in native, which should only \
happen in JavaScript. You need to conditionally run it via let%browser_only \
or switch%platform. More info at \
https://ml-in-barcelona.github.io/server-reason-react/server-reason-react/browser_ppx.html"]
================================================
FILE: packages/melange.ppx/tests/mel_send.t
================================================
Labelled args with @@mel.send
$ cat > input.ml << EOF
> external init : string -> param:int -> string = "init" [@@mel.send]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let init : string -> param:int -> string =
fun _ ~param:_ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "init")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Labelled and unlabelled args with @@mel.obj
$ cat > input.ml << EOF
> external makeInitParam : onLoad:string -> unit -> < onLoad : string > = "" [@@mel.obj]
> let onLoad = (makeInitParam ~onLoad:"ready" ())##onLoad
> external makeOptional : ?retries:int -> unit -> < retries : int option > = "" [@@mel.obj]
> let retries = (makeOptional ())##retries
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let makeInitParam : onLoad:string -> unit -> < onLoad : string > =
fun ~onLoad _ ->
let __js_obj_cell_0, __js_obj_entry_0 =
Js.Obj.Internal.slot_ref ~method_name:"onLoad" ~js_name:"onLoad"
~present:true onLoad
in
let __js_obj =
object
method onLoad = !__js_obj_cell_0
end
in
(Js.Obj.Internal.register_abstract __js_obj [ __js_obj_entry_0 ]
: < onLoad : string >)
let onLoad = (makeInitParam ~onLoad:"ready" ())#onLoad
let makeOptional : ?retries:int -> unit -> < retries : int option > =
fun ?retries _ ->
let __js_obj_cell_0, __js_obj_entry_0 =
Js.Obj.Internal.slot_ref ~method_name:"retries" ~js_name:"retries"
~present:(match retries with None -> false | Some _ -> true)
retries
in
let __js_obj =
object
method retries = !__js_obj_cell_0
end
in
(Js.Obj.Internal.register_abstract __js_obj [ __js_obj_entry_0 ]
: < retries : int option >)
let retries = (makeOptional ())#retries
$ cat > main.ml << EOF
> module Js = struct
> module Obj = struct
> module Internal = struct
> type entry = unit
> let slot_ref ~method_name:_ ~js_name:_ ~present:_ value = (ref value, ())
> let register_abstract obj _ = obj
> end
> end
> end
> EOF
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
mel.send
$ cat > input.ml << EOF
> type t
> external fillStyle : t -> 'a = "fillStyle" [@@mel.send]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t
let fillStyle : t -> 'a =
fun _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "fillStyle")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
================================================
FILE: packages/melange.ppx/tests/mel_send_pipe.t
================================================
[@@mel.send.pipe: t] should generate a function with the piped argument,
both on the type annotation, also on the function expression.
$ cat > input.ml << EOF
> external getPropertyPriority: string -> string = "getPropertyPriority" [@@mel.send.pipe: t]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let getPropertyPriority : string -> t -> string =
fun _ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "getPropertyPriority")
$ echo "module Runtime = struct" >> main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ ocamlc -c main.ml
Make sure is placed correctly
$ cat > input.ml << EOF
> external createDocumentType : qualifiedName:string -> publicId:string -> systemId:string -> Dom.documentType = "createDocumentType" [@@mel.send.pipe: t]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let createDocumentType :
qualifiedName:string ->
publicId:string ->
systemId:string ->
t ->
Dom.documentType =
fun ~qualifiedName:_ ~publicId:_ ~systemId:_ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "createDocumentType")
$ echo "type t" > main.ml
$ echo "module Dom = struct type documentType end" >> main.ml
$ echo "module Runtime = struct" >> main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Single argument (Ptyp_constr)
$ cat > input.ml << EOF
> external arrayBuffer : arrayBuffer Js.Promise.t = "arrayBuffer" [@@mel.send.pipe: T.t]
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let arrayBuffer : arrayBuffer Js.Promise.t -> T.t =
fun _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "arrayBuffer")
Labelled arguments
$ cat > input.ml << EOF
> type t
> external scale : x:float -> y:float -> unit = "scale"[@@mel.send.pipe : t]
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t
let scale : x:float -> y:float -> t -> unit =
fun ~x:_ ~y:_ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "scale")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Nonlabelled arguments as functions
$ cat > input.ml << EOF
> type t
> external forEach : (string -> int -> unit) -> unit = "forEach" [@@mel.send.pipe : t]
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t
let forEach : (string -> int -> unit) -> t -> unit =
fun _ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "forEach")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
'a
$ cat > input.ml << EOF
> external postMessage : 'a -> string -> unit = "postMessage" [@@mel.send]
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let postMessage : 'a -> string -> unit =
fun _ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "postMessage")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
Send pipe with 'a
$ cat > input.ml << EOF
> type t_window
> external postMessage : 'a -> string -> unit = "postMessage" [@@mel.send.pipe : t_window]
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
type t_window
let postMessage : 'a -> t_window -> string -> unit =
fun _ _ _ ->
let () =
Printf.printf
{|
There is a Melange's external (for example: [@mel.get]) call from native code.
Melange externals are bindings to JavaScript code, which can't run on the server and should be wrapped with browser_only ppx or only run it only on the client side. If there's any issue, try wrapping the expression with a try/catch as a workaround.
|}
in
raise (Runtime.fail_impossible_action_in_ssr "postMessage")
$ echo "module Runtime = struct" > main.ml
$ cat $INSIDE_DUNE/packages/runtime/Runtime.ml >> main.ml
$ echo "end" >> main.ml
$ cat output.ml >> main.ml
$ ocamlc -c main.ml
================================================
FILE: packages/melange.ppx/tests/pipe_first.t/input.ml
================================================
let f1 : int -> int = fun x -> x + 1
let f2 : int -> int -> int = fun a b -> a + b
let f3 : int -> b:int -> c:int -> int = fun a ~b ~c -> a + b + c
let f4 : int -> int -> int = fun a b -> a + b
let f5 : int -> int -> int -> int = fun a b c -> a + b + c
let () =
let x : int option = 1 |. Some in
match x with Some 1 -> assert true | _ -> assert false
let () =
let x : int option option = 1 |. Some |. Some in
match x with Some (Some 1) -> assert true | _ -> assert false
let x : int = (1, 2) |. fun (a, b) -> a + b
let () =
let f : int -> int = fun x -> x + 1 in
let x : int * int * int = 1 |. (f, f, f) in
match x with 2, 2, 2 -> assert true | _ -> assert false
let () =
let f : int -> a:int -> b:int -> int = fun x ~a ~b -> x + a + b in
let x : int * int * int = 1 |. (f ~a:2 ~b:3, f ~a:2 ~b:3, f ~a:2 ~b:3) in
match x with 6, 6, 6 -> assert true | _ -> assert false
let () =
let x : int option * int option * int option = 1 |. (Some, Some, Some) in
match x with Some 1, Some 1, Some 1 -> assert true | _ -> assert false
let () =
let x =
(1, 2) |. ((fun (a, b) -> a + b), (fun (a, b) -> a + b), fun (a, b) -> a + b)
in
match x with 3, 3, 3 -> assert true | _ -> assert false
let fn1 ?foo () = 1 + match foo with Some x -> x | None -> 2
let fn2 ?bar x =
let bar = match bar with Some bar -> bar | None -> 4 in
2 + bar + x
type field = { send : int -> int }
let self = { send = (fun a -> a + 1) }
let adder a b = a + b
let addFive = 5 |. adder
let ten1 = 5 |. addFive
let ten2 = 5 |. (5 |. adder)
let _ =
Lwt_js.sleep 1.
|.
let open Lwt in
bind (fun () ->
print_endline "foo";
return ())
================================================
FILE: packages/melange.ppx/tests/pipe_first.t/run.t
================================================
$ ../standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let f1 : int -> int = fun x -> x + 1
let f2 : int -> int -> int = fun a b -> a + b
let f3 : int -> b:int -> c:int -> int = fun a ~b ~c -> a + b + c
let f4 : int -> int -> int = fun a b -> a + b
let f5 : int -> int -> int -> int = fun a b c -> a + b + c
let () =
let x : int option = Some 1 in
match x with Some 1 -> assert true | _ -> assert false
let () =
let x : int option option = Some (Some 1) in
match x with Some (Some 1) -> assert true | _ -> assert false
let x : int = (fun (a, b) -> a + b) (1, 2)
let () =
let f : int -> int = fun x -> x + 1 in
let x : int * int * int = (f, f, f) 1 in
match x with 2, 2, 2 -> assert true | _ -> assert false
let () =
let f : int -> a:int -> b:int -> int = fun x ~a ~b -> x + a + b in
let x : int * int * int = (f ~a:2 ~b:3, f ~a:2 ~b:3, f ~a:2 ~b:3) 1 in
match x with 6, 6, 6 -> assert true | _ -> assert false
let () =
let x : int option * int option * int option = (Some, Some, Some) 1 in
match x with Some 1, Some 1, Some 1 -> assert true | _ -> assert false
let () =
let x =
((fun (a, b) -> a + b), (fun (a, b) -> a + b), fun (a, b) -> a + b) (1, 2)
in
match x with 3, 3, 3 -> assert true | _ -> assert false
let fn1 ?foo () = 1 + match foo with Some x -> x | None -> 2
let fn2 ?bar x =
let bar = match bar with Some bar -> bar | None -> 4 in
2 + bar + x
type field = { send : int -> int }
let self = { send = (fun a -> a + 1) }
let adder a b = a + b
let addFive = adder 5
let ten1 = addFive 5
let ten2 = adder 5 5
let _ =
(let open Lwt in
bind (fun () ->
print_endline "foo";
return ()))
(Lwt_js.sleep 1.)
================================================
FILE: packages/melange.ppx/tests/private.t
================================================
let%private attribute
$ cat > input.ml << EOF
> [%%private let privi = 22]
> let print () = Js.log privi
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
open! struct
let privi = 22
end
let print () = Js.log privi
$ cat > input.ml << EOF
> [%%private module Lol = struct let x = 22 end]
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
File "input.ml", line 1, characters 11-45:
1 | [%%private module Lol = struct let x = 22 end]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: the structure is not supported in local extension
================================================
FILE: packages/melange.ppx/tests/regex.t/input.ml
================================================
let basic = [%re "/foo/"]
let flag_global = [%re "/foo/g"]
let flags_global_multiline_insensitive = [%re "/foo/gim"]
let scape_digis_with_global = [%re "/(\\d+)/g"]
let payload_should_be_a_string = [%re apply]
================================================
FILE: packages/melange.ppx/tests/regex.t/run.t
================================================
$ ../standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl
let basic = Js.Re.fromString "foo"
let flag_global = Js.Re.fromStringWithFlags ~flags:"g" "foo"
let flags_global_multiline_insensitive =
Js.Re.fromStringWithFlags ~flags:"gim" "foo"
let scape_digis_with_global = Js.Re.fromStringWithFlags ~flags:"g" "(\\d+)"
let payload_should_be_a_string =
[%ocaml.error
"[server-reason-react.melange_ppx] payload should be a string literal"]
================================================
FILE: packages/melange.ppx/tests/standalone.ml
================================================
(* To run as a standalone binary, run the registered drivers *)
let () = Ppxlib.Driver.standalone ()
================================================
FILE: packages/melange.ppx/tests/string_interpolation.t
================================================
Test cases on string interpolation, most of them imported from
https://github.com/melange-re/melange/blob/fb1466fed7d6e5aafd3ee266bbd4ec70c8fb857a/test/blackbox-tests/utf8-string-interp.t
$ export OCAML_COLOR=always
$ cat > input.ml < let lola = "flores"
> let () = print_endline {j| Hello, \$(lola)|j}
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let lola = "flores"
let () = print_endline (Stdlib.( ^ ) {js| Hello, |js} lola)
$ ocaml output.ml
Hello, flores
Variable that doesn't exist
$ cat > input.ml < let x = {j| Hello, \$(lola)|j}
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let x = Stdlib.( ^ ) {js| Hello, |js} lola
$ ocaml output.ml
File "./output.ml", line 1, characters 38-42:
1 | let x = Stdlib.( ^ ) {js| Hello, |js} lola
^^^^
Error: Unbound value lola
[2]
Using invalid identifiers
$ cat > input.ml < let x = {j| Hello, \$()|j}
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
File "input.ml", line 1, characters 19-22:
1 | let x = {j| Hello, $()|j}
^^^
Error: `' is not a valid syntax of interpolated identifer
$ cat > input.ml < let x = {j| Hello, \$( )|j}
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
File "input.ml", line 1, characters 19-25:
1 | let x = {j| Hello, $( )|j}
^^^^^^
Error: ` ' is not a valid syntax of interpolated identifer
`{j| .. |j}` interpolation is strict about string arguments
$ cat > input.ml < let x =
> let y = 3 in
> {j| Hello, \$(y)|j}
> EOF
$ ./standalone.exe -impl input.ml | ocamlformat - --enable-outside-detected-project --impl | tee output.ml
let x =
let y = 3 in
Stdlib.( ^ ) {js| Hello, |js} y
$ ocaml output.ml
File "./output.ml", line 3, characters 32-33:
3 | Stdlib.( ^ ) {js| Hello, |js} y
^
Error: The value y has type int but an expression was expected of type string
[2]
================================================
FILE: packages/melange.ppx/xxhash/XXH64.ml
================================================
(* https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md#xxh64-algorithm-description *)
module UInt64 = Unsigned.UInt64
let ( += ) r v = r := !r + v
let ( -= ) r v = r := !r - v
let ( <<< ) x n =
let a = UInt64.shift_left x n in
let b = UInt64.shift_right x (64 - n) in
UInt64.logor a b
let ( >> ) = UInt64.shift_right
let ( + ) = UInt64.add
let ( * ) = UInt64.mul
let ( - ) = UInt64.sub
let logxor = UInt64.logxor
let prime64_1 = UInt64.of_int64 0x9E3779B185EBCA87L
(* 0b1001111000110111011110011011000110000101111010111100101010000111 *)
let prime64_2 = UInt64.of_int64 0xC2B2AE3D27D4EB4FL
(* 0b1100001010110010101011100011110100100111110101001110101101001111 *)
let prime64_3 = UInt64.of_int64 0x165667B19E3779F9L
(* 0b0001011001010110011001111011000110011110001101110111100111111001 *)
let prime64_4 = UInt64.of_int64 0x85EBCA77C2B2AE63L
(* 0b1000010111101011110010100111011111000010101100101010111001100011 *)
let prime64_5 = UInt64.of_int64 0x27D4EB2F165667C5L
(* 0b0010011111010100111010110010111100010110010101100110011111000101 *)
(*
round(accN,laneN):
accN = accN + (laneN * PRIME64_2);
accN = accN <<< 31;
return accN * PRIME64_1;
*)
let round acc lane = (acc + (lane * prime64_2) <<< 31) * prime64_1
let get_int64_le str i = UInt64.of_int64 (String.get_int64_le str i)
(* mergeAccumulator(acc,accN):
acc = acc xor round(0, accN);
acc = acc * PRIME64_1;
return acc + PRIME64_4; *)
let merge accN acc = (logxor acc (round UInt64.zero !accN) * prime64_1) + prime64_4
let hash ?(seed = Int64.zero) input =
let seed = UInt64.of_int64 seed in
let len = String.length input in
let pos = ref 0 in
let have n = Int.add !pos n <= len in
let acc =
if len < 32 then seed + prime64_5
else
let acc1 = ref @@ (seed + prime64_1 + prime64_2) in
let acc2 = ref @@ (seed + prime64_2) in
let acc3 = ref @@ seed in
let acc4 = ref @@ (seed - prime64_1) in
while have 32 do
acc1 := round !acc1 (get_int64_le input !pos);
pos += 8;
acc2 := round !acc2 (get_int64_le input !pos);
pos += 8;
acc3 := round !acc3 (get_int64_le input !pos);
pos += 8;
acc4 := round !acc4 (get_int64_le input !pos);
pos += 8
done;
(*
acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18);
acc = mergeAccumulator(acc, acc1);
acc = mergeAccumulator(acc, acc2);
acc = mergeAccumulator(acc, acc3);
acc = mergeAccumulator(acc, acc4);
*)
let acc = (!acc1 <<< 1) + (!acc2 <<< 7) + (!acc3 <<< 12) + (!acc4 <<< 18) in
acc |> merge acc1 |> merge acc2 |> merge acc3 |> merge acc4
in
let acc = ref @@ (acc + UInt64.of_int len) in
while have 8 do
let lane = get_int64_le input !pos in
acc := ((logxor !acc (round UInt64.zero lane) <<< 27) * prime64_1) + prime64_4;
pos += 8
done;
if have 4 then (
let lane =
UInt64.logand (UInt64.of_int64 0xFF_FF_FF_FFL)
@@ (String.get_int32_le input !pos |> Int64.of_int32 |> UInt64.of_int64)
in
acc := ((logxor !acc (lane * prime64_1) <<< 23) * prime64_2) + prime64_3;
pos += 4)
else ();
while have 1 do
let lane = UInt64.of_int @@ Char.code @@ String.get input !pos in
acc := (logxor !acc (lane * prime64_5) <<< 11) * prime64_1;
pos += 1
done;
let acc = logxor !acc (!acc >> 33) in
let acc = acc * prime64_2 in
let acc = logxor acc (acc >> 29) in
let acc = acc * prime64_3 in
UInt64.to_int64 (logxor acc (acc >> 32))
let to_hex hash = Printf.sprintf "%Lx" hash
================================================
FILE: packages/melange.ppx/xxhash/dune
================================================
(library
(name xxhash)
(modules XXH64)
(public_name server-reason-react.xxhash)
(wrapped false)
(libraries integers base32))
(test
(name test_xxh64)
(modules test_xxh64)
(libraries xxhash fmt alcotest integers))
================================================
FILE: packages/melange.ppx/xxhash/test_xxh64.ml
================================================
let check_int64 = Alcotest.(check int64)
let data =
[
("", 0xef46db3751d8e999L);
("a", 0xd24ec4f1a98c6e5bL);
("as", 0x1c330fb2d66be179L);
("asd", 0x631c37ce72a97393L);
("asdf", 0x415872f599cea71eL);
("abc", 0x44bc2cf5ad770999L);
("abc", 0x44bc2cf5ad770999L);
("abcd", 0xde0327b0d25d92ccL);
("abcde", 0x07e3670c0c8dc7ebL);
("abcdef", 0xfa8afd82c423144dL);
("abcdefg", 0x1860940e2902822dL);
("abcdefgh", 0x3ad351775b4634b7L);
("abcdefghi", 0x27f1a34fdbb95e13L);
("abcdefghij", 0xd6287a1de5498bb2L);
("abcdefghijklmnopqrstuvwxyz012345", 0xbf2cd639b4143b80L);
("abcdefghijklmnopqrstuvwxyz0123456789", 0x64f23ecf1609b766L);
(* Exactly 63 characters, which exercises all code paths *)
("Call me Ishmael. Some years ago--never mind how long precisely-", 0x02a2e85470d6fd96L);
( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore \
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo \
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est \
laborum.",
0xc5a8b11443765630L );
]
let hash_test_cases =
List.map
(fun (input, expected) ->
let test () = check_int64 input expected (XXH64.hash input) in
(Printf.sprintf "%S" input, `Quick, test))
data
let data = [ ("I want an unsigned 64-bit seed!", "d4cb0a70a2b8c7c1") ]
let hex_test_cases =
List.map
(fun (input, expected) ->
let test () =
let output = input |> XXH64.hash |> XXH64.to_hex in
Alcotest.(check string) input expected output
in
(Printf.sprintf "%S" input, `Quick, test))
data
let () = Alcotest.run "XXH64" [ ("hash", hash_test_cases); ("hash · to_hex", hex_test_cases) ]
================================================
FILE: packages/promise/js/dune
================================================
(library
(name promise_js)
(public_name server-reason-react.promise-js)
(modes melange)
(modules promise)
(wrapped false)
(libraries melange.belt melange.js)
(preprocess
(pps melange.ppx)))
================================================
FILE: packages/promise/js/promise.re
================================================
/* This file is part of reason-promise, released under the MIT license. See
LICENSE.md for details, or visit
https://github.com/aantron/promise/blob/master/LICENSE.md. */
type rejectable(+'a, +'e);
type never;
type promise(+'a) = rejectable('a, never);
type t(+'a) = promise('a);
let onUnhandledException =
ref(exn => {
prerr_endline("Unhandled exception in promise callback:");
Js.Console.error(exn);
});
[%%mel.raw
{|
function PromiseBox(p) {
this.nested = p;
};
function unbox(value) {
if (value instanceof PromiseBox)
return value.nested;
else
return value;
}
function box(value) {
if (value != null && typeof value.then === 'function')
return new PromiseBox(value);
else
return value;
}
function make(executor) {
return new Promise(function (resolve, reject) {
var boxingResolve = function(value) {
resolve(box(value));
};
executor(boxingResolve, reject);
});
};
function resolved(value) {
return Promise.resolve(box(value));
};
function then(promise, callback) {
return promise.then(function (value) {
try {
return callback(unbox(value));
}
catch (exception) {
onUnhandledException.contents(exception);
return new Promise(function() {});
}
});
};
function catch_(promise, callback) {
var safeCallback = function (error) {
try {
return callback(error);
}
catch (exception) {
onUnhandledException.contents(exception);
return new Promise(function() {});
}
};
return promise.catch(safeCallback);
};
|}
];
module Js_ = {
type t('a, 'e) = rejectable('a, 'e);
external relax: promise('a) => rejectable('a, _) = "%identity";
external jsNew: (('a => unit, 'e => unit) => unit) => rejectable('a, 'e) =
"make";
let pending = () => {
let resolve = ref(ignore);
let reject = ref(ignore);
let p =
jsNew((resolve', reject') => {
resolve := resolve';
reject := reject';
});
(p, resolve^, reject^);
};
external resolved: 'a => rejectable('a, _) = "resolved";
external flatMap:
(rejectable('a, 'e), 'a => rejectable('b, 'e)) => rejectable('b, 'e) =
"then";
let map = (promise, callback) =>
flatMap(promise, v => resolved(callback(v)));
let get = (promise, callback) => ignore(map(promise, callback));
let tap = (promise, callback) =>
map(
promise,
v => {
callback(v);
v;
},
);
[@mel.scope "Promise"]
external rejected: 'e => rejectable(_, 'e) = "reject";
external catch:
(rejectable('a, 'e), 'e => rejectable('a, 'e2)) => rejectable('a, 'e2) =
"catch_";
external unbox: 'a => 'a = "unbox";
[@mel.scope "Promise"] external jsAll: 'a => 'b = "all";
let allArray = promises =>
map(jsAll(promises), promises => Belt.Array.map(promises, unbox));
let all = promises =>
map(allArray(Belt.List.toArray(promises)), Belt.List.fromArray);
let all2 = (p1, p2) => jsAll((p1, p2));
let all3 = (p1, p2, p3) => jsAll((p1, p2, p3));
let all4 = (p1, p2, p3, p4) => jsAll((p1, p2, p3, p4));
let all5 = (p1, p2, p3, p4, p5) => jsAll((p1, p2, p3, p4, p5));
let all6 = (p1, p2, p3, p4, p5, p6) => jsAll((p1, p2, p3, p4, p5, p6));
[@mel.scope "Promise"]
external jsRace: array(rejectable('a, 'e)) => rejectable('a, 'e) = "race";
let race = promises =>
if (promises == []) {
raise(Invalid_argument("Promise.race([]) would be pending forever"));
} else {
jsRace(Belt.List.toArray(promises));
};
let toResult = promise =>
catch(map(promise, v => Ok(v)), e => resolved(Error(e)));
let fromResult = promise =>
flatMap(
relax(promise),
fun
| Ok(v) => resolved(v)
| Error(e) => rejected(e),
);
external fromBsPromise:
Js.Promise.t('a) => rejectable('a, Js.Promise.error) =
"%identity";
external toBsPromise: rejectable('a, _) => Js.Promise.t('a) = "%identity";
};
let pending = () => {
let (p, resolve, _) = Js_.pending();
(p, resolve);
};
let exec = executor => {
let (p, resolve) = pending();
executor(resolve);
p;
};
let resolved = Js_.resolved;
let flatMap = Js_.flatMap;
let map = Js_.map;
let get = Js_.get;
let tap = Js_.tap;
let all = Js_.all;
let all2 = Js_.all2;
let all3 = Js_.all3;
let all4 = Js_.all4;
let all5 = Js_.all5;
let all6 = Js_.all6;
let allArray = Js_.allArray;
let race = Js_.race;
let flatMapOk = (promise, callback) =>
flatMap(promise, result =>
switch (result) {
| Ok(v) => callback(v)
| Error(_) as error => resolved(error)
}
);
let flatMapError = (promise, callback) =>
flatMap(promise, result =>
switch (result) {
| Ok(_) as ok => resolved(ok)
| Error(e) => callback(e)
}
);
let mapOk = (promise, callback) =>
map(promise, result =>
switch (result) {
| Ok(v) => Ok(callback(v))
| Error(_) as error => error
}
);
let mapError = (promise, callback) =>
map(promise, result =>
switch (result) {
| Ok(_) as ok => ok
| Error(e) => Error(callback(e))
}
);
let getOk = (promise, callback) =>
get(promise, result =>
switch (result) {
| Ok(v) => callback(v)
| Error(_) => ()
}
);
let getError = (promise, callback) =>
get(promise, result =>
switch (result) {
| Ok(_) => ()
| Error(e) => callback(e)
}
);
let tapOk = (promise, callback) => {
getOk(promise, callback);
promise;
};
let tapError = (promise, callback) => {
getError(promise, callback);
promise;
};
let allOkArray = promises => {
let promiseCount = Belt.Array.length(promises);
if (promiseCount == 0) {
resolved(Ok([||]));
} else {
let resultValues = Belt.Array.make(promiseCount, None);
let resultCount = ref(0);
let (resultPromise, resolve) = pending();
let (callbackRemover, removeCallbacks) = pending();
promises->Belt.Array.forEachWithIndex((index, promise)
/* Because callbacks are added to the user's promises through calls to the
JS runtime's Promise.race, this function leaks memory if and only if
the JS runtime's Promise functions leak memory. In particular, if one
of the promises resolves with Error(_), the callbacks on the other
promises should be removed. If not done, and long-pending promises are
repeatedly passed to allOk in a loop, they will gradually accumulate
huge lists of stale callbacks. This is also true of Promise.race, so we
rely on the quality of the runtime's Promise.race implementation to
proactively remove these callbacks. */
=>
race([promise, callbackRemover])
|> (
wrapped =>
get(wrapped, result =>
switch (result) {
| Ok(v) =>
resultValues->Belt.Array.setExn(index, Some(v));
incr(resultCount);
if (resultCount^ >= promiseCount) {
resultValues->Belt.Array.map(v =>
switch (v) {
| Some(v) => v
| None => assert(false)
}
)
|> (values => resolve(Ok(values)));
};
| Error(e) =>
resolve(Error(e));
removeCallbacks(Error(e));
}
)
)
);
resultPromise;
};
};
let allOk = promises =>
mapOk(allOkArray(Belt.List.toArray(promises)), Belt.List.fromArray);
let allOk2 =
(p1: promise(result('a, 'err)), p2: promise(result('b, 'err)))
: promise(result(('a, 'b), 'err)) =>
Obj.magic(allOkArray, (p1, p2));
let allOk3 =
(
p1: promise(result('a, 'err)),
p2: promise(result('b, 'err)),
p3: promise(result('c, 'err)),
)
: promise(result(('a, 'b, 'c), 'err)) =>
Obj.magic(allOkArray, (p1, p2, p3));
let allOk4 =
(
p1: promise(result('a, 'err)),
p2: promise(result('b, 'err)),
p3: promise(result('c, 'err)),
p4: promise(result('d, 'err)),
)
: promise(result(('a, 'b, 'c, 'd), 'err)) =>
Obj.magic(allOkArray, (p1, p2, p3, p4));
let allOk5 =
(
p1: promise(result('a, 'err)),
p2: promise(result('b, 'err)),
p3: promise(result('c, 'err)),
p4: promise(result('d, 'err)),
p5: promise(result('e, 'err)),
)
: promise(result(('a, 'b, 'c, 'd, 'e), 'err)) =>
Obj.magic(allOkArray, (p1, p2, p3, p4, p5));
let allOk6 =
(
p1: promise(result('a, 'err)),
p2: promise(result('b, 'err)),
p3: promise(result('c, 'err)),
p4: promise(result('d, 'err)),
p5: promise(result('e, 'err)),
p6: promise(result('f, 'err)),
)
: promise(result(('a, 'b, 'c, 'd, 'e, 'f), 'err)) =>
Obj.magic(allOkArray, (p1, p2, p3, p4, p5, p6));
module Operators = {
let (>|=) = mapOk;
let (>>=) = flatMapOk;
};
let flatMapSome = (promise, callback) =>
flatMap(promise, option =>
switch (option) {
| Some(v) => callback(v)
| None => resolved(None)
}
);
let mapSome = (promise, callback) =>
map(promise, option =>
switch (option) {
| Some(v) => Some(callback(v))
| None => None
}
);
let getSome = (promise, callback) =>
get(promise, option =>
switch (option) {
| Some(v) => callback(v)
| None => ()
}
);
let tapSome = (promise, callback) => {
getSome(promise, callback);
promise;
};
module PipeFirst = {};
module Js = Js_;
================================================
FILE: packages/promise/js/promise.rei
================================================
/* This file is part of reason-promise, released under the MIT license. See
LICENSE.md for details, or visit
https://github.com/aantron/promise/blob/master/LICENSE.md. */
/* Internal type names; don't use these. Instead, use Promise.t and Promise.Js.t
from outside this library. */
type rejectable(+'a, +'e); /* Internal; use Promise.Js.t. */
type never;
type promise(+'a) = rejectable('a, never); /* Internal; use Promise.t. */
/* The main, public promise type (Promise.t). */
type t(+'a) = promise('a);
/* Making promises. */
let pending: unit => (promise('a), 'a => unit);
let resolved: 'a => promise('a);
let exec: (('a => unit) => unit) => promise('a);
/* Using promises. */
let get: (promise('a), 'a => unit) => unit;
let tap: (promise('a), 'a => unit) => promise('a);
let map: (promise('a), 'a => 'b) => promise('b);
let flatMap: (promise('a), 'a => promise('b)) => promise('b);
/* Results. */
let getOk: (promise(result('a, 'e)), 'a => unit) => unit;
let tapOk:
(promise(result('a, 'e)), 'a => unit) => promise(result('a, 'e));
let mapOk: (promise(result('a, 'e)), 'a => 'b) => promise(result('b, 'e));
let flatMapOk:
(promise(result('a, 'e)), 'a => promise(result('b, 'e))) =>
promise(result('b, 'e));
let getError: (promise(result('a, 'e)), 'e => unit) => unit;
let tapError:
(promise(result('a, 'e)), 'e => unit) => promise(result('a, 'e));
let mapError:
(promise(result('a, 'e)), 'e => 'e2) => promise(result('a, 'e2));
let flatMapError:
(promise(result('a, 'e)), 'e => promise(result('a, 'e2))) =>
promise(result('a, 'e2));
module Operators: {
[@ocaml.deprecated "Use bs-let"]
let (>|=):
(promise(result('a, 'e)), 'a => 'b) => promise(result('b, 'e));
[@ocaml.deprecated "Use bs-let"]
let (>>=):
(promise(result('a, 'e)), 'a => promise(result('b, 'e))) =>
promise(result('b, 'e));
};
/* Options. */
let getSome: (promise(option('a)), 'a => unit) => unit;
let tapSome: (promise(option('a)), 'a => unit) => promise(option('a));
let mapSome: (promise(option('a)), 'a => 'b) => promise(option('b));
let flatMapSome:
(promise(option('a)), 'a => promise(option('b))) => promise(option('b));
/* Combining promises. */
let race: list(promise('a)) => promise('a);
let all: list(promise('a)) => promise(list('a));
let allArray: array(promise('a)) => promise(array('a));
let all2: (promise('a), promise('b)) => promise(('a, 'b));
let all3:
(promise('a), promise('b), promise('c)) => promise(('a, 'b, 'c));
let all4:
(promise('a), promise('b), promise('c), promise('d)) =>
promise(('a, 'b, 'c, 'd));
let all5:
(promise('a), promise('b), promise('c), promise('d), promise('e)) =>
promise(('a, 'b, 'c, 'd, 'e));
let all6:
(
promise('a),
promise('b),
promise('c),
promise('d),
promise('e),
promise('f)
) =>
promise(('a, 'b, 'c, 'd, 'e, 'f));
let allOk:
list(promise(result('a, 'e))) => promise(result(list('a), 'e));
let allOkArray:
array(promise(result('a, 'e))) => promise(result(array('a), 'e));
let allOk2:
(promise(result('a, 'err)), promise(result('b, 'err))) =>
promise(result(('a, 'b), 'err));
let allOk3:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err))
) =>
promise(result(('a, 'b, 'c), 'err));
let allOk4:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err))
) =>
promise(result(('a, 'b, 'c, 'd), 'err));
let allOk5:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err)),
promise(result('e, 'err))
) =>
promise(result(('a, 'b, 'c, 'd, 'e), 'err));
let allOk6:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err)),
promise(result('e, 'err)),
promise(result('f, 'err))
) =>
promise(result(('a, 'b, 'c, 'd, 'e, 'f), 'err));
/* For writing bindings. */
module Js: {
type t(+'a, +'e) = rejectable('a, 'e);
/* Making. */
let pending: unit => (rejectable('a, 'e), 'a => unit, 'e => unit);
let resolved: 'a => rejectable('a, 'e);
let rejected: 'e => rejectable('a, 'e);
/* Handling fulfillment. */
let get: (rejectable('a, 'e), 'a => unit) => unit;
let tap: (rejectable('a, 'e), 'a => unit) => rejectable('a, 'e);
let map: (rejectable('a, 'e), 'a => 'b) => rejectable('b, 'e);
let flatMap:
(rejectable('a, 'e), 'a => rejectable('b, 'e)) => rejectable('b, 'e);
/* Handling rejection. */
let catch:
(rejectable('a, 'e), 'e => rejectable('a, 'e2)) => rejectable('a, 'e2);
/* Combining. */
let all: list(rejectable('a, 'e)) => rejectable(list('a), 'e);
let race: list(rejectable('a, 'e)) => rejectable('a, 'e);
/* Conversions. */
let relax: promise('a) => rejectable('a, 'e);
let toResult: rejectable('a, 'e) => promise(result('a, 'e));
let fromResult: promise(result('a, 'e)) => rejectable('a, 'e);
let fromBsPromise: Js.Promise.t('a) => rejectable('a, Js.Promise.error);
let toBsPromise: rejectable('a, _) => Js.Promise.t('a);
};
module PipeFirst: {};
let onUnhandledException: ref(exn => unit);
================================================
FILE: packages/promise/native/dune
================================================
(library
(name promise_native)
(public_name server-reason-react.promise-native)
(wrapped false)
(modules promise)
(libraries lwt server-reason-react.belt)
(preprocess
(pps lwt_ppx)))
================================================
FILE: packages/promise/native/promise.re
================================================
/* This file is part of reason-promise, released under the MIT license. See
LICENSE.md for details, or visit
https://github.com/aantron/promise/blob/master/LICENSE.md. */
module type MutableList = {
/* Mutable doubly-linked lists, like in a typical imperative language. These are
used for callback lists, because reason-promise needs fast append and fast
deletion of any node in the list, when the reference to the target node is
already be held by the deleting code. */
type list('a);
type node('a);
let create: unit => list('a);
let isEmpty: list(_) => bool;
let append: (list('a), 'a) => node('a);
let iter: ('a => unit, list('a)) => unit;
let remove: (list('a), node('a)) => unit;
/* Concatenates list1 and list2. Afterwards, the reference list1 has a correct
internal list structure, and the reference list2 should not be used
anymore. */
let concatenate: (list('a), list('a)) => unit;
};
module MutableList: MutableList = {
/* This file is part of reason-promise, released under the MIT license. See
LICENSE.md for details, or visit
https://github.com/aantron/promise/blob/master/LICENSE.md. */
type node('a) = {
mutable previous: option(node('a)),
mutable next: option(node('a)),
content: 'a,
};
type listEnds('a) = {
mutable first: node('a),
mutable last: node('a),
};
type list('a) =
ref(
[
| `Empty
| `NonEmpty(listEnds('a))
],
);
let create = () => ref(`Empty);
let isEmpty = list => list^ == `Empty;
let append = (list, value) =>
switch (list^) {
| `Empty =>
let node = {
previous: None,
next: None,
content: value,
};
list :=
`NonEmpty({
first: node,
last: node,
});
node;
| `NonEmpty(ends) =>
let node = {
previous: Some(ends.last),
next: None,
content: value,
};
ends.last.next = Some(node);
ends.last = node;
node;
};
let concatenate = (list1, list2) =>
switch (list2^) {
| `Empty =>
/* If the second list is empty, we can just return the first list, because
it already has the correct final structure, and there is nothing to
do. */
()
| `NonEmpty(list2Ends) =>
switch (list1^) {
| `Empty =>
/* If the second list is non-empty, but the first list is empty, we need
to change the end-of-list references in the first list to point to the
structure of the second list. This is because the caller depends on the
first list having the correct structure after the call. */
list1 := list2^
| `NonEmpty(list1Ends) =>
/* Otherwise, we have to splice the ending nodes of the two lists. */
list1Ends.last.next = Some(list2Ends.first);
list2Ends.first.previous = Some(list1Ends.last);
list1Ends.last = list2Ends.last;
}
};
let iter = (callback, list) =>
switch (list^) {
| `Empty => ()
| `NonEmpty(ends) =>
let rec loop = node => {
callback(node.content);
switch (node.next) {
| None => ()
| Some(nextNode) => loop(nextNode)
};
};
loop(ends.first);
};
let remove = (list, node) => {
/* This function is difficult enough to implement and use that it is
probably time to switch representations for callback lists soon. */
switch (list^) {
| `Empty => ()
| `NonEmpty(ends) =>
switch (node.previous) {
| None =>
if (ends.first === node) {
switch (node.next) {
| None => list := `Empty
| Some(secondNode) => ends.first = secondNode
};
}
| Some(previousNode) => previousNode.next = node.next
};
switch (node.next) {
| None =>
if (ends.last === node) {
switch (node.previous) {
| None => list := `Empty
| Some(secondToLastNode) => ends.last = secondToLastNode
};
}
| Some(nextNode) => nextNode.previous = node.previous
};
};
node.previous = None;
node.next = None;
};
};
type callbacks('a, 'e) = {
onResolve: MutableList.list('a => unit),
onReject: MutableList.list('e => unit),
};
type rejectable('a, 'e) =
ref(
[
| `Fulfilled('a)
| `Rejected('e)
| `Pending(callbacks('a, 'e))
| `Merged(rejectable('a, 'e))
],
);
type never;
type promise('a) = rejectable('a, never);
type t('a) = promise('a);
/* The `Merged constructor and this function, underlying, are used to avoid a
memory leak that arises when flatMap is called on promises in a loop. See the
description in the associated test "promise loop memory leak". The rest of
this comment is based on that description.
The solution to the memory leak is to merge nested promises created on the
second and subsequent iterations of loops into the outer promise created on
the first iteration. This is performed by the internal helper
makePromiseBehaveAs, below.
When promises are merged, the callback lists of the nested promise are
merged into the callback lists of the outer promise, and afterwards the
nested promise object becomes just a proxy that refers to the outer promise.
As a result, most internal operations on promises have to first call
underlying, in order to find the true merged (outer) promise on which
operations should be performed, rather than working directly on proxies. */
let rec underlying = p =>
switch (p^) {
| `Fulfilled(_)
| `Rejected(_)
| `Pending(_) => p
| `Merged(p') =>
let p'' = underlying(p');
if (p'' !== p') {
p := `Merged(p'');
};
p'';
};
let onUnhandledException =
ref(exn => {
prerr_endline("Unhandled exception in promise callback:");
prerr_endline(Printexc.to_string(exn));
Printexc.print_backtrace(stderr);
});
module ReadyCallbacks = {
let callbacks: ref(MutableList.list(unit => unit)) =
ref(MutableList.create());
let callbacksPending = () => !MutableList.isEmpty(callbacks^);
let defer = (callback, value) =>
MutableList.append(callbacks^, () => callback(value)) |> ignore;
let deferMultiple = (newCallbacks, value) =>
newCallbacks |> MutableList.iter(callback => defer(callback, value));
type snapshot = MutableList.list(unit => unit);
let snapshot = () => {
let theSnapshot = callbacks^;
callbacks := MutableList.create();
theSnapshot;
};
let isEmpty = snapshot => MutableList.isEmpty(snapshot);
let call = snapshot => snapshot |> MutableList.iter(callback => callback());
};
let newInternal = () =>
ref(
`Pending({
onResolve: MutableList.create(),
onReject: MutableList.create(),
}),
);
let resolveInternal = (p, value) =>
switch ((underlying(p))^) {
| `Fulfilled(_)
| `Rejected(_) => ()
| `Pending(callbacks) =>
ReadyCallbacks.deferMultiple(callbacks.onResolve, value);
p := `Fulfilled(value);
| `Merged(_) =>
/* This case is impossible, because we called underyling on the promise,
above. */
assert(false)
};
let rejectInternal = (p, error) =>
switch ((underlying(p))^) {
| `Fulfilled(_)
| `Rejected(_) => ()
| `Pending(callbacks) =>
ReadyCallbacks.deferMultiple(callbacks.onReject, error);
p := `Rejected(error);
| `Merged(_) =>
/* This case is impossible, because we called underyling on the promise,
above. */
assert(false)
};
let resolved = value => ref(`Fulfilled(value));
let rejected = error => ref(`Rejected(error));
let makePromiseBehaveAs = (outerPromise, nestedPromise) => {
let underlyingNested = underlying(nestedPromise);
switch (underlyingNested^) {
| `Fulfilled(value) => resolveInternal(outerPromise, value)
| `Rejected(error) => rejectInternal(outerPromise, error)
| `Pending(callbacks) =>
let underlyingOuter = underlying(outerPromise);
switch (underlyingOuter^) {
| `Fulfilled(_)
| `Rejected(_) =>
/* These two cases are impossible, because if makePromiseBehaveAs is
called, flatMap or catch_ called the callback that was passed to it, so
the outer promise is still pending. It is this function which resolves
the outer promise. */
assert(false)
| `Pending(outerCallbacks) =>
MutableList.concatenate(outerCallbacks.onResolve, callbacks.onResolve);
MutableList.concatenate(outerCallbacks.onReject, callbacks.onReject);
underlyingNested := `Merged(underlyingOuter);
| `Merged(_) =>
/* This case is impossible, because we called underlying above. */
assert(false)
};
| `Merged(_) =>
/* Impossible because we are working on the underlying promise. */
assert(false)
};
};
let flatMap = (promise, callback) => {
let outerPromise = newInternal();
let onResolve = value =>
switch (callback(value)) {
| exception exn => ignore(onUnhandledException^(exn))
| nestedPromise => makePromiseBehaveAs(outerPromise, nestedPromise)
};
switch ((underlying(promise))^) {
| `Fulfilled(value) => ReadyCallbacks.defer(onResolve, value)
| `Rejected(error) => rejectInternal(outerPromise, error)
| `Pending(callbacks) =>
MutableList.append(callbacks.onResolve, onResolve) |> ignore;
MutableList.append(callbacks.onReject, rejectInternal(outerPromise))
|> ignore;
| `Merged(_) =>
/* This case is impossible, cause of the call to underlying above. */
assert(false)
};
outerPromise;
};
let map = (promise, mapper) =>
flatMap(promise, value => resolved(mapper(value)));
let get = (promise, callback) => ignore(map(promise, callback));
let tap = (promise, callback) => {
get(promise, callback);
promise;
};
let catch = (promise, callback) => {
let outerPromise = newInternal();
let onReject = error =>
switch (callback(error)) {
| exception exn => ignore(onUnhandledException^(exn))
| nestedPromise => makePromiseBehaveAs(outerPromise, nestedPromise)
};
switch ((underlying(promise))^) {
| `Fulfilled(value) => resolveInternal(outerPromise, value)
| `Rejected(error) => ReadyCallbacks.defer(onReject, error)
| `Pending(callbacks) =>
MutableList.append(callbacks.onResolve, resolveInternal(outerPromise))
|> ignore;
MutableList.append(callbacks.onReject, onReject) |> ignore;
| `Merged(_) =>
/* This case is impossible, because of the call to underlying above. */
assert(false)
};
outerPromise;
};
/* Promise.all and Promise.race have to remove callbacks in some circumstances;
see test/native/test_ffi.re for details. */
module CallbackRemovers = {
let empty = () => ref([]);
let call = removers => {
removers^ |> List.iter(remover => remover());
removers := [];
};
let add = (removers, promise, whichList, callbackNode) => {
let remover = () =>
switch ((underlying(promise))^) {
| `Pending(callbacks) =>
MutableList.remove(whichList(callbacks), callbackNode)
| _ => ()
};
removers := [remover, ...removers^];
};
};
let all = promises => {
let callbackRemovers = CallbackRemovers.empty();
let finalPromise = newInternal();
let unresolvedPromiseCount = ref(List.length(promises));
let results = ref([]);
let onResolve = (cell, value) => {
cell := Some(value);
unresolvedPromiseCount := unresolvedPromiseCount^ - 1;
if (unresolvedPromiseCount^ == 0) {
results^
|> List.map(cell =>
switch (cell^) {
| None => assert(false)
| Some(value) => value
}
)
|> resolveInternal(finalPromise);
};
};
let rejectFinalPromise = error => {
CallbackRemovers.call(callbackRemovers);
rejectInternal(finalPromise, error);
};
results :=
promises
|> List.map(promise => {
let cell = ref(None);
switch ((underlying(promise))^) {
| `Fulfilled(value) =>
/* It's very important to defer here instead of resolving the final
promise immediately. Doing the latter will cause the callback removal
mechanism to forget about removing callbacks which will be added later
in the iteration over the promise list. It is possible to resolve
immediately but then the code has to be changed, probably to perform
two passes over the promise list. */
ReadyCallbacks.defer(onResolve(cell), value)
| `Rejected(error) =>
ReadyCallbacks.defer(rejectFinalPromise, error)
| `Pending(callbacks) =>
let callbackNode =
MutableList.append(callbacks.onResolve, onResolve(cell));
CallbackRemovers.add(
callbackRemovers,
promise,
callbacks => callbacks.onResolve,
callbackNode,
);
let callbackNode =
MutableList.append(callbacks.onReject, rejectFinalPromise);
CallbackRemovers.add(
callbackRemovers,
promise,
callbacks => callbacks.onReject,
callbackNode,
);
| `Merged(_) =>
/* Impossible because of the call to underlying above. */
assert(false)
};
cell;
});
finalPromise;
};
let allArray = promises => map(all(Array.to_list(promises)), Array.of_list);
/* Not a "legitimate" implementation. To get a legitimate one, the tricky parts
of "all," above, should be factoed out. */
let all2 = (p1, p2) => {
let promises = [Obj.magic(p1), Obj.magic(p2)];
map(
all(promises),
fun
| [v1, v2] => (Obj.magic(v1), Obj.magic(v2))
| _ => assert(false),
);
};
let all3 = (p1, p2, p3) => {
let promises = [Obj.magic(p1), Obj.magic(p2), Obj.magic(p3)];
map(
all(promises),
fun
| [v1, v2, v3] => (Obj.magic(v1), Obj.magic(v2), Obj.magic(v3))
| _ => assert(false),
);
};
let all4 = (p1, p2, p3, p4) => {
let promises = [
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
];
map(
all(promises),
fun
| [v1, v2, v3, v4] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
)
| _ => assert(false),
);
};
let all5 = (p1, p2, p3, p4, p5) => {
let promises = [
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
Obj.magic(p5),
];
map(
all(promises),
fun
| [v1, v2, v3, v4, v5] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
Obj.magic(v5),
)
| _ => assert(false),
);
};
let all6 = (p1, p2, p3, p4, p5, p6) => {
let promises = [
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
Obj.magic(p5),
Obj.magic(p6),
];
map(
all(promises),
fun
| [v1, v2, v3, v4, v5, v6] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
Obj.magic(v5),
Obj.magic(v6),
)
| _ => assert(false),
);
};
let race = promises => {
if (promises == []) {
raise(Invalid_argument("Promise.race([]) would be pending forever"));
};
let callbackRemovers = CallbackRemovers.empty();
let finalPromise = newInternal();
let resolveFinalPromise = value => {
CallbackRemovers.call(callbackRemovers);
resolveInternal(finalPromise, value);
};
let rejectFinalPromise = error => {
CallbackRemovers.call(callbackRemovers);
rejectInternal(finalPromise, error);
};
promises
|> List.iter(promise =>
switch ((underlying(promise))^) {
| `Fulfilled(value) =>
ReadyCallbacks.defer(resolveFinalPromise, value)
| `Rejected(error) => ReadyCallbacks.defer(rejectFinalPromise, error)
| `Pending(callbacks) =>
let callbackNode =
MutableList.append(callbacks.onResolve, resolveFinalPromise);
CallbackRemovers.add(
callbackRemovers,
promise,
callbacks => callbacks.onResolve,
callbackNode,
);
let callbackNode =
MutableList.append(callbacks.onReject, rejectFinalPromise);
CallbackRemovers.add(
callbackRemovers,
promise,
callbacks => callbacks.onReject,
callbackNode,
);
| `Merged(_) =>
/* Impossible, because of the call to underlying above. */
assert(false)
}
);
finalPromise;
};
let flatMapOk = (promise, callback) =>
flatMap(
promise,
fun
| Result.Ok(value) => callback(value)
| Result.Error(_) as error => resolved(error),
);
let flatMapError = (promise, callback) =>
flatMap(
promise,
fun
| Result.Ok(_) as ok => resolved(ok)
| Result.Error(error) => callback(error),
);
let mapOk = (promise, callback) =>
map(
promise,
fun
| Result.Ok(value) => Result.Ok(callback(value))
| Result.Error(_) as error => error,
);
let mapError = (promise, callback) =>
map(
promise,
fun
| Result.Ok(_) as ok => ok
| Result.Error(error) => Result.Error(callback(error)),
);
let getOk = (promise, callback) =>
get(
promise,
fun
| Result.Ok(value) => callback(value)
| Result.Error(_) => (),
);
let getError = (promise, callback) =>
get(
promise,
fun
| Result.Ok(_) => ()
| Result.Error(error) => callback(error),
);
let tapOk = (promise, callback) => {
getOk(promise, callback);
promise;
};
let tapError = (promise, callback) => {
getError(promise, callback);
promise;
};
module Operators = {
let (>|=) = mapOk;
let (>>=) = flatMapOk;
};
let flatMapSome = (promise, callback) =>
flatMap(
promise,
fun
| Some(value) => callback(value)
| None => resolved(None),
);
let mapSome = (promise, callback) =>
map(
promise,
fun
| Some(value) => Some(callback(value))
| None => None,
);
let getSome = (promise, callback) =>
get(
promise,
fun
| Some(value) => callback(value)
| None => (),
);
let tapSome = (promise, callback) => {
getSome(promise, callback);
promise;
};
module Js = {
type t('a, 'e) = rejectable('a, 'e);
external relax: promise('a) => rejectable('a, _) = "%identity";
let pending = () => {
let p = newInternal();
let resolve = resolveInternal(p);
let reject = rejectInternal(p);
(p, resolve, reject);
};
let resolved = resolved;
let rejected = rejected;
let flatMap = flatMap;
let map = map;
let get = get;
let tap = tap;
let catch = catch;
let all = all;
let race = race;
let toResult = promise =>
catch(map(promise, v => Result.Ok(v)), e => resolved(Result.Error(e)));
let fromResult = promise =>
flatMap(
relax(promise),
fun
| Result.Ok(v) => resolved(v)
| Result.Error(e) => rejected(e),
);
};
let pending = () => {
let (p, resolve, _) = Js.pending();
(p, resolve);
};
let exec = executor => {
let (p, resolve) = pending();
executor(resolve);
p;
};
let allOkArray = promises => {
let promiseCount = Array.length(promises);
if (promiseCount == 0) {
resolved(Result.Ok([||]));
} else {
let resultValues = Array.make(promiseCount, None);
let resultCount = ref(0);
let (resultPromise, resolve) = pending();
let (callbackRemover, removeCallbacks) = pending();
promises
|> Array.iteri((index, promise)
/* Because callbacks are added to the user's promises through calls to the
JS runtime's Promise.race, this function leaks memory if and only if
the JS runtime's Promise functions leak memory. In particular, if one
of the promises resolves with Error(_), the callbacks on the other
promises should be removed. If not done, and long-pending promises are
repeatedly passed to allOk in a loop, they will gradually accumulate
huge lists of stale callbacks. This is also true of Promise.race, so we
rely on the quality of the runtime's Promise.race implementation to
proactively remove these callbacks. */
=>
race([promise, callbackRemover])
|> (
wrapped =>
get(wrapped, result =>
switch (result) {
| Result.Ok(v) =>
resultValues[index] = Some(v);
incr(resultCount);
if (resultCount^ >= promiseCount) {
resultValues
|> Array.map(v =>
switch (v) {
| Some(v) => v
| None => assert(false)
}
)
|> (values => resolve(Result.Ok(values)));
};
| Result.Error(e) =>
resolve(Result.Error(e));
removeCallbacks(Result.Error(e));
}
)
)
);
resultPromise;
};
};
let allOk = promises =>
mapOk(allOkArray(Array.of_list(promises)), Array.to_list);
let allOk2 = (p1, p2) => {
let promises = [|Obj.magic(p1), Obj.magic(p2)|];
mapOk(
allOkArray(promises),
fun
| [|v1, v2|] => (Obj.magic(v1), Obj.magic(v2))
| _ => assert(false),
);
};
let allOk3 = (p1, p2, p3) => {
let promises = [|Obj.magic(p1), Obj.magic(p2), Obj.magic(p3)|];
mapOk(
allOkArray(promises),
fun
| [|v1, v2, v3|] => (Obj.magic(v1), Obj.magic(v2), Obj.magic(v3))
| _ => assert(false),
);
};
let allOk4 = (p1, p2, p3, p4) => {
let promises = [|
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
|];
mapOk(
allOkArray(promises),
fun
| [|v1, v2, v3, v4|] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
)
| _ => assert(false),
);
};
let allOk5 = (p1, p2, p3, p4, p5) => {
let promises = [|
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
Obj.magic(p5),
|];
mapOk(
allOkArray(promises),
fun
| [|v1, v2, v3, v4, v5|] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
Obj.magic(v5),
)
| _ => assert(false),
);
};
let allOk6 = (p1, p2, p3, p4, p5, p6) => {
let promises = [|
Obj.magic(p1),
Obj.magic(p2),
Obj.magic(p3),
Obj.magic(p4),
Obj.magic(p5),
Obj.magic(p6),
|];
mapOk(
allOkArray(promises),
fun
| [|v1, v2, v3, v4, v5, v6|] => (
Obj.magic(v1),
Obj.magic(v2),
Obj.magic(v3),
Obj.magic(v4),
Obj.magic(v5),
Obj.magic(v6),
)
| _ => assert(false),
);
};
module PipeFirst = {
let (|.) = (v, f) => f(v);
};
================================================
FILE: packages/promise/native/promise.rei
================================================
/* This file is part of reason-promise, released under the MIT license. See
LICENSE.md for details, or visit
https://github.com/aantron/promise/blob/master/LICENSE.md. */
/* Internal type names; don't use these. Instead, use Promise.t and Promise.Js.t
from outside this library. */
type rejectable('a, 'e); /* Internal; use Promise.Js.t. */
type never;
type promise('a) = rejectable('a, never); /* Internal; use Promise.t. */
/* The main, public promise type (Promise.t). */
type t('a) = promise('a);
/* Making promises. */
let pending: unit => (promise('a), 'a => unit);
let resolved: 'a => promise('a);
let exec: (('a => unit) => unit) => promise('a);
/* Using promises. */
let get: (promise('a), 'a => unit) => unit;
let tap: (promise('a), 'a => unit) => promise('a);
let map: (promise('a), 'a => 'b) => promise('b);
let flatMap: (promise('a), 'a => promise('b)) => promise('b);
/* Results. */
let getOk: (promise(result('a, 'e)), 'a => unit) => unit;
let tapOk:
(promise(result('a, 'e)), 'a => unit) => promise(result('a, 'e));
let mapOk: (promise(result('a, 'e)), 'a => 'b) => promise(result('b, 'e));
let flatMapOk:
(promise(result('a, 'e)), 'a => promise(result('b, 'e))) =>
promise(result('b, 'e));
let getError: (promise(result('a, 'e)), 'e => unit) => unit;
let tapError:
(promise(result('a, 'e)), 'e => unit) => promise(result('a, 'e));
let mapError:
(promise(result('a, 'e)), 'e => 'e2) => promise(result('a, 'e2));
let flatMapError:
(promise(result('a, 'e)), 'e => promise(result('a, 'e2))) =>
promise(result('a, 'e2));
module Operators: {
[@ocaml.deprecated "Use the let* syntax"]
let (>|=):
(promise(result('a, 'e)), 'a => 'b) => promise(result('b, 'e));
[@ocaml.deprecated "Use the let* syntax"]
let (>>=):
(promise(result('a, 'e)), 'a => promise(result('b, 'e))) =>
promise(result('b, 'e));
};
/* Options. */
let getSome: (promise(option('a)), 'a => unit) => unit;
let tapSome: (promise(option('a)), 'a => unit) => promise(option('a));
let mapSome: (promise(option('a)), 'a => 'b) => promise(option('b));
let flatMapSome:
(promise(option('a)), 'a => promise(option('b))) => promise(option('b));
/* Combining promises. */
let race: list(promise('a)) => promise('a);
let all: list(promise('a)) => promise(list('a));
let allArray: array(promise('a)) => promise(array('a));
let all2: (promise('a), promise('b)) => promise(('a, 'b));
let all3:
(promise('a), promise('b), promise('c)) => promise(('a, 'b, 'c));
let all4:
(promise('a), promise('b), promise('c), promise('d)) =>
promise(('a, 'b, 'c, 'd));
let all5:
(promise('a), promise('b), promise('c), promise('d), promise('e)) =>
promise(('a, 'b, 'c, 'd, 'e));
let all6:
(
promise('a),
promise('b),
promise('c),
promise('d),
promise('e),
promise('f)
) =>
promise(('a, 'b, 'c, 'd, 'e, 'f));
let allOk:
list(promise(result('a, 'e))) => promise(result(list('a), 'e));
let allOkArray:
array(promise(result('a, 'e))) => promise(result(array('a), 'e));
let allOk2:
(promise(result('a, 'err)), promise(result('b, 'err))) =>
promise(result(('a, 'b), 'err));
let allOk3:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err))
) =>
promise(result(('a, 'b, 'c), 'err));
let allOk4:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err))
) =>
promise(result(('a, 'b, 'c, 'd), 'err));
let allOk5:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err)),
promise(result('e, 'err))
) =>
promise(result(('a, 'b, 'c, 'd, 'e), 'err));
let allOk6:
(
promise(result('a, 'err)),
promise(result('b, 'err)),
promise(result('c, 'err)),
promise(result('d, 'err)),
promise(result('e, 'err)),
promise(result('f, 'err))
) =>
promise(result(('a, 'b, 'c, 'd, 'e, 'f), 'err));
/* Shouldn't be used; provided for compatibility with Js. */
module Js: {
type t('a, 'e) = rejectable('a, 'e);
/* Making. */
let pending: unit => (rejectable('a, 'e), 'a => unit, 'e => unit);
let resolved: 'a => rejectable('a, 'e);
let rejected: 'e => rejectable('a, 'e);
/* Handling fulfillment. */
let get: (rejectable('a, 'e), 'a => unit) => unit;
let tap: (rejectable('a, 'e), 'a => unit) => rejectable('a, 'e);
let map: (rejectable('a, 'e), 'a => 'b) => rejectable('b, 'e);
let flatMap:
(rejectable('a, 'e), 'a => rejectable('b, 'e)) => rejectable('b, 'e);
/* Handling rejection. */
let catch:
(rejectable('a, 'e), 'e => rejectable('a, 'e2)) => rejectable('a, 'e2);
/* Combining. */
let all: list(rejectable('a, 'e)) => rejectable(list('a), 'e);
let race: list(rejectable('a, 'e)) => rejectable('a, 'e);
/* Conversions. */
let relax: promise('a) => rejectable('a, 'e);
let toResult: rejectable('a, 'e) => promise(result('a, 'e));
let fromResult: promise(result('a, 'e)) => rejectable('a, 'e);
};
module PipeFirst: {
let (|.): ('a, 'a => 'b) => 'b;
};
let onUnhandledException: ref(exn => unit);
/* This is not part of the public API. It is used by I/O libraries to drive
native promise callbacks on each tick. */
module ReadyCallbacks: {
let callbacksPending: unit => bool;
/* When about to iterate over the ready callbacks, reason-promise first takes
a snapshot of them, and iterates over the snapshot. This is to prevent new
ready callbacks, that may be created by the processing of the current ones,
from being processed immediately. That could lead to I/O loop starvation
and other problems. */
type snapshot;
let snapshot: unit => snapshot;
let isEmpty: snapshot => bool;
let call: snapshot => unit;
};
================================================
FILE: packages/react/src/React.ml
================================================
type 'value ref = { mutable current : 'value }
type domRef = CallbackDomRef of (Dom.element Js.nullable -> unit) | CurrentDomRef of Dom.element Js.nullable ref
module Ref = struct
type t = domRef
type currentDomRef = Dom.element Js.nullable ref
type callbackDomRef = Dom.element Js.nullable -> unit
let domRef (v : currentDomRef) = CurrentDomRef v
let callbackDomRef (v : callbackDomRef) = CallbackDomRef v
end
let createRef () = { current = None }
let useRef value = { current = value }
let forwardRef f = f ()
module Event = struct
type 'a synthetic
type target_like =
< checked : bool
; className : string
; id : string
; innerHTML : string
; name : string
; tagName : string
; textContent : string
; value : string >
let fail name = Runtime.fail_impossible_action_in_ssr ("React.Event." ^ name)
module MakeEventWithType (Type : sig
type t
end) =
struct
let bubbles : Type.t -> bool = fun _ -> fail "bubbles"
let cancelable : Type.t -> bool = fun _ -> fail "cancelable"
let currentTarget : Type.t -> target_like = fun _ -> fail "currentTarget"
let defaultPrevented : Type.t -> bool = fun _ -> fail "defaultPrevented"
let eventPhase : Type.t -> int = fun _ -> fail "eventPhase"
let isTrusted : Type.t -> bool = fun _ -> fail "isTrusted"
let nativeEvent : Type.t -> target_like = fun _ -> fail "nativeEvent"
let preventDefault : Type.t -> unit = fun _ -> fail "preventDefault"
let isDefaultPrevented : Type.t -> bool = fun _ -> fail "isDefaultPrevented"
let stopPropagation : Type.t -> unit = fun _ -> fail "stopPropagation"
let isPropagationStopped : Type.t -> bool = fun _ -> fail "isPropagationStopped"
let target : Type.t -> target_like = fun _ -> fail "target"
let timeStamp : Type.t -> float = fun _ -> fail "timeStamp"
let type_ : Type.t -> string = fun _ -> fail "type_"
let persist : Type.t -> unit = fun _ -> fail "persist"
end
module Synthetic = struct
type tag
type t = tag synthetic
let bubbles : 'a synthetic -> bool = fun _ -> fail "Synthetic.bubbles"
let cancelable : 'a synthetic -> bool = fun _ -> fail "Synthetic.cancelable"
let currentTarget : 'a synthetic -> target_like = fun _ -> fail "Synthetic.currentTarget"
let defaultPrevented : 'a synthetic -> bool = fun _ -> fail "Synthetic.defaultPrevented"
let eventPhase : 'a synthetic -> int = fun _ -> fail "Synthetic.eventPhase"
let isTrusted : 'a synthetic -> bool = fun _ -> fail "Synthetic.isTrusted"
let nativeEvent : 'a synthetic -> target_like = fun _ -> fail "Synthetic.nativeEvent"
let preventDefault : 'a synthetic -> unit = fun _ -> fail "Synthetic.preventDefault"
let isDefaultPrevented : 'a synthetic -> bool = fun _ -> fail "Synthetic.isDefaultPrevented"
let stopPropagation : 'a synthetic -> unit = fun _ -> fail "Synthetic.stopPropagation"
let isPropagationStopped : 'a synthetic -> bool = fun _ -> fail "Synthetic.isPropagationStopped"
let target : 'a synthetic -> target_like = fun _ -> fail "Synthetic.target"
let timeStamp : 'a synthetic -> float = fun _ -> fail "Synthetic.timeStamp"
let type_ : 'a synthetic -> string = fun _ -> fail "Synthetic.type_"
let persist : 'a synthetic -> unit = fun _ -> fail "Synthetic.persist"
end
(* let toSyntheticEvent : 'a synthetic -> Synthetic.t = i -> i *)
module Clipboard = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let clipboardData : t -> target_like = fun _ -> fail "Clipboard.clipboardData"
end
module Composition = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let data : t -> string = fun _ -> fail "Composition.data"
end
module Keyboard = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let altKey : t -> bool = fun _ -> fail "Keyboard.altKey"
let charCode : t -> int = fun _ -> fail "Keyboard.charCode"
let ctrlKey : t -> bool = fun _ -> fail "Keyboard.ctrlKey"
let getModifierState : t -> string -> bool = fun _ _ -> fail "Keyboard.getModifierState"
let key : t -> string = fun _ -> fail "Keyboard.key"
let keyCode : t -> int = fun _ -> fail "Keyboard.keyCode"
let locale : t -> string = fun _ -> fail "Keyboard.locale"
let location : t -> int = fun _ -> fail "Keyboard.location"
let metaKey : t -> bool = fun _ -> fail "Keyboard.metaKey"
let repeat : t -> bool = fun _ -> fail "Keyboard.repeat"
let shiftKey : t -> bool = fun _ -> fail "Keyboard.shiftKey"
let which : t -> int = fun _ -> fail "Keyboard.which"
end
module Focus = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let relatedTarget : t -> target_like option = fun _ -> fail "Focus.relatedTarget"
end
module Form = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
end
module Mouse = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let altKey : t -> bool = fun _ -> fail "Mouse.altKey"
let button : t -> int = fun _ -> fail "Mouse.button"
let buttons : t -> int = fun _ -> fail "Mouse.buttons"
let clientX : t -> int = fun _ -> fail "Mouse.clientX"
let clientY : t -> int = fun _ -> fail "Mouse.clientY"
let ctrlKey : t -> bool = fun _ -> fail "Mouse.ctrlKey"
let getModifierState : t -> string -> bool = fun _ _ -> fail "Mouse.getModifierState"
let metaKey : t -> bool = fun _ -> fail "Mouse.metaKey"
let movementX : t -> int = fun _ -> fail "Mouse.movementX"
let movementY : t -> int = fun _ -> fail "Mouse.movementY"
let pageX : t -> int = fun _ -> fail "Mouse.pageX"
let pageY : t -> int = fun _ -> fail "Mouse.pageY"
let relatedTarget : t -> target_like option = fun _ -> fail "Mouse.relatedTarget"
let screenX : t -> int = fun _ -> fail "Mouse.screenX"
let screenY : t -> int = fun _ -> fail "Mouse.screenY"
let shiftKey : t -> bool = fun _ -> fail "Mouse.shiftKey"
end
module Pointer = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let detail : t -> int = fun _ -> fail "Pointer.detail"
(* let view : t -> Dom.window *)
let screenX : t -> int = fun _ -> fail "Pointer.screenX"
let screenY : t -> int = fun _ -> fail "Pointer.screenY"
let clientX : t -> int = fun _ -> fail "Pointer.clientX"
let clientY : t -> int = fun _ -> fail "Pointer.clientY"
let pageX : t -> int = fun _ -> fail "Pointer.pageX"
let pageY : t -> int = fun _ -> fail "Pointer.pageY"
let movementX : t -> int = fun _ -> fail "Pointer.movementX"
let movementY : t -> int = fun _ -> fail "Pointer.movementY"
let ctrlKey : t -> bool = fun _ -> fail "Pointer.ctrlKey"
let shiftKey : t -> bool = fun _ -> fail "Pointer.shiftKey"
let altKey : t -> bool = fun _ -> fail "Pointer.altKey"
let metaKey : t -> bool = fun _ -> fail "Pointer.metaKey"
let getModifierState : t -> string -> bool = fun _ _ -> fail "Pointer.getModifierState"
let button : t -> int = fun _ -> fail "Pointer.button"
let buttons : t -> int = fun _ -> fail "Pointer.buttons"
let relatedTarget : t -> target_like option = fun _ -> fail "Pointer.relatedTarget"
(* let pointerId : t -> Dom.eventPointerId *)
let width : t -> float = fun _ -> fail "Pointer.width"
let height : t -> float = fun _ -> fail "Pointer.height"
let pressure : t -> float = fun _ -> fail "Pointer.pressure"
let tangentialPressure : t -> float = fun _ -> fail "Pointer.tangentialPressure"
let tiltX : t -> int = fun _ -> fail "Pointer.tiltX"
let tiltY : t -> int = fun _ -> fail "Pointer.tiltY"
let twist : t -> int = fun _ -> fail "Pointer.twist"
let pointerType : t -> string = fun _ -> fail "Pointer.pointerType"
let isPrimary : t -> bool = fun _ -> fail "Pointer.isPrimary"
end
module Selection = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
end
module Touch = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let altKey : t -> bool = fun _ -> fail "Touch.altKey"
let changedTouches : t -> target_like = fun _ -> fail "Touch.changedTouches"
let ctrlKey : t -> bool = fun _ -> fail "Touch.ctrlKey"
let getModifierState : t -> string -> bool = fun _ _ -> fail "Touch.getModifierState"
let metaKey : t -> bool = fun _ -> fail "Touch.metaKey"
let shiftKey : t -> bool = fun _ -> fail "Touch.shiftKey"
let targetTouches : t -> target_like = fun _ -> fail "Touch.targetTouches"
let touches : t -> target_like = fun _ -> fail "Touch.touches"
end
module UI = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let detail : t -> int = fun _ -> fail "UI.detail"
(* let view : t -> Dom.window *)
end
module Wheel = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let deltaMode : t -> int = fun _ -> fail "Wheel.deltaMode"
let deltaX : t -> float = fun _ -> fail "Wheel.deltaX"
let deltaY : t -> float = fun _ -> fail "Wheel.deltaY"
let deltaZ : t -> float = fun _ -> fail "Wheel.deltaZ"
end
module Media = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
end
module Image = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
end
module Animation = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let animationName : t -> string = fun _ -> fail "Animation.animationName"
let pseudoElement : t -> string = fun _ -> fail "Animation.pseudoElement"
let elapsedTime : t -> float = fun _ -> fail "Animation.elapsedTime"
end
module Transition = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let propertyName : t -> string = fun _ -> fail "Transition.propertyName"
let pseudoElement : t -> string = fun _ -> fail "Transition.pseudoElement"
let elapsedTime : t -> float = fun _ -> fail "Transition.elapsedTime"
end
module Drag = struct
type tag
type t = tag synthetic
include MakeEventWithType (struct
type nonrec t = t [@@nonrec]
end)
let altKey : t -> bool = fun _ -> fail "Drag.altKey"
let button : t -> int = fun _ -> fail "Drag.button"
let buttons : t -> int = fun _ -> fail "Drag.buttons"
let clientX : t -> int = fun _ -> fail "Drag.clientX"
let clientY : t -> int = fun _ -> fail "Drag.clientY"
let ctrlKey : t -> bool = fun _ -> fail "Drag.ctrlKey"
let getModifierState : t -> string -> bool = fun _ _ -> fail "Drag.getModifierState"
let metaKey : t -> bool = fun _ -> fail "Drag.metaKey"
let movementX : t -> int = fun _ -> fail "Drag.movementX"
let movementY : t -> int = fun _ -> fail "Drag.movementY"
let pageX : t -> int = fun _ -> fail "Drag.pageX"
let pageY : t -> int = fun _ -> fail "Drag.pageY"
let relatedTarget : t -> target_like option = fun _ -> fail "Drag.relatedTarget"
let screenX : t -> int = fun _ -> fail "Drag.screenX"
let screenY : t -> int = fun _ -> fail "Drag.screenY"
let shiftKey : t -> bool = fun _ -> fail "Drag.shiftKey"
let dataTransfer : t -> target_like = fun _ -> fail "Drag.dataTransfer"
end
end
module JSX = struct
type event =
| Drag of (Event.Drag.t -> unit)
| Mouse of (Event.Mouse.t -> unit)
| Selection of (Event.Selection.t -> unit)
| Touch of (Event.Touch.t -> unit)
| UI of (Event.UI.t -> unit)
| Wheel of (Event.Wheel.t -> unit)
| Clipboard of (Event.Clipboard.t -> unit)
| Composition of (Event.Composition.t -> unit)
| Transition of (Event.Transition.t -> unit)
| Animation of (Event.Animation.t -> unit)
| Pointer of (Event.Pointer.t -> unit)
| Keyboard of (Event.Keyboard.t -> unit)
| Focus of (Event.Focus.t -> unit)
| Form of (Event.Form.t -> unit)
| Media of (Event.Media.t -> unit)
| Inline of string
type prop =
| Action : (string * string * _ Runtime.server_function) -> prop
| Bool of (string * string * bool)
| String of (string * string * string)
| Style of (string * string * string) list
| DangerouslyInnerHtml of string
| Ref of Ref.t
| Event of string * event
let bool name jsxName value = Bool (name, jsxName, value)
let string name jsxName value = String (name, jsxName, value)
let style value = Style value
let int name jsxName value = String (name, jsxName, Int.to_string value)
let float name jsxName value = String (name, jsxName, Float.to_string value)
let dangerouslyInnerHtml value = DangerouslyInnerHtml value#__html
let ref value = Ref value
let event key value = Event (key, value)
module Event = struct
let drag key value = event key (Drag value)
let mouse key value = event key (Mouse value)
let selection key value = event key (Selection value)
let touch key value = event key (Touch value)
let ui key value = event key (UI value)
let wheel key value = event key (Wheel value)
let clipboard key value = event key (Clipboard value)
let composition key value = event key (Composition value)
let transition key value = event key (Transition value)
let animation key value = event key (Animation value)
let pointer key value = event key (Pointer value)
let keyboard key value = event key (Keyboard value)
let focus key value = event key (Focus value)
let form key value = event key (Form value)
let media key value = event key (Media value)
end
end
type error = { message : string; stack : Yojson.Basic.t; env : string; digest : string }
module Model = struct
type 'element t =
| Function : 'server_function Runtime.server_function -> 'element t
| List : 'element t list -> 'element t
| Assoc : (string * 'element t) list -> 'element t
| Json : Yojson.Basic.t -> 'element t
| Error : error -> 'element t
| Element : 'element -> 'element t
| Promise : 'a Js.Promise.t * ('a -> 'element t) -> 'element t
end
type ('props, 'return) componentLike = ?key:string -> 'props -> 'return
and element =
| Lower_case_element of lower_case_element
| Upper_case_component of string * (unit -> element)
| Async_component of string * (unit -> element Lwt.t)
| Client_component of {
key : string option;
props : client_props;
client : element;
import_module : string;
import_name : string;
}
| List of element list
| Array of element array
| Text of string
| Static of { prerendered : string; original : element }
| Writer of { emit : Buffer.t -> unit; original : unit -> element }
(** Like [Static] but writes directly into the caller's buffer. Used by the PPX for subtrees with static skeleton
\+ dynamic string/int/float holes (the [Needs_string_concat] and [Needs_buffer] tiers).
[original] is a thunk that rebuilds the variant-tree form on-demand for [cloneElement] and RSC consumers. Same
name as [Static.original] for symmetry; [Writer]'s version is lazy so the render-to-string fast path pays no
allocation for the fallback. *)
| Fragment of element
| Empty
| Provider of { children : element; push : unit -> unit -> unit; async_key : Obj.t Lwt.key; async_value : Obj.t }
| Consumer of element
| Suspense of { key : string option; children : element; fallback : element }
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
and client_props = (string * element Model.t) list
and model_value = element Model.t
exception Invalid_children of string
let compare_attribute (left : JSX.prop) (right : JSX.prop) =
match (left, right) with
| Bool (left_key, _, _), Bool (right_key, _, _) | String (left_key, _, _), String (right_key, _, _) ->
String.compare left_key right_key
| Style left_styles, Style right_styles ->
List.compare
(fun (left_property, _, left_value) (right_property, _, right_value) ->
Int.compare (String.compare left_property right_property) (String.compare left_value right_value))
left_styles right_styles
| _ -> 0
let clone_attribute acc (attr : JSX.prop) (new_attr : JSX.prop) =
match (attr, new_attr) with
| Bool (left, _, _), Bool (right, _, _) when left == right -> new_attr :: acc
| String (left, _, _), String (right, _, _) when left == right -> new_attr :: acc
| _ -> new_attr :: acc
module StringMap = Map.Make (String)
let attributes_to_map attributes =
List.fold_left
(fun acc (attr : JSX.prop) ->
match attr with
| (Bool (key, _, _) | String (key, _, _)) as prop -> acc |> StringMap.add key prop
(* The following constructors shoudn't be part of the StringMap *)
| DangerouslyInnerHtml _ -> acc
| Ref _ -> acc
| Event _ -> acc
| Action _ -> acc
| Style _ -> acc)
StringMap.empty attributes
let clone_attributes attributes new_attributes =
let attribute_map = attributes_to_map attributes in
let new_attribute_map = attributes_to_map new_attributes in
StringMap.merge
(fun _key attr new_attr ->
match (attr, new_attr) with
| Some attr, Some new_attr -> Some (clone_attribute [] attr new_attr)
| Some attr, None -> Some [ attr ]
| None, Some new_attr -> Some [ new_attr ]
| None, None -> None)
attribute_map new_attribute_map
|> StringMap.bindings
|> List.map (fun (_, attrs) -> attrs)
|> List.flatten |> List.rev |> List.sort compare_attribute
let create_element_with_key ?key tag attributes children =
match Html.is_self_closing_tag tag with
| true when List.length children > 0 ->
raise (Invalid_children (Printf.sprintf {|"%s" is a self-closing tag and must not have "children".\n|} tag))
| true when List.exists (function JSX.DangerouslyInnerHtml _ -> true | _ -> false) attributes ->
raise
(Invalid_children
(Printf.sprintf {|"%s" is a self-closing tag and must not have "dangerouslySetInnerHTML".\n|} tag))
| true -> Lower_case_element { key; tag; attributes; children = [] }
| false -> Lower_case_element { key; tag; attributes; children }
let createElement = create_element_with_key ?key:None
let createElementWithKey = create_element_with_key
let clone_component_error name =
Printf.sprintf
"React.cloneElement: cannot clone '%s'. In server-reason-react, component props are compile-time labelled \
arguments (and extending them with new props at runtime is not supported). React.cloneElement only works with \
lowercase DOM elements."
name
let rec cloneElement element new_attributes =
match element with
| Lower_case_element { key; tag; attributes; children } ->
Lower_case_element { key; tag; attributes = clone_attributes attributes new_attributes; children }
| Upper_case_component (name, _) -> raise (Invalid_argument (clone_component_error name))
| Async_component (name, _) -> raise (Invalid_argument (clone_component_error name))
| Client_component { import_name; _ } -> raise (Invalid_argument (clone_component_error import_name))
| Static { original; prerendered = _ } -> cloneElement original new_attributes
| Writer { original; emit = _ } -> cloneElement (original ()) new_attributes
| Fragment _ -> raise (Invalid_argument "React.cloneElement: cannot clone a Fragment")
| Text _ -> raise (Invalid_argument "React.cloneElement: cannot clone a Text element")
| Empty -> raise (Invalid_argument "React.cloneElement: cannot clone a null element")
| List _ -> raise (Invalid_argument "React.cloneElement: cannot clone a List")
| Array _ -> raise (Invalid_argument "React.cloneElement: cannot clone an Array")
| Provider _ -> raise (Invalid_argument "React.cloneElement: cannot clone a Provider")
| Consumer _ -> raise (Invalid_argument "React.cloneElement: cannot clone a Consumer")
| Suspense _ -> raise (Invalid_argument "React.cloneElement: cannot clone a Suspense")
module Fragment = struct
let makeProps ~children () : < children : element > Js.t =
object
method children = children
end
let make ?key:_ props = Fragment props#children
end
let fragment children = Fragment.make (Fragment.makeProps ~children ())
(* ReasonReact APIs *)
let string txt = Text txt
let null = Empty
let int i = Text (string_of_int i)
(* FIXME: float_of_string might be different from the browser *)
let float f = Text (string_of_float f)
let array arr = Array arr
let list l = List l
type 'a provider = value:'a -> children:element -> unit -> element
module Context = struct
type 'a t = {
current_value : 'a ref;
async_key : Obj.t Lwt.key;
provider : 'a provider;
consumer : children:element -> element;
}
let makeProps ~value ~children () : < value : 'a ; children : element > Js.t =
object
method value = value
method children = children
end
let provider ctx ?key:_ props = ctx.provider ~value:props#value ~children:props#children ()
end
let createContext (initial_value : 'a) : 'a Context.t =
let ref_value = { current = initial_value } in
let async_key = Lwt.new_key () in
let provider ~value ~children () =
Provider
{
children;
push =
(fun () ->
let prev = ref_value.current in
ref_value.current <- value;
fun () -> ref_value.current <- prev);
async_key;
async_value = Obj.repr value;
}
in
let consumer ~children = Consumer children in
{ current_value = ref_value; async_key; provider; consumer }
module Suspense = struct
let or_react_null = function None -> null | Some x -> x
let makeProps ?fallback ?children () : < fallback : element option ; children : element option > Js.t =
object
method fallback = fallback
method children = children
end
let make ?key props =
Suspense { key; fallback = or_react_null props#fallback; children = or_react_null props#children }
end
module Cache = struct
type cache_entry = Ok of Obj.t | Error of exn
type fn_cache = (Obj.t, cache_entry) Hashtbl.t
type request_cache = (int, fn_cache) Hashtbl.t
let async_key : request_cache Lwt.key = Lwt.new_key ()
let fn_id_counter : int Stdlib.ref = Stdlib.ref 0
let with_request_cache f =
let cache = Hashtbl.create 16 in
Lwt.with_value async_key (Some cache) f
let with_request_cache_async f =
let cache = Hashtbl.create 16 in
Lwt.with_value async_key (Some cache) f
end
let memo f _component = f
let memoCustomCompareProps f _compare _component = f
let cache fn =
let fn_id = !Cache.fn_id_counter in
Cache.fn_id_counter := fn_id + 1;
fun arg ->
match Lwt.get Cache.async_key with
| None -> fn arg
| Some cache_map -> (
let fn_cache =
match Hashtbl.find_opt cache_map fn_id with
| Some cache -> cache
| None ->
let cache = Hashtbl.create 8 in
Hashtbl.add cache_map fn_id cache;
cache
in
let arg_key = Obj.repr arg in
match Hashtbl.find_opt fn_cache arg_key with
| Some (Cache.Ok value) -> Obj.obj value
| Some (Cache.Error error) -> raise error
| None -> (
try
let result = fn arg in
Hashtbl.add fn_cache arg_key (Cache.Ok (Obj.repr result));
result
with exn ->
Hashtbl.add fn_cache arg_key (Cache.Error exn);
raise exn))
let useContext (context : 'a Context.t) =
match Lwt.get context.async_key with Some v -> (Obj.obj v : 'a) | None -> context.current_value.current
let useState (make_initial_value : unit -> 'state) =
let initial_value : 'state = make_initial_value () in
let setState (fn : 'state -> 'state) =
let _ = fn initial_value in
()
in
(initial_value, setState)
type ('input, 'output) callback = 'input -> 'output
let useSyncExternalStore ~subscribe:_ ~getSnapshot = getSnapshot ()
let useSyncExternalStoreWithServer ~subscribe:_ ~getSnapshot:_ ~getServerSnapshot = getServerSnapshot ()
(* Tree context for useId — implements the same bit-packing algorithm as React's
ReactFizzTree_context.js to produce hydration-compatible IDs.
IDs are base-32 strings whose binary representation corresponds to the
position of a node in a tree. Every time the tree forks into multiple
children, additional bits encode the position of the child within the
current level of children. *)
module Tree_context = struct
type t = { id : int; overflow : string }
let empty = { id = 1; overflow = "" }
(* Count leading zeros in a 32-bit representation.
Uses the same algorithm as Math.clz32 in JavaScript. *)
let clz32 x =
if x = 0 then 32
else
let n = Stdlib.ref 0 in
let v = Stdlib.ref x in
if !v land 0xFFFF0000 = 0 then (
n := !n + 16;
v := !v lsl 16);
if !v land 0xFF000000 = 0 then (
n := !n + 8;
v := !v lsl 8);
if !v land 0xF0000000 = 0 then (
n := !n + 4;
v := !v lsl 4);
if !v land 0xC0000000 = 0 then (
n := !n + 2;
v := !v lsl 2);
if !v land 0x80000000 = 0 then n := !n + 1;
!n
let get_bit_length n = 32 - clz32 n
let get_leading_bit id = 1 lsl (get_bit_length id - 1)
(* Convert a non-negative integer to a base-32 string (digits: 0-9, a-v).
Matches JavaScript's Number.prototype.toString(32). *)
let int_to_base32 n =
if n = 0 then "0"
else
let digits = "0123456789abcdefghijklmnopqrstuv" in
let buf = Buffer.create 8 in
let rec go n =
if n > 0 then begin
go (n / 32);
Buffer.add_char buf (String.get digits (n mod 32))
end
in
go n;
Buffer.contents buf
let get_tree_id ctx =
let overflow = ctx.overflow in
let id_with_leading_bit = ctx.id in
let id = id_with_leading_bit land lnot (get_leading_bit id_with_leading_bit) in
int_to_base32 id ^ overflow
let push base_ctx ~total_children ~index =
let base_id_with_leading_bit = base_ctx.id in
let base_overflow = base_ctx.overflow in
let base_length = get_bit_length base_id_with_leading_bit - 1 in
let base_id = base_id_with_leading_bit land lnot (1 lsl base_length) in
let slot = index + 1 in
let length = get_bit_length total_children + base_length in
if length > 30 then begin
(* Overflow: convert some bits to base-32 string *)
let number_of_overflow_bits = base_length - (base_length mod 5) in
let new_overflow_bits = (1 lsl number_of_overflow_bits) - 1 in
let new_overflow = int_to_base32 (base_id land new_overflow_bits) in
let rest_of_base_id = base_id asr number_of_overflow_bits in
let rest_of_base_length = base_length - number_of_overflow_bits in
let rest_of_length = get_bit_length total_children + rest_of_base_length in
let rest_of_new_bits = slot lsl rest_of_base_length in
let id = rest_of_new_bits lor rest_of_base_id in
let overflow = new_overflow ^ base_overflow in
{ id = (1 lsl rest_of_length) lor id; overflow }
end
else
(* Normal path *)
let new_bits = slot lsl base_length in
let id = new_bits lor base_id in
{ id = (1 lsl length) lor id; overflow = base_overflow }
end
(* Rendering hook context — mutable state set by the renderer (ReactDOM) and
read by hooks (useId). This mirrors React's currentlyRenderingTask pattern
in ReactFizzHooks.js. *)
let current_tree_context : Tree_context.t Stdlib.ref = Stdlib.ref Tree_context.empty
let local_id_counter : int Stdlib.ref = Stdlib.ref 0
let did_render_id_hook : bool Stdlib.ref = Stdlib.ref false
let identifier_prefix : string option Stdlib.ref = Stdlib.ref None
let reset_component_id_state (ctx : Tree_context.t) =
current_tree_context := ctx;
local_id_counter := 0;
did_render_id_hook := false
let check_did_render_id_hook () = !did_render_id_hook
let reset_id_rendering ?prefix () =
current_tree_context := Tree_context.empty;
local_id_counter := 0;
did_render_id_hook := false;
identifier_prefix := prefix
(* React 19 uses \u00ab (LEFT-POINTING DOUBLE ANGLE QUOTATION MARK) and
\u00bb (RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK) as ID delimiters. *)
let id_start = "\xc2\xab"
let id_end = "\xc2\xbb"
let useId () =
let tree_id = Tree_context.get_tree_id !current_tree_context in
let local_id = !local_id_counter in
local_id_counter := local_id + 1;
did_render_id_hook := true;
match (!identifier_prefix, local_id > 0) with
| None, false -> Printf.sprintf "%sR%s%s" id_start tree_id id_end
| None, true -> Printf.sprintf "%sR%sH%s%s" id_start tree_id (Tree_context.int_to_base32 local_id) id_end
| Some prefix, false -> Printf.sprintf "%s%sR%s%s" id_start prefix tree_id id_end
| Some prefix, true ->
Printf.sprintf "%s%sR%sH%s%s" id_start prefix tree_id (Tree_context.int_to_base32 local_id) id_end
let useMemo fn = fn ()
let useMemo0 fn = fn ()
let useMemo1 fn _ = fn ()
let useMemo2 fn _ = fn ()
let useMemo3 fn _ = fn ()
let useMemo4 fn _ = fn ()
let useMemo5 fn _ = fn ()
let useMemo6 fn _ = fn ()
let useCallback fn = fn
let useCallback0 fn = fn
let useCallback1 fn _ = fn
let useCallback2 fn _ = fn
let useCallback3 fn _ = fn
let useCallback4 fn _ = fn
let useCallback5 fn _ = fn
let useCallback6 fn _ = fn
let useReducer _ s = (s, fun _ -> ())
let useReducerWithMapState _ s mapper = (mapper s, fun _ -> ())
let useEffect _ = ()
let useEffect0 _ = ()
let useEffect1 _ _ = ()
let useEffect2 _ _ = ()
let useEffect3 _ _ = ()
let useEffect4 _ _ = ()
let useEffect5 _ _ = ()
let useEffect6 _ _ = ()
let useLayoutEffect0 _ = ()
let useLayoutEffect1 _ _ = ()
let useLayoutEffect2 _ _ = ()
let useLayoutEffect3 _ _ = ()
let useLayoutEffect4 _ _ = ()
let useLayoutEffect5 _ _ = ()
let useLayoutEffect6 _ _ = ()
module Children = struct
let map element fn =
match element with
| List children -> List.map fn children |> list
| Array children -> Array.map fn children |> array
| _ -> fn element
let mapWithIndex element fn =
match element with
| List children -> List.mapi (fun index element -> fn element index) children |> list
| Array children -> Array.mapi (fun index element -> fn element index) children |> array
| _ -> fn element 0
let forEach element fn =
match element with
| List children -> List.iter fn children
| Array children -> Array.iter fn children
| _ ->
let _ = fn element in
()
let forEachWithIndex element fn =
match element with
| List children -> List.iteri (fun index element -> fn element index) children
| Array children -> Array.iteri (fun index element -> fn element index) children
| _ ->
let _ = fn element 0 in
()
let count element =
match element with
| List children -> List.length children
| Array children -> Array.length children
| Empty -> 0
| _ -> 1
let only element =
match element with
| List (child :: _) -> child
| List [] -> raise (Invalid_argument "Expected at least one child")
| Array children ->
if Array.length children >= 1 then Array.get children 0
else raise (Invalid_argument "Expected at least one child")
| _ -> element
(* TODO: silly way to convert children to array, but isn't necessary in most cases *)
let toArray element = [| element |]
end
let setDisplayName _ _ = ()
let useTransition () = (false, fun (_cb : unit -> unit) -> ())
let useDebugValue : 'value -> ?format:('value -> string) -> unit = fun[@warning "-16"] _ ?format:_ -> ()
let useDeferredValue value = value
(* `exception Suspend of 'a Lwt`
exceptions can't have type params, this is called existential wrapper *)
type any_promise = Any_promise : 'a Lwt.t -> any_promise
exception Suspend of any_promise
let suspend promise = raise (Suspend (Any_promise promise))
module Experimental = struct
let usePromise promise =
match Lwt.state promise with
| Sleep -> suspend promise
(* TODO: Fail should raise a FailedSupense and catch at renderTo*? *)
| Fail e -> raise e
| Return v -> v
let useActionState ?permalink:_ _action state = (state, (), false)
end
================================================
FILE: packages/react/src/React.mli
================================================
(** The React library *)
type domRef
type 'value ref = { mutable current : 'value }
module Ref : sig
type t = domRef
type currentDomRef = Dom.element Js.nullable ref
type callbackDomRef = Dom.element Js.nullable -> unit
val domRef : currentDomRef -> t
val callbackDomRef : callbackDomRef -> t
end
val createRef : unit -> 'a option ref
val useRef : 'a -> 'a ref
val forwardRef : (unit -> 'a) -> 'a
module Event : sig
type 'a synthetic
type target_like =
< checked : bool
; className : string
; id : string
; innerHTML : string
; name : string
; tagName : string
; textContent : string
; value : string >
module MakeEventWithType : functor
(Type : sig
type t
end)
-> sig
val bubbles : Type.t -> bool
val cancelable : Type.t -> bool
val currentTarget : Type.t -> target_like
val defaultPrevented : Type.t -> bool
val eventPhase : Type.t -> int
val isTrusted : Type.t -> bool
val nativeEvent : Type.t -> target_like
val preventDefault : Type.t -> unit
val isDefaultPrevented : Type.t -> bool
val stopPropagation : Type.t -> unit
val isPropagationStopped : Type.t -> bool
val target : Type.t -> target_like
val timeStamp : Type.t -> float
val type_ : Type.t -> string
val persist : Type.t -> unit
end
module Synthetic : sig
type tag
type t = tag synthetic
val bubbles : 'a synthetic -> bool
val cancelable : 'a synthetic -> bool
val currentTarget : 'a synthetic -> target_like
val defaultPrevented : 'a synthetic -> bool
val eventPhase : 'a synthetic -> int
val isTrusted : 'a synthetic -> bool
val nativeEvent : 'a synthetic -> target_like
val preventDefault : 'a synthetic -> unit
val isDefaultPrevented : 'a synthetic -> bool
val stopPropagation : 'a synthetic -> unit
val isPropagationStopped : 'a synthetic -> bool
val target : 'a synthetic -> target_like
val timeStamp : 'a synthetic -> float
val type_ : 'a synthetic -> string
val persist : 'a synthetic -> unit
end
module Clipboard : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val clipboardData : t -> target_like
end
module Composition : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val data : t -> string
end
module Keyboard : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val altKey : t -> bool
val charCode : t -> int
val ctrlKey : t -> bool
val getModifierState : t -> string -> bool
val key : t -> string
val keyCode : t -> int
val locale : t -> string
val location : t -> int
val metaKey : t -> bool
val repeat : t -> bool
val shiftKey : t -> bool
val which : t -> int
end
module Focus : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val relatedTarget : t -> target_like option
end
module Form : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
end
module Mouse : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val altKey : t -> bool
val button : t -> int
val buttons : t -> int
val clientX : t -> int
val clientY : t -> int
val ctrlKey : t -> bool
val getModifierState : t -> string -> bool
val metaKey : t -> bool
val movementX : t -> int
val movementY : t -> int
val pageX : t -> int
val pageY : t -> int
val relatedTarget : t -> target_like option
val screenX : t -> int
val screenY : t -> int
val shiftKey : t -> bool
end
module Pointer : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val detail : t -> int
val screenX : t -> int
val screenY : t -> int
val clientX : t -> int
val clientY : t -> int
val pageX : t -> int
val pageY : t -> int
val movementX : t -> int
val movementY : t -> int
val ctrlKey : t -> bool
val shiftKey : t -> bool
val altKey : t -> bool
val metaKey : t -> bool
val getModifierState : t -> string -> bool
val button : t -> int
val buttons : t -> int
val relatedTarget : t -> target_like option
val width : t -> float
val height : t -> float
val pressure : t -> float
val tangentialPressure : t -> float
val tiltX : t -> int
val tiltY : t -> int
val twist : t -> int
val pointerType : t -> string
val isPrimary : t -> bool
end
module Selection : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
end
module Touch : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val altKey : t -> bool
val changedTouches : t -> target_like
val ctrlKey : t -> bool
val getModifierState : t -> string -> bool
val metaKey : t -> bool
val shiftKey : t -> bool
val targetTouches : t -> target_like
val touches : t -> target_like
end
module UI : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val detail : t -> int
end
module Wheel : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val deltaMode : t -> int
val deltaX : t -> float
val deltaY : t -> float
val deltaZ : t -> float
end
module Media : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
end
module Image : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
end
module Animation : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val animationName : t -> string
val pseudoElement : t -> string
val elapsedTime : t -> float
end
module Transition : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val propertyName : t -> string
val pseudoElement : t -> string
val elapsedTime : t -> float
end
module Drag : sig
type tag
type t = tag synthetic
val bubbles : t -> bool
val cancelable : t -> bool
val currentTarget : t -> target_like
val defaultPrevented : t -> bool
val eventPhase : t -> int
val isTrusted : t -> bool
val nativeEvent : t -> target_like
val preventDefault : t -> unit
val isDefaultPrevented : t -> bool
val stopPropagation : t -> unit
val isPropagationStopped : t -> bool
val target : t -> target_like
val timeStamp : t -> float
val type_ : t -> string
val persist : t -> unit
val altKey : t -> bool
val button : t -> int
val buttons : t -> int
val clientX : t -> int
val clientY : t -> int
val ctrlKey : t -> bool
val getModifierState : t -> string -> bool
val metaKey : t -> bool
val movementX : t -> int
val movementY : t -> int
val pageX : t -> int
val pageY : t -> int
val relatedTarget : t -> target_like option
val screenX : t -> int
val screenY : t -> int
val shiftKey : t -> bool
val dataTransfer : t -> target_like
end
end
(** All of those types are used by the server-reason-react.ppx internally to represent valid React code from the server.
It currently different from reason-react-ppx due to a need for knowing the types since ReactDOM needs to render
differently depending on the type. *)
module JSX : sig
(** All event callbacks *)
type event =
| Drag of (Event.Drag.t -> unit)
| Mouse of (Event.Mouse.t -> unit)
| Selection of (Event.Selection.t -> unit)
| Touch of (Event.Touch.t -> unit)
| UI of (Event.UI.t -> unit)
| Wheel of (Event.Wheel.t -> unit)
| Clipboard of (Event.Clipboard.t -> unit)
| Composition of (Event.Composition.t -> unit)
| Transition of (Event.Transition.t -> unit)
| Animation of (Event.Animation.t -> unit)
| Pointer of (Event.Pointer.t -> unit)
| Keyboard of (Event.Keyboard.t -> unit)
| Focus of (Event.Focus.t -> unit)
| Form of (Event.Form.t -> unit)
| Media of (Event.Media.t -> unit)
| Inline of string
(** JSX.prop is the representation of HTML/SVG attributes and DOM events *)
type prop =
| Action : (string * string * _ Runtime.server_function) -> prop
| Bool of (string * string * bool)
| String of (string * string * string)
| Style of (string * string * string) list
| DangerouslyInnerHtml of string
| Ref of domRef
| Event of string * event
(** Helpers to create JSX.prop without variants, helpful for function application *)
val bool : string -> string -> bool -> prop
val string : string -> string -> string -> prop
val style : (string * string * string) list -> prop
val dangerouslyInnerHtml : < __html : string ; .. > -> prop
val int : string -> string -> int -> prop
val float : string -> string -> float -> prop
val ref : domRef -> prop
val event : string -> event -> prop
module Event : sig
val drag : string -> (Event.Drag.t -> unit) -> prop
val mouse : string -> (Event.Mouse.t -> unit) -> prop
val selection : string -> (Event.Selection.t -> unit) -> prop
val touch : string -> (Event.Touch.t -> unit) -> prop
val ui : string -> (Event.UI.t -> unit) -> prop
val wheel : string -> (Event.Wheel.t -> unit) -> prop
val clipboard : string -> (Event.Clipboard.t -> unit) -> prop
val composition : string -> (Event.Composition.t -> unit) -> prop
val transition : string -> (Event.Transition.t -> unit) -> prop
val animation : string -> (Event.Animation.t -> unit) -> prop
val pointer : string -> (Event.Pointer.t -> unit) -> prop
val keyboard : string -> (Event.Keyboard.t -> unit) -> prop
val focus : string -> (Event.Focus.t -> unit) -> prop
val form : string -> (Event.Form.t -> unit) -> prop
val media : string -> (Event.Media.t -> unit) -> prop
end
end
type error = { message : string; stack : Yojson.Basic.t; env : string; digest : string }
module Model : sig
type 'element t =
| Function : 'server_function Runtime.server_function -> 'element t
| List : 'element t list -> 'element t
| Assoc : (string * 'element t) list -> 'element t
| Json : Yojson.Basic.t -> 'element t
| Error : error -> 'element t
| Element : 'element -> 'element t
| Promise : 'a Js.Promise.t * ('a -> 'element t) -> 'element t
end
type ('props, 'return) componentLike = ?key:string -> 'props -> 'return
and element =
| Lower_case_element of lower_case_element
| Upper_case_component of string * (unit -> element)
| Async_component of string * (unit -> element Lwt.t)
| Client_component of {
key : string option;
props : client_props;
client : element;
import_module : string;
import_name : string;
}
| List of element list
| Array of element array
| Text of string
| Static of { prerendered : string; original : element }
| Writer of { emit : Buffer.t -> unit; original : unit -> element }
(** Subtree with static skeleton + dynamic string/int/float/element holes. [emit] writes directly into the
caller's buffer, avoiding the intermediate buffer + Buffer.contents allocation that a [Static] wrapping would
require.
[original] is a thunk that rebuilds the variant-tree form on-demand for [cloneElement] / RSC consumers. Same
name as [Static.original] for symmetry; this variant's version is lazy. Emitted by the PPX for the
[Needs_string_concat] and [Needs_buffer] analysis tiers. *)
| Fragment of element
| Empty
| Provider of { children : element; push : unit -> unit -> unit; async_key : Obj.t Lwt.key; async_value : Obj.t }
| Consumer of element
| Suspense of { key : string option; children : element; fallback : element }
and lower_case_element = { key : string option; tag : string; attributes : JSX.prop list; children : element list }
and client_props = (string * element Model.t) list
and model_value = element Model.t
exception Invalid_children of string
module Fragment : sig
val makeProps : children:element -> unit -> < children : element > Js.t
val make : (< children : element > Js.t, element) componentLike
end
val createElement : string -> JSX.prop list -> element list -> element
val createElementWithKey : ?key:string -> string -> JSX.prop list -> element list -> element
val fragment : element -> element
val cloneElement : element -> JSX.prop list -> element
val string : string -> element
val null : element
val int : int -> element
val float : float -> element
val array : element array -> element
val list : element list -> element
type 'a provider = value:'a -> children:element -> unit -> element
module Context : sig
type 'a t = {
current_value : 'a ref;
async_key : Obj.t Lwt.key;
provider : 'a provider;
consumer : children:element -> element;
}
val makeProps : value:'a -> children:element -> unit -> < value : 'a ; children : element > Js.t
val provider : 'a t -> (< value : 'a ; children : element > Js.t, element) componentLike
end
val createContext : 'a -> 'a Context.t
module Suspense : sig
val makeProps :
?fallback:element -> ?children:element -> unit -> < fallback : element option ; children : element option > Js.t
val make : (< fallback : element option ; children : element option > Js.t, element) componentLike
end
module Cache : sig
val with_request_cache : (unit -> 'a) -> 'a
val with_request_cache_async : (unit -> 'a Lwt.t) -> 'a Lwt.t
end
type any_promise = Any_promise : 'a Lwt.t -> any_promise
exception Suspend of any_promise
val memo : ('props * 'props -> bool) -> 'a -> 'props * 'props -> bool
val memoCustomCompareProps : ('props * 'props -> bool) -> ('props * 'props -> bool) -> 'a -> 'props * 'props -> bool
val cache : ('a -> 'b) -> 'a -> 'b
val useContext : 'a Context.t -> 'a
val useState : (unit -> 'state) -> 'state * (('state -> 'state) -> unit)
val useMemo : (unit -> 'a) -> 'a
val useMemo0 : (unit -> 'a) -> 'a
val useMemo1 : (unit -> 'a) -> 'b -> 'a
val useMemo2 : (unit -> 'a) -> 'b -> 'a
val useMemo3 : (unit -> 'a) -> 'b -> 'a
val useMemo4 : (unit -> 'a) -> 'b -> 'a
val useMemo5 : (unit -> 'a) -> 'b -> 'a
val useMemo6 : (unit -> 'a) -> 'b -> 'a
val useCallback : 'a -> 'a
val useCallback0 : 'a -> 'a
val useCallback1 : 'a -> 'b -> 'a
val useCallback2 : 'a -> 'b -> 'a
val useCallback3 : 'a -> 'b -> 'a
val useCallback4 : 'a -> 'b -> 'a
val useCallback5 : 'a -> 'b -> 'a
val useCallback6 : 'a -> 'b -> 'a
module Tree_context : sig
type t
val empty : t
val push : t -> total_children:int -> index:int -> t
end
val current_tree_context : Tree_context.t Stdlib.ref
(** Rendering hook context — called by the renderer before/after rendering each function component. *)
val reset_component_id_state : Tree_context.t -> unit
val check_did_render_id_hook : unit -> bool
val reset_id_rendering : ?prefix:string -> unit -> unit
val useId : unit -> string
type ('input, 'output) callback = 'input -> 'output
val useSyncExternalStore :
subscribe:((unit -> unit) -> (unit, unit) callback) -> getSnapshot:(unit -> 'snapshot) -> 'snapshot
[@@deprecated "Use useSyncExternalStoreWithServer instead"]
val useSyncExternalStoreWithServer :
subscribe:((unit -> unit) -> (unit, unit) callback) ->
getSnapshot:(unit -> 'snapshot) ->
getServerSnapshot:(unit -> 'snapshot) ->
'snapshot
val useReducer : ('state -> 'action -> 'state) -> 'state -> 'state * ('action -> unit)
val useReducerWithMapState :
('state -> 'action -> 'initialState) -> 'initialState -> ('initialState -> 'state) -> 'state * ('action -> unit)
val useEffect : (unit -> (unit -> unit) option) -> unit
val useEffect0 : (unit -> (unit -> unit) option) -> unit
val useEffect1 : (unit -> (unit -> unit) option) -> 'dependency array -> unit
val useEffect2 : (unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 -> unit
val useEffect3 : (unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 -> unit
val useEffect4 : (unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 -> unit
val useEffect5 :
(unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 * 'dependency5 -> unit
val useEffect6 :
(unit -> (unit -> unit) option) ->
'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 * 'dependency5 * 'dependency6 ->
unit
val useLayoutEffect0 : (unit -> (unit -> unit) option) -> unit
val useLayoutEffect1 : (unit -> (unit -> unit) option) -> 'dependency array -> unit
val useLayoutEffect2 : (unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 -> unit
val useLayoutEffect3 : (unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 -> unit
val useLayoutEffect4 :
(unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 -> unit
val useLayoutEffect5 :
(unit -> (unit -> unit) option) -> 'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 * 'dependency5 -> unit
val useLayoutEffect6 :
(unit -> (unit -> unit) option) ->
'dependency1 * 'dependency2 * 'dependency3 * 'dependency4 * 'dependency5 * 'dependency6 ->
unit
val setDisplayName : 'component -> string -> unit
module Children : sig
val map : element -> (element -> element) -> element
val mapWithIndex : element -> (element -> int -> element) -> element
val forEach : element -> (element -> unit) -> unit
val forEachWithIndex : element -> (element -> int -> unit) -> unit
val count : element -> int
val only : element -> element
val toArray : element -> element array
end
val suspend : 'a Lwt.t -> unit
module Experimental : sig
val usePromise : 'a Lwt.t -> 'a
val useActionState : ?permalink:string -> 'action -> 'state -> 'state * unit * bool
end
val useTransition : unit -> bool * ((unit -> unit) -> unit)
val useDebugValue : 'value -> ?format:('value -> string) -> unit
val useDeferredValue : 'value -> 'value
================================================
FILE: packages/react/src/ReactEvent.ml
================================================
include React.Event
================================================
FILE: packages/react/src/ReasonReactRouter.ml
================================================
let hash _location = ""
(* TODO: Maybe this should be implemented? *)
let path ?serverUrlString:_ () = []
(* TODO: Maybe this should be implemented? *)
let search ?serverUrlString:_ () = ""
let push (_path : string) = ()
let replace (_path : string) = ()
type url = { path : string list; hash : string; search : string }
type watcherID = unit -> unit
let url ?serverUrlString () = { path = path ?serverUrlString (); hash = hash (); search = search ?serverUrlString () }
let dangerouslyGetInitialUrl = url
let watchUrl _callback () = ()
let unwatchUrl _watcherID = ()
let useUrl ?(serverUrl : url option) () = match serverUrl with Some serverUrl -> serverUrl | None -> url ()
================================================
FILE: packages/react/src/ReasonReactRouter.mli
================================================
val push : string -> unit
(** update the url with the string path. Example: `push("/book/1")`, `push("/books#title")` *)
val replace : string -> unit
(** update the url with the string path. modifies the current history entry instead of creating a new one. Example:
`replace("/book/1")`, `replace("/books#title")` *)
type watcherID
type url = {
(* path takes window.location.path, like "/book/title/edit" and turns it into `["book", "title", "edit"]` *)
path : string list;
(* the url's hash, if any. The # symbol is stripped out for you *)
hash : string;
(* the url's query params, if any. The ? symbol is stripped out for you *)
search : string;
}
val watchUrl : (url -> unit) -> watcherID
(** start watching for URL changes. Returns a subscription token. Upon url change, calls the callback and passes it the
url record *)
(** stop watching for URL changes *)
val unwatchUrl : watcherID -> unit
(** this is marked as "dangerous" because you technically shouldn't be accessing the URL outside of watchUrl's callback
you'd read a potentially stale url, instead of the fresh one inside watchUrl.
But this helper is sometimes needed, if you'd like to initialize a page whose display/state depends on the URL,
instead of reading from it in watchUrl's callback, which you'd probably have put inside didMount (aka too late, the
page's already rendered).
So, the correct (and idiomatic) usage of this helper is to only use it in a component that's also subscribed to
watchUrl. Please see https://github.com/reasonml-community/reason-react-example/blob/master/src/todomvc/TodoItem.re
for an example. *)
val dangerouslyGetInitialUrl : ?serverUrlString:string -> unit -> url
val useUrl : ?serverUrl:url -> unit -> url
(** hook for watching url changes. * serverUrl is used for ssr. it allows you to specify the url without relying on
browser apis existing/working as expected *)
================================================
FILE: packages/react/src/dune
================================================
(library
(name react)
(wrapped false)
(public_name server-reason-react.react)
(libraries
server-reason-react.runtime
server-reason-react.js
server-reason-react.html
server-reason-react.dom
lwt
yojson))
================================================
FILE: packages/react/test/dune
================================================
(test
(name test)
(modules :standard)
(libraries
alcotest
fmt
lwt
lwt.unix
server-reason-react.react
server-reason-react.reactDom)
(preprocess
(pps server-reason-react.ppx lwt_ppx)))
================================================
FILE: packages/react/test/test.ml
================================================
let () = Alcotest.run "React" [ Test_cloneElement.tests; Test_react.tests ]
================================================
FILE: packages/react/test/test_cloneElement.ml
================================================
let equal_attrs (a1 : React.JSX.prop) (a2 : React.JSX.prop) =
match (a1, a2) with
| Bool (k1, x1, v1), Bool (k2, x2, v2) -> k1 == k2 && x1 == x2 && v1 = v2
| String (k1, x1, v1), String (k2, x2, v2) -> k1 == k2 && x1 == x2 && v1 == v2
| Style s1, Style s2 -> s1 == s2
| DangerouslyInnerHtml s1, DangerouslyInnerHtml s2 -> s1 == s2
| Event (k1, _v1), Event (k2, _v2) -> k1 == k2
| _ -> false
let equal_elements (c1 : React.element) (c2 : React.element) =
let rec equal_rec (c1 : React.element) (c2 : React.element) =
match (c1, c2) with
| Lower_case_element lc1, Lower_case_element lc2 ->
lc1.tag == lc2.tag
&& List.for_all2 equal_rec lc1.children lc2.children
&& List.for_all2 equal_attrs lc1.attributes lc2.attributes
| Upper_case_component (name1, cf1), Upper_case_component (name2, cf2) ->
name1 == name2 && equal_rec (cf1 ()) (cf2 ())
| List cl1, List cl2 -> List.for_all2 equal_rec cl1 cl2
| Array cl1, Array cl2 -> Array.for_all2 equal_rec cl1 cl2
| Text t1, Text t2 -> t1 == t2
| Fragment fl1, Fragment fl2 -> equal_rec fl1 fl2
| Empty, Empty -> true
| Static { original = original1; prerendered = _ }, Static { original = original2; prerendered = _ } ->
equal_rec original1 original2
| Writer { original = original1; emit = _ }, Writer { original = original2; emit = _ } ->
equal_rec (original1 ()) (original2 ())
| _, _ -> false
in
equal_rec c1 c2
let assert_element left right = Alcotest.(check bool) "should be equal" true (equal_elements left right)
let clone_empty () =
let element = React.createElement "div" [ React.JSX.Bool ("hidden", "hidden", true) ] [] in
assert_element element (React.cloneElement element [])
let clone_attributes () =
let element = React.createElement "div" [ React.JSX.String ("val", "val", "33") ] [] in
let expected =
React.createElement "div" [ React.JSX.String ("val", "val", "31"); React.JSX.Bool ("lola", "lola", true) ] []
in
let cloned =
React.cloneElement element [ React.JSX.Bool ("lola", "lola", true); React.JSX.String ("val", "val", "31") ]
in
assert_element cloned expected
let clone_order_attributes () =
let element = React.createElement "div" [] [] in
let expected =
React.createElement "div" [ React.JSX.String ("val", "val", "31"); React.JSX.Bool ("lola", "lola", true) ] []
in
let cloned =
React.cloneElement element [ React.JSX.Bool ("lola", "lola", true); React.JSX.String ("val", "val", "31") ]
in
assert_element cloned expected
let clone_uppercase_component_raises () =
let element = React.Upper_case_component ("MyComponent", fun () -> React.null) in
Alcotest.check_raises "cloneElement with uppercase component raises Invalid_argument"
(Invalid_argument
"React.cloneElement: cannot clone 'MyComponent'. In server-reason-react, component props are compile-time \
labelled arguments (and extending them with new props at runtime is not supported). React.cloneElement only \
works with lowercase DOM elements.") (fun () -> ignore (React.cloneElement element []))
let clone_async_component_raises () =
let element = React.Async_component ("AsyncComponent", fun () -> Lwt.return React.null) in
Alcotest.check_raises "cloneElement with async component raises Invalid_argument"
(Invalid_argument
"React.cloneElement: cannot clone 'AsyncComponent'. In server-reason-react, component props are compile-time \
labelled arguments (and extending them with new props at runtime is not supported). React.cloneElement only \
works with lowercase DOM elements.") (fun () -> ignore (React.cloneElement element []))
let clone_client_component_raises () =
let element =
React.Client_component
{
key = None;
props = [];
client = React.null;
import_module = "./MyClient.js";
import_name = "MyClientComponent";
}
in
Alcotest.check_raises "cloneElement with client component raises Invalid_argument"
(Invalid_argument
"React.cloneElement: cannot clone 'MyClientComponent'. In server-reason-react, component props are compile-time \
labelled arguments (and extending them with new props at runtime is not supported). React.cloneElement only \
works with lowercase DOM elements.") (fun () -> ignore (React.cloneElement element []))
let clone_static_unwraps () =
let original = React.createElement "div" [ React.JSX.Bool ("hidden", "hidden", true) ] [] in
let static_element = React.Static { prerendered = {||}; original } in
let cloned = React.cloneElement static_element [] in
assert_element cloned original
let clone_static_with_new_attributes () =
let original = React.createElement "div" [ React.JSX.String ("id", "id", "root") ] [] in
let static_element = React.Static { prerendered = {||}; original } in
let cloned = React.cloneElement static_element [ React.JSX.String ("class", "className", "container") ] in
let expected =
React.createElement "div"
[ React.JSX.String ("class", "className", "container"); React.JSX.String ("id", "id", "root") ]
[]
in
assert_element cloned expected
let clone_static_overrides_attributes () =
let original = React.createElement "span" [ React.JSX.String ("id", "id", "old") ] [] in
let static_element = React.Static { prerendered = {||}; original } in
let cloned = React.cloneElement static_element [ React.JSX.String ("id", "id", "new") ] in
let expected = React.createElement "span" [ React.JSX.String ("id", "id", "new") ] [] in
assert_element cloned expected
let clone_static_result_is_not_static () =
let original = React.createElement "div" [] [] in
let static_element = React.Static { prerendered = ""; original } in
let cloned = React.cloneElement static_element [] in
match cloned with
| React.Lower_case_element _ -> ()
| React.Static _ -> Alcotest.fail "cloneElement on Static should return a Lower_case_element, not Static"
| _ -> Alcotest.fail "cloneElement on Static should return a Lower_case_element"
let clone_static_preserves_children () =
let children = [ React.createElement "span" [] []; React.string "hello" ] in
let original = React.createElement "div" [ React.JSX.String ("id", "id", "parent") ] children in
let static_element = React.Static { prerendered = {|
hello
|}; original } in
let cloned = React.cloneElement static_element [ React.JSX.String ("class", "className", "wrapper") ] in
let expected =
React.createElement "div"
[ React.JSX.String ("class", "className", "wrapper"); React.JSX.String ("id", "id", "parent") ]
children
in
assert_element cloned expected
let clone_nested_static () =
let inner_original = React.createElement "p" [ React.JSX.String ("id", "id", "inner") ] [] in
let inner_static = React.Static { prerendered = {||}; original = inner_original } in
let outer_original = React.createElement "div" [] [ inner_static ] in
let outer_static = React.Static { prerendered = {|
|}; original = outer_original } in
let cloned = React.cloneElement outer_static [ React.JSX.Bool ("hidden", "hidden", true) ] in
let expected = React.createElement "div" [ React.JSX.Bool ("hidden", "hidden", true) ] [ inner_static ] in
assert_element cloned expected
let case title fn = Alcotest.test_case title `Quick fn
let tests =
( "cloneElement",
[
case "empty component" clone_empty;
case "attributes component" clone_attributes;
case "ordered attributes component" clone_order_attributes;
case "uppercase component raises" clone_uppercase_component_raises;
case "async component raises" clone_async_component_raises;
case "client component raises" clone_client_component_raises;
case "static unwraps to original" clone_static_unwraps;
case "static adds new attributes" clone_static_with_new_attributes;
case "static overrides existing attributes" clone_static_overrides_attributes;
case "static result is Lower_case_element not Static" clone_static_result_is_not_static;
case "static preserves children" clone_static_preserves_children;
case "static nested static unwraps outer only" clone_nested_static;
] )
================================================
FILE: packages/react/test/test_react.ml
================================================
let test title fn = Alcotest.test_case title `Quick fn
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let assert_int left right = Alcotest.check Alcotest.int "should be equal" right left
let use_state_doesnt_fire () =
let app =
React.Upper_case_component
( "app",
fun () ->
let state, setState = React.useState (fun () -> "foo") in
(* You wouldn't have this code in prod, but just for testing purposes *)
setState (fun _prev -> "bar");
React.createElement "div" [] [ React.string state ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "
foo
"
let use_sync_external_store_with_server () =
let app =
React.Upper_case_component
( "app",
fun () ->
let value =
React.useSyncExternalStoreWithServer
~getServerSnapshot:(fun () -> "foo")
~subscribe:(fun _ () -> ())
~getSnapshot:(fun _ -> "bar")
in
React.createElement "div" [] [ React.string value ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "
foo
"
let use_effect_doesnt_fire () =
let app =
React.Upper_case_component
( "app",
fun () ->
let ref = React.useRef "foo" in
React.useEffect0 (fun () ->
ref.current <- "bar";
None);
React.createElement "div" [] [ React.string ref.current ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "
foo
"
module Gap = struct
let make ~children =
React.Children.map children (fun element ->
if element = React.null then React.null
else React.createElement "div" [ React.JSX.String ("class", "className", "divider") ] [ element ])
end
let children_map_one_element () =
let app = React.Upper_case_component ("app", fun () -> Gap.make ~children:(React.string "foo")) in
assert_string (ReactDOM.renderToStaticMarkup app) "
foo
"
let children_map_list_element () =
let app =
React.Upper_case_component
("app", fun () -> Gap.make ~children:(React.list [ React.string "foo"; React.string "lola" ]))
in
assert_string (ReactDOM.renderToStaticMarkup app) "
foo
lola
"
let use_ref_works () =
let app =
React.Upper_case_component
( "app",
fun () ->
let isLive = React.useRef true in
React.useEffect0 (fun () ->
isLive.current <- false;
None);
React.createElement "span" [] [ React.string (string_of_bool isLive.current) ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "true"
let invalid_children () =
let raises () =
let _ = React.createElement "input" [ React.JSX.String ("type", "type", "text") ] [ React.string "Hellow" ] in
()
in
Alcotest.check_raises "Expected invalid argument"
(React.Invalid_children {|"input" is a self-closing tag and must not have "children".\n|})
raises
let invalid_dangerouslySetInnerHtml () =
let raises () =
let _ =
React.createElement "meta"
[ React.JSX.String ("char-set", "charSet", "utf-8"); React.JSX.DangerouslyInnerHtml "Hellow" ]
[]
in
()
in
Alcotest.check_raises "Expected invalid argument"
(React.Invalid_children {|"meta" is a self-closing tag and must not have "dangerouslySetInnerHTML".\n|})
raises
let raw_element () =
let original = React.createElement "div" [] [ React.string "Hello" ] in
let app = React.Upper_case_component ("app", fun () -> React.Static { prerendered = "
Hello
"; original }) in
assert_string (ReactDOM.renderToStaticMarkup app) "
Hello
"
let cache_hits_within_request () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
value ^ "-ok")
in
React.Cache.with_request_cache (fun () ->
assert_string (cached "a") "a-ok";
assert_string (cached "a") "a-ok");
assert_int !calls 1
let cache_error_is_cached () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
if value = "boom" then raise (Failure "boom");
"ok")
in
let raises () =
let _ = cached "boom" in
()
in
React.Cache.with_request_cache (fun () ->
Alcotest.check_raises "cache error" (Failure "boom") raises;
Alcotest.check_raises "cache error" (Failure "boom") raises);
assert_int !calls 1
let cache_separate_per_call () =
let calls = ref 0 in
let make_cached () =
React.cache (fun value ->
calls := !calls + 1;
value + 1)
in
let cached1 = make_cached () in
let cached2 = make_cached () in
React.Cache.with_request_cache (fun () ->
ignore (cached1 1);
ignore (cached1 1);
ignore (cached2 1));
assert_int !calls 2
let cache_resets_between_requests () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
value)
in
React.Cache.with_request_cache (fun () ->
ignore (cached "a");
ignore (cached "a"));
React.Cache.with_request_cache (fun () -> ignore (cached "a"));
assert_int !calls 2
let cache_error_different_args () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
if value = "boom1" then raise (Failure "boom1");
if value = "boom2" then raise (Failure "boom2");
"ok")
in
let raises1 () = ignore (cached "boom1") in
let raises2 () = ignore (cached "boom2") in
React.Cache.with_request_cache (fun () ->
Alcotest.check_raises "first error" (Failure "boom1") raises1;
Alcotest.check_raises "second error" (Failure "boom2") raises2;
Alcotest.check_raises "first error cached" (Failure "boom1") raises1;
Alcotest.check_raises "second error cached" (Failure "boom2") raises2);
assert_int !calls 2
let cache_error_mixed_with_success () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
if value = "boom" then raise (Failure "boom");
value ^ "-ok")
in
let raises () = ignore (cached "boom") in
React.Cache.with_request_cache (fun () ->
assert_string (cached "good") "good-ok";
Alcotest.check_raises "error" (Failure "boom") raises;
assert_string (cached "good") "good-ok";
Alcotest.check_raises "error cached" (Failure "boom") raises);
assert_int !calls 2
let cache_error_resets_between_requests () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
calls := !calls + 1;
if value = "boom" then raise (Failure "boom");
"ok")
in
let raises () = ignore (cached "boom") in
React.Cache.with_request_cache (fun () ->
Alcotest.check_raises "first request error" (Failure "boom") raises;
Alcotest.check_raises "first request error cached" (Failure "boom") raises);
React.Cache.with_request_cache (fun () -> Alcotest.check_raises "second request error" (Failure "boom") raises);
assert_int !calls 2
let cache_error_same_instance () =
let original_exn = Failure "unique" in
let cached_exn = ref None in
let cached = React.cache (fun () -> raise original_exn) in
let capture_exn () = try ignore (cached ()) with exn -> cached_exn := Some exn in
React.Cache.with_request_cache (fun () ->
capture_exn ();
let first = !cached_exn in
cached_exn := None;
capture_exn ();
let second = !cached_exn in
match (first, second) with
| Some e1, Some e2 ->
Alcotest.(check bool) "same exception instance" true (e1 == e2);
Alcotest.(check bool) "is original exception" true (e1 == original_exn)
| _ -> Alcotest.fail "expected exceptions to be captured")
let lwt_test title fn = Alcotest.test_case title `Quick (fun () -> Lwt_main.run (fn ()))
let cache_async_hits_within_request () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
incr calls;
value ^ "-ok")
in
React.Cache.with_request_cache_async (fun () ->
assert_string (cached "a") "a-ok";
let%lwt () = Lwt.pause () in
assert_string (cached "a") "a-ok";
Lwt.return ());%lwt
assert_int !calls 1;
Lwt.return ()
let cache_async_resets_between_requests () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
incr calls;
value)
in
React.Cache.with_request_cache_async (fun () ->
ignore (cached "a");
ignore (cached "a");
Lwt.return ());%lwt
React.Cache.with_request_cache_async (fun () ->
ignore (cached "a");
Lwt.return ());%lwt
assert_int !calls 2;
Lwt.return ()
let cache_async_concurrent_isolation () =
let calls = ref 0 in
let cached =
React.cache (fun value ->
incr calls;
value ^ "-ok")
in
let p1 =
React.Cache.with_request_cache_async (fun () ->
assert_string (cached "a") "a-ok";
(* Yield to scheduler — lets p2 run and install its own cache *)
let%lwt () = Lwt.pause () in
(* Must still hit p1's cache, not p2's *)
assert_string (cached "a") "a-ok";
Lwt.return ())
in
let p2 =
React.Cache.with_request_cache_async (fun () ->
(* Independent cache — fresh call even though p1 already cached "a" *)
assert_string (cached "a") "a-ok";
Lwt.return ())
in
Lwt.join [ p1; p2 ];%lwt
(* 2 calls total: one per request, each caching "a" independently *)
assert_int !calls 2;
Lwt.return ()
let cache_async_no_cross_request_leaking () =
let cached = React.cache (fun value -> value ^ "-ok") in
let seen_in_p2 = ref "" in
let p1 =
React.Cache.with_request_cache_async (fun () ->
ignore (cached "from-p1");
let%lwt () = Lwt.pause () in
Lwt.return ())
in
let p2 =
React.Cache.with_request_cache_async (fun () ->
(* p2 should compute its own value, not see p1's cached result *)
seen_in_p2 := cached "from-p2";
Lwt.return ())
in
Lwt.join [ p1; p2 ];%lwt
assert_string !seen_in_p2 "from-p2-ok";
Lwt.return ()
let tests =
( "React",
[
test "useState" use_state_doesnt_fire;
test "useSyncExternalStoreWithServer" use_sync_external_store_with_server;
test "useEffect" use_effect_doesnt_fire;
test "Children.map" children_map_one_element;
test "Children.map" children_map_list_element;
test "useRef" use_ref_works;
test "invalid_children" invalid_children;
test "invalid_dangerouslySetInnerHtml" invalid_dangerouslySetInnerHtml;
test "raw_element" raw_element;
test "cache hits within request" cache_hits_within_request;
test "cache errors are cached" cache_error_is_cached;
test "cache is separate per call" cache_separate_per_call;
test "cache resets between requests" cache_resets_between_requests;
test "cache errors different args" cache_error_different_args;
test "cache errors mixed with success" cache_error_mixed_with_success;
test "cache errors reset between requests" cache_error_resets_between_requests;
test "cache errors same instance" cache_error_same_instance;
lwt_test "cache async hits within request" cache_async_hits_within_request;
lwt_test "cache async resets between requests" cache_async_resets_between_requests;
lwt_test "cache async concurrent isolation" cache_async_concurrent_isolation;
lwt_test "cache async no cross-request leaking" cache_async_no_cross_request_leaking;
] )
================================================
FILE: packages/react-server-dom-esbuild/ReactServerDOMEsbuild.js
================================================
/*
* This file is a bundler integration between react (react-client/flight), esbuild and server-reason-react.
*
* React's Flight client (`react-client/flight`) is a factory function that accepts a `$$$config`
* object with bundler-specific implementations. Each official React integration (webpack, parcel, etc.)
* provides its own config. This file is the esbuild-specific config for server-reason-react.
*
* The `$$$config` object is composed from three groups of options plus renderer metadata:
* 1. **Stream config** — how to decode binary chunks from the RSC stream into strings.
* 2. **Console config** — how to replay server-side console logs on the client (dev only).
* 3. **Bundler config** — how to resolve and load client/server modules at runtime.
* 4. **Renderer metadata** — version and package name for React DevTools integration.
*
* Similar resources:
* - **react-server-dom-webpack**: https://github.com/facebook/react/blob/5c56b873efb300b4d1afc4ba6f16acf17e4e5800/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js#L156-L194
* - **react-server-dom-parcel**: https://github.com/facebook/react/pull/31725
*
* ## Why `@pedrobslisboa/react-client`?
*
* React's `react-client` package (which provides the Flight protocol deserializer)
* is an internal package that is NOT published to npm by the React team.
* It is only consumed internally by React's own bundler integrations (webpack, parcel, esm).
*
* Since server-reason-react needs a custom esbuild integration, and `react-client`
* is the intended extension point (via the `$$$config` injection pattern), Pedro
* (a core contributor to server-reason-react) republished the package under
* `@pedrobslisboa/react-client` so this project can use the Flight client factory directly.
*/
import ReactClientFlight from "@pedrobslisboa/react-client/flight";
const isDebug = false;
const debug = (...args) => {
if (isDebug && process.env.NODE_ENV === "development") {
console.log(...args);
}
};
/*
* Stream config — tells the Flight client how to decode binary chunks into strings.
*
* These three functions are called during `processBinaryChunk` to turn raw
* `Uint8Array` buffers from the ReadableStream into string content that the
* Flight protocol parser can process.
*/
const ReactFlightClientStreamConfigWeb = {
/*
* Creates a TextDecoder instance used for all subsequent string decoding.
* Stored as `response._stringDecoder` on the Flight response object.
*/
createStringDecoder() {
return new TextDecoder();
},
/*
* Decodes a partial binary chunk in streaming mode (`{ stream: true }`).
* Called for every buffer segment except the last one in a row.
* The `stream: true` option prevents the decoder from flushing incomplete
* multi-byte characters, allowing them to be completed by subsequent chunks.
*/
readPartialStringChunk(decoder, buffer) {
return decoder.decode(buffer, { stream: true });
},
/*
* Decodes the final binary chunk of a row (without `stream: true`).
* This flushes any remaining bytes in the decoder's internal buffer.
*/
readFinalStringChunk(decoder, buffer) {
return decoder.decode(buffer);
},
};
const badgeFormat = "%c%s%c ";
// Same badge styling as DevTools.
const badgeStyle =
// We use a fixed background if light-dark is not supported, otherwise
// we use a transparent background.
"background: #e6e6e6;" +
"background: light-dark(rgba(0,0,0,0.1), rgba(255,255,255,0.25));" +
"color: #000000;" +
"color: light-dark(#000000, #ffffff);" +
"border-radius: 2px";
const resetStyle = "";
const pad = " ";
const bind = Function.prototype.bind;
/*
* Console config — tells the Flight client how to replay server-side console
* logs on the client with a badge indicating the server environment.
*
* In production builds, `bindToConsole` is extracted from the config but
* never actually called (dead code). In development, it is called for each
* replayed server console message with the method name, args, and environment
* badge name.
*/
const ReactClientConsoleConfigBrowser = {
/*
* Wraps a console method call with badge formatting so that replayed
* server logs appear with a visual tag (e.g., "[Server]") in the browser console.
*
* @param methodName - The console method (e.g., "log", "warn", "error", "assert")
* @param args - The original arguments passed to the console method on the server
* @param badgeName - The environment name to display as a badge (e.g., "Server")
* @returns A bound console function ready to be called
*/
bindToConsole(methodName, args, badgeName) {
let offset = 0;
switch (methodName) {
case "dir":
case "dirxml":
case "groupEnd":
case "table": {
// These methods cannot be colorized because they don't take a formatting string.
return bind.apply(console[methodName], [console].concat(args));
}
case "assert": {
// assert takes formatting options as the second argument.
offset = 1;
}
}
const newArgs = args.slice(0);
if (typeof newArgs[offset] === "string") {
newArgs.splice(
offset,
1,
badgeFormat + newArgs[offset],
badgeStyle,
pad + badgeName + pad,
resetStyle
);
} else {
newArgs.splice(
offset,
0,
badgeFormat,
badgeStyle,
pad + badgeName + pad,
resetStyle
);
}
// The "this" binding in the "bind";
newArgs.unshift(console);
return bind.apply(console[methodName], newArgs);
},
};
/* Indices into the metadata tuple returned by the RSC stream for client component references. */
const ID = 0;
const NAME = 1;
const BUNDLES = 2;
/*
* Bundler config — tells the Flight client how to resolve and load modules.
*
* These functions bridge between the abstract module references in the RSC
* stream and the actual runtime modules available in the browser. In the
* esbuild integration, client components and server functions are registered
* in global manifest maps (`window.__client_manifest_map` and
* `window.__server_functions_manifest_map`) by the esbuild build plugin.
*/
const ReactFlightClientConfigBundlerEsbuild = {
/*
* Called when the Flight client encounters a client module reference in the stream.
* Allows the integration to initiate loading of scripts/stylesheets needed by the module.
*
* In the esbuild integration this is a no-op because all client bundles are
* already loaded via script tags — there's no dynamic chunk loading.
*
* @param moduleLoading - The `moduleLoading` config passed to `createResponse` (null for esbuild)
* @param nonce - CSP nonce for script injection (undefined for esbuild)
* @param metadata - The parsed module metadata from the RSC stream
*/
prepareDestinationForModule(moduleLoading, nonce, metadata) {
debug("prepareDestinationForModule", moduleLoading, nonce, metadata);
return;
},
/*
* Called to resolve a client component reference from the RSC stream into
* a bundler-specific reference object. The returned object is later passed
* to `preloadModule` and `requireModule`.
*
* In the esbuild integration, metadata comes as a tuple [id, name, bundles]
* and we restructure it into a typed object.
*
* @param bundlerConfig - The `bundlerConfig` passed to `createResponse` (null for esbuild)
* @param metadata - The serialized module reference from the RSC stream [id, name, bundles]
* @returns An object with { type, id, name, bundles } used by `requireModule`
*/
resolveClientReference(bundlerConfig, metadata) {
debug("resolveClientReference", bundlerConfig, metadata);
// Reference is already resolved during the build
return {
type: "ClientComponent",
id: metadata[ID],
name: metadata[NAME],
bundles: metadata[BUNDLES],
};
},
/*
* Called to resolve a server function reference from the RSC stream.
* Only called when `serverReferenceConfig` (second arg to `createResponse`)
* is truthy. When falsy, server references fall back to `createBoundServerReference`
* which uses `callServer` directly.
*
* @param bundlerConfig - The `serverReferenceConfig` passed to `createResponse` ({} for esbuild)
* @param ref - The server reference ID string from the RSC stream
* @returns An object with { type, id } used by `requireModule`
*/
resolveServerReference(bundlerConfig, ref) {
debug("resolveServerReference", bundlerConfig, ref);
return {
type: "ServerFunction",
id: ref,
};
},
/*
* Called to optionally preload a module before it's required. Should return
* a thenable/promise if async loading is needed, or a falsy value if the
* module is already available synchronously.
*
* In the esbuild integration this always returns undefined because all modules
* are pre-loaded via the global manifest maps.
*
* @param metadata - The resolved reference from `resolveClientReference` or `resolveServerReference`
* @returns undefined (no preloading needed)
*/
preloadModule(metadata) {
debug("preloadModule", metadata);
/* TODO: Does it make sense to preload a module in esbuild? */
return undefined;
},
/*
* Called to synchronously obtain the actual module export (component or function).
* This is the final step — the returned value is what React will render or invoke.
*
* Looks up modules from two global manifest maps populated by the esbuild build plugin:
* - `window.__client_manifest_map` — maps client component IDs to their React components
* - `window.__server_functions_manifest_map` — maps server function IDs to their callable functions
*
* @param metadata - The resolved reference with { type, id } from resolve*Reference
* @returns The actual React component or server function
* @throws If the module is not found in the manifest
*/
requireModule(metadata) {
const getModule = (type, id) => {
switch (type) {
case "ServerFunction":
const fn = window.__server_functions_manifest_map[id];
return fn;
case "ClientComponent":
const component = window.__client_manifest_map[id];
return component
}
}
const module = getModule(metadata.type, metadata.id);
if (!module) {
throw new Error(`Could not find module of type ${metadata.type} with id: ${metadata.id}`);
}
return module
},
};
/*
* The assembled config object passed to `ReactClientFlight($$$config)`.
*
* Combines all three config groups plus renderer metadata. The Flight client
* destructures this to extract each function/value at module initialization time.
*
* TODO: Can we use the real thing, instead of mocks/vendored code here?
*/
const ReactServerDOMEsbuildConfig = {
...ReactFlightClientStreamConfigWeb,
...ReactClientConsoleConfigBrowser,
...ReactFlightClientConfigBundlerEsbuild,
/* Reported to React DevTools via `__REACT_DEVTOOLS_GLOBAL_HOOK__` for identification. */
rendererVersion: "19.1.0",
/* Reported to React DevTools via `__REACT_DEVTOOLS_GLOBAL_HOOK__` for identification. */
rendererPackageName: "react-server-dom-esbuild",
/*
* Indicates this Flight client is used with SSR. Currently extracted from the config
* but NOT read by the `react-client/flight` internals — it has no runtime effect.
* May be a forward-looking property for future React versions.
*/
usedWithSSR: true,
};
/*
* Initialize the Flight client with our esbuild-specific config.
* This returns an object with the core Flight protocol functions.
*/
const {
/* Creates a new Flight response object that accumulates streamed RSC data. */
createResponse,
/* Creates a reference to a server function that can be called from the client. */
createServerReference: createServerReferenceImpl,
/* Serializes a value (e.g., server action arguments) into a format suitable for sending to the server. */
processReply,
/* Returns the root promise of a Flight response — resolves to the React element tree. */
getRoot,
/* Reports a top-level error to all pending chunks in the response. */
reportGlobalError,
/* Processes a binary chunk from the ReadableStream into the Flight response. */
processBinaryChunk,
/* Creates a stream state object used to track binary chunk processing. */
createStreamState,
/* Signals that the stream is complete and no more chunks will arrive. */
close,
} = ReactClientFlight(ReactServerDOMEsbuildConfig);
/*
* Reads from a ReadableStream and feeds binary chunks into the Flight response.
* Continues reading until the stream is done, then closes the response.
*/
function startReadingFromStream(response, stream) {
const streamState = createStreamState();
const reader = stream.getReader();
function progress({
done,
value,
}) {
if (done) {
close(response);
return;
}
const buffer = value;
processBinaryChunk(response, streamState, buffer);
return reader.read().then(progress).catch(error);
}
function error(e) {
reportGlobalError(response, e);
}
reader.read().then(progress).catch(error);
}
/*
* Wraps `callServer` to provide a helpful error if no callback was registered.
* The returned function is passed as the `callServer` parameter to `createResponse`.
*/
function callCurrentServerCallback(callServer) {
return function (id, args) {
if (!callServer) {
throw new Error(
"No server callback has been registered. Call setServerCallback to register one."
);
}
return callServer(id, args);
};
}
/*
* Public API: Creates a Flight response from a ReadableStream.
* Returns a thenable that resolves to the deserialized React element tree.
*
* @param stream - A ReadableStream containing the RSC payload
* @param options - Optional config: { callServer, temporaryReferences }
*/
export function createFromReadableStream(stream, options) {
const response = createResponseFromOptions(options);
startReadingFromStream(response, stream);
return getRoot(response);
}
/*
* Internal helper to create a Flight response object from user-provided options.
*
* Maps the public API options to the internal `createResponse` parameters.
*
* Parameters to `createResponse`:
* 1. bundlerConfig — null: client references are pre-resolved at build time by esbuild
* 2. serverReferenceConfig — {}: truthy but empty, forces the `resolveServerReference` code path
* (when null/falsy, server refs fall back to `createBoundServerReference` using only `callServer`)
* 3. moduleLoading — null: no dynamic module loading config needed (scripts are pre-loaded)
* 4. callServer — callback invoked when a server action is called from the client
* 5. encodeFormAction — undefined: no custom form action encoding (uses default)
* 6. nonce — undefined: no CSP nonce needed for script injection
* 7. temporaryReferences — allows objects to be passed by reference between server/client
* 8. findSourceMapURL — undefined: no source map resolution (DEV only)
* 9. replayConsoleLogs — false: server console log replay is disabled
* 10. environmentName — undefined: no custom environment badge name (DEV only, defaults to "Server")
*/
function createResponseFromOptions(options) {
let response = createResponse(
null, // bundlerConfig
{}, // serverReferenceConfig, this is the manifest that can contain configs related to server functions. It requires it to not be null, to run resolveServerReference
null, // moduleLoading
callCurrentServerCallback(options ? options.callServer : undefined),
undefined, // encodeFormAction
undefined, // nonce
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
false /* __DEV__ ? (options ? options.replayConsoleLogs !== false : true) */,
undefined /* __DEV__ && options && options.environmentName
? options.environmentName
: undefined */
);
return response;
}
/*
* Public API: Creates a Flight response from a fetch() promise.
* Waits for the fetch to resolve, then reads the response body as a stream.
*
* @param promise - A Promise (e.g., from `fetch("/rsc")`)
* @param options - Optional config: { callServer, temporaryReferences }
*/
export function createFromFetch(promise, options) {
const response = createResponseFromOptions(options);
promise.then(
function (r) {
startReadingFromStream(response, r.body);
},
function (e) {
reportGlobalError(response, e);
}
);
return getRoot(response);
}
/*
* Public API: Re-export of `createServerReference` from the Flight client.
* Creates a callable reference to a server function identified by its ID.
*/
export const createServerReference = createServerReferenceImpl;
/*
* Public API: Serializes a value into a format the server can decode.
* Used to encode arguments when calling server actions.
*
* @param value - The value to serialize (can include React elements, FormData, etc.)
* @param options.temporaryReferences - Optional map for temporary references
* @param options.signal - Optional AbortSignal to cancel the encoding
* @returns A Promise that resolves to the serialized form (string or FormData)
*/
export const encodeReply = (
value,
options = { temporaryReferences: undefined, signal: undefined }
) => {
return new Promise((resolve, reject) => {
const abort = processReply(
value,
"",
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
resolve,
reject
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(signal.reason);
} else {
const listener = () => {
abort(signal.reason);
signal.removeEventListener("abort", listener);
};
signal.addEventListener("abort", listener);
}
}
});
};
================================================
FILE: packages/react-server-dom-esbuild/ReactServerDOMEsbuild.re
================================================
type arg;
type callServer = (string, list(arg)) => Js.Promise.t(React.element);
type options = {callServer};
[@mel.module "./ReactServerDOMEsbuild.js"]
external createFromReadableStreamImpl:
(Webapi.ReadableStream.t, ~options: options=?, unit) => Js.Promise.t('a) =
"createFromReadableStream";
[@mel.module "./ReactServerDOMEsbuild.js"]
external createFromFetchImpl:
(Js.Promise.t(Fetch.response), ~options: options=?, unit) =>
Js.Promise.t('a) =
"createFromFetch";
[@mel.module "./ReactServerDOMEsbuild.js"]
external createServerReferenceImpl:
(
string, // ServerReferenceId
callServer,
// EncodeFormActionCallback (optional) (We're not using this right now)
option('encodeFormActionCallback),
// FindSourceMapURLCallback (optional, DEV-only) (We're not using this right now)
option('findSourceMapURLCallback),
// functionName (optional)
option(string)
) =>
// actionCallback is a function that takes N arguments and returns a promise
// As we don't have control over the number of arguments, we need to pass it as 'actionCallback
'action =
"createServerReference";
[@mel.module "./ReactServerDOMEsbuild.js"]
external encodeReply: list('arg) => Js.Promise.t(string) = "encodeReply";
/* let callServerRef: ref(option(callServer('arg, 'result))) = ref(None); */
let callServerRef: ref(option(callServer)) = ref(None);
let setCallServer = callServer => {
callServerRef := Some(callServer);
};
let getCallServer = () => {
callServerRef^;
};
let createFromReadableStream = (~callServer=?, stream): Js.Promise.t('a) => {
switch (callServer) {
| Some(callServer) =>
setCallServer(callServer);
createFromReadableStreamImpl(
stream,
~options={ callServer: callServer },
(),
);
| None => createFromReadableStreamImpl(stream, ())
};
};
let createFromFetch = (~callServer=?, promise) => {
switch (callServer) {
| Some(callServer) =>
setCallServer(callServer);
createFromFetchImpl(promise, ~options={ callServer: callServer }, ());
| None => createFromFetchImpl(promise, ())
};
};
let createServerReference = serverReferenceId => {
let callServer =
switch (getCallServer()) {
| Some(callServer) => callServer
| None =>
raise(
Invalid_argument(
"No callServer has been set, you are trying to create a server function without passing callServer to createFromFetch or createFromReadableStream",
),
)
};
createServerReferenceImpl(serverReferenceId, callServer, None, None, None);
};
================================================
FILE: packages/react-server-dom-esbuild/dune
================================================
(library
(name ReactServerDOMEsbuild)
(modules ReactServerDOMEsbuild)
(modes melange)
(public_name server-reason-react.react-server-dom-esbuild)
(libraries reason-react melange-webapi melange-fetch)
(melange.runtime_deps ReactServerDOMEsbuild.js)
(preprocess
(pps melange.ppx)))
================================================
FILE: packages/react-server-dom-esbuild/package.json
================================================
{
"name": "server-reason-react-server-dom-esbuild",
"description": "React Server DOM with esbuild. This is intended to be integrated with server-reason-react.",
"version": "0.1.0",
"exports": {
".": "./ReactServerDOMEsbuild.js"
},
"homepage": "https://github.com/ml-in-barcelona/server-reason-react",
"bugs": "https://github.com/ml-in-barcelona/server-reason-react/issues",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ml-in-barcelona/server-reason-react.git",
"directory": "packages/react-server-dom-esbuild"
},
"dependencies": {
"@pedrobslisboa/react-client": "^19.1.0",
"esbuild": "^0.21.4"
}
}
================================================
FILE: packages/reactDom/src/Push_stream.ml
================================================
let make () =
let stream, push_to_stream = Lwt_stream.create () in
let push v = push_to_stream (Some v) in
let close () = push_to_stream None in
(stream, push, close)
let subscribe ~fn stream = Lwt_stream.iter_s fn stream
================================================
FILE: packages/reactDom/src/ReactDOM.ml
================================================
module Style = ReactDOMStyle
module Ref = React.Ref
type domRef = Ref.t
let is_react_custom_attribute attr =
match attr with
| "dangerouslySetInnerHTML" | "ref" | "key" | "suppressContentEditableWarning" | "suppressHydrationWarning" -> true
| _ -> false
let write_attribute_to_buffer buf (attr : React.JSX.prop) =
match attr with
(* ignores "ref" prop *)
| Ref _ -> ()
(* react custom attributes are not rendered *)
| Bool (name, _, _) when is_react_custom_attribute name -> ()
(* false attributes don't get rendered *)
| Bool (_name, _, false) -> ()
(* true attributes render solely the attribute name *)
| Bool (name, _, true) ->
Buffer.add_char buf ' ';
Buffer.add_string buf name
| Action (_, _, _) -> ()
| Style styles ->
Buffer.add_string buf " style=\"";
Style.write_to_buffer buf styles;
Buffer.add_char buf '"'
| String (name, _, _value) when is_react_custom_attribute name -> ()
| String (name, _, value) ->
Buffer.add_char buf ' ';
Buffer.add_string buf name;
Buffer.add_string buf "=\"";
Html.escape buf value;
Buffer.add_char buf '"'
(* Events don't get rendered on SSR *)
| Event _ -> ()
(* Since we extracted the attribute as children, we are sure there's nothing to render here *)
| DangerouslyInnerHtml _ -> ()
let write_attributes_and_extract_inner_html buf attrs =
let inner_html = ref None in
List.iter
(fun (attr : React.JSX.prop) ->
match attr with DangerouslyInnerHtml str -> inner_html := Some str | _ -> write_attribute_to_buffer buf attr)
attrs;
!inner_html
let attribute_to_html (attr : React.JSX.prop) =
match attr with
(* ignores "ref" prop *)
| Ref _ -> Html.omitted ()
(* react custom attributes are not rendered *)
| Bool (name, _, _) when is_react_custom_attribute name -> Html.omitted ()
(* false attributes don't get rendered *)
| Bool (_name, _, false) -> Html.omitted ()
(* true attributes render solely the attribute name *)
| Bool (name, _, true) -> Html.present name
| Action (_, _, _) -> Html.omitted ()
| Style styles -> Html.attribute "style" (ReactDOMStyle.to_string styles)
| String (name, _, _value) when is_react_custom_attribute name -> Html.omitted ()
| String (name, _, value) -> Html.attribute name value
(* Events don't get rendered on SSR *)
| Event _ -> Html.omitted ()
(* Since we extracted the attribute as children, we are sure there's nothing to render here *)
| DangerouslyInnerHtml _ -> Html.omitted ()
let attributes_to_html attrs = List.map attribute_to_html attrs
let getDangerouslyInnerHtml attributes =
List.find_map (function React.JSX.DangerouslyInnerHtml str -> Some str | _ -> None) attributes
let render_upper_case_component render_element component =
let saved_ctx = !React.current_tree_context in
React.reset_component_id_state saved_ctx;
match component () with
| result -> (
let did_use_id = React.check_did_render_id_hook () in
if did_use_id then React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:1 ~index:0;
match render_element result with
| () -> React.current_tree_context := saved_ctx
| exception exn ->
React.current_tree_context := saved_ctx;
raise exn)
| exception exn ->
React.current_tree_context := saved_ctx;
raise exn
let render_children_list render_element list =
match list with
| [] -> ()
| [ single ] -> render_element single
| _ -> (
let saved_ctx = !React.current_tree_context in
match
let total = List.length list in
List.iteri
(fun i el ->
React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:total ~index:i;
render_element el)
list
with
| () -> React.current_tree_context := saved_ctx
| exception exn ->
React.current_tree_context := saved_ctx;
raise exn)
let render_children_array render_element arr =
let total = Array.length arr in
if total = 0 then ()
else if total = 1 then render_element (Array.unsafe_get arr 0)
else
let saved_ctx = !React.current_tree_context in
match
for i = 0 to total - 1 do
React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:total ~index:i;
render_element (Array.unsafe_get arr i)
done
with
| () -> React.current_tree_context := saved_ctx
| exception exn ->
React.current_tree_context := saved_ctx;
raise exn
let render_upper_case_component_lwt render_element component =
let saved_ctx = !React.current_tree_context in
React.reset_component_id_state saved_ctx;
let result =
try component ()
with exn ->
React.current_tree_context := saved_ctx;
raise_notrace exn
in
let did_use_id = React.check_did_render_id_hook () in
if did_use_id then React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:1 ~index:0;
try%lwt
let%lwt () = render_element result in
React.current_tree_context := saved_ctx;
Lwt.return ()
with exn ->
React.current_tree_context := saved_ctx;
raise exn
let render_children_list_lwt render_element list =
match list with
| [] -> Lwt.return ()
| [ single ] -> render_element single
| _ -> (
let saved_ctx = !React.current_tree_context in
try%lwt
let total = List.length list in
let%lwt () =
Lwt_list.iteri_s
(fun i el ->
React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:total ~index:i;
render_element el)
list
in
React.current_tree_context := saved_ctx;
Lwt.return ()
with exn ->
React.current_tree_context := saved_ctx;
raise exn)
let render_children_array_lwt render_element arr =
let total = Array.length arr in
if total = 0 then Lwt.return ()
else if total = 1 then render_element (Array.unsafe_get arr 0)
else
let saved_ctx = !React.current_tree_context in
let rec loop i =
if i >= total then Lwt.return ()
else (
React.current_tree_context := React.Tree_context.push saved_ctx ~total_children:total ~index:i;
let%lwt () = render_element (Array.unsafe_get arr i) in
loop (i + 1))
in
try%lwt
let%lwt () = loop 0 in
React.current_tree_context := saved_ctx;
Lwt.return ()
with exn ->
React.current_tree_context := saved_ctx;
raise exn
type mode = String | Markup
let render_to_buffer ~mode buf element =
let add_separator_between_text_nodes = mode = String in
let previous_node_was_text = ref false in
let should_add_doctype = ref true in
let rec render_element ~buf element =
match (element : React.element) with
| Empty -> ()
| Static { prerendered; _ } ->
should_add_doctype := false;
Buffer.add_string buf prerendered
| Writer { emit; _ } ->
should_add_doctype := false;
emit buf
| Client_component { import_module; _ } ->
raise
(Invalid_argument
("Client components can't be rendered on the server via renderToString or renderToStaticMarkup. Please \
use the React server components API instead. module: " ^ import_module))
| Provider { children; push; _ } ->
let pop = push () in
render_element ~buf children;
pop ()
| Consumer children -> render_element ~buf children
| Fragment children -> render_element ~buf children
| List list -> render_children_list (render_element ~buf) list
| Array arr -> render_children_array (render_element ~buf) arr
| Upper_case_component (_, component) -> render_upper_case_component (render_element ~buf) component
| Async_component (_name, _component) ->
raise
(Invalid_argument
"Async components can't be rendered to static markup, since rendering is synchronous. Please use \
`renderToStream` instead.")
| Lower_case_element { key; tag; attributes; children } -> render_lower_case ~buf ~key tag attributes children
| Text text ->
let is_previous_text_node = !previous_node_was_text in
previous_node_was_text := true;
if is_previous_text_node && add_separator_between_text_nodes then Buffer.add_string buf "";
Html.escape buf text;
should_add_doctype := false
| Suspense { key = _; children; fallback } -> (
let suspense_inner_buf = Buffer.create 128 in
match render_element ~buf:suspense_inner_buf children with
| () ->
Buffer.add_string buf "";
Buffer.add_buffer buf suspense_inner_buf;
Buffer.add_string buf ""
| exception _e ->
Buffer.add_string buf "";
render_element ~buf fallback;
Buffer.add_string buf "")
and render_lower_case ~buf ~key:_ tag attributes children =
if Html.is_self_closing_tag tag then (
should_add_doctype := false;
if add_separator_between_text_nodes then previous_node_was_text := false;
Buffer.add_char buf '<';
Buffer.add_string buf tag;
let _ = write_attributes_and_extract_inner_html buf attributes in
Buffer.add_string buf " />")
else
let doctype = !should_add_doctype in
should_add_doctype := false;
if add_separator_between_text_nodes then previous_node_was_text := false;
if tag = "html" && doctype then Buffer.add_string buf "";
Buffer.add_char buf '<';
Buffer.add_string buf tag;
let inner_html = write_attributes_and_extract_inner_html buf attributes in
Buffer.add_char buf '>';
(match inner_html with
| Some html -> Buffer.add_string buf html
| None -> render_children_list (render_element ~buf) children);
Buffer.add_string buf "";
Buffer.add_string buf tag;
Buffer.add_char buf '>';
if add_separator_between_text_nodes then previous_node_was_text := false
in
render_element ~buf element
let write_to_buffer buf element =
let rec render ~buf element =
match (element : React.element) with
| Empty -> ()
| Static { prerendered; _ } -> Buffer.add_string buf prerendered
| Writer { emit; _ } -> emit buf
| Client_component { import_module; _ } ->
raise (Invalid_argument ("Client components can't be rendered via write_to_buffer. module: " ^ import_module))
| Provider { children; push; _ } ->
let pop = push () in
render ~buf children;
pop ()
| Consumer children -> render ~buf children
| Fragment children -> render ~buf children
| List list -> render_children_list (render ~buf) list
| Array arr -> render_children_array (render ~buf) arr
| Upper_case_component (_, component) -> render_upper_case_component (render ~buf) component
| Async_component (_name, _component) ->
raise (Invalid_argument "Async components can't be rendered synchronously via write_to_buffer.")
| Lower_case_element { key = _; tag; attributes; children } ->
if Html.is_self_closing_tag tag then (
Buffer.add_char buf '<';
Buffer.add_string buf tag;
let _ = write_attributes_and_extract_inner_html buf attributes in
Buffer.add_string buf " />")
else (
Buffer.add_char buf '<';
Buffer.add_string buf tag;
let inner_html = write_attributes_and_extract_inner_html buf attributes in
Buffer.add_char buf '>';
(match inner_html with
| Some html -> Buffer.add_string buf html
| None -> render_children_list (render ~buf) children);
Buffer.add_string buf "";
Buffer.add_string buf tag;
Buffer.add_char buf '>')
| Text text -> Html.escape buf text
| Suspense { children; fallback; _ } -> (
let suspense_inner_buf = Buffer.create 128 in
match render ~buf:suspense_inner_buf children with
| () -> Buffer.add_buffer buf suspense_inner_buf
| exception _e -> render ~buf fallback)
in
render ~buf element
let escape_to_buffer = Html.escape
let renderToString ?identifier_prefix element =
(* TODO: try catch to avoid React.use usages *)
React.reset_id_rendering ?prefix:identifier_prefix ();
let buf = Buffer.create 1024 in
render_to_buffer ~mode:String buf element;
Buffer.contents buf
let renderToStaticMarkup ?identifier_prefix element =
(* TODO: try catch to avoid React.use usages *)
React.reset_id_rendering ?prefix:identifier_prefix ();
let buf = Buffer.create 1024 in
render_to_buffer ~mode:Markup buf element;
Buffer.contents buf
type stream_context = {
push : string -> unit;
close : unit -> unit;
mutable closed : bool;
mutable has_rc_script_been_injected : bool;
mutable boundary_id : int;
mutable suspense_id : int;
mutable waiting : int;
}
(* https://github.com/facebook/react/blob/493f72b0a7111b601c16b8ad8bc2649d82c184a0/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js#L46 *)
let complete_boundary_script =
{|function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}}|}
let write_inline_complete_boundary_script buf has_rc_script_been_injected boundary_id suspense_id =
let rc_call = Printf.sprintf "$RC('B:%i','S:%i')" boundary_id suspense_id in
if not has_rc_script_been_injected then (
Buffer.add_string buf "")
else (
Buffer.add_string buf "")
let write_suspense_resolved_element buf ~id html =
Buffer.add_string buf "
"
[ "" ]
let text_with_ampersand () =
let app = React.createElement "div" [] [ React.string "Tom & Jerry" ] in
assert_html
~shell:
"
Tom & Jerry
"
app []
let text_with_html_entity () =
let app = React.createElement "div" [] [ React.string "Tom & Jerry" ] in
assert_html
~shell:
"
Tom & Jerry
"
app []
let text_with_single_quote () =
let app = React.createElement "div" [] [ React.string "it's" ] in
assert_html
~shell:
"
it's
"
app []
let text_with_script_tag () =
let app = React.createElement "div" [] [ React.string "" ] in
assert_html
~shell:
"
</script><script>alert('xss')</script>
"
app []
let timeout_closes_stream_for_hanging_suspense () =
let never_resolves () =
let promise, _resolver = Lwt.wait () in
promise
in
let app =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "NeverResolves",
fun () ->
let%lwt () = never_resolves () in
Lwt.return (React.string "Should never appear") ))
()
in
let subscribed_elements = ref [] in
let%lwt _html, subscribe = ReactServerDOM.render_html ~timeout:0.02 app in
let%lwt () =
subscribe (fun element ->
subscribed_elements := !subscribed_elements @ [ element ];
Lwt.return ())
in
Alcotest.(check bool) "stream completed" true (List.length !subscribed_elements > 0);
let all_content = String.concat "" !subscribed_elements in
let end_script = "" in
Alcotest.(check bool) "stream end script received" true (String.ends_with ~suffix:end_script all_content);
Lwt.return ()
let timeout_does_not_affect_fast_renders () =
let app =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "FastComponent",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return (React.string "Fast content") ))
()
in
let subscribed_elements = ref [] in
let%lwt _html, subscribe = ReactServerDOM.render_html ~timeout:1.0 app in
let%lwt () =
subscribe (fun element ->
subscribed_elements := !subscribed_elements @ [ element ];
Lwt.return ())
in
let all_content = String.concat "" !subscribed_elements in
let end_script = "" in
Alcotest.(check bool) "stream end script received" true (String.ends_with ~suffix:end_script all_content);
Alcotest.(check bool)
"async content was received" true
(Str.string_match (Str.regexp ".*
let%lwt () = Lwt.pause () in
Lwt.return (React.string "Async content") ))
()
in
let chunks_small = ref [] in
let%lwt _html1, subscribe1 = ReactServerDOM.render_html ~progressive_chunk_size:1 app in
let%lwt () =
subscribe1 (fun element ->
chunks_small := !chunks_small @ [ element ];
Lwt.return ())
in
let chunks_large = ref [] in
let%lwt _html2, subscribe2 = ReactServerDOM.render_html ~progressive_chunk_size:8192 app in
let%lwt () =
subscribe2 (fun element ->
chunks_large := !chunks_large @ [ element ];
Lwt.return ())
in
Alcotest.(check bool)
"larger chunk size produces fewer or equal chunks" true
(List.length !chunks_large <= List.length !chunks_small);
let small_content = String.concat "" !chunks_small in
let large_content = String.concat "" !chunks_large in
Alcotest.(check string) "same content regardless of chunk size" small_content large_content;
Lwt.return ()
let timeout_end_script_appears_exactly_once () =
let app =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "AlmostDone",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return (React.string "Just in time") ))
()
in
let subscribed_elements = ref [] in
let%lwt _html, subscribe = ReactServerDOM.render_html ~timeout:0.01 app in
let%lwt () =
subscribe (fun element ->
subscribed_elements := !subscribed_elements @ [ element ];
Lwt.return ())
in
let all_content = String.concat "" !subscribed_elements in
let end_script = "" in
let count_occurrences hay needle =
let len = String.length needle in
let rec aux acc start =
match String.index_from_opt hay start needle.[0] with
| None -> acc
| Some i ->
if i + len <= String.length hay && String.sub hay i len = needle then aux (acc + 1) (i + 1)
else aux acc (i + 1)
in
if String.length hay = 0 || String.length needle = 0 then 0 else aux 0 0
in
let occurrences = count_occurrences all_content end_script in
Alcotest.(check int) "end script appears exactly once" 1 occurrences;
Lwt.return ()
let progressive_chunk_size_zero_does_not_raise () =
let app = React.createElement "div" [] [ React.string "Hello" ] in
let%lwt _html, subscribe = ReactServerDOM.render_html ~progressive_chunk_size:0 app in
let%lwt () = subscribe (fun _element -> Lwt.return ()) in
Lwt.return ()
let progressive_chunk_size_negative_does_not_raise () =
let app = React.createElement "div" [] [ React.string "Hello" ] in
let%lwt _html, subscribe = ReactServerDOM.render_html ~progressive_chunk_size:(-1) app in
let%lwt () = subscribe (fun _element -> Lwt.return ()) in
Lwt.return ()
let skip_root_omits_html_content () =
let app = React.createElement "div" [] [ React.string "Should not appear" ] in
let%lwt html, _subscribe = ReactServerDOM.render_html ~skipRoot:true app in
let has_div = Str.string_match (Str.regexp ".*
.*") html 0 in
Alcotest.(check bool) "should not contain div" false has_div;
let has_script = Str.string_match (Str.regexp ".*
let test_promise = fn () in
test_promise);
] )
let html ?(attributes = []) children = React.createElement "html" attributes children
let head children = React.createElement "head" [] children
let body children = React.createElement "body" [] children
let input attributes = React.createElement "input" attributes []
let div attributes children = React.createElement "div" attributes children
let script ~async ~src () =
React.createElement "script" [ React.JSX.Bool ("async", "async", async); React.JSX.String ("src", "src", src) ] []
let link ?precedence ~rel ~href () =
React.createElement "link"
([ React.JSX.String ("href", "href", href); React.JSX.String ("rel", "rel", rel) ]
@
match precedence with
| Some precedence -> [ React.JSX.String ("precedence", "precedence", precedence) ]
| None -> [])
[]
let assert_html ?(skipRoot = false) ?(shell = "") ?bootstrapModules ?bootstrapScriptContent element =
let script_html =
Printf.sprintf
{||}
in
let subscribed_elements = ref [] in
let%lwt html, subscribe =
ReactServerDOM.render_html ~progressive_chunk_size:1 ~skipRoot ?bootstrapModules ?bootstrapScriptContent element
in
let%lwt () =
subscribe (fun element ->
subscribed_elements := !subscribed_elements @ [ element ];
Lwt.return ())
in
let remove_begin_and_end str = Str.replace_first (Str.regexp_string script_html) "" str in
let diff = remove_begin_and_end html in
assert_string diff shell;
Lwt.return ()
let just_an_html_node () =
let app = html [] in
assert_html app
~shell:
""
let doctype () =
let app = html [ head []; body [] ] in
assert_html app
~shell:
""
let no_head_no_body_nothing_just_an_html_node () =
let app = input [] in
assert_html app
~shell:""
let html_with_a_node () =
let app = html [ input [] ] in
assert_html app
~shell:
""
let html_with_only_a_body () =
let app = html [ body [ div [] [ React.string "Just body content" ] ] ] in
assert_html app
~shell:
"
Just body content
"
let html_with_no_srr_html_body () =
let app = html [ body [ div [] [ React.string "Just body content" ] ] ] in
assert_html app ~skipRoot:true
~shell:
""
let head_with_content () =
let app =
html
[
head
[
React.createElement "title" [] [ React.string "Titulaso" ];
React.createElement "meta" [ React.JSX.String ("charset", "charSet", "utf-8") ] [];
];
]
in
assert_html app
~shell:
"Titulaso"
let html_inside_a_div () =
let app = React.createElement "div" [] [ html [] ] in
assert_html app
~shell:
""
let html_inside_a_fragment () =
let app = React.Fragment (React.list [ html [ React.createElement "div" [] [] ] ]) in
assert_html app
~shell:
""
let html_with_head_like_elements_not_in_head () =
let app =
html
[
React.createElement "meta" [ React.JSX.String ("charset", "charSet", "utf-8") ] [];
React.createElement "title" [] [ React.string "Implicit Head?" ];
]
in
assert_html app
~shell:
"Implicit Head?"
let html_without_body_and_bootstrap_scripts () =
let app = html [ React.createElement "input" [ React.JSX.String ("id", "id", "sidebar-search-input") ] [] ] in
assert_html app ~bootstrapModules:[ "react"; "react-dom" ] ~bootstrapScriptContent:"console.log('hello')"
~shell:
""
let html_with_body_and_bootstrap_scripts () =
let app =
html [ body [ React.createElement "input" [ React.JSX.String ("id", "id", "sidebar-search-input") ] [] ] ]
in
assert_html app ~bootstrapModules:[ "react"; "react-dom" ] ~bootstrapScriptContent:"console.log('hello')"
~shell:
""
let input_and_bootstrap_scripts () =
let app = React.createElement "input" [ React.JSX.String ("id", "id", "sidebar-search-input") ] [] in
assert_html app ~bootstrapModules:[ "react"; "react-dom" ] ~bootstrapScriptContent:"console.log('hello')"
~shell:
""
let title_and_meta_populates_to_the_head () =
let app =
html
[
body
[
head
[
React.createElement "title" [] [ React.string "Hey Yah" ];
React.createElement "meta"
[
React.JSX.String ("name", "name", "viewport");
React.JSX.String ("content", "content", "width=device-width,initial-scale=1");
]
[];
];
];
]
in
assert_html app
~shell:
"Hey \
Yah"
let async_scripts_to_head () =
let app = html [ body [ script ~async:true ~src:"https://cdn.com/jquery.min.js" () ] ] in
assert_html app
~shell:
""
let async_scripts_gets_deduplicated () =
let app =
html
[
body
[
script ~async:true ~src:"https://cdn.com/jquery.min.js" ();
script ~async:true ~src:"https://cdn.com/jquery.min.js" ();
script ~async:true ~src:"https://cdn.com/jquery.min.js" ();
];
]
in
(* Model faithfully represents the virtual DOM tree: regular DOM elements are not deduplicated in the model. Only client component references (I chunks) are deduplicated, matching React.js behavior. *)
assert_html app
~shell:
""
let async_scripts_gets_deduplicated_2 () =
let app =
html
[
body
[
script ~async:true ~src:"https://cdn.com/duplicated.js" ();
script ~async:true ~src:"https://cdn.com/duplicated.js" ();
script ~async:false ~src:"https://cdn.com/non-async.js" ();
];
]
in
(* sync scripts aren't hoisted to the head *)
assert_html app
~shell:
""
let link_with_rel_and_precedence () =
let app =
html
[
body
[
link ~rel:"stylesheet" ~precedence:"high" ~href:"https://cdn.com/main.css" ();
link ~rel:"stylesheet" ~precedence:"low" ~href:"https://cdn.com/main.css" ();
];
]
in
(* Here the precedence "high" remains in the head because it's the first one, there's no update with the 2nd link *)
assert_html app
~shell:
""
let links_gets_pushed_to_the_head () =
let app =
html
[
body
[
link ~rel:"stylesheet" ~precedence:"low" ~href:"https://cdn.com/main.css" ();
link ~rel:"icon" ~href:"favicon.ico" ();
link ~rel:"icon" ~href:"favicon.ico" ();
link ~rel:"pingback" ~href:"http://www.example.com/xmlrpc.php" ();
];
]
in
(* Model faithfully represents the virtual DOM tree: regular DOM elements are not deduplicated in the model. Only client component references (I chunks) are deduplicated, matching React.js behavior. Links that aren't hoisted to the head are not deduplicated. Here favicon is duplicated. *)
assert_html app
~shell:
""
let no_async_scripts_to_remain () =
let app = html [ body [ script ~async:false ~src:"https://cdn.com/jquery.min.js" () ] ] in
assert_html app ~bootstrapModules:[ "jquery"; "jquery-mobile" ]
~shell:
""
let self_closing_with_dangerously () =
let app =
div []
[
input [];
(* When dangerouslySetInnerHtml is used, children gets ignored *)
React.createElement "p" [ React.JSX.DangerouslyInnerHtml "unsafe!" ] [ React.string "xxx" ];
]
in
assert_html
~shell:
"
unsafe!
"
app
let self_closing_with_dangerously_in_head () =
let app =
html
[
head
[
React.createElement "meta" [ React.JSX.String ("char-set", "charSet", "utf-8") ] [];
React.createElement "style" [ React.JSX.DangerouslyInnerHtml "* { display: none; }" ] [];
];
]
in
assert_html
~shell:
""
app
let upper_case_component_with_resources () =
let app () =
html
[
head
[
React.createElement "link"
[
React.JSX.String ("rel", "rel", "stylesheet");
React.JSX.String ("href", "href", "/styles.css");
React.JSX.String ("precedence", "precedence", "default");
]
[];
React.createElement "script"
[ React.JSX.String ("src", "src", "/app.js"); React.JSX.Bool ("async", "async", true) ]
[];
];
body [ div [] [ React.string "Page content" ] ];
]
in
assert_html
(React.Upper_case_component ("Page", app))
~shell:
"
Page content
"
let hoisted_elements_order_issue () =
(* This test demonstrates the ordering issue with hoisted elements.
When multiple elements are hoisted (title, meta, link, scripts),
their order in the final HTML may not match the order they were defined *)
let app =
html
[
body
[
(* These elements will be hoisted to head but their order might not be preserved *)
React.createElement "title" [] [ React.string "First Title" ];
React.createElement "meta"
[
React.JSX.String ("name", "name", "description");
React.JSX.String ("content", "content", "Page description");
]
[];
link ~rel:"stylesheet" ~href:"/first.css" ();
React.createElement "title" [] [ React.string "Second Title" ];
(* Will override first *)
React.createElement "meta"
[ React.JSX.String ("name", "name", "keywords"); React.JSX.String ("content", "content", "react, ssr") ]
[];
link ~rel:"stylesheet" ~href:"/second.css" ();
script ~async:true ~src:"/first.js" ();
link ~rel:"stylesheet" ~precedence:"high" ~href:"/third.css" ();
(* This is a resource *)
script ~async:true ~src:"/second.js" ();
React.createElement "meta"
[ React.JSX.String ("name", "name", "author"); React.JSX.String ("content", "content", "Developer") ]
[];
div [] [ React.string "Body content" ];
];
]
in
assert_html app
~shell:
"First TitleSecond Title
Body \
content
"
let head_reorders_children_by_priority () =
let app =
html
[
head
[
React.createElement "meta" [ React.JSX.String ("charset", "charSet", "utf-8") ] [];
React.createElement "style" [ React.JSX.DangerouslyInnerHtml ".custom { color: red; }" ] [];
link ~rel:"stylesheet" ~href:"/main.css" ();
React.createElement "title" [] [ React.string "My App" ];
React.createElement "meta"
[
React.JSX.String ("name", "name", "viewport");
React.JSX.String ("content", "content", "width=device-width");
]
[];
script ~async:true ~src:"/app.js" ();
];
body [ div [] [ React.string "Content" ] ];
]
in
assert_html app
~shell:
"My App
Content
"
let html_attributes_are_preserved () =
let app = html ~attributes:[ React.JSX.String ("lang", "lang", "en") ] [] in
assert_html app
~shell:
""
let tests =
[
test "doctype" doctype;
test "just_an_html_node" just_an_html_node;
test "no_head_no_body_nothing_just_an_html_node" no_head_no_body_nothing_just_an_html_node;
test "html_with_no_srr_html_body" html_with_no_srr_html_body;
test "html_with_a_node" html_with_a_node;
test "html_inside_a_div" html_inside_a_div;
test "html_inside_a_fragment" html_inside_a_fragment;
test "head_with_content" head_with_content;
test "html_with_only_a_body" html_with_only_a_body;
test "html_with_head_like_elements_not_in_head" html_with_head_like_elements_not_in_head;
test "html_without_body_and_bootstrap_scripts" html_without_body_and_bootstrap_scripts;
test "html_with_body_and_bootstrap_scripts" html_with_body_and_bootstrap_scripts;
test "input_and_bootstrap_scripts" input_and_bootstrap_scripts;
test "title_and_meta_populates_to_the_head" title_and_meta_populates_to_the_head;
test "async_scripts_to_head" async_scripts_to_head;
test "no_async_scripts_to_remain" no_async_scripts_to_remain;
test "async_scripts_gets_deduplicated" async_scripts_gets_deduplicated;
test "async_scripts_gets_deduplicated_2" async_scripts_gets_deduplicated_2;
test "link_with_rel_and_precedence" link_with_rel_and_precedence;
test "links_gets_pushed_to_the_head" links_gets_pushed_to_the_head;
test "self_closing_with_dangerously" self_closing_with_dangerously;
test "self_closing_with_dangerously_in_head" self_closing_with_dangerously_in_head;
test "upper_case_component_with_resources" upper_case_component_with_resources;
test "hoisted_elements_order_issue" hoisted_elements_order_issue;
test "head_reorders_children_by_priority" head_reorders_children_by_priority;
test "html_attributes_are_preserved" html_attributes_are_preserved;
]
================================================
FILE: packages/reactDom/test/test_RSC_model.ml
================================================
let yojson = Alcotest.testable Yojson.Safe.pretty_print ( = )
let check_json = Alcotest.check yojson "should be equal"
let assert_json left right = Alcotest.check yojson "should be equal" right left
let assert_list (type a) (ty : a Alcotest.testable) (left : a list) (right : a list) =
Alcotest.check (Alcotest.list ty) "should be equal" right left
let assert_list_of_strings left right = Alcotest.check (Alcotest.list Alcotest.string) "should be equal" right left
let uuid_re =
Str.regexp
"[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
let replace_uuids s = Str.global_replace uuid_re "" s
let sleep ~ms =
let%lwt () = Lwt_unix.sleep (Int.to_float ms /. 1000.0) in
Lwt.return ()
let test title fn =
let test_case _switch () =
let start = Unix.gettimeofday () in
let timeout =
let%lwt () = sleep ~ms:20 in
Alcotest.failf "Test '%s' timed out" title
in
let%lwt test_promise = Lwt.pick [ fn (); timeout ] in
let epsilon = 0.001 in
let duration = Unix.gettimeofday () -. start in
if abs_float duration >= epsilon then
Printf.printf " \027[1m\027[33m[WARNING]\027[0m Test '%s' took %.3f seconds\n" title duration
else ();
Lwt.return test_promise
in
(Printf.sprintf "ReactServerDOM.render_model / %s" title, [ Alcotest_lwt.test_case "" `Quick test_case ])
let mk_suspense ?key ?fallback ?children () = React.Suspense.make ?key (React.Suspense.makeProps ?fallback ?children ())
let mk_context context ~value ~children () =
React.Context.provider context (React.Context.makeProps ~value ~children ())
let[@warning "-27"] skip title _fn =
let test_case _switch () = Lwt.return () in
(Printf.sprintf "ReactServerDOM.render_model / %s" title, [ Alcotest_lwt.test_case "" `Quick test_case ])
let assert_stream (stream : string Lwt_stream.t) expected =
let%lwt content = Lwt_stream.to_list stream in
if content = [] then Lwt.return @@ Alcotest.fail "stream should not be empty"
else Lwt.return @@ assert_list_of_strings content expected
let capture_stream () =
let output = ref [] in
let subscribe chunk =
output := !output @ [ chunk ];
Lwt.return ()
in
(output, subscribe)
let drop_all_frames _ _ = false
let text ~children () = React.createElement "span" [] children
(* ***** *)
(* Tests *)
(* ***** *)
let null_element () =
let app = React.null in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:null\n" ];
Lwt.return ()
let string_element () =
let app = React.string "hi" in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:\"hi\"\n" ];
Lwt.return ()
let lower_case_component () =
let app = React.createElement "div" (ReactDOM.domProps ~className:"foo" ()) [] in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:[\"$\",\"div\",null,{\"className\":\"foo\"},null,null,1]\n" ];
Lwt.return ()
let lower_case_with_children () =
let app =
React.createElement "div" []
[ React.createElement "span" [] [ React.string "Home" ]; React.createElement "span" [] [ React.string "Nohome" ] ]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"div\",null,{\"children\":[[\"$\",\"span\",null,{\"children\":\"Home\"},null,null,1],[\"$\",\"span\",null,{\"children\":\"Nohome\"},null,null,1]]},null,null,1]\n";
];
Lwt.return ()
let lower_case_component_nested () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.createElement "div" []
[
React.createElement "section" []
[ React.createElement "article" [] [ React.string "Deep Server Content" ] ];
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"0:[\"$\",\"div\",null,{\"children\":[\"$\",\"section\",null,{\"children\":[\"$\",\"article\",null,{\"children\":\"Deep \
Server Content\"},null,null,1]},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let dangerouslySetInnerHtml () =
let app =
React.createElement "script"
[
React.JSX.String ("type", "type", "application/javascript"); React.JSX.DangerouslyInnerHtml "console.log('Hi!')";
]
[]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"script\",null,{\"type\":\"application/javascript\",\"dangerouslySetInnerHTML\":{\"__html\":\"console.log('Hi!')\"}},null,null,1]\n";
];
Lwt.return ()
let upper_case_component () =
let app codition =
React.Upper_case_component
( "app",
fun () ->
let text = if codition then "foo" else "bar" in
React.createElement "span" [] [ React.string text ] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app true) in
assert_list_of_strings !output [ "0:[\"$\",\"span\",null,{\"children\":\"foo\"},null,null,1]\n" ];
Lwt.return ()
let nested_upper_case_components () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.Upper_case_component ("Foo", fun () -> React.Upper_case_component ("Bar", fun () -> React.string "Bar"))
)
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output [ "0:\"Bar\"\n" ];
Lwt.return ()
let upper_case_with_list () =
let app () =
React.Fragment
(React.list
[
React.Upper_case_component ("Text", text ~children:[ React.string "hi" ]);
React.Upper_case_component ("Text", text ~children:[ React.string "hola" ]);
])
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"0:[[\"$\",\"span\",null,{\"children\":\"hi\"},null,null,1],[\"$\",\"span\",null,{\"children\":\"hola\"},null,null,1]]\n";
];
Lwt.return ()
let upper_case_with_children () =
let layout ~children () = React.createElement "div" [] children in
let app () =
React.Upper_case_component
( "Layout",
layout
~children:
[
React.Upper_case_component ("Text", text ~children:[ React.string "hi" ]);
React.Upper_case_component ("Text", text ~children:[ React.string "hola" ]);
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"0:[\"$\",\"div\",null,{\"children\":[[\"$\",\"span\",null,{\"children\":\"hi\"},null,null,1],[\"$\",\"span\",null,{\"children\":\"hola\"},null,null,1]]},null,null,1]\n";
];
Lwt.return ()
let suspense_without_promise () =
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.createElement "div" []
[
React.Upper_case_component ("Text", text ~children:[ React.string "hi" ]);
React.Upper_case_component ("Text", text ~children:[ React.string "hola" ]);
])
()
in
let main = React.Upper_case_component ("App", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"span\",null,{\"children\":\"hi\"},null,null,1],[\"$\",\"span\",null,{\"children\":\"hola\"},null,null,1]]},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let suspense_with_promise () =
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "suspense_with_promise",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return (React.string "lol") ))
()
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
"1:\"lol\"\n";
];
Lwt.return ()
let suspense_with_error () =
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:(React.Upper_case_component (__FUNCTION__, fun () -> raise (Failure "lol")))
()
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
];
Lwt.return ()
let suspense_with_error_in_async () =
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:(React.Async_component (__FUNCTION__, fun () -> Lwt.fail (Failure "lol")))
()
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
];
Lwt.return ()
let suspense_with_error_under_lowercase () =
let app () =
React.createElement "div" []
[
mk_suspense ~fallback:(React.string "Loading...")
~children:(React.Async_component (__FUNCTION__, fun () -> Lwt.fail (Failure "lol")))
();
]
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:[\"$\",\"div\",null,{\"children\":[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let error_without_suspense () =
let app () = React.Upper_case_component (__FUNCTION__, fun () -> raise (Failure "lol")) in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[ "1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n"; "0:\"$L1\"\n" ];
Lwt.return ()
let error_in_toplevel () =
let app () = raise (Failure "lol") in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[ "1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n"; "0:\"$L1\"\n" ];
Lwt.return ()
let error_in_toplevel_in_async () =
let app () = Lwt.fail (Failure "lol") in
let main = React.Async_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[ "1:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n"; "0:\"$L1\"\n" ];
Lwt.return ()
let await_tick ?(raise = false) ?(ms = 1) num =
React.Async_component
( "await_tick",
fun () ->
let%lwt () = sleep ~ms in
if raise then Lwt.fail (Failure "lol") else Lwt.return (React.string num) )
let suspense_in_a_list () =
let fallback = React.string "Loading..." in
let app () =
React.Fragment
(React.list
[
mk_suspense ~fallback ~children:(await_tick ~ms:1 "A") ();
mk_suspense ~fallback ~children:(await_tick ~ms:2 "B") ();
mk_suspense ~fallback ~children:(await_tick ~ms:3 "C") ();
mk_suspense ~fallback ~children:(await_tick ~ms:4 "D") ();
mk_suspense ~fallback ~children:(await_tick ~ms:5 "E") ();
])
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L2\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L3\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L4\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L5\"},null,null,1]]\n";
"1:\"A\"\n";
"2:\"B\"\n";
"3:\"C\"\n";
"4:\"D\"\n";
"5:\"E\"\n";
];
Lwt.return ()
let suspense_in_a_list_with_error () =
let fallback = React.string "Loading..." in
let app () =
React.Fragment
(React.list
[
mk_suspense ~fallback ~children:(await_tick ~ms:1 "A") ();
mk_suspense ~fallback ~children:(await_tick ~ms:2 ~raise:true "B") ();
mk_suspense ~fallback ~children:(await_tick ~ms:3 "C") ();
mk_suspense ~fallback ~children:(await_tick ~ms:4 "D") ();
mk_suspense ~fallback ~children:(await_tick ~ms:5 "E") ();
])
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L2\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L3\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L4\"},null,null,1],[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L5\"},null,null,1]]\n";
"1:\"A\"\n";
"2:E{\"message\":\"Failure(\\\"lol\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"3:\"C\"\n";
"4:\"D\"\n";
"5:\"E\"\n";
];
Lwt.return ()
let suspense_with_immediate_promise () =
let resolved_component =
React.Async_component
( __FUNCTION__,
fun () ->
let value = "DONE :)" in
Lwt.return (React.string value) )
in
let app = mk_suspense ~fallback:(React.string "Loading...") ~children:resolved_component in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[ "0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"DONE :)\"},null,null,1]\n" ];
Lwt.return ()
let delayed_value value =
let%lwt () = Lwt.pause () in
Lwt.return value
let suspense () =
let suspended_component =
React.Async_component
( __FUNCTION__,
fun () ->
let%lwt value = delayed_value "DONE :)" in
Lwt.return (React.string value) )
in
let app () = mk_suspense ~fallback:(React.string "Loading...") ~children:suspended_component () in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
"1:\"DONE :)\"\n";
];
Lwt.return ()
let nested_suspense () =
let deffered_component =
React.Async_component
( __FUNCTION__,
fun () ->
let%lwt value = delayed_value "DONE :)" in
Lwt.return (React.string value) )
in
let app () = mk_suspense ~fallback:(React.string "Loading...") ~children:deffered_component () in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
"1:\"DONE :)\"\n";
];
Lwt.return ()
let async_component_without_suspense () =
(* Because there's no Suspense. We await for the promise to resolve before rendering the component *)
let app =
React.Async_component
( __FUNCTION__,
fun () ->
let%lwt value = delayed_value "DONE :)" in
Lwt.return (React.string value) )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:\"$L1\"\n"; "1:\"DONE :)\"\n" ];
Lwt.return ()
let async_component_without_suspense_immediate () =
let app =
React.Async_component
( __FUNCTION__,
fun () ->
let%lwt value = delayed_value "DONE :)" in
Lwt.return (React.string value) )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:\"$L1\"\n"; "1:\"DONE :)\"\n" ];
Lwt.return ()
let client_without_props () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props = [];
client = React.string "Client without Props";
import_module = "./client-without-props.js";
import_name = "ClientWithoutProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-without-props.js\",[],\"ClientWithoutProps\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server Content\"},null,null,1],[\"$\",\"$1\",null,{},null,null,1]]\n";
];
Lwt.return ()
let client_with_json_props () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props =
[
("null", React.Model.Json `Null);
("string", React.Model.Json (`String "Title"));
("int", React.Model.Json (`Int 1));
("float", React.Model.Json (`Float 1.1));
("bool true", React.Model.Json (`Bool true));
("bool false", React.Model.Json (`Bool false));
("string list", React.Model.Json (`List [ `String "Item 1"; `String "Item 2" ]));
("object", React.Model.Json (`Assoc [ ("name", `String "John"); ("age", `Int 30) ]));
];
client = React.string "Client with Props";
import_module = "./client-with-props.js";
import_name = "ClientWithProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server \
Content\"},null,null,1],[\"$\",\"$1\",null,{\"null\":null,\"string\":\"Title\",\"int\":1,\"float\":1.1,\"bool \
true\":true,\"bool false\":false,\"string list\":[\"Item 1\",\"Item \
2\"],\"object\":{\"name\":\"John\",\"age\":30}},null,null,1]]\n";
];
Lwt.return ()
let client_with_element_props () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props = [ ("children", React.Model.Element (React.string "Client Content")) ];
client = React.string "Client with Props";
import_module = "./client-with-props.js";
import_name = "ClientWithProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server Content\"},null,null,1],[\"$\",\"$1\",null,{\"children\":\"Client \
Content\"},null,null,1]]\n";
];
Lwt.return ()
let client_with_promise_props () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props =
[
( "promise",
React.Model.Promise (delayed_value "||| Resolved |||", fun res -> React.Model.Json (`String res))
);
];
client = React.string "Client with Props";
import_module = "./client-with-props.js";
import_name = "ClientWithProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server \
Content\"},null,null,1],[\"$\",\"$1\",null,{\"promise\":\"$@2\"},null,null,1]]\n";
"2:\"||| Resolved |||\"\n";
];
Lwt.return ()
let client_with_promise_failed_props () =
let app () =
let promise =
React.Model.Promise
( (let%lwt _str = delayed_value "||| Resolved |||" in
Lwt.fail (Failure "Already failed")),
fun res -> React.Model.Json (`String res) )
in
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props = [ ("promise", promise) ];
client = React.string "Client with Props";
import_module = "./client-with-props.js";
import_name = "ClientWithProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server \
Content\"},null,null,1],[\"$\",\"$1\",null,{\"promise\":\"$@2\"},null,null,1]]\n";
"2:E{\"message\":\"Failure(\\\"Already failed\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
];
Lwt.return ()
let client_with_promise_already_failed_props () =
let app () =
let promise =
React.Model.Promise (Lwt.fail (Failure "Already failed"), fun res -> React.Model.Json (`String res))
in
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props = [ ("promise", promise) ];
client = React.string "Client with Props";
import_module = "./client-with-props.js";
import_name = "ClientWithProps";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-props.js\",[],\"ClientWithProps\"]\n";
"2:E{\"message\":\"Failure(\\\"Already failed\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server \
Content\"},null,null,1],[\"$\",\"$1\",null,{\"promise\":\"$@2\"},null,null,1]]\n";
];
Lwt.return ()
let mixed_server_and_client () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "header" [] [ React.string "Server Header" ];
React.Client_component
{
key = None;
props = [];
client = React.string "Client 1";
import_module = "./client-1.js";
import_name = "Client1";
};
React.createElement "footer" [] [ React.string "Server Footer" ];
React.Client_component
{
key = None;
props = [];
client = React.string "Client 2";
import_module = "./client-2.js";
import_name = "Client2";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-1.js\",[],\"Client1\"]\n";
"2:I[\"./client-2.js\",[],\"Client2\"]\n";
"0:[[\"$\",\"header\",null,{\"children\":\"Server \
Header\"},null,null,1],[\"$\",\"$1\",null,{},null,null,1],[\"$\",\"footer\",null,{\"children\":\"Server \
Footer\"},null,null,1],[\"$\",\"$2\",null,{},null,null,1]]\n";
];
Lwt.return ()
let client_with_server_children () =
let server_child () = React.createElement "div" [] [ React.string "Server Component Inside Client" ] in
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "div" [] [ React.string "Server Content" ];
React.Client_component
{
key = None;
props = [ ("children", React.Model.Element (React.Upper_case_component ("Server", server_child))) ];
client = React.string "Client with Server Children";
import_module = "./client-with-server-children.js";
import_name = "ClientWithServerChildren";
};
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client-with-server-children.js\",[],\"ClientWithServerChildren\"]\n";
"0:[[\"$\",\"div\",null,{\"children\":\"Server \
Content\"},null,null,1],[\"$\",\"$1\",null,{\"children\":[\"$\",\"div\",null,{\"children\":\"Server Component \
Inside Client\"},null,null,1]},null,null,1]]\n";
];
Lwt.return ()
let key_renders_outside_of_props () =
let app =
React.createElementWithKey ~key:"important key" "section"
[ React.JSX.String ("className", "className", "sidebar-header") ]
[ React.createElement "strong" [] [ React.string "React Notes" ] ]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"section\",\"important key\",{\"children\":[\"$\",\"strong\",null,{\"children\":\"React \
Notes\"},null,null,1],\"className\":\"sidebar-header\"},null,null,1]\n";
];
Lwt.return ()
let style_as_json () =
let app =
React.createElement "div"
[ React.JSX.style (ReactDOMStyle.make ~color:"red" ~background:"blue" ~zIndex:"34" ()) ]
[]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[ "0:[\"$\",\"div\",null,{\"style\":{\"zIndex\":\"34\",\"color\":\"red\",\"background\":\"blue\"}},null,null,1]\n" ];
Lwt.return ()
let act_with_simple_response () =
let response = Lwt.return (React.Model.Json (`String "Server Content")) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.create_action_response ~subscribe response in
assert_list_of_strings !output [ "0:\"Server Content\"\n" ];
Lwt.return ()
let act_with_error () =
let output, subscribe = capture_stream () in
let response = Lwt.fail (Failure "Error") in
let%lwt () = ReactServerDOM.create_action_response ~subscribe response in
assert_list_of_strings (List.map replace_uuids !output)
[
"1:E{\"message\":\"Failure(\\\"Error\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:\"$Z1\"\n";
];
Lwt.return ()
(* Test that simulates the streamFunctionResponse pattern:
a failing server function's error is serialized into the RSC stream
(not swallowed or re-raised as an HTTP 500). *)
let act_with_error_from_handler () =
let output, subscribe = capture_stream () in
(* Simulate what streamFunctionResponse does:
1. Run the handler (which may fail)
2. Capture the outcome as a promise (success or failure)
3. Pass the promise to create_action_response *)
let action_promise =
Lwt.catch
(fun () ->
(* Simulate a failing server function handler *)
let%lwt _result = Lwt.fail (Failure "Error from server") in
Lwt.return (Lwt.return _result))
(fun exn -> Lwt.return (Lwt.fail exn))
in
let%lwt action_promise = action_promise in
let%lwt () = ReactServerDOM.create_action_response ~subscribe action_promise in
assert_list_of_strings (List.map replace_uuids !output)
[
"1:E{\"message\":\"Failure(\\\"Error from server\\\")\",\"stack\":[],\"env\":\"Server\",\"digest\":\"\"}\n";
"0:\"$Z1\"\n";
];
Lwt.return ()
(* Test that a successful action followed by create_action_response works *)
let act_with_success_from_handler () =
let output, subscribe = capture_stream () in
let action_promise =
Lwt.catch
(fun () ->
let%lwt result = Lwt.return (React.Model.Json (`String "Success")) in
Lwt.return (Lwt.return result))
(fun exn -> Lwt.return (Lwt.fail exn))
in
let%lwt action_promise = action_promise in
let%lwt () = ReactServerDOM.create_action_response ~subscribe action_promise in
assert_list_of_strings !output [ "0:\"Success\"\n" ];
Lwt.return ()
(* Test that decodeReply errors produce Error, not exceptions *)
let act_decode_error_does_not_raise () =
(match ReactServerDOM.decodeReply "not valid json at all" with
| Error msg -> if not (String.length msg > 0) then Alcotest.fail "expected non-empty error message"
| Ok _ -> Alcotest.fail "expected Error for invalid JSON");
(match ReactServerDOM.decodeReply "[\"$@1\"]" with
| Error msg ->
if not (String.starts_with ~prefix:"decodeReply: Promise" msg) then
Alcotest.fail (Printf.sprintf "unexpected error message: %s" msg)
| Ok _ -> Alcotest.fail "expected Error for unsupported type");
Lwt.return ()
let env_development_adds_debug_info () =
let app =
React.Upper_case_component
( "app",
fun () ->
let value = "my friend" in
React.createElement "input"
[
React.JSX.String ("id", "id", "sidebar-search-input");
React.JSX.String ("placeholder", "placeholder", "Search");
React.JSX.String ("value", "value", value);
]
[] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:true ~filter_stack_frame:drop_all_frames app in
assert_list_of_strings !output
[
"1:{\"name\":\"app\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[],\"props\":{}}\n";
"0:D\"$1\"\n";
"0:[\"$\",\"input\",null,{\"id\":\"sidebar-search-input\",\"placeholder\":\"Search\",\"value\":\"my \
friend\"},null,null,1]\n";
];
Lwt.return ()
(* let env_development_adds_debug_info_2 () =
let app () =
React.Fragment
(React.list
[
React.Upper_case_component ("Text", text ~children:[ React.string "hi" ]);
React.Upper_case_component ("Text", text ~children:[ React.string "hola" ]);
])
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:{\"name\":\"App\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[[\"module \
code\",\"/Users/davesnx/Code/github/ml-in-barcelona/server-reason-react/arch/server/render-rsc-to-stream.js\",54,42]],\"props\":{}}";
"0:D\"$1\"";
"3:{\"name\":\"Comp\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"stack\":[[\"App\",\"/Users/davesnx/Code/github/ml-in-barcelona/server-reason-react/arch/server/render-rsc-to-stream.js\",50,15]],\"props\":{\"name\":\"hi\"}}";
"2:D\"$3\"";
"2:[\"$\",\"h1\",null,{\"children\":[\"Hello \",\"hi\"]},\"$1\",null,1]";
"5:{\"name\":\"Comp\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"stack\":[[\"App\",\"/Users/davesnx/Code/github/ml-in-barcelona/server-reason-react/arch/server/render-rsc-to-stream.js\",51,15]],\"props\":{\"name\":\"Hola\"}}";
"4:D\"$5\"";
"4:[\"$\",\"h1\",null,{\"children\":[\"Hello \",\"Hola\"]},\"$1\",null,1]";
"0:[\"$2\",\"$4\"]";
];
Lwt.return () *)
let client_component_with_resources_metadata () =
(* Test that resources are tracked in the RSC payload *)
let app () =
React.Upper_case_component
( "Page",
fun () ->
React.list
[
React.createElement "html" []
[
React.createElement "head" []
[
React.createElement "link"
[
React.JSX.String ("rel", "rel", "stylesheet");
React.JSX.String ("href", "href", "/styles.css");
React.JSX.String ("precedence", "precedence", "default");
]
[];
React.createElement "script"
[ React.JSX.String ("src", "src", "/app.js"); React.JSX.Bool ("async", "async", true) ]
[];
];
React.createElement "body" []
[
React.Client_component
{
key = None;
props = [];
client = React.string "Client Component";
import_module = "./client.js";
import_name = "Client";
};
];
];
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
(* Check that client reference is created *)
let has_client_ref = List.exists (fun s -> Str.string_match (Str.regexp ".*:I\\[\"./client.js\".*") s 0) !output in
Alcotest.(check bool) "should have client reference" true has_client_ref;
(* Check that the resources are in the model payload *)
let has_head_with_resources =
List.exists
(fun s ->
Str.string_match (Str.regexp ".*\"head\".*") s 0
&& Str.string_match (Str.regexp ".*\"link\".*") s 0
&& Str.string_match (Str.regexp ".*\"script\".*") s 0)
!output
in
Alcotest.(check bool) "should have head with resources" true has_head_with_resources;
Lwt.return ()
let client_component_with_async_component () =
let async_component =
React.Async_component
( __FUNCTION__,
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return (React.string "Async Component") )
in
let app ~children =
React.Upper_case_component
( "app",
fun () ->
React.Client_component
{
key = None;
import_module = "./client.js";
import_name = "Client";
props = [ ("children", React.Model.Element children) ];
client = children;
} )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ~children:async_component) in
assert_list_of_strings !output
[
"1:I[\"./client.js\",[],\"Client\"]\n";
"0:[\"$\",\"$1\",null,{\"children\":\"$L2\"},null,null,1]\n";
"2:\"Async Component\"\n";
];
Lwt.return ()
let page_with_hoisted_resources () =
(* Test that resources like scripts and styles are properly hoisted *)
let app () =
React.Upper_case_component
( "Page",
fun () ->
React.list
[
React.createElement "div" []
[
React.createElement "link"
[
React.JSX.String ("rel", "rel", "stylesheet");
React.JSX.String ("href", "href", "/main.css");
React.JSX.String ("precedence", "precedence", "high");
]
[];
React.createElement "script"
[ React.JSX.String ("src", "src", "/runtime.js"); React.JSX.Bool ("async", "async", true) ]
[];
React.createElement "h1" [] [ React.string "Page Title" ];
];
] )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
(* Check that the output contains the expected structure *)
Alcotest.(check bool) "should have output" (List.length !output > 0) true;
(* Check that h1 with Page Title is in the output *)
let has_page_title = List.exists (fun s -> Str.string_match (Str.regexp ".*\"h1\".*\"Page Title\".*") s 0) !output in
Alcotest.(check bool) "should have page title" true has_page_title;
Lwt.return ()
let nested_context () =
let context = React.createContext React.null in
let provider ~value ~children = mk_context context ~value ~children () in
let client_provider ~value ~children =
React.Upper_case_component
( "client_provider",
fun () ->
React.Client_component
{
key = None;
import_module = "./provider.js";
import_name = "Provider";
props = [ ("value", React.Model.Element value); ("children", React.Model.Element children) ];
client = provider ~value ~children;
} )
in
let client_consumer () =
React.Client_component
{
key = None;
import_module = "./consumer.js";
import_name = "Consumer";
props = [];
client =
React.Upper_case_component
( "client_consumer",
fun () ->
let context = React.useContext context in
context );
}
in
let content () =
React.Upper_case_component
("content", fun () -> client_provider ~value:React.null ~children:(React.string "Hey you"))
in
let me () =
React.Upper_case_component
( "me",
fun () ->
client_provider ~value:(content ()) ~children:(React.array [| React.string "/me"; client_consumer () |]) )
in
let about () =
React.Upper_case_component
( "about",
fun () -> client_provider ~value:(me ()) ~children:(React.array [| React.string "/about"; client_consumer () |])
)
in
let app () =
React.Upper_case_component
( "root",
fun () ->
client_provider ~value:(about ()) ~children:(React.array [| React.string "/root"; client_consumer () |]) )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./provider.js\",[],\"Provider\"]\n";
"2:I[\"./consumer.js\",[],\"Consumer\"]\n";
"0:[\"$\",\"$1\",null,{\"value\":[\"$\",\"$1\",null,{\"value\":[\"$\",\"$1\",null,{\"value\":[\"$\",\"$1\",null,{\"value\":null,\"children\":\"Hey \
you\"},null,null,1],\"children\":[\"/me\",[\"$\",\"$2\",null,{},null,null,1]]},null,null,1],\"children\":[\"/about\",[\"$\",\"$2\",null,{},null,null,1]]},null,null,1],\"children\":[\"/root\",[\"$\",\"$2\",null,{},null,null,1]]},null,null,1]\n";
];
Lwt.return ()
let suspense_with_nested_upper_case () =
(* Server components are always inlined, matching React.js behavior. Everything resolves in chunk 0. *)
let inner () = React.Upper_case_component ("Inner", fun () -> React.string "inner-value") in
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:(React.Upper_case_component ("Wrapper", fun () -> React.createElement "div" [] [ inner () ]))
()
in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe main in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":[\"$\",\"div\",null,{\"children\":\"inner-value\"},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let suspense_at_root () =
(* React: 0:["$","$Sreact.suspense",null,{"fallback":"Loading...","children":"Resolved content"}] *)
let app = mk_suspense ~fallback:(React.string "Loading...") ~children:(React.string "Resolved content") () in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"Resolved content\"},null,null,1]\n";
];
Lwt.return ()
let suspense_at_root_with_upper_case_children () =
(* Server components inside Suspense are inlined, matching React.js.
React: 0:["$","$Sreact.suspense",null,{"fallback":"Loading...","children":["$","div",null,{"children":"Hello"}]}] *)
let app =
mk_suspense ~fallback:(React.string "Loading...")
~children:(React.Upper_case_component ("Inner", fun () -> React.createElement "div" [] [ React.string "Hello" ]))
()
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":[\"$\",\"div\",null,{\"children\":\"Hello\"},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let suspense_at_root_with_nested_components () =
(* Server components are always inlined, matching React.js behavior.
0:["$","$Sreact.suspense",null,{"fallback":"Loading...",
"children":["$","div",null,{"children":["$","div",null,{"children":"Hello"}]}]}] *)
let inner () =
React.Upper_case_component ("Inner", fun () -> React.createElement "div" [] [ React.string "Hello" ])
in
let wrapper () = React.Upper_case_component ("Wrapper", fun () -> React.createElement "div" [] [ inner () ]) in
let app = mk_suspense ~fallback:(React.string "Loading...") ~children:(wrapper ()) () in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":[\"$\",\"div\",null,{\"children\":[\"$\",\"div\",null,{\"children\":\"Hello\"},null,null,1]},null,null,1]},null,null,1]\n";
];
Lwt.return ()
let suspense_at_root_with_async () =
(* Async children inside root Suspense create a lazy ref.
React: 0:["$","$Sreact.suspense",null,{"fallback":"Loading...","children":"$L1"}] then 1:resolved *)
let app =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "async",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return (React.createElement "span" [] [ React.string "Async resolved" ]) ))
()
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"$Sreact.suspense\",null,{\"fallback\":\"Loading...\",\"children\":\"$L1\"},null,null,1]\n";
"1:[\"$\",\"span\",null,{\"children\":\"Async resolved\"},null,null,1]\n";
];
Lwt.return ()
let root_async_component_immediate () =
(* Immediately resolved async component at root inlines at chunk 0.
React: 0:["$","span",null,{"children":"Immediate async"}] *)
let app =
React.Async_component
("immediate", fun () -> Lwt.return (React.createElement "span" [] [ React.string "Immediate async" ]))
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:[\"$\",\"span\",null,{\"children\":\"Immediate async\"},null,null,1]\n" ];
Lwt.return ()
let root_upper_case_chain () =
(* Chained root server components all inline in chunk 0.
React: 0:["$","div",null,{"children":"Hello"}] *)
let inner () =
React.Upper_case_component ("Inner", fun () -> React.createElement "div" [] [ React.string "Hello" ])
in
let layout () = React.Upper_case_component ("Layout", fun () -> inner ()) in
let app = React.Upper_case_component ("App", fun () -> layout ()) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output [ "0:[\"$\",\"div\",null,{\"children\":\"Hello\"},null,null,1]\n" ];
Lwt.return ()
let model_list_value () =
let list =
React.Model.List
[
React.Model.Json (`String "Item 1");
React.Model.Element
(React.Upper_case_component
("Component", fun () -> React.createElement "div" [] [ React.string "Hello world" ]));
]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model_value ~subscribe list in
assert_list_of_strings !output [ "0:[\"Item 1\",[\"$\",\"div\",null,{\"children\":\"Hello world\"},null,null,1]]\n" ];
Lwt.return ()
let model_value_assoc () =
let assoc =
React.Model.Assoc
[
("key", React.Model.Json (`String "value"));
( "component",
React.Model.Element
(React.Upper_case_component
("Component", fun () -> React.createElement "div" [] [ React.string "Hello world" ])) );
]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model_value ~subscribe assoc in
assert_list_of_strings !output
[ "0:{\"key\":\"value\",\"component\":[\"$\",\"div\",null,{\"children\":\"Hello world\"},null,null,1]}\n" ];
Lwt.return ()
let special_characters_not_html_encoded () =
let app =
React.createElement "div" []
[
React.string "Tom & Jerry";
React.string "";
React.string "it's a \"test\"";
React.string "& < >";
]
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe app in
assert_list_of_strings !output
[
"0:[\"$\",\"div\",null,{\"children\":[\"Tom & Jerry\",\"\",\"it's a \
\\\"test\\\"\",\"& < >\"]},null,null,1]\n";
];
Lwt.return ()
let debug_nested_owner_chain () =
let app =
React.Upper_case_component
( "App",
fun () -> React.Upper_case_component ("Child", fun () -> React.createElement "div" [] [ React.string "hello" ])
)
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:true ~filter_stack_frame:drop_all_frames app in
assert_list_of_strings !output
[
"1:{\"name\":\"App\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[],\"props\":{}}\n";
"0:D\"$1\"\n";
"3:{\"name\":\"Child\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"stack\":[],\"props\":{}}\n";
"2:D\"$3\"\n";
"2:[\"$\",\"div\",null,{\"children\":\"hello\"},\"$1\",null,1]\n";
"0:\"$2\"\n";
];
Lwt.return ()
let debug_async_component () =
let app =
React.Upper_case_component
( "App",
fun () ->
React.Async_component
("AsyncChild", fun () -> Lwt.return (React.createElement "span" [] [ React.string "async" ])) )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:true ~filter_stack_frame:drop_all_frames app in
assert_list_of_strings !output
[
"1:{\"name\":\"App\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[],\"props\":{}}\n";
"0:D\"$1\"\n";
"3:{\"name\":\"AsyncChild\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"stack\":[],\"props\":{}}\n";
"2:D\"$3\"\n";
"2:[\"$\",\"span\",null,{\"children\":\"async\"},\"$1\",null,1]\n";
"0:\"$2\"\n";
];
Lwt.return ()
let debug_outlines_components () =
let app = React.Upper_case_component ("App", fun () -> React.createElement "h1" [] [ React.string "title" ]) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:true ~filter_stack_frame:drop_all_frames app in
assert_list_of_strings !output
[
"1:{\"name\":\"App\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[],\"props\":{}}\n";
"0:D\"$1\"\n";
"0:[\"$\",\"h1\",null,{\"children\":\"title\"},null,null,1]\n";
];
Lwt.return ()
let debug_not_emitted_without_flag () =
let app = React.Upper_case_component ("App", fun () -> React.createElement "div" [] [ React.string "no debug" ]) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:false app in
assert_list_of_strings !output [ "0:[\"$\",\"div\",null,{\"children\":\"no debug\"},null,null,1]\n" ];
Lwt.return ()
(* Validates the React 19 RSC wire format for debug info with a 3-level component chain:
- Element tuple: ["$", type, key, props, debugOwner, debugStack, validated]
- debugOwner must be a "$" chunk reference (not a bare integer)
- debugStack must be null when absent (not an empty list)
- Debug info chunks use the D prefix and reference their parent via "$" *)
let debug_wire_format () =
let app =
React.Upper_case_component
( "GrandParent",
fun () ->
React.Upper_case_component
( "Parent",
fun () ->
React.Upper_case_component ("Child", fun () -> React.createElement "em" [] [ React.string "deep" ]) ) )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe ~debug:true ~filter_stack_frame:drop_all_frames app in
assert_list_of_strings !output
[
(* GrandParent debug info (chunk 1): root component, no owner *)
"1:{\"name\":\"GrandParent\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"stack\":[],\"props\":{}}\n";
"0:D\"$1\"\n";
(* Parent debug info (chunk 3): owner is GrandParent via "$1" *)
"3:{\"name\":\"Parent\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"stack\":[],\"props\":{}}\n";
(* Child debug info (chunk 5): owner is Parent via "$3" *)
"5:{\"name\":\"Child\",\"env\":\"Server\",\"key\":null,\"owner\":\"$3\",\"stack\":[],\"props\":{}}\n";
"4:D\"$5\"\n";
(* Element tuple: debugOwner="$3" (chunk ref, not bare int), debugStack=null, validated=1 *)
"4:[\"$\",\"em\",null,{\"children\":\"deep\"},\"$3\",null,1]\n";
(* Outlined chunks resolve *)
"2:D\"$3\"\n";
"2:\"$4\"\n";
"0:\"$2\"\n";
];
Lwt.return ()
let server_function_as_model_prop () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.Client_component
{
key = None;
props =
[
( "onSubmit",
React.Model.Function
{ Runtime.id = "action-id-123"; call = (fun () -> Lwt.return (React.Model.Json `Null)) } );
];
client = React.string "Client";
import_module = "./client.js";
import_name = "Client";
} )
in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client.js\",[],\"Client\"]\n";
"2:{\"id\":\"action-id-123\",\"bound\":null}\n";
"0:[\"$\",\"$1\",null,{\"onSubmit\":\"$F2\"},null,null,1]\n";
];
Lwt.return ()
let error_in_prod_hides_message () =
let app () = React.Upper_case_component (__FUNCTION__, fun () -> raise (Failure "secret")) in
let main = React.Upper_case_component ("app", app) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~env:`Prod ~subscribe main in
assert_list_of_strings !output [ "1:E{\"digest\":\"\"}\n"; "0:\"$L1\"\n" ];
Lwt.return ()
let duplicate_client_component_deduplicates_ref () =
let make_client () =
React.Client_component
{ key = None; props = []; client = React.string "Client"; import_module = "./client.js"; import_name = "Client" }
in
let app () = React.Upper_case_component ("app", fun () -> React.list [ make_client (); make_client () ]) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client.js\",[],\"Client\"]\n";
"0:[[\"$\",\"$1\",null,{},null,null,1],[\"$\",\"$1\",null,{},null,null,1]]\n";
];
Lwt.return ()
let keyed_duplicate_client_component_preserves_keys () =
let make_client key =
React.Client_component
{
key = Some key;
props = [];
client = React.string "Client";
import_module = "./client.js";
import_name = "Client";
}
in
let app () = React.Upper_case_component ("app", fun () -> React.list [ make_client "first"; make_client "second" ]) in
let output, subscribe = capture_stream () in
let%lwt () = ReactServerDOM.render_model ~subscribe (app ()) in
assert_list_of_strings !output
[
"1:I[\"./client.js\",[],\"Client\"]\n";
"0:[[\"$\",\"$1\",\"first\",{},null,null,1],[\"$\",\"$1\",\"second\",{},null,null,1]]\n";
];
Lwt.return ()
let tests =
[
test "null_element" null_element;
test "special_characters_not_html_encoded" special_characters_not_html_encoded;
test "string_element" string_element;
test "key_renders_outside_of_props" key_renders_outside_of_props;
test "style_as_json" style_as_json;
test "lower_case_component" lower_case_component;
test "lower_case_component_nested" lower_case_component_nested;
test "lower_case_with_children" lower_case_with_children;
test "dangerouslySetInnerHtml" dangerouslySetInnerHtml;
test "upper_case_component" upper_case_component;
test "nested_upper_case_components" nested_upper_case_components;
test "upper_case_with_list" upper_case_with_list;
test "upper_case_with_children" upper_case_with_children;
test "suspense_without_promise" suspense_without_promise;
test "suspense_with_promise" suspense_with_promise;
test "suspense_with_error" suspense_with_error;
test "suspense_with_error_in_async" suspense_with_error_in_async;
test "suspense_with_immediate_promise" suspense_with_immediate_promise;
test "suspense" suspense;
test "async_component_without_suspense" async_component_without_suspense;
test "suspense_in_a_list" suspense_in_a_list;
test "client_with_promise_props" client_with_promise_props;
test "async_component_without_suspense_immediate" async_component_without_suspense_immediate;
test "mixed_server_and_client" mixed_server_and_client;
test "client_with_json_props" client_with_json_props;
test "client_without_props" client_without_props;
test "client_with_element_props" client_with_element_props;
test "client_with_server_children" client_with_server_children;
test "client_component_with_async_component" client_component_with_async_component;
test "act_with_simple_response" act_with_simple_response;
test "env_development_adds_debug_info" env_development_adds_debug_info;
test "debug_nested_owner_chain" debug_nested_owner_chain;
test "debug_async_component" debug_async_component;
test "debug_outlines_components" debug_outlines_components;
test "debug_not_emitted_without_flag" debug_not_emitted_without_flag;
test "debug_wire_format" debug_wire_format;
test "act_with_error" act_with_error;
test "act_with_error_from_handler" act_with_error_from_handler;
test "act_with_success_from_handler" act_with_success_from_handler;
test "act_decode_error_does_not_raise" act_decode_error_does_not_raise;
test "error_without_suspense" error_without_suspense;
test "keyed_duplicate_client_component_preserves_keys" keyed_duplicate_client_component_preserves_keys;
test "error_in_toplevel" error_in_toplevel;
test "error_in_toplevel_in_async" error_in_toplevel_in_async;
test "suspense_in_a_list_with_error" suspense_in_a_list_with_error;
test "suspense_with_error_under_lowercase" suspense_with_error_under_lowercase;
test "client_component_with_resources_metadata" client_component_with_resources_metadata;
test "page_with_hoisted_resources" page_with_hoisted_resources;
test "nested_context" nested_context;
test "suspense_with_nested_upper_case" suspense_with_nested_upper_case;
test "suspense_at_root" suspense_at_root;
test "suspense_at_root_with_upper_case_children" suspense_at_root_with_upper_case_children;
test "suspense_at_root_with_nested_components" suspense_at_root_with_nested_components;
test "suspense_at_root_with_async" suspense_at_root_with_async;
test "root_async_component_immediate" root_async_component_immediate;
test "root_upper_case_chain" root_upper_case_chain;
test "model_list_value" model_list_value;
test "model_value_assoc" model_value_assoc;
test "client_with_promise_failed_props" client_with_promise_failed_props;
test "client_with_promise_already_failed_props" client_with_promise_already_failed_props;
test "server_function_as_model_prop" server_function_as_model_prop;
test "error_in_prod_hides_message" error_in_prod_hides_message;
test "duplicate_client_component_deduplicates_ref" duplicate_client_component_deduplicates_ref;
]
================================================
FILE: packages/reactDom/test/test_reactDOMStyle.ml
================================================
let assert_styles styles str = Alcotest.check Alcotest.string "should be equal" str (ReactDOM.Style.to_string styles)
let one_styles () =
let styles = ReactDOM.Style.make ~background:"#333" () in
assert_styles styles "background:#333"
let two_styles () =
let styles = ReactDOM.Style.make ~background:"#333" ~fontSize:"24px" () in
assert_styles styles "font-size:24px;background:#333"
let zero_styles () =
let styles = ReactDOM.Style.make () in
assert_styles styles ""
let emtpy_value () =
let styles = ReactDOM.Style.make ~background:"" () in
assert_styles styles ""
let emtpy_value_with_more () =
let styles = ReactDOM.Style.make ~background:"" ~color:"transparent" () in
assert_styles styles "color:transparent"
let unsafe_add_prop () =
let styles = ReactDOM.Style.unsafeAddProp (ReactDOM.Style.make ~background:"#333" ()) "colorScheme" "dark" in
assert_styles styles "color-scheme:dark;background:#333"
let unsafe_add_prop_css_custom_property () =
let styles = ReactDOM.Style.unsafeAddProp (ReactDOM.Style.make ()) "--var-136njlt_1" "8px" in
assert_styles styles "--var-136njlt_1:8px"
let unsafe_add_prop_css_custom_property_with_var_value () =
let styles = ReactDOM.Style.unsafeAddProp (ReactDOM.Style.make ()) "--var-qog-9iu" "var(--alt-background--box)" in
assert_styles styles "--var-qog-9iu:var(--alt-background--box)"
let unsafe_add_prop_css_custom_property_simple () =
let styles = ReactDOM.Style.unsafeAddProp (ReactDOM.Style.make ()) "--var-5uugbw" "16px" in
assert_styles styles "--var-5uugbw:16px"
let unsafe_add_prop_camel_with_digits () =
(* Digits and underscores in non-custom-property keys must not insert dashes. *)
let styles = ReactDOM.Style.unsafeAddProp (ReactDOM.Style.make ()) "grid_area1" "main" in
assert_styles styles "grid_area1:main"
let style_order_matters () =
let styles = ReactDOM.Style.make ~lineBreak:"100px" ~overflowWrap:"break-word" () in
assert_styles styles "overflow-wrap:break-word;line-break:100px"
let style_order_matters_2 () =
let styles = ReactDOM.Style.make ~opacity:"1.0" ~stress:"0" ~width:"20" ~backgroundColor:"red" ~columnGap:"2px" () in
assert_styles styles "column-gap:2px;opacity:1.0;width:20;stress:0;background-color:red"
let test title fn = (Printf.sprintf "ReactDOM.Style.make / %s" title, [ Alcotest_lwt.test_case_sync "" `Quick fn ])
let tests =
[
test "generate empty style" zero_styles;
test "generate one style" one_styles;
test "generate more than one style" two_styles;
test "unsafeAddProp should be kebab-case" unsafe_add_prop;
test "unsafeAddProp preserves CSS custom property keys verbatim" unsafe_add_prop_css_custom_property;
test "unsafeAddProp preserves CSS custom property with var() value"
unsafe_add_prop_css_custom_property_with_var_value;
test "unsafeAddProp preserves simple CSS custom property" unsafe_add_prop_css_custom_property_simple;
test "unsafeAddProp does not insert dashes around digits/underscores" unsafe_add_prop_camel_with_digits;
(* TODO: Add more test for unsafeAddProp *)
test "order matters" style_order_matters;
test "order matters II" style_order_matters_2;
]
================================================
FILE: packages/reactDom/test/test_renderToStaticMarkup.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let single_empty_tag () =
let div = React.createElement "div" [] [] in
assert_string (ReactDOM.renderToStaticMarkup div) ""
let html_doctype () =
let app = React.createElement "html" [] [] in
assert_string (ReactDOM.renderToStaticMarkup app) ""
let empty_string_attribute () =
let div = React.createElement "div" [ React.JSX.String ("class", "className", "") ] [] in
assert_string (ReactDOM.renderToStaticMarkup div) ""
let string_attributes () =
let a =
React.createElement "a"
[ React.JSX.String ("href", "href", "google.html"); React.JSX.String ("target", "target", "_blank") ]
[]
in
assert_string (ReactDOM.renderToStaticMarkup a) ""
let bool_attributes () =
let a =
React.createElement "input"
[
React.JSX.String ("type", "type", "checkbox");
React.JSX.String ("name", "name", "cheese");
React.JSX.Bool ("checked", "checked", true);
React.JSX.Bool ("disabled", "disabled", false);
]
[]
in
assert_string (ReactDOM.renderToStaticMarkup a) ""
let truthy_attributes () =
let component = React.createElement "input" [ React.JSX.String ("aria-hidden", "ariaHidden", "true") ] [] in
assert_string (ReactDOM.renderToStaticMarkup component) ""
let self_closing_tag () =
let input = React.createElement "input" [] [] in
assert_string (ReactDOM.renderToStaticMarkup input) ""
let dom_element_innerHtml () =
let p = React.createElement "p" [] [ React.string "text" ] in
assert_string (ReactDOM.renderToStaticMarkup p) "
text
"
let children () =
let children = React.createElement "div" [] [] in
let div = React.createElement "div" [] [ children ] in
assert_string (ReactDOM.renderToStaticMarkup div) "
"
let ignored_attributes_on_jsx () =
let div =
React.createElement "div"
[
React.JSX.String ("key", "key", "uniqueKeyId");
React.JSX.Bool ("suppressContentEditableWarning", "suppressContentEditableWarning", true);
]
[]
in
assert_string (ReactDOM.renderToStaticMarkup div) ""
let fragment () =
let div = React.createElement "div" [] [] in
let component = React.fragment (React.list [ div; div ]) in
assert_string (ReactDOM.renderToStaticMarkup component) ""
let ignore_nulls () =
let div = React.createElement "div" [] [] in
let span = React.createElement "span" [] [] in
let component = React.createElement "div" [] [ div; span; React.null ] in
assert_string (ReactDOM.renderToStaticMarkup component) "
"
let fragments_and_texts () =
let component =
React.createElement "div" []
[ React.fragment (React.list [ React.string "foo" ]); React.string "bar"; React.createElement "b" [] [] ]
in
assert_string (ReactDOM.renderToStaticMarkup component) "
foobar
"
let lists_and_arrays () =
let component =
React.createElement "div" []
[
React.fragment (React.list [ React.string "This feels "; React.int 100 ]);
React.createElement "br" [] [];
React.fragment
(React.array [| React.string "This doesn't "; React.string "feel right"; React.string " but it works." |]);
]
in
assert_string
(ReactDOM.renderToStaticMarkup component)
"
This feels 100 This doesn't feel right but it works.
"
let inline_styles () =
let component =
React.createElement "button" [ React.JSX.style (ReactDOMStyle.make ~color:"red" ~border:"none" ()) ] []
in
assert_string (ReactDOM.renderToStaticMarkup component) ""
let encode_attributes () =
let component =
React.createElement "div"
[
React.JSX.String ("about", "about", "\' <");
React.JSX.String ("data-user-path", "data-user-path", "what/the/path");
]
[ React.string "& \"" ]
in
assert_string
(ReactDOM.renderToStaticMarkup component)
"
& "
"
let dangerouslySetInnerHtml () =
let component =
React.createElement "script"
[
React.JSX.String ("type", "type", "application/javascript");
React.JSX.DangerouslyInnerHtml "console.log(\"Hi!\")";
]
[]
in
assert_string
(ReactDOM.renderToStaticMarkup component)
""
let context = React.createContext 10
module ContextProvider = struct
include React.Context
let make = React.Context.provider context
end
module ContextConsumer = struct
let make () =
let value = React.useContext context in
React.createElement "section" [] [ React.int value ]
[@@react.component]
end
let context () =
let component =
React.Upper_case_component
( "component",
fun () ->
ContextProvider.make
(ContextProvider.makeProps ~value:20
~children:
(React.Upper_case_component ("context", fun () -> ContextConsumer.make (ContextConsumer.makeProps ())))
()) )
in
assert_string (ReactDOM.renderToStaticMarkup component) "20"
let nested_context () =
let component =
React.Upper_case_component
( "app",
fun () ->
ContextProvider.make
(ContextProvider.makeProps ~value:10
~children:
(React.list
[
React.Upper_case_component
("inner_consumer", fun () -> ContextConsumer.make (ContextConsumer.makeProps ()));
React.Upper_case_component
( "nested_provider",
fun () ->
ContextProvider.make
(ContextProvider.makeProps ~value:20
~children:
(React.Upper_case_component
("nested_consumer", fun () -> ContextConsumer.make (ContextConsumer.makeProps ())))
()) );
React.Upper_case_component
("after_nested_consumer", fun () -> ContextConsumer.make (ContextConsumer.makeProps ()));
])
()) )
in
assert_string
(ReactDOM.renderToStaticMarkup component)
"102010"
let use_state () =
let state, setState = React.useState (fun () -> "LOL") in
let onClick _event = setState (fun _prev -> "OMG") in
let component =
React.createElement "div" []
[
React.createElement "button" [ React.JSX.Event ("onClick", Mouse onClick) ] [];
React.createElement "span" [] [ React.string state ];
]
in
assert_string (ReactDOM.renderToStaticMarkup component) "
LOL
"
let use_memo () =
let memo = React.useMemo (fun () -> 23) in
let component = React.createElement "header" [] [ React.int memo ] in
assert_string (ReactDOM.renderToStaticMarkup component) "23"
let use_callback () =
let memo = React.useCallback (fun () -> 23) in
let component = React.createElement "header" [] [ React.int (memo ()) ] in
assert_string (ReactDOM.renderToStaticMarkup component) "23"
let inner_html () =
let component = React.createElement "div" [ React.JSX.DangerouslyInnerHtml "foo" ] [] in
assert_string (ReactDOM.renderToStaticMarkup component) "
foo
"
let make ~name () =
let onClick (event : React.Event.Mouse.t) : unit = ignore event in
React.createElement "button"
[
React.JSX.String ("name", "name", (name : string));
React.JSX.Event ("onClick", React.JSX.Mouse (onClick : React.Event.Mouse.t -> unit));
]
[]
let event () = assert_string (ReactDOM.renderToStaticMarkup (make ~name:"json" ())) ""
let className () =
let div = React.createElement "div" [ React.JSX.String ("class", "className", "lol") ] [] in
assert_string (ReactDOM.renderToStaticMarkup div) ""
let className_2 () =
let component =
React.createElement "div" [ React.JSX.String ("class", "className", "flex xs:justify-center overflow-hidden") ] []
in
assert_string (ReactDOM.renderToStaticMarkup component) ""
let className_3 () =
let component =
React.fragment
(React.list
[
React.createElement "div" [ React.JSX.String ("class", "className", "flex") ] [];
React.createElement "div" (ReactDOM.domProps ~className:"flex" ()) [];
])
in
assert_string (ReactDOM.renderToStaticMarkup component) ""
let render_with_doc_type () =
let div = React.createElement "div" [] [ React.createElement "span" [] [ React.string "This is valid HTML5" ] ] in
assert_string (ReactDOM.renderToStaticMarkup div) "
This is valid HTML5
"
let dom_props_should_work () =
let div = React.createElement "div" (ReactDOM.domProps ~key:"uniq" ~className:"mabutton" ()) [] in
assert_string (ReactDOM.renderToStaticMarkup div) ""
let render_svg () =
let path =
React.createElement "path"
[
React.JSX.String
( "d",
"d",
"M 5 3 C 3.9069372 3 3 3.9069372 3 5 L 3 19 C 3 20.093063 3.9069372 21 5 21 L 19 21 C 20.093063 21 21 \
20.093063 21 19 L 21 12 L 19 12 L 19 19 L 5 19 L 5 5 L 12 5 L 12 3 L 5 3 z M 14 3 L 14 5 L 17.585938 5 L \
8.2929688 14.292969 L 9.7070312 15.707031 L 19 6.4140625 L 19 10 L 21 10 L 21 3 L 14 3 z" );
]
[]
in
let svg =
React.createElement "svg"
[
React.JSX.String ("xmlns", "xmlns", "http://www.w3.org/2000/svg");
React.JSX.String ("viewBox", "viewBox", "0 0 24 24");
React.JSX.String ("width", "width", "24px");
React.JSX.String ("height", "height", "24px");
]
[ path ]
in
assert_string (ReactDOM.renderToStaticMarkup svg)
""
(* TODO: add cases for React.Suspense
function Button() {
return ;
}
function SuspendedButton() {
throw new Promise(() => {});
return ;
}
ReactDOMServer.renderToString(
This is a callback}>
);
//
ReactDOMServer.renderToString(
This is a callback}>
);
//
This is a callback
*)
let ref_as_callback_prop_works () =
let app =
React.Upper_case_component
( "app",
fun () ->
React.createElement "span"
[ React.JSX.Ref (ReactDOM.Ref.callbackDomRef (fun _ -> ())) ]
[ React.string "yow" ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "yow"
let ref_as_prop_works () =
let app =
React.Upper_case_component
( "app",
fun () ->
let tableRootRef = React.useRef Js.Nullable.null in
React.createElement "span" [ React.JSX.Ref (ReactDOM.Ref.domRef tableRootRef) ] [ React.string "yow" ] )
in
assert_string (ReactDOM.renderToStaticMarkup app) "yow"
let async_component () =
let app =
React.Async_component ("app", fun () -> Lwt.return (React.createElement "span" [] [ React.string "yow" ]))
in
let raises () =
let _ = ReactDOM.renderToStaticMarkup app in
()
in
Alcotest.check_raises "Expected invalid argument"
(Invalid_argument
"Async components can't be rendered to static markup, since rendering is synchronous. Please use \
`renderToStream` instead.")
raises
let test title fn =
(Printf.sprintf "ReactDOM.renderToStaticMarkup / %s" title, [ Alcotest_lwt.test_case_sync "" `Quick fn ])
let tests =
[
test "html_doctype" html_doctype;
test "single_empty_tag" single_empty_tag;
test "empty_string_attribute" empty_string_attribute;
test "bool_attributes" bool_attributes;
test "truthy_attributes" truthy_attributes;
test "ignore_nulls" ignore_nulls;
test "string_attributes" string_attributes;
test "self_closing_tag" self_closing_tag;
test "dom_element_innerHtml" dom_element_innerHtml;
test "children" children;
test "className" className;
test "className_2" className_2;
test "className_3" className_3;
test "fragment" fragment;
test "fragments_and_texts" fragments_and_texts;
test "ignored_attributes_on_jsx" ignored_attributes_on_jsx;
test "inline_styles" inline_styles;
test "encode_attributes" encode_attributes;
test "dom_props_should_work" dom_props_should_work;
test "dangerouslySetInnerHtml" dangerouslySetInnerHtml;
test "context" context;
test "nested_context" nested_context;
test "use_state" use_state;
test "use_memo" use_memo;
test "use_callback" use_callback;
test "inner_html" inner_html;
test "event" event;
test "render_with_doc_type" render_with_doc_type;
test "render_svg" render_svg;
test "ref_as_prop_works" ref_as_prop_works;
test "ref_as_callback_prop_works" ref_as_callback_prop_works;
test "async" async_component;
test "lists_and_arrays" lists_and_arrays;
]
================================================
FILE: packages/reactDom/test/test_renderToStream.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let assert_list ty left right = Alcotest.check (Alcotest.list ty) "should be equal" right left
let test title fn =
let isCI = match Sys.getenv_opt "CI" with Some _ -> true | None -> false in
( Printf.sprintf "ReactDOM.renderToStream / %s" title,
[
Alcotest_lwt.test_case "" `Quick (fun _switch () ->
let start = Unix.gettimeofday () in
let timeout =
let%lwt () = Lwt_unix.sleep (if isCI then 0.05 else 0.02) in
Alcotest.failf "Test '%s' timed out" title
in
let%lwt test_promise = Lwt.pick [ fn (); timeout ] in
let epsilon = 0.001 in
let duration = Unix.gettimeofday () -. start in
if abs_float duration >= epsilon then
Printf.printf " \027[1m\027[33m[WARNING]\027[0m Test '%s' took %.3f seconds\n" title duration
else ();
Lwt.return test_promise);
] )
let assert_stream (stream : string Lwt_stream.t) expected =
let%lwt content = Lwt_stream.to_list stream in
if content = [] then Lwt.return (Alcotest.fail "stream should not be empty")
else Lwt.return (assert_list Alcotest.string content expected)
let mk_suspense ?key ?fallback ?children () = React.Suspense.make ?key (React.Suspense.makeProps ?fallback ?children ())
let mk_context context ~value ~children () =
React.Context.provider context (React.Context.makeProps ~value ~children ())
module Sleep = struct
let cached = ref false
let destroy () = cached := false
let delay v =
if cached.contents then Lwt.return v
else (
cached.contents <- true;
let%lwt () = Lwt.pause () in
Lwt.return v)
end
let deffered_component ~seconds ~children () =
React.Async_component
( "deffered_component",
fun () ->
let%lwt () = if seconds <= 0. then Lwt.pause () else Lwt_unix.sleep seconds in
Lwt.return
(React.createElement "div" []
[ React.string ("Sleep " ^ Float.to_string seconds ^ " seconds"); React.string ", "; children ]) )
let silly_stream () =
let stream, push = Lwt_stream.create () in
push (Some "first");
push (Some "secondo");
push (Some "trienio");
push None;
assert_stream stream [ "first"; "secondo"; "trienio" ]
let react_use_without_suspense () =
Sleep.destroy ();
let app =
React.Upper_case_component
( "app",
fun () ->
let delay = React.Experimental.usePromise (Sleep.delay 0.001) in
React.createElement "div" [] [ React.createElement "span" [] [ React.string "Hello "; React.float delay ] ] )
in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "
Hello 0.001
" ]
let suspense_without_promise () =
let hi =
React.Upper_case_component
("hi", fun () -> React.createElement "div" [] [ React.createElement "span" [] [ React.string "Hello" ] ])
in
let app () = mk_suspense ~fallback:(React.string "Loading...") ~children:hi () in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
" ]
let suspense_with_resolved_text_after_element_with_text_child () =
let app () =
let deferred () =
React.Async_component
( "deferred",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return
(React.createElement "div" []
[
React.string "before "; React.createElement "span" [] [ React.string "inner" ]; React.string " after";
]) )
in
mk_suspense ~fallback:(React.string "Loading...") ~children:(deferred ()) ()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"Loading...";
"
before inner after
";
"";
]
let assert_raises exn fn =
match%lwt fn () with
| exception exn -> Lwt.return (assert_string (Printexc.to_string exn) (Printexc.to_string exn))
| _ -> Alcotest.failf "Expected exception %s" (Printexc.to_string exn)
let always_throwing_component () =
React.Upper_case_component ("always throwing", fun () -> raise (Failure "always throwing"))
let uppercase_component_always_throwing () =
let app () = always_throwing_component () in
assert_raises (Failure "always throwing") (fun () ->
ReactDOM.renderToStream (React.Upper_case_component ("app", app)))
let suspense_with_always_throwing () =
(* This test is very fragile since it relies on the stack trace being the same (so line numbers and methods should match).
We disable backtracing to avoid having to match the backtrace *)
let prev = Printexc.backtrace_status () in
Printexc.record_backtrace false;
let app () = mk_suspense ~fallback:(React.string "Loading...") ~children:(always_throwing_component ()) () in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
Printexc.record_backtrace prev;
assert_stream stream
[ "Loading..." ]
let suspense_with_react_use () =
Sleep.destroy ();
let time =
React.Upper_case_component
( "time",
fun () ->
let delay = React.Experimental.usePromise (Sleep.delay 0.005) in
React.createElement "div" [] [ React.createElement "span" [] [ React.string "Hello "; React.float delay ] ] )
in
let app () = mk_suspense ~fallback:(React.string "Loading...") ~children:time () in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"Loading...";
"
Hello 0.005
";
"";
]
let with_custom_component () =
let custom_component =
React.Upper_case_component
( "custom component",
fun () -> React.createElement "div" [] [ React.createElement "span" [] [ React.string "Custom Component" ] ] )
in
let app () = React.createElement "div" [] [ custom_component ] in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
Custom Component
" ]
let with_multiple_custom_components () =
let custom_component =
React.Upper_case_component
( "custom component",
fun () -> React.createElement "div" [] [ React.createElement "span" [] [ React.string "Custom Component" ] ] )
in
let app () = React.createElement "div" [] [ custom_component; custom_component ] in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
Custom Component
Custom Component
" ]
let async_component () =
let app () =
React.Async_component ("app", fun () -> Lwt.return (React.createElement "span" [] [ React.string "yow" ]))
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "yow" ]
let suspense_with_async_component () =
let app () =
React.createElement "div" []
[
mk_suspense ~fallback:(React.string "Fallback 1")
~children:(deffered_component ~seconds:0. ~children:(React.string "lol") ())
();
]
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"
";
"";
]
let abort_streaming () =
let app () =
React.createElement "div" []
[
mk_suspense ~fallback:(React.string "Loading 1")
~children:(deffered_component ~seconds:0.005 ~children:(React.string "Content 1") ())
();
mk_suspense ~fallback:(React.string "Loading 2")
~children:(deffered_component ~seconds:0.001 ~children:(React.string "Content 2") ())
();
]
in
let%lwt stream, abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
let%lwt first_chunk = Lwt_stream.get stream in
(* Abort after first chunk *)
abort ();
let%lwt remaining = Lwt_stream.to_list stream in
assert_list Alcotest.string remaining [];
match first_chunk with
| Some chunk ->
Lwt.return
(assert_string chunk
"
Loading 1Loading 2
")
| None -> Alcotest.fail "Expected at least one chunk before abort"
let context_basic () =
let context = React.createContext "default" in
let consumer =
React.Upper_case_component
( "consumer",
fun () ->
let value = React.useContext context in
React.createElement "span" [] [ React.string value ] )
in
let app () = mk_context context ~value:"provided" ~children:consumer () in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "provided" ]
let context_default_value () =
let context = React.createContext "fallback" in
let app =
React.Upper_case_component
( "consumer",
fun () ->
let value = React.useContext context in
React.createElement "span" [] [ React.string value ] )
in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "fallback" ]
let context_nested_providers () =
let context = React.createContext "default" in
let consumer () =
React.Upper_case_component
( "consumer",
fun () ->
let value = React.useContext context in
React.createElement "span" [] [ React.string value ] )
in
let app () =
mk_context context ~value:"outer"
~children:
(React.list
[
consumer ();
React.Upper_case_component
("inner_provider", fun () -> mk_context context ~value:"inner" ~children:(consumer ()) ());
consumer ();
])
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "outerinnerouter" ]
let context_multiple_independent () =
let context_a = React.createContext "a-default" in
let context_b = React.createContext "b-default" in
let consumer () =
React.Upper_case_component
( "consumer",
fun () ->
let a = React.useContext context_a in
let b = React.useContext context_b in
React.createElement "div" [] [ React.string a; React.string "-"; React.string b ] )
in
let app () =
mk_context context_a ~value:"a-provided"
~children:(mk_context context_b ~value:"b-provided" ~children:(consumer ()) ())
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
a-provided-b-provided
" ]
let context_with_suspense () =
let context = React.createContext "default" in
let consumer =
React.Upper_case_component
( "consumer",
fun () ->
let value = React.useContext context in
React.createElement "span" [] [ React.string value ] )
in
let app () =
mk_context context ~value:"provided"
~children:(mk_suspense ~fallback:(React.string "loading") ~children:consumer ())
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "provided" ]
let async_component_with_use_id () =
let app =
React.Async_component
( "app",
fun () ->
let id = React.useId () in
Lwt.return (React.createElement "div" [ React.JSX.String ("id", "id", id) ] []) )
in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "" ]
let async_component_with_use_id_and_sibling () =
let async_with_id =
React.Async_component
( "AsyncWithId",
fun () ->
let id = React.useId () in
Lwt.return (React.createElement "div" [ React.JSX.String ("id", "id", id) ] []) )
in
let sync_with_id =
React.Upper_case_component
( "SyncWithId",
fun () ->
let id = React.useId () in
React.createElement "span" [ React.JSX.String ("id", "id", id) ] [] )
in
let app = React.createElement "div" [] [ async_with_id; sync_with_id ] in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "
" ]
let async_component_with_use_id_in_suspense () =
let async_with_id =
React.Async_component
( "AsyncWithId",
fun () ->
let id = React.useId () in
let%lwt () = Lwt.pause () in
Lwt.return (React.createElement "div" [ React.JSX.String ("id", "id", id) ] []) )
in
let app = mk_suspense ~fallback:(React.string "loading") ~children:async_with_id () in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream
[
"loading";
"
";
"";
]
let async_component_with_multiple_use_ids () =
let app =
React.Async_component
( "app",
fun () ->
let id1 = React.useId () in
let id2 = React.useId () in
Lwt.return
(React.createElement "div"
[ React.JSX.String ("data-id1", "data-id1", id1); React.JSX.String ("data-id2", "data-id2", id2) ]
[]) )
in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "" ]
let multiple_async_components_without_suspense () =
let app () =
React.createElement "div" []
[
deffered_component ~seconds:0. ~children:(React.string "First") ();
deffered_component ~seconds:0. ~children:(React.string "Second") ();
]
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"
Sleep 0. seconds, First
Sleep 0. seconds, Second
";
]
let context_provider_with_suspended_consumer () =
let context = React.createContext "default" in
let async_consumer =
React.Async_component
( "async_consumer",
fun () ->
(* useContext must be called synchronously, before yielding *)
let value = React.useContext context in
let%lwt () = Lwt.pause () in
Lwt.return (React.createElement "span" [] [ React.string value ]) )
in
let app () =
(* Provider inside Suspense children — so it's re-rendered in the deferred path *)
mk_suspense ~fallback:(React.string "loading")
~children:(mk_context context ~value:"from-provider" ~children:async_consumer ())
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"loading";
"
from-provider
";
"";
]
let async_component_returning_suspense_with_async_children () =
let app () =
mk_suspense ~fallback:(React.string "Outer loading")
~children:
(React.Async_component
( "outer_async",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return
(mk_suspense ~fallback:(React.string "Inner loading")
~children:(deffered_component ~seconds:0. ~children:(React.string "deep") ())
()) ))
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"Outer loading";
"
Inner loading
";
"";
"
Sleep 0. seconds, deep
";
"";
]
let static_element_in_stream () =
let original = React.createElement "div" [] [ React.string "Hello" ] in
let app = React.Static { prerendered = "
Hello
"; original } in
let%lwt stream, _abort = ReactDOM.renderToStream app in
assert_stream stream [ "
Hello
" ]
let client_component_error_in_stream () =
let app =
React.Client_component
{ key = None; props = []; client = React.Empty; import_module = "test_module"; import_name = "TestComponent" }
in
assert_raises
(Invalid_argument
"Client components can't be rendered on the server via renderToStream. Please use the React server components \
API instead. module: test_module") (fun () -> ReactDOM.renderToStream app)
let suspense_with_failed_promise () =
let prev = Printexc.backtrace_status () in
Printexc.record_backtrace false;
let app () =
mk_suspense ~fallback:(React.string "Error fallback")
~children:(React.Async_component ("failing_async", fun () -> Lwt.fail (Failure "async failure")))
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
Printexc.record_backtrace prev;
assert_stream stream
[ "Error fallback" ]
let fragment_in_stream () =
let app () =
React.Fragment
(React.createElement "div" []
[ React.createElement "span" [] [ React.string "a" ]; React.createElement "span" [] [ React.string "b" ] ])
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
" ]
let empty_element_in_stream () =
let app () = React.createElement "div" [] [ React.Empty; React.string "hello"; React.Empty ] in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream [ "
hello
" ]
let dangerous_html_in_suspense () =
let app () =
mk_suspense ~fallback:(React.string "Loading...")
~children:
(React.Async_component
( "Dangerous and sleep",
fun () ->
let%lwt () = Lwt.pause () in
Lwt.return
(React.createElement "div"
[
React.JSX.dangerouslyInnerHtml
(let html_content = "
Dangerous HTML
" in
object
method __html = html_content
end);
]
[]) ))
()
in
let%lwt stream, _abort = ReactDOM.renderToStream (React.Upper_case_component ("app", app)) in
assert_stream stream
[
"Loading...";
"
Dangerous HTML
";
"";
]
let tests =
[
test "silly_stream" silly_stream;
test "render_inner_html" render_inner_html;
test "react_use_without_suspense" react_use_without_suspense;
test "uppercase_component_always_throwing" uppercase_component_always_throwing;
test "suspense_with_react_use" suspense_with_react_use;
test "async component" async_component;
test "async_component_without_suspense" async_component_without_suspense;
test "suspense_without_promise" suspense_without_promise;
test "text_after_element_with_text_child" text_after_element_with_text_child;
test "suspense_with_resolved_text_after_element_with_text_child"
suspense_with_resolved_text_after_element_with_text_child;
test "suspense_with_async_component" suspense_with_async_component;
test "suspense_with_always_throwing" suspense_with_always_throwing;
test "suspense_with_nested_suspense" suspense_with_nested_suspense;
test "suspense_with_nested_suspenses" suspense_with_nested_suspenses;
test "suspense_with_nested_suspense_with_error" suspense_with_nested_suspense_with_error;
test "suspense_with_multiple_children" suspense_with_multiple_children;
test "suspense_with_multiple_children_reordered" suspense_with_multiple_children_reordered;
test "suspense_with_concurrent_suspenses" suspense_with_concurrent_suspenses;
test "suspense_with_comments" suspense_with_comments;
(* test "abort_streaming" abort_streaming; *)
test "context_basic" context_basic;
test "context_default_value" context_default_value;
test "context_nested_providers" context_nested_providers;
test "context_multiple_independent" context_multiple_independent;
test "context_with_suspense" context_with_suspense;
test "async_component_with_use_id" async_component_with_use_id;
test "async_component_with_use_id_and_sibling" async_component_with_use_id_and_sibling;
test "async_component_with_use_id_in_suspense" async_component_with_use_id_in_suspense;
test "async_component_with_multiple_use_ids" async_component_with_multiple_use_ids;
test "multiple_async_components_without_suspense" multiple_async_components_without_suspense;
test "context_provider_with_suspended_consumer" context_provider_with_suspended_consumer;
test "async_component_returning_suspense_with_async_children" async_component_returning_suspense_with_async_children;
test "static_element_in_stream" static_element_in_stream;
test "client_component_error_in_stream" client_component_error_in_stream;
test "suspense_with_failed_promise" suspense_with_failed_promise;
test "fragment_in_stream" fragment_in_stream;
test "list_in_stream" list_in_stream;
test "array_in_stream" array_in_stream;
test "empty_element_in_stream" empty_element_in_stream;
test "dangerous_html_in_suspense" dangerous_html_in_suspense;
]
================================================
FILE: packages/reactDom/test/test_renderToString.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let react_root_one_element () =
let div = React.createElement "div" [] [] in
assert_string (ReactDOM.renderToString div) ""
let react_root_two_elements () =
let div = React.createElement "div" [] [ React.createElement "span" [] [] ] in
assert_string (ReactDOM.renderToString div) "
"
let text_single_node () =
let div = React.createElement "div" [] [ React.createElement "span" [] [ React.string "Hello" ] ] in
assert_string (ReactDOM.renderToString div) "
Hello
"
let consecutives_text_nodes () =
let div =
React.createElement "div" [] [ React.createElement "span" [] [ React.string "Hello"; React.string "Hello" ] ]
in
assert_string (ReactDOM.renderToString div) "
HelloHello
"
let separated_text_nodes_by_other_parents () =
let app () =
React.Upper_case_component
( "app",
fun () ->
React.list
[
React.createElement "main" [] [ React.string "Hi"; React.createElement "span" [] [ React.string "chat" ] ];
] )
in
assert_string (ReactDOM.renderToString (app ())) "Hichat"
let text_after_element_with_text_child () =
let div =
React.createElement "div" []
[ React.string "before "; React.createElement "span" [] [ React.string "inner" ]; React.string " after" ]
in
assert_string (ReactDOM.renderToString div) "
before inner after
"
let suspense_children_render_once () =
let render_count = ref 0 in
let child () =
React.Upper_case_component
( "Child",
fun () ->
render_count := !render_count + 1;
React.createElement "div" [] [ React.string "hello" ] )
in
let el =
React.Suspense
{ key = None; children = child (); fallback = React.createElement "div" [] [ React.string "loading" ] }
in
let html = ReactDOM.renderToString el in
assert_string html "
hello
";
Alcotest.(check int) "children should render exactly once" 1 !render_count
let suspense_fallback_on_error () =
let el =
React.Suspense
{
key = None;
children = React.Upper_case_component ("Throws", fun () -> raise (Failure "boom"));
fallback = React.createElement "div" [] [ React.string "fallback" ];
}
in
let html = ReactDOM.renderToString el in
assert_string html "
fallback
"
let test title fn = (Printf.sprintf "ReactDOM.renderToString / %s" title, [ Alcotest_lwt.test_case_sync "" `Quick fn ])
let tests =
[
test "react_root_one_element" react_root_one_element;
test "react_root_two_elements" react_root_two_elements;
test "text_single_node should not add " text_single_node;
test "consecutives_text_nodes should add " consecutives_text_nodes;
test "separated_text_nodes_by_other_parents" separated_text_nodes_by_other_parents;
test "text_after_element_with_text_child" text_after_element_with_text_child;
test "suspense children render exactly once" suspense_children_render_once;
test "suspense renders fallback on error" suspense_fallback_on_error;
]
================================================
FILE: packages/reactDom/test/test_useId.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
(* Helper components *)
let div_with_id () =
React.Upper_case_component
( "DivWithId",
fun () ->
let id = React.useId () in
React.createElement "div" [ React.JSX.String ("id", "id", id) ] [] )
let div_with_two_ids () =
React.Upper_case_component
( "DivWithTwoIds",
fun () ->
let id1 = React.useId () in
let id2 = React.useId () in
React.createElement "div"
[ React.JSX.String ("data-id1", "data-id1", id1); React.JSX.String ("data-id2", "data-id2", id2) ]
[] )
let div_with_three_ids () =
React.Upper_case_component
( "DivWithThreeIds",
fun () ->
let id1 = React.useId () in
let id2 = React.useId () in
let id3 = React.useId () in
React.createElement "div"
[
React.JSX.String ("data-id1", "data-id1", id1);
React.JSX.String ("data-id2", "data-id2", id2);
React.JSX.String ("data-id3", "data-id3", id3);
]
[] )
let wrapper children =
React.Upper_case_component
("Wrapper", fun () -> React.createElement "div" [ React.JSX.String ("class", "className", "wrapper") ] [ children ])
let parent_with_id children =
React.Upper_case_component
( "ParentWithId",
fun () ->
let id = React.useId () in
React.createElement "div" [ React.JSX.String ("id", "id", id) ] [ children ] )
let mk_provider ctx ~value ~children () = React.Context.provider ctx (React.Context.makeProps ~value ~children ())
(* All expected values verified against React 19.1.0 (bun arch/server/test-useid.js)
React 19 ID format: \xc2\xab (U+00AB «) + prefix + R + treeId + \xc2\xbb (U+00BB ») *)
let single_component_with_use_id () =
let html = ReactDOM.renderToString (div_with_id ()) in
assert_string html ""
let two_sibling_components () =
let el = React.createElement "div" [] [ div_with_id (); div_with_id () ] in
let html = ReactDOM.renderToString el in
assert_string html "
"
let nested_components () =
let el = parent_with_id (div_with_id ()) in
let html = ReactDOM.renderToString el in
assert_string html "
"
let multiple_use_id_calls () =
let html = ReactDOM.renderToString (div_with_two_ids ()) in
assert_string html ""
let three_use_id_calls () =
let html = ReactDOM.renderToString (div_with_three_ids ()) in
assert_string html
""
let siblings_with_nested_children () =
let el = React.createElement "div" [] [ parent_with_id (div_with_id ()); div_with_id () ] in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let deep_nesting () =
let el = parent_with_id (parent_with_id (div_with_id ())) in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let wrapper_without_use_id () =
let el = wrapper (div_with_id ()) in
let html = ReactDOM.renderToString el in
assert_string html "
"
let three_siblings () =
let el = React.createElement "div" [] [ div_with_id (); div_with_id (); div_with_id () ] in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let complex_siblings_with_nested () =
let el =
React.createElement "div" []
[
parent_with_id (React.Fragment (React.List [ div_with_id (); div_with_id () ])); parent_with_id (div_with_id ());
]
in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let separate_renders_same_ids () =
let html1 = ReactDOM.renderToString (div_with_id ()) in
let html2 = ReactDOM.renderToString (div_with_id ()) in
assert_string html1 html2
let static_markup_use_id () =
let html = ReactDOM.renderToStaticMarkup (div_with_id ()) in
assert_string html ""
let identifier_prefix () =
let html = ReactDOM.renderToString ~identifier_prefix:"myapp" (div_with_id ()) in
assert_string html ""
(* ── Edge case tests (verified against React 19.1.0 output) ────────────────── *)
let use_id_inside_suspense () =
let el =
React.Suspense
{ key = None; children = div_with_id (); fallback = React.createElement "div" [] [ React.string "loading" ] }
in
let html = ReactDOM.renderToString el in
assert_string html ""
let use_id_suspense_and_sibling () =
let el =
React.createElement "div" []
[
React.Suspense
{ key = None; children = div_with_id (); fallback = React.createElement "div" [] [ React.string "loading" ] };
div_with_id ();
]
in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let fragment_single_child () =
let el = React.createElement "div" [] [ React.Fragment (div_with_id ()) ] in
let html = ReactDOM.renderToString el in
assert_string html "
"
let fragment_multiple_children () =
let el = React.createElement "div" [] [ React.Fragment (React.List [ div_with_id (); div_with_id () ]) ] in
let html = ReactDOM.renderToString el in
assert_string html "
"
let nested_fragments () =
let el = React.createElement "div" [] [ React.Fragment (React.Fragment (div_with_id ())) ] in
let html = ReactDOM.renderToString el in
assert_string html "
"
let null_between_siblings () =
let el = React.createElement "div" [] [ div_with_id (); React.Empty; div_with_id () ] in
let html = ReactDOM.renderToString el in
assert_string html "
"
let many_siblings () =
let children = List.init 10 (fun _ -> div_with_id ()) in
let el = React.createElement "div" [] children in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let provider_transparent () =
let ctx = React.createContext "default" in
let el = mk_provider ctx ~value:"provided" ~children:(div_with_id ()) () in
let html = ReactDOM.renderToString el in
assert_string html ""
let kitchen_sink () =
let ctx = React.createContext "default" in
let el =
React.createElement "div" []
[
mk_provider ctx ~value:"a"
~children:
(React.Fragment
(React.List
[
div_with_id ();
React.Suspense
{
key = None;
children = div_with_id ();
fallback = React.createElement "span" [] [ React.string "..." ];
};
]))
();
div_with_id ();
]
in
let html = ReactDOM.renderToString el in
assert_string html
"
"
let test title fn =
(Printf.sprintf "ReactDOM.renderToString / useId / %s" title, [ Alcotest_lwt.test_case_sync "" `Quick fn ])
let tests =
[
test "single component with useId" single_component_with_use_id;
test "two sibling components" two_sibling_components;
test "nested components" nested_components;
test "multiple useId calls in one component" multiple_use_id_calls;
test "three useId calls in one component" three_use_id_calls;
test "siblings with nested children" siblings_with_nested_children;
test "deep nesting (3 levels)" deep_nesting;
test "wrapper without useId is transparent" wrapper_without_use_id;
test "three siblings" three_siblings;
test "complex siblings with nested" complex_siblings_with_nested;
test "separate renders produce same IDs" separate_renders_same_ids;
test "renderToStaticMarkup also works" static_markup_use_id;
test "identifier_prefix" identifier_prefix;
test "useId inside Suspense (sync)" use_id_inside_suspense;
test "Suspense with useId + sibling" use_id_suspense_and_sibling;
test "Fragment single child is transparent" fragment_single_child;
test "Fragment multiple children forks" fragment_multiple_children;
test "Nested fragments transparent" nested_fragments;
test "Null/Empty between siblings preserves slots" null_between_siblings;
test "Many siblings (10, base-32 at Ra)" many_siblings;
test "Provider is transparent" provider_transparent;
test "Kitchen sink (Provider + Fragment + Suspense)" kitchen_sink;
]
================================================
FILE: packages/reactDom/test/test_write_to_buffer.ml
================================================
let assert_string left right = Alcotest.check Alcotest.string "should be equal" right left
let write element =
let buf = Buffer.create 128 in
ReactDOM.write_to_buffer buf element;
Buffer.contents buf
let empty_element () = assert_string (write React.null) ""
let single_element () =
let div = React.createElement "div" [] [] in
assert_string (write div) ""
let nested_elements () =
let div = React.createElement "div" [] [ React.createElement "span" [] [] ] in
assert_string (write div) "
"
let self_closing_tag () =
let input = React.createElement "input" [] [] in
assert_string (write input) ""
let text_content () =
let p = React.createElement "p" [] [ React.string "hello" ] in
assert_string (write p) "
hello
"
let no_text_separators () =
(* write_to_buffer should NOT add between consecutive text nodes *)
let div = React.createElement "div" [] [ React.string "hello"; React.string "world" ] in
assert_string (write div) "
helloworld
"
let no_doctype () =
(* write_to_buffer should NOT inject *)
let html = React.createElement "html" [] [] in
assert_string (write html) ""
let string_attributes () =
let a =
React.createElement "a"
[ React.JSX.String ("href", "href", "page.html"); React.JSX.String ("target", "target", "_blank") ]
[]
in
assert_string (write a) {||}
let bool_true_attribute () =
let input = React.createElement "input" [ React.JSX.Bool ("checked", "checked", true) ] [] in
assert_string (write input) ""
let bool_false_attribute () =
let input = React.createElement "input" [ React.JSX.Bool ("disabled", "disabled", false) ] [] in
assert_string (write input) ""
let style_attribute () =
let div = React.createElement "div" [ React.JSX.style (ReactDOMStyle.make ~color:"red" ~border:"none" ()) ] [] in
assert_string (write div) {||}
let html_escaping () =
let div = React.createElement "div" [] [ React.string "a < b & c > d \"e\" 'f'" ] in
assert_string (write div) "
a < b & c > d "e" 'f'
"
let attribute_escaping () =
let div = React.createElement "div" [ React.JSX.String ("title", "title", "a < b & \"c\"") ] [] in
assert_string (write div) {||}
let dangerously_set_inner_html () =
let div = React.createElement "div" [ React.JSX.DangerouslyInnerHtml "raw" ] [] in
assert_string (write div) "
raw
"
let fragment () =
let component = React.fragment (React.list [ React.createElement "div" [] []; React.createElement "span" [] [] ]) in
assert_string (write component) ""
let list_children () =
let div = React.createElement "div" [] [ React.list [ React.string "a"; React.string "b" ] ] in
assert_string (write div) "
ab
"
let array_children () =
let div = React.createElement "div" [] [ React.array [| React.string "x"; React.string "y" |] ] in
assert_string (write div) "
xy
"
let upper_case_component () =
let app = React.Upper_case_component ("app", fun () -> React.createElement "div" [] [ React.string "component" ]) in
assert_string (write app) "
component
"
let suspense_success () =
(* On success, write_to_buffer renders children without suspense markers *)
let el =
React.Suspense
{
key = None;
children = React.createElement "div" [] [ React.string "ok" ];
fallback = React.createElement "div" [] [ React.string "loading" ];
}
in
assert_string (write el) "
ok
"
let suspense_fallback_on_error () =
(* On error, write_to_buffer renders fallback without suspense markers *)
let el =
React.Suspense
{
key = None;
children = React.Upper_case_component ("Throws", fun () -> raise (Failure "boom"));
fallback = React.createElement "div" [] [ React.string "fallback" ];
}
in
assert_string (write el) "
fallback
"
let static_element () =
let original = React.createElement "div" [] [ React.string "Hello" ] in
let app = React.Static { prerendered = "
Hello
"; original } in
assert_string (write app) "
Hello
"
let event_attributes_ignored () =
let onClick (_event : React.Event.Mouse.t) : unit = () in
let button =
React.createElement "button"
[ React.JSX.String ("name", "name", "btn"); React.JSX.Event ("onClick", React.JSX.Mouse onClick) ]
[]
in
assert_string (write button) {||}
let ref_attributes_ignored () =
let app =
React.Upper_case_component
( "app",
fun () ->
React.createElement "span" [ React.JSX.Ref (ReactDOM.Ref.callbackDomRef (fun _ -> ())) ] [ React.string "hi" ]
)
in
assert_string (write app) "hi"
let react_custom_attributes_ignored () =
let div =
React.createElement "div"
[
React.JSX.String ("key", "key", "k1");
React.JSX.Bool ("suppressContentEditableWarning", "suppressContentEditableWarning", true);
React.JSX.String ("class", "className", "test");
]
[]
in
assert_string (write div) {||}
let async_component_raises () =
let app = React.Async_component ("app", fun () -> Lwt.return (React.createElement "span" [] [ React.string "hi" ])) in
let raises () =
let _result = write app in
()
in
Alcotest.check_raises "Expected invalid argument"
(Invalid_argument "Async components can't be rendered synchronously via write_to_buffer.")
raises
let context () =
let ctx = React.createContext "default" in
let provider = React.Context.provider ctx in
let consumer () =
let value = React.useContext ctx in
React.createElement "span" [] [ React.string value ]
in
let app =
React.Upper_case_component
( "app",
fun () ->
provider
(React.Context.makeProps ~value:"provided"
~children:(React.Upper_case_component ("consumer", fun () -> consumer ()))
()) )
in
assert_string (write app) "provided"
let test title fn = (Printf.sprintf "ReactDOM.write_to_buffer / %s" title, [ Alcotest_lwt.test_case_sync "" `Quick fn ])
let tests =
[
test "empty element" empty_element;
test "single element" single_element;
test "nested elements" nested_elements;
test "self-closing tag" self_closing_tag;
test "text content" text_content;
test "no text separators between consecutive text nodes" no_text_separators;
test "no doctype injection for html tag" no_doctype;
test "string attributes" string_attributes;
test "bool true attribute" bool_true_attribute;
test "bool false attribute" bool_false_attribute;
test "style attribute" style_attribute;
test "html escaping" html_escaping;
test "attribute escaping" attribute_escaping;
test "dangerouslySetInnerHTML" dangerously_set_inner_html;
test "fragment" fragment;
test "list children" list_children;
test "array children" array_children;
test "upper case component" upper_case_component;
test "suspense success renders without markers" suspense_success;
test "suspense fallback renders without markers" suspense_fallback_on_error;
test "static element" static_element;
test "event attributes ignored" event_attributes_ignored;
test "ref attributes ignored" ref_attributes_ignored;
test "react custom attributes ignored" react_custom_attributes_ignored;
test "async component raises" async_component_raises;
test "context" context;
]
================================================
FILE: packages/rsc/README.md
================================================
`rsc` is a tiny fork of `melange-json` for React Server Component's protocol, with those differences from `melange-json`:
- It supports additional values to plain JSON (for example `React.element`, promises, and server functions) that aligns with `React.Model`.
- Deriving and attributes are renamed from `json` to `rsc` (`[@@deriving rsc]`, `to_rsc`/`of_rsc`, `[@rsc.*]`).
================================================
FILE: packages/rsc/js/RSC.ml
================================================
type t = Js.Json.t
type of_rsc_error = Rsc_error of string | Unexpected_variant of string
exception Of_rsc_error of of_rsc_error
let of_rsc_error_to_string = function Rsc_error msg -> msg | Unexpected_variant msg -> "unexpected variant: " ^ msg
let is_null value = (Obj.magic value : 'a Js.null) == Js.null
let is_undefined value = Js.typeof value = "undefined"
let is_nullish value = is_null value || is_undefined value
let describe value = if is_null value then "null" else if Js.Array.isArray value then "array" else Js.typeof value
let of_rsc_msg_error msg = raise (Of_rsc_error (Rsc_error msg))
let of_rsc_msg_unexpected_variant msg = raise (Of_rsc_error (Unexpected_variant msg))
let of_rsc_error ?depth:_ ?width:_ ~rsc msg = of_rsc_msg_error (msg ^ "; received " ^ describe (Obj.magic rsc))
let of_rsc_unexpected_variant ?depth:_ ?width:_ ~rsc msg =
of_rsc_msg_unexpected_variant (msg ^ "; received " ^ describe (Obj.magic rsc))
let promise_cache_key = "__server_reason_react_rsc_promise"
let cached_promise decode promise =
let cache = (Obj.magic promise : 'a Js.Promise.t Js.Dict.t) in
match Js.Dict.get cache promise_cache_key with
| Some promise -> promise
| None ->
let decoded =
(Obj.magic (Js.Promise.resolve promise) : t Js.Promise.t)
|> Js.Promise.then_ (fun value -> Js.Promise.resolve (decode value))
in
Js.Dict.set cache promise_cache_key decoded;
decoded
module Primitives = struct
let string_to_rsc value = Obj.magic value
let bool_to_rsc value = Obj.magic value
let float_to_rsc value = Obj.magic value
let int_to_rsc value = Obj.magic value
let int64_to_rsc value = Obj.magic (Int64.to_string value)
let char_to_rsc value = Obj.magic (String.make 1 value)
let unit_to_rsc () = Obj.magic Js.null
let option_to_rsc to_rsc = function None -> unit_to_rsc () | Some value -> to_rsc value
let list_values_to_rsc values = Obj.magic (Array.of_list values)
let assoc_to_rsc values =
let dict = Js.Dict.empty () in
List.iter (fun (key, value) -> Js.Dict.set dict key value) values;
Obj.magic dict
let result_to_rsc ok_to_rsc error_to_rsc = function
| Ok value -> list_values_to_rsc [ string_to_rsc "Ok"; ok_to_rsc value ]
| Error value -> list_values_to_rsc [ string_to_rsc "Error"; error_to_rsc value ]
let list_to_rsc to_rsc values = list_values_to_rsc (List.map to_rsc values)
let array_to_rsc to_rsc values = values |> Array.to_list |> List.map to_rsc |> list_values_to_rsc
let tuple2_to_rsc a_to_rsc b_to_rsc (a, b) = list_values_to_rsc [ a_to_rsc a; b_to_rsc b ]
let tuple3_to_rsc a_to_rsc b_to_rsc c_to_rsc (a, b, c) = list_values_to_rsc [ a_to_rsc a; b_to_rsc b; c_to_rsc c ]
let tuple4_to_rsc a_to_rsc b_to_rsc c_to_rsc d_to_rsc (a, b, c, d) =
list_values_to_rsc [ a_to_rsc a; b_to_rsc b; c_to_rsc c; d_to_rsc d ]
let react_element_to_rsc element = Obj.magic element
let promise_to_rsc to_rsc promise =
Obj.magic (Js.Promise.then_ (fun value -> Js.Promise.resolve (to_rsc value)) promise)
let server_function_to_rsc action = Obj.magic action
let string_of_rsc rsc = if Js.typeof rsc = "string" then Obj.magic rsc else of_rsc_error ~rsc "expected a string"
let bool_of_rsc rsc = if Js.typeof rsc = "boolean" then Obj.magic rsc else of_rsc_error ~rsc "expected a bool"
let int_of_rsc rsc =
if Js.typeof rsc = "number" then
let value = (Obj.magic rsc : float) in
if Js.Math.floor_float value == value then Obj.magic value else of_rsc_error ~rsc "expected an int"
else of_rsc_error ~rsc "expected an int"
let int64_of_rsc rsc =
if Js.typeof rsc = "string" then
match Int64.of_string_opt (Obj.magic rsc : string) with
| Some value -> value
| None -> of_rsc_error ~rsc "expected int64 as string"
else of_rsc_error ~rsc "expected int64 as string"
let float_of_rsc rsc = if Js.typeof rsc = "number" then Obj.magic rsc else of_rsc_error ~rsc "expected a float"
let char_of_rsc rsc =
let value = string_of_rsc rsc in
if String.length value = 1 then String.get value 0 else of_rsc_error ~rsc "expected a single-character string"
let unit_of_rsc rsc = if is_nullish (Obj.magic rsc) then () else of_rsc_error ~rsc "expected null"
let option_of_rsc of_rsc rsc = if is_nullish (Obj.magic rsc) then None else Some (of_rsc rsc)
let array_of_rsc of_rsc rsc =
if Js.Array.isArray rsc then Array.map of_rsc (Obj.magic rsc : t array) else of_rsc_error ~rsc "expected an array"
let list_of_rsc of_rsc rsc = array_of_rsc of_rsc rsc |> Array.to_list
let tuple2_of_rsc a_of_rsc b_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b ] -> (a_of_rsc a, b_of_rsc b)
| _ -> of_rsc_error ~rsc "expected a tuple of length 2"
let tuple3_of_rsc a_of_rsc b_of_rsc c_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b; c ] -> (a_of_rsc a, b_of_rsc b, c_of_rsc c)
| _ -> of_rsc_error ~rsc "expected a tuple of length 3"
let tuple4_of_rsc a_of_rsc b_of_rsc c_of_rsc d_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b; c; d ] -> (a_of_rsc a, b_of_rsc b, c_of_rsc c, d_of_rsc d)
| _ -> of_rsc_error ~rsc "expected a tuple of length 4"
let result_of_rsc ok_of_rsc error_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ tag; value ] ->
let tag = string_of_rsc tag in
if tag = "Ok" then Ok (ok_of_rsc value)
else if tag = "Error" then Error (error_of_rsc value)
else of_rsc_unexpected_variant ~rsc {|expected ["Ok"; _] or ["Error"; _]|}
| _ -> of_rsc_error ~rsc {|expected ["Ok"; _] or ["Error"; _]|}
let react_element_of_rsc rsc = Obj.magic rsc
let promise_of_rsc of_rsc rsc = cached_promise of_rsc (Obj.magic rsc)
let server_function_of_rsc rsc = Obj.magic rsc
end
================================================
FILE: packages/rsc/js/RSC.mli
================================================
type t
type of_rsc_error = Rsc_error of string | Unexpected_variant of string
exception Of_rsc_error of of_rsc_error
val of_rsc_error_to_string : of_rsc_error -> string
val of_rsc_error : ?depth:int -> ?width:int -> rsc:t -> string -> 'a
val of_rsc_msg_error : string -> 'a
val of_rsc_unexpected_variant : ?depth:int -> ?width:int -> rsc:t -> string -> 'a
val of_rsc_msg_unexpected_variant : string -> 'a
module Primitives : sig
val string_of_rsc : t -> string
val bool_of_rsc : t -> bool
val float_of_rsc : t -> float
val int_of_rsc : t -> int
val int64_of_rsc : t -> int64
val char_of_rsc : t -> char
val option_of_rsc : (t -> 'a) -> t -> 'a option
val unit_of_rsc : t -> unit
val result_of_rsc : (t -> 'a) -> (t -> 'b) -> t -> ('a, 'b) result
val list_of_rsc : (t -> 'a) -> t -> 'a list
val array_of_rsc : (t -> 'a) -> t -> 'a array
val tuple2_of_rsc : (t -> 'a) -> (t -> 'b) -> t -> 'a * 'b
val tuple3_of_rsc : (t -> 'a) -> (t -> 'b) -> (t -> 'c) -> t -> 'a * 'b * 'c
val tuple4_of_rsc : (t -> 'a) -> (t -> 'b) -> (t -> 'c) -> (t -> 'd) -> t -> 'a * 'b * 'c * 'd
val react_element_of_rsc : t -> React.element
val promise_of_rsc : (t -> 'a) -> t -> 'a Js.Promise.t
val server_function_of_rsc : t -> 'callback Runtime.server_function
val list_values_to_rsc : t list -> t
val assoc_to_rsc : (string * t) list -> t
val string_to_rsc : string -> t
val bool_to_rsc : bool -> t
val float_to_rsc : float -> t
val int_to_rsc : int -> t
val int64_to_rsc : int64 -> t
val char_to_rsc : char -> t
val option_to_rsc : ('a -> t) -> 'a option -> t
val unit_to_rsc : unit -> t
val result_to_rsc : ('a -> t) -> ('b -> t) -> ('a, 'b) result -> t
val list_to_rsc : ('a -> t) -> 'a list -> t
val array_to_rsc : ('a -> t) -> 'a array -> t
val tuple2_to_rsc : ('a -> t) -> ('b -> t) -> 'a * 'b -> t
val tuple3_to_rsc : ('a -> t) -> ('b -> t) -> ('c -> t) -> 'a * 'b * 'c -> t
val tuple4_to_rsc : ('a -> t) -> ('b -> t) -> ('c -> t) -> ('d -> t) -> 'a * 'b * 'c * 'd -> t
val react_element_to_rsc : React.element -> t
val promise_to_rsc : ('a -> t) -> 'a Js.Promise.t -> t
val server_function_to_rsc : 'callback Runtime.server_function -> t
end
================================================
FILE: packages/rsc/js/dune
================================================
(library
(name rsc_js)
(public_name server-reason-react.rsc)
(modes melange)
(wrapped false)
(libraries reason-react server-reason-react.runtime melange.js)
(preprocess
(pps melange.ppx)))
================================================
FILE: packages/rsc/native/RSC.ml
================================================
module Model = React.Model
type t = React.element Model.t
type of_rsc_error = Rsc_error of string | Unexpected_variant of string
exception Of_rsc_error of of_rsc_error
let of_rsc_error_to_string = function Rsc_error msg -> msg | Unexpected_variant msg -> "unexpected variant: " ^ msg
let describe_model : t -> string = function
| Model.Json `Null -> "null"
| Model.Json (`Bool _) -> "bool"
| Model.Json (`Int _) -> "int"
| Model.Json (`Float _) -> "float"
| Model.Json (`String _) -> "string"
| Model.Json (`Assoc _) -> "json object"
| Model.Json (`List _) -> "json array"
| Model.Element _ -> "React.element"
| Model.Promise _ -> "Promise"
| Model.Function _ -> "server function"
| Model.Assoc _ -> "object"
| Model.List _ -> "array"
| Model.Error _ -> "error"
let of_rsc_msg_error msg = raise (Of_rsc_error (Rsc_error msg))
let of_rsc_msg_unexpected_variant msg = raise (Of_rsc_error (Unexpected_variant msg))
let of_rsc_error ?depth:_ ?width:_ ~rsc msg = of_rsc_msg_error (msg ^ "; received " ^ describe_model rsc)
let of_rsc_unexpected_variant ?depth:_ ?width:_ ~rsc msg =
of_rsc_msg_unexpected_variant (msg ^ "; received " ^ describe_model rsc)
let of_model model = model
let to_model model = model
let map_json_list decode values = List.map (fun value -> decode (of_model (Model.Json value))) values
module Primitives = struct
let list_values_to_rsc values = of_model (Model.List (List.map to_model values))
let assoc_to_rsc values = of_model (Model.Assoc (List.map (fun (key, value) -> (key, to_model value)) values))
let string_to_rsc value = of_model (Model.Json (`String value))
let bool_to_rsc value = of_model (Model.Json (`Bool value))
let float_to_rsc value = of_model (Model.Json (`Float value))
let int_to_rsc value = of_model (Model.Json (`Int value))
let int64_to_rsc value = of_model (Model.Json (`String (Int64.to_string value)))
let char_to_rsc value = string_to_rsc (String.make 1 value)
let unit_to_rsc () = of_model (Model.Json `Null)
let option_to_rsc to_rsc = function None -> unit_to_rsc () | Some value -> to_rsc value
let result_to_rsc ok_to_rsc error_to_rsc = function
| Ok value -> list_values_to_rsc [ string_to_rsc "Ok"; ok_to_rsc value ]
| Error value -> list_values_to_rsc [ string_to_rsc "Error"; error_to_rsc value ]
let list_to_rsc to_rsc values = list_values_to_rsc (List.map to_rsc values)
let array_to_rsc to_rsc values = values |> Array.to_list |> List.map to_rsc |> list_values_to_rsc
let tuple2_to_rsc a_to_rsc b_to_rsc (a, b) = list_values_to_rsc [ a_to_rsc a; b_to_rsc b ]
let tuple3_to_rsc a_to_rsc b_to_rsc c_to_rsc (a, b, c) = list_values_to_rsc [ a_to_rsc a; b_to_rsc b; c_to_rsc c ]
let tuple4_to_rsc a_to_rsc b_to_rsc c_to_rsc d_to_rsc (a, b, c, d) =
list_values_to_rsc [ a_to_rsc a; b_to_rsc b; c_to_rsc c; d_to_rsc d ]
let react_element_to_rsc element = of_model (Model.Element element)
let promise_to_rsc to_rsc promise = of_model (Model.Promise (promise, fun value -> to_model (to_rsc value)))
let server_function_to_rsc action = of_model (Model.Function action)
let string_of_rsc rsc =
match to_model rsc with Model.Json (`String value) -> value | model -> of_rsc_error ~rsc:model "expected a string"
let bool_of_rsc rsc =
match to_model rsc with Model.Json (`Bool value) -> value | model -> of_rsc_error ~rsc:model "expected a bool"
let int_of_rsc rsc =
match to_model rsc with Model.Json (`Int value) -> value | model -> of_rsc_error ~rsc:model "expected an int"
let int64_of_rsc rsc =
match to_model rsc with
| Model.Json (`String value) -> (
match Int64.of_string_opt value with
| Some value -> value
| None -> of_rsc_error ~rsc:(to_model rsc) "expected int64 as string")
| model -> of_rsc_error ~rsc:model "expected int64 as string"
let float_of_rsc rsc =
match to_model rsc with
| Model.Json (`Float value) -> value
| Model.Json (`Int value) -> float_of_int value
| model -> of_rsc_error ~rsc:model "expected a float"
let char_of_rsc rsc =
let value = string_of_rsc rsc in
if String.length value = 1 then String.get value 0
else of_rsc_error ~rsc:(to_model rsc) "expected a single-character string"
let unit_of_rsc rsc =
match to_model rsc with Model.Json `Null -> () | model -> of_rsc_error ~rsc:model "expected null"
let option_of_rsc of_rsc rsc = match to_model rsc with Model.Json `Null -> None | _ -> Some (of_rsc rsc)
let list_of_rsc of_rsc rsc =
match to_model rsc with
| Model.List values -> List.map of_rsc (List.map of_model values)
| Model.Json (`List values) -> map_json_list of_rsc values
| model -> of_rsc_error ~rsc:model "expected an array"
let array_of_rsc of_rsc rsc = list_of_rsc of_rsc rsc |> Array.of_list
let tuple2_of_rsc a_of_rsc b_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b ] -> (a_of_rsc a, b_of_rsc b)
| _ -> of_rsc_error ~rsc:(to_model rsc) "expected a tuple of length 2"
let tuple3_of_rsc a_of_rsc b_of_rsc c_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b; c ] -> (a_of_rsc a, b_of_rsc b, c_of_rsc c)
| _ -> of_rsc_error ~rsc:(to_model rsc) "expected a tuple of length 3"
let tuple4_of_rsc a_of_rsc b_of_rsc c_of_rsc d_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ a; b; c; d ] -> (a_of_rsc a, b_of_rsc b, c_of_rsc c, d_of_rsc d)
| _ -> of_rsc_error ~rsc:(to_model rsc) "expected a tuple of length 4"
let result_of_rsc ok_of_rsc error_of_rsc rsc =
match list_of_rsc (fun value -> value) rsc with
| [ tag; value ] ->
let tag = string_of_rsc tag in
if tag = "Ok" then Ok (ok_of_rsc value)
else if tag = "Error" then Error (error_of_rsc value)
else of_rsc_unexpected_variant ~rsc:(to_model rsc) {|expected ["Ok"; _] or ["Error"; _]|}
| _ -> of_rsc_error ~rsc:(to_model rsc) {|expected ["Ok"; _] or ["Error"; _]|}
let react_element_of_rsc rsc =
match to_model rsc with
| Model.Element element -> element
| model -> of_rsc_error ~rsc:model "expected a React.element"
let promise_of_rsc of_rsc rsc =
match to_model rsc with
| Model.Promise (promise, to_rsc) ->
Js.Promise.then_ (fun value -> Js.Promise.resolve (of_rsc (of_model (to_rsc value)))) promise
| model -> of_rsc_error ~rsc:model "expected a promise"
let server_function_of_rsc rsc =
match to_model rsc with
| Model.Function _ ->
of_rsc_msg_error "decoding Runtime.server_function from native RSC values is only supported on the client"
| model -> of_rsc_error ~rsc:model "expected a server function"
end
================================================
FILE: packages/rsc/native/RSC.mli
================================================
type t
type of_rsc_error = Rsc_error of string | Unexpected_variant of string
exception Of_rsc_error of of_rsc_error
val of_rsc_error_to_string : of_rsc_error -> string
val of_rsc_error : ?depth:int -> ?width:int -> rsc:t -> string -> 'a
val of_rsc_msg_error : string -> 'a
val of_rsc_unexpected_variant : ?depth:int -> ?width:int -> rsc:t -> string -> 'a
val of_rsc_msg_unexpected_variant : string -> 'a
val of_model : React.element React.Model.t -> t
val to_model : t -> React.element React.Model.t
module Primitives : sig
val string_of_rsc : t -> string
val bool_of_rsc : t -> bool
val float_of_rsc : t -> float
val int_of_rsc : t -> int
val int64_of_rsc : t -> int64
val char_of_rsc : t -> char
val option_of_rsc : (t -> 'a) -> t -> 'a option
val unit_of_rsc : t -> unit
val result_of_rsc : (t -> 'a) -> (t -> 'b) -> t -> ('a, 'b) result
val list_of_rsc : (t -> 'a) -> t -> 'a list
val array_of_rsc : (t -> 'a) -> t -> 'a array
val tuple2_of_rsc : (t -> 'a) -> (t -> 'b) -> t -> 'a * 'b
val tuple3_of_rsc : (t -> 'a) -> (t -> 'b) -> (t -> 'c) -> t -> 'a * 'b * 'c
val tuple4_of_rsc : (t -> 'a) -> (t -> 'b) -> (t -> 'c) -> (t -> 'd) -> t -> 'a * 'b * 'c * 'd
val react_element_of_rsc : t -> React.element
val promise_of_rsc : (t -> 'a) -> t -> 'a Js.Promise.t
val server_function_of_rsc : t -> 'callback Runtime.server_function
val list_values_to_rsc : t list -> t
val assoc_to_rsc : (string * t) list -> t
val string_to_rsc : string -> t
val bool_to_rsc : bool -> t
val float_to_rsc : float -> t
val int_to_rsc : int -> t
val int64_to_rsc : int64 -> t
val char_to_rsc : char -> t
val option_to_rsc : ('a -> t) -> 'a option -> t
val unit_to_rsc : unit -> t
val result_to_rsc : ('a -> t) -> ('b -> t) -> ('a, 'b) result -> t
val list_to_rsc : ('a -> t) -> 'a list -> t
val array_to_rsc : ('a -> t) -> 'a array -> t
val tuple2_to_rsc : ('a -> t) -> ('b -> t) -> 'a * 'b -> t
val tuple3_to_rsc : ('a -> t) -> ('b -> t) -> ('c -> t) -> 'a * 'b * 'c -> t
val tuple4_to_rsc : ('a -> t) -> ('b -> t) -> ('c -> t) -> ('d -> t) -> 'a * 'b * 'c * 'd -> t
val react_element_to_rsc : React.element -> t
val promise_to_rsc : ('a -> t) -> 'a Js.Promise.t -> t
val server_function_to_rsc : 'callback Runtime.server_function -> t
end
================================================
FILE: packages/rsc/native/dune
================================================
(library
(name rsc_native)
(public_name server-reason-react.rsc-native)
(wrapped false)
(libraries
server-reason-react.react
server-reason-react.runtime
server-reason-react.js
lwt
yojson))
================================================
FILE: packages/rsc/ppx_common/dune
================================================
(library
(name rsc_ppx_common)
(public_name server-reason-react.rsc-ppx-common)
(wrapped false)
(libraries ppxlib)
(preprocess
(pps ppxlib.metaquot)))
================================================
FILE: packages/rsc/ppx_common/ppx_deriving_tools.ml
================================================
open Printf
open Ppxlib
open Ast_builder.Default
open StdLabels
open Expansion_helpers
let not_supported ~loc what = Location.raise_errorf ~loc "%s are not supported" what
let map_loc f a_loc = { a_loc with txt = f a_loc.txt }
let gen_bindings ~loc prefix n =
List.split
(List.init ~len:n ~f:(fun i ->
let id = sprintf "%s_%i" prefix i in
let patt = ppat_var ~loc { loc; txt = id } in
let expr = pexp_ident ~loc { loc; txt = lident id } in
(patt, expr)))
let gen_tuple ~loc prefix n =
let ps, es = gen_bindings ~loc prefix n in
(ps, pexp_tuple ~loc es)
let gen_record ~loc prefix fs =
let ps, es =
List.split
(List.map fs ~f:(fun (n, _attrs, _t) ->
let id = sprintf "%s_%s" prefix n.txt in
let patt = ppat_var ~loc { loc = n.loc; txt = id } in
let expr = pexp_ident ~loc { loc = n.loc; txt = lident id } in
((map_loc lident n, patt), expr)))
in
let ns, ps = List.split ps in
(ps, pexp_record ~loc (List.combine ns es) None)
let gen_pat_tuple ~loc prefix n =
let patts, exprs = gen_bindings ~loc prefix n in
(ppat_tuple ~loc patts, exprs)
let gen_pat_list ~loc prefix n =
let patts, exprs = gen_bindings ~loc prefix n in
let patt = List.fold_left (List.rev patts) ~init:[%pat? []] ~f:(fun prev patt -> [%pat? [%p patt] :: [%p prev]]) in
(patt, exprs)
let gen_pat_record ~loc prefix ns =
let xs =
List.map ns ~f:(fun n ->
let id = sprintf "%s_%s" prefix n.txt in
let patt = ppat_var ~loc { loc = n.loc; txt = id } in
let expr = pexp_ident ~loc { loc = n.loc; txt = lident id } in
((map_loc lident n, patt), expr))
in
(ppat_record ~loc (List.map xs ~f:fst) Closed, List.map xs ~f:snd)
let ( --> ) pc_lhs pc_rhs = { pc_lhs; pc_rhs; pc_guard = None }
let derive_of_label name = mangle (Suffix name)
let derive_of_longident name = mangle_lid (Suffix name)
let rsc_primitives_ident ~loc name =
pexp_ident ~loc { loc; txt = Longident.Ldot (Longident.Ldot (Longident.Lident "RSC", "Primitives"), name) }
let builtin_deriver_name suffix = function
| Longident.Lident "string" -> Some ("string_" ^ suffix)
| Longident.Lident "bool" -> Some ("bool_" ^ suffix)
| Longident.Lident "float" -> Some ("float_" ^ suffix)
| Longident.Lident "int" -> Some ("int_" ^ suffix)
| Longident.Lident "int64" -> Some ("int64_" ^ suffix)
| Longident.Lident "char" -> Some ("char_" ^ suffix)
| Longident.Lident "unit" -> Some ("unit_" ^ suffix)
| Longident.Lident "option" -> Some ("option_" ^ suffix)
| Longident.Lident "list" -> Some ("list_" ^ suffix)
| Longident.Lident "array" -> Some ("array_" ^ suffix)
| Longident.Lident "result" -> Some ("result_" ^ suffix)
| Longident.Ldot (Longident.Lident "React", "element") -> Some ("react_element_" ^ suffix)
| Longident.Ldot (Longident.Ldot (Longident.Lident "Js", "Promise"), "t") -> Some ("promise_" ^ suffix)
| Longident.Ldot (Longident.Lident "Runtime", "server_function") -> Some ("server_function_" ^ suffix)
| _ -> None
let ederiver name (lid : Longident.t loc) =
match builtin_deriver_name name lid.txt with
| Some builtin -> rsc_primitives_ident ~loc:lid.loc builtin
| None -> pexp_ident ~loc:lid.loc (map_loc (derive_of_longident name) lid)
type deriver = As_fun of (expression -> expression) | As_val of expression
let as_val ~loc deriver x = match deriver with As_fun f -> f x | As_val f -> [%expr [%e f] [%e x]]
let as_fun ~loc deriver = match deriver with As_fun f -> [%expr fun x -> [%e f [%expr x]]] | As_val f -> f
class virtual deriving =
object
method virtual name : label
method virtual extension : loc:location -> path:label -> core_type -> expression
method virtual str_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> structure
method virtual sig_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> signature
end
let register ?deps deriving =
let args = Deriving.Args.empty in
let str_type_decl = deriving#str_type_decl in
let sig_type_decl = deriving#sig_type_decl in
Deriving.add deriving#name ~extension:deriving#extension
~str_type_decl:(Deriving.Generator.V2.make ?deps args str_type_decl)
~sig_type_decl:(Deriving.Generator.V2.make ?deps args sig_type_decl)
let register_combined ?deps name derivings =
let args = Deriving.Args.empty in
let str_type_decl ~ctxt bindings =
List.fold_left derivings ~init:[] ~f:(fun str d -> d#str_type_decl ~ctxt bindings @ str)
in
let sig_type_decl ~ctxt bindings =
List.fold_left derivings ~init:[] ~f:(fun str d -> d#sig_type_decl ~ctxt bindings @ str)
in
Deriving.add name
~str_type_decl:(Deriving.Generator.V2.make ?deps args str_type_decl)
~sig_type_decl:(Deriving.Generator.V2.make ?deps args sig_type_decl)
module Schema = struct
let repr_row_field field =
match field.prf_desc with
| Rtag (id, _, []) -> `Rtag (id, [])
| Rtag (id, _, [ { ptyp_desc = Ptyp_tuple ts; _ } ]) -> `Rtag (id, ts)
| Rtag (id, _, [ t ]) -> `Rtag (id, [ t ])
| Rtag (_, _, _ :: _) -> not_supported ~loc:field.prf_loc "polyvariant constructor with more than one argument"
| Rinherit { ptyp_desc = Ptyp_constr (id, ts); _ } -> `Rinherit (id, ts)
| Rinherit _ -> not_supported ~loc:field.prf_loc "this polyvariant inherit"
let repr_core_type ty =
let loc = ty.ptyp_loc in
match ty.ptyp_desc with
| Ptyp_tuple ts -> `Ptyp_tuple ts
| Ptyp_constr (id, ts) -> `Ptyp_constr (id, ts)
| Ptyp_var txt -> `Ptyp_var { txt; loc = ty.ptyp_loc }
| Ptyp_variant (fs, Closed, None) -> `Ptyp_variant fs
| Ptyp_variant _ -> not_supported ~loc "non closed polyvariants"
| Ptyp_arrow _ -> not_supported ~loc "function types"
| Ptyp_open _ -> not_supported ~loc "open type expressions"
| Ptyp_any -> not_supported ~loc "type placeholders"
| Ptyp_object _ -> not_supported ~loc "object types"
| Ptyp_class _ -> not_supported ~loc "class types"
| Ptyp_poly _ -> not_supported ~loc "polymorphic type expressions"
| Ptyp_package _ -> not_supported ~loc "packaged module types"
| Ptyp_extension _ -> not_supported ~loc "extension nodes"
| Ptyp_alias _ -> not_supported ~loc "type aliases"
let repr_type_declaration td =
let loc = td.ptype_loc in
match (td.ptype_kind, td.ptype_manifest) with
| Ptype_abstract, None -> not_supported ~loc "abstract types"
| Ptype_abstract, Some t -> `Ptype_core_type t
| Ptype_variant ctors, _ -> `Ptype_variant ctors
| Ptype_record fs, _ -> `Ptype_record fs
| Ptype_open, _ -> not_supported ~loc "open types"
let gen_type_ascription (td : type_declaration) =
let loc = td.ptype_loc in
ptyp_constr ~loc
{ loc; txt = lident td.ptype_name.txt }
(List.map td.ptype_params ~f:(fun (p, _) ->
match p.ptyp_desc with
| Ptyp_var name -> ptyp_var ~loc name
| Ptyp_any -> ptyp_any ~loc
| _ -> Location.raise_errorf ~loc "this cannot be a type parameter"))
let derive_sig_type_decl ~derive_t ~derive_label ~ctxt (_rec_flag, tds) =
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
List.map tds ~f:(fun td ->
let name = td.ptype_name in
let type_ = derive_t ~loc name (gen_type_ascription td) in
let type_ =
List.fold_left (List.rev td.ptype_params) ~init:type_ ~f:(fun acc (t, _) ->
let loc = t.ptyp_loc in
let name =
match t.ptyp_desc with
| Ptyp_var txt -> { txt; loc }
| _ -> Location.raise_errorf ~loc "type variable is not a variable"
in
let t = derive_t ~loc name t in
ptyp_arrow ~loc Nolabel t acc)
in
psig_value ~loc (value_description ~loc ~prim:[] ~name:(derive_label name) ~type_))
class virtual deriving1 =
object (self)
inherit deriving
method virtual t : loc:location -> label loc -> core_type -> core_type
method derive_of_tuple : core_type -> core_type list -> expression -> expression =
fun t _ _ ->
let loc = t.ptyp_loc in
not_supported "tuple types" ~loc
method derive_of_record : type_declaration -> label_declaration list -> expression -> expression =
fun td _ _ ->
let loc = td.ptype_loc in
not_supported "record types" ~loc
method derive_of_variant : type_declaration -> constructor_declaration list -> expression -> expression =
fun td _ _ ->
let loc = td.ptype_loc in
not_supported "variant types" ~loc
method derive_of_polyvariant : core_type -> row_field list -> expression -> expression =
fun t _ _ ->
let loc = t.ptyp_loc in
not_supported "polyvariant types" ~loc
method private derive_type_ref_name : label -> longident loc -> expression = fun name n -> ederiver name n
method private derive_type_ref' ~loc name n ts =
let f = self#derive_type_ref_name name n in
match n.txt with
| Longident.Ldot (Longident.Lident "Runtime", "server_function") -> As_val f
| _ ->
let args =
List.fold_left (List.rev ts) ~init:[] ~f:(fun args a ->
let a = as_fun ~loc (self#derive_of_core_type' a) in
(Nolabel, a) :: args)
in
As_val (pexp_apply ~loc f args)
method derive_type_ref ~loc name n ts x = as_val ~loc (self#derive_type_ref' ~loc name n ts) x
method private derive_of_core_type' t =
let loc = t.ptyp_loc in
match repr_core_type t with
| `Ptyp_tuple ts -> As_fun (self#derive_of_tuple t ts)
| `Ptyp_var label -> As_val (ederiver self#name (map_loc lident label))
| `Ptyp_constr (id, ts) -> self#derive_type_ref' self#name ~loc id ts
| `Ptyp_variant fs -> As_fun (self#derive_of_polyvariant t fs)
method derive_of_core_type t x =
let loc = x.pexp_loc in
as_val ~loc (self#derive_of_core_type' t) x
method private derive_type_decl_label name = map_loc (derive_of_label self#name) name
method derive_of_type_declaration td =
let loc = td.ptype_loc in
let name = td.ptype_name in
let rev_params =
List.rev_map td.ptype_params ~f:(fun (t, _) ->
match t.ptyp_desc with
| Ptyp_var txt -> { txt; loc = t.ptyp_loc }
| Ptyp_any -> { txt = gen_symbol ~prefix:"_" (); loc = t.ptyp_loc }
| _ -> Location.raise_errorf ~loc "type variable is not a variable")
in
let x = [%expr x] in
let expr =
match repr_type_declaration td with
| `Ptype_core_type t -> self#derive_of_core_type t x
| `Ptype_variant ctors -> self#derive_of_variant td ctors x
| `Ptype_record fs -> self#derive_of_record td fs x
in
let expr = [%expr (fun x -> [%e expr] : [%t self#t ~loc name (gen_type_ascription td)])] in
let expr =
List.fold_left rev_params ~init:expr ~f:(fun body param ->
pexp_fun ~loc Nolabel None (ppat_var ~loc (map_loc (derive_of_label self#name) param)) body)
in
[ value_binding ~loc ~pat:(ppat_var ~loc (self#derive_type_decl_label name)) ~expr ]
method extension : loc:location -> path:label -> core_type -> expression =
fun ~loc:_ ~path:_ ty ->
let loc = ty.ptyp_loc in
as_fun ~loc (self#derive_of_core_type' ty)
method str_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> structure =
fun ~ctxt (_rec_flag, tds) ->
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
let bindings = List.concat_map tds ~f:self#derive_of_type_declaration in
[%str
[@@@ocaml.warning "-39-11-27"]
[%%i pstr_value ~loc Recursive bindings]]
method sig_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> signature =
derive_sig_type_decl ~derive_t:self#t ~derive_label:self#derive_type_decl_label
end
end
let rec get_variant_names ~loc c =
match Schema.repr_row_field c with
| `Rtag (name, ts) ->
[ Printf.sprintf {|["%s"%s]|} name.txt (ts |> List.map ~f:(fun _ -> ", _") |> String.concat ~sep:"") ]
| `Rinherit (n, ts) -> (
match Schema.repr_core_type (ptyp_constr ~loc:n.loc n ts) with
| `Ptyp_variant fields -> List.concat_map fields ~f:(get_variant_names ~loc)
| _ -> [])
let get_constructor_names cs =
List.map cs ~f:(fun c ->
let name = c.pcd_name in
match c.pcd_args with
| Pcstr_record _fs -> Printf.sprintf {|["%s", { _ }]|} name.txt
| Pcstr_tuple li ->
Printf.sprintf {|["%s"%s]|} name.txt (li |> List.map ~f:(fun _ -> ", _") |> String.concat ~sep:""))
module Conv = struct
type 'ctx tuple = { tpl_loc : location; tpl_types : core_type list; tpl_ctx : 'ctx }
type 'ctx record = { rcd_loc : location; rcd_fields : label_declaration list; rcd_ctx : 'ctx }
type variant_case =
| Vcs_tuple of label loc * variant_case_ctx tuple
| Vcs_record of label loc * variant_case_ctx record
and variant_case_ctx = Vcs_ctx_variant of constructor_declaration | Vcs_ctx_polyvariant of row_field
type variant = { vrt_loc : location; vrt_cases : variant_case list; vrt_ctx : variant_ctx }
and variant_ctx = Vrt_ctx_variant of type_declaration | Vrt_ctx_polyvariant of core_type
let repr_polyvariant_cases cs = List.rev cs |> List.map ~f:(fun c -> (c, Schema.repr_row_field c))
let repr_variant_cases cs = List.rev cs
let deriving_of ~name ~of_t ~is_allow_any_constr ~derive_of_tuple ~derive_of_record ~derive_of_variant
~derive_of_variant_case () =
(object (self)
inherit Schema.deriving1
method name = name
method t ~loc _name t = [%type: [%t of_t ~loc] -> [%t t]]
method! derive_of_tuple t ts x =
let t = { tpl_loc = t.ptyp_loc; tpl_types = ts; tpl_ctx = t } in
derive_of_tuple self#derive_of_core_type t x
method! derive_of_record td fs x =
let t = { rcd_loc = td.ptype_loc; rcd_fields = fs; rcd_ctx = td } in
derive_of_record self#derive_of_core_type t x
method! derive_of_variant td cs x =
let loc = td.ptype_loc in
let cs = repr_variant_cases cs in
let allow_any_constr =
cs
|> List.find_opt ~f:(fun cs -> is_allow_any_constr (Vcs_ctx_variant cs))
|> Option.map (fun cs e -> econstruct cs (Some e))
in
let cs = List.filter ~f:(fun cs -> not (is_allow_any_constr (Vcs_ctx_variant cs))) cs in
let body, cases =
List.fold_left cs
~init:
(match allow_any_constr with
| Some allow_any_constr -> (allow_any_constr x, [])
| None ->
let error_message =
Printf.sprintf "expected %s" (get_constructor_names cs |> String.concat ~sep:" or ")
in
([%expr RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc error_message]], []))
~f:(fun (next, cases) c ->
let make (n : label loc) arg = pexp_construct (map_loc lident n) ~loc:n.loc arg in
let ctx = Vcs_ctx_variant c in
let n = c.pcd_name in
match c.pcd_args with
| Pcstr_record fs ->
let t =
let t = { rcd_loc = loc; rcd_fields = fs; rcd_ctx = ctx } in
Vcs_record (n, t)
in
let next = derive_of_variant_case self#derive_of_core_type (make n) t ~allow_any_constr next in
(next, t :: cases)
| Pcstr_tuple ts ->
let case =
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
Vcs_tuple (n, t)
in
let next = derive_of_variant_case self#derive_of_core_type (make n) case ~allow_any_constr next in
(next, case :: cases))
in
let t = { vrt_loc = loc; vrt_cases = cases; vrt_ctx = Vrt_ctx_variant td } in
derive_of_variant self#derive_of_core_type t ~allow_any_constr body x
method! derive_of_polyvariant t (cs : row_field list) x =
let loc = t.ptyp_loc in
let allow_any_constr =
cs
|> List.find_opt ~f:(fun cs -> is_allow_any_constr (Vcs_ctx_polyvariant cs))
|> Option.map (fun cs ->
match cs.prf_desc with
| Rinherit _ -> failwith "[@allow_any] placed on inherit clause"
| Rtag (n, _, _) -> fun e -> pexp_variant ~loc:n.loc n.txt (Some e))
in
let cs = List.filter ~f:(fun cs -> not (is_allow_any_constr (Vcs_ctx_polyvariant cs))) cs in
let cases = repr_polyvariant_cases cs in
let body, cases =
List.fold_left cases
~init:
(match allow_any_constr with
| Some allow_any_constr -> (allow_any_constr x, [])
| None ->
let error_message =
Printf.sprintf "expected %s"
(cs |> List.concat_map ~f:(get_variant_names ~loc) |> String.concat ~sep:" or ")
in
([%expr RSC.of_rsc_unexpected_variant ~rsc:x [%e estring ~loc error_message]], []))
~f:(fun (next, cases) (c, r) ->
let ctx = Vcs_ctx_polyvariant c in
match r with
| `Rtag (n, ts) ->
let make arg = pexp_variant ~loc:n.loc n.txt arg in
let case =
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
Vcs_tuple (n, t)
in
let next = derive_of_variant_case self#derive_of_core_type make case ~allow_any_constr next in
(next, case :: cases)
| `Rinherit (n, ts) ->
let maybe_e = self#derive_type_ref ~loc self#name n ts x in
let t = ptyp_variant ~loc cs Closed None in
let next =
[%expr
match [%e maybe_e] with
| e -> (e :> [%t t])
| exception RSC.Of_rsc_error (RSC.Unexpected_variant _) -> [%e next]]
in
(next, cases))
in
let t = { vrt_loc = loc; vrt_cases = cases; vrt_ctx = Vrt_ctx_polyvariant t } in
derive_of_variant self#derive_of_core_type t ~allow_any_constr body x
end
:> deriving)
let deriving_of_match ~name ~of_t ~cmp_sort_vcs ~derive_of_tuple ~derive_of_record ~derive_of_variant_case () =
(object (self)
inherit Schema.deriving1
method name = name
method t ~loc _name t = [%type: [%t of_t ~loc] -> [%t t]]
method! derive_of_tuple t ts x =
let t = { tpl_loc = t.ptyp_loc; tpl_types = ts; tpl_ctx = t } in
derive_of_tuple self#derive_of_core_type t x
method! derive_of_record td fs x =
let t = { rcd_loc = td.ptype_loc; rcd_fields = fs; rcd_ctx = td } in
derive_of_record self#derive_of_core_type t x
method! derive_of_variant td cs x =
let loc = td.ptype_loc in
let error_message = Printf.sprintf "expected %s" (get_constructor_names cs |> String.concat ~sep:" or ") in
let cs = repr_variant_cases cs in
let cs =
List.stable_sort
~cmp:(fun cs1 cs2 ->
let vcs1 = Vcs_ctx_variant cs1 and vcs2 = Vcs_ctx_variant cs2 in
cmp_sort_vcs vcs1 vcs2)
cs
in
let cases =
List.fold_left cs
~init:[ [%pat? _] --> [%expr RSC.of_rsc_error ~rsc:x [%e estring ~loc error_message]] ]
~f:(fun next (c : constructor_declaration) ->
let ctx = Vcs_ctx_variant c in
let make (n : label loc) arg = pexp_construct (map_loc lident n) ~loc:n.loc arg in
let n = c.pcd_name in
match c.pcd_args with
| Pcstr_record fs ->
let t =
let r = { rcd_loc = loc; rcd_fields = fs; rcd_ctx = ctx } in
Vcs_record (n, r)
in
derive_of_variant_case self#derive_of_core_type (make n) t :: next
| Pcstr_tuple ts ->
let t =
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
Vcs_tuple (n, t)
in
derive_of_variant_case self#derive_of_core_type (make n) t :: next)
in
pexp_match ~loc x cases
method! derive_of_polyvariant t (cs : row_field list) x =
let loc = t.ptyp_loc in
let cases = repr_polyvariant_cases cs in
let cases =
List.stable_sort
~cmp:(fun (cs1, _) (cs2, _) ->
let vcs1 = Vcs_ctx_polyvariant cs1 and vcs2 = Vcs_ctx_polyvariant cs2 in
cmp_sort_vcs vcs1 vcs2)
cases
in
let ctors, inherits =
List.partition_map cases ~f:(fun (c, r) ->
let ctx = Vcs_ctx_polyvariant c in
match r with
| `Rtag (n, ts) ->
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
Left (n, Vcs_tuple (n, t))
| `Rinherit (n, ts) -> Right (n, ts))
in
let catch_all =
[%pat? x]
--> List.fold_left (List.rev inherits)
~init:
(let error_message =
Printf.sprintf "expected %s"
(cs |> List.concat_map ~f:(get_variant_names ~loc) |> String.concat ~sep:" or ")
in
[%expr RSC.of_rsc_unexpected_variant ~rsc:x [%e estring ~loc error_message]])
~f:(fun next (n, ts) ->
let maybe = self#derive_type_ref ~loc self#name n ts x in
let t = ptyp_variant ~loc cs Closed None in
[%expr
match [%e maybe] with
| x -> (x :> [%t t])
| exception RSC.Of_rsc_error (RSC.Unexpected_variant _) -> [%e next]])
in
let cases =
List.fold_left ctors ~init:[ catch_all ] ~f:(fun next ((n : label loc), t) ->
let make arg = pexp_variant ~loc:n.loc n.txt arg in
derive_of_variant_case self#derive_of_core_type make t :: next)
in
pexp_match ~loc x cases
end
:> deriving)
let deriving_to ~name ~t_to ~derive_of_tuple ~derive_of_record ~derive_of_variant_case () =
(object (self)
inherit Schema.deriving1
method name = name
method t ~loc _name t = [%type: [%t t] -> [%t t_to ~loc]]
method! derive_of_tuple t ts x =
let loc = t.ptyp_loc in
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = t } in
let n = List.length ts in
let p, es = gen_pat_tuple ~loc "x" n in
pexp_match ~loc x [ p --> derive_of_tuple self#derive_of_core_type t es ]
method! derive_of_record td fs x =
let t = { rcd_loc = td.ptype_loc; rcd_fields = fs; rcd_ctx = td } in
let loc = td.ptype_loc in
let p, es = gen_pat_record ~loc "x" (List.map fs ~f:(fun f -> f.pld_name)) in
pexp_match ~loc x [ p --> derive_of_record self#derive_of_core_type t es ]
method! derive_of_variant td cs x =
let loc = td.ptype_loc in
let ctor_pat (n : label loc) pat = ppat_construct ~loc:n.loc (map_loc lident n) pat in
let cs = repr_variant_cases cs in
pexp_match ~loc x
(List.rev_map cs ~f:(fun c ->
let n = c.pcd_name in
let ctx = Vcs_ctx_variant c in
match c.pcd_args with
| Pcstr_record fs ->
let p, es = gen_pat_record ~loc "x" (List.map fs ~f:(fun f -> f.pld_name)) in
let t =
let t = { rcd_loc = loc; rcd_fields = fs; rcd_ctx = ctx } in
Vcs_record (n, t)
in
ctor_pat n (Some p) --> derive_of_variant_case self#derive_of_core_type t es
| Pcstr_tuple ts ->
let arity = List.length ts in
let t =
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
Vcs_tuple (n, t)
in
let p, es = gen_pat_tuple ~loc "x" arity in
ctor_pat n (if arity = 0 then None else Some p)
--> derive_of_variant_case self#derive_of_core_type t es))
method! derive_of_polyvariant t (cs : row_field list) x =
let loc = t.ptyp_loc in
let cases = repr_polyvariant_cases cs in
let cases =
List.rev_map cases ~f:(fun (c, r) ->
let ctx = Vcs_ctx_polyvariant c in
match r with
| `Rtag (n, []) ->
let t =
let t = { tpl_loc = loc; tpl_types = []; tpl_ctx = ctx } in
Vcs_tuple (n, t)
in
ppat_variant ~loc n.txt None --> derive_of_variant_case self#derive_of_core_type t []
| `Rtag (n, ts) ->
let t = { tpl_loc = loc; tpl_types = ts; tpl_ctx = ctx } in
let ps, es = gen_pat_tuple ~loc "x" (List.length ts) in
ppat_variant ~loc n.txt (Some ps)
--> derive_of_variant_case self#derive_of_core_type (Vcs_tuple (n, t)) es
| `Rinherit (n, ts) ->
[%pat? [%p ppat_type ~loc n] as x]
--> self#derive_of_core_type (ptyp_constr ~loc:n.loc n ts) [%expr x])
in
pexp_match ~loc x cases
end
:> deriving)
end
include Schema
================================================
FILE: packages/rsc/ppx_common/ppx_deriving_tools.mli
================================================
(** A collection of tools to make it easy to build ppx deriving plugins. *)
open Ppxlib
(** A deriver is represented by this api *)
class virtual deriving : object
method virtual name : label
(** name of the deriver *)
method virtual extension : loc:location -> path:label -> core_type -> expression
(** a deriver can be applied to as type expression as extension node. *)
method virtual str_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> structure
(** or it can be attached to a type declaration. *)
method virtual sig_type_decl : ctxt:Expansion_context.Deriver.t -> rec_flag * type_declaration list -> signature
end
val register : ?deps:Deriving.t list -> deriving -> Deriving.t
(** handles registration of the deriver *)
val register_combined : ?deps:Deriving.t list -> label -> deriving list -> Deriving.t
(** multiple derivers can be registered under the same name *)
(** A common scheme to define data conversions (like to_json/of_json). *)
module Conv : sig
(** A simplified parsetree representation.
We define a few types to represent the data we want to derive conversions for. Such types are less verbose but
less precise than the original parsetree, though it is enough for conversion purposes.
The types still keep the original parsetree nodes as context (this is also needed to play well with
Ppxlib.Attributes API). *)
type 'ctx tuple = { tpl_loc : location; tpl_types : core_type list; tpl_ctx : 'ctx }
type 'ctx record = { rcd_loc : location; rcd_fields : label_declaration list; rcd_ctx : 'ctx }
type variant_case =
| Vcs_tuple of label loc * variant_case_ctx tuple
| Vcs_record of label loc * variant_case_ctx record
and variant_case_ctx = Vcs_ctx_variant of constructor_declaration | Vcs_ctx_polyvariant of row_field
type variant = { vrt_loc : location; vrt_cases : variant_case list; vrt_ctx : variant_ctx }
and variant_ctx = Vrt_ctx_variant of type_declaration | Vrt_ctx_polyvariant of core_type
type derive_of_core_type := core_type -> expression -> expression
val deriving_to :
name:label ->
t_to:(loc:location -> core_type) ->
derive_of_tuple:(derive_of_core_type -> core_type tuple -> expression list -> expression) ->
derive_of_record:(derive_of_core_type -> type_declaration record -> expression list -> expression) ->
derive_of_variant_case:(derive_of_core_type -> variant_case -> expression list -> expression) ->
unit ->
deriving
(** Define a serializer. *)
val deriving_of :
name:label ->
of_t:(loc:location -> core_type) ->
is_allow_any_constr:(variant_case_ctx -> bool) ->
derive_of_tuple:(derive_of_core_type -> core_type tuple -> expression -> expression) ->
derive_of_record:(derive_of_core_type -> type_declaration record -> expression -> expression) ->
derive_of_variant:
(derive_of_core_type ->
variant ->
allow_any_constr:(expression -> expression) option ->
expression ->
expression ->
expression) ->
derive_of_variant_case:
(derive_of_core_type ->
(expression option -> expression) ->
variant_case ->
allow_any_constr:(expression -> expression) option ->
expression ->
expression) ->
unit ->
deriving
(** Define a deserializer. *)
val deriving_of_match :
name:label ->
of_t:(loc:location -> core_type) ->
cmp_sort_vcs:(variant_case_ctx -> variant_case_ctx -> int) ->
derive_of_tuple:(derive_of_core_type -> core_type tuple -> expression -> expression) ->
derive_of_record:(derive_of_core_type -> type_declaration record -> expression -> expression) ->
derive_of_variant_case:(derive_of_core_type -> (expression option -> expression) -> variant_case -> case) ->
unit ->
deriving
(** Define a deserializer using pattern matching.
This is a less general but more compact variant of [deriving_of], for cases where the serialized data can be
inspected with pattern matching. *)
end
val not_supported : loc:location -> string -> 'a
(** [not_supported what] terminates ppx with an error message telling [what] unsupported. *)
val gen_tuple : loc:location -> label -> int -> pattern list * expression
(** [let patts, expr = gen_tuple label n in ...] creates a tuple expression and a corresponding list of patterns. *)
(** Auxiliary functions to generate record expressions and patterns. *)
val gen_record : loc:location -> label -> (label loc * attributes * 'a) list -> pattern list * expression
(** [let patts, expr = gen_tuple label n in ...] creates a record expression and a corresponding list of patterns. *)
val gen_pat_tuple : loc:location -> string -> int -> pattern * expression list
(** [let patt, exprs = gen_pat_tuple ~loc prefix n in ...] generates a pattern to match a tuple of size [n] and a list
of expressions [exprs] to refer to names bound in this pattern. *)
val gen_pat_record : loc:location -> string -> label loc list -> pattern * expression list
(** [let patt, exprs = gen_pat_record ~loc prefix fs in ...] generates a pattern to match record with fields [fs] and a
list of expressions [exprs] to refer to names bound in this pattern. *)
val gen_pat_list : loc:location -> string -> int -> pattern * expression list
(** [let patt, exprs = gen_pat_list ~loc prefix n in ...] generates a pattern to match a list of size [n] and a list of
expressions [exprs] to refer to names bound in this pattern. *)
val ( --> ) : pattern -> expression -> case
(** A shortcut to define a pattern matching case. *)
val map_loc : ('a -> 'b) -> 'a loc -> 'b loc
(** Map over data with location, useful to lift derive_of_label, derive_of_longident *)
(** Low-level deriver classes. *)
(** 1-arity deriver *)
class virtual deriving1 : object
inherit deriving
method virtual t : loc:location -> label loc -> core_type -> core_type
(** the type of the term generated by the deriver *)
(** ESSENTIAL METHODS *)
method derive_of_polyvariant : core_type -> row_field list -> expression -> expression
method derive_of_record : type_declaration -> label_declaration list -> expression -> expression
method derive_of_tuple : core_type -> core_type list -> expression -> expression
method derive_of_variant : type_declaration -> constructor_declaration list -> expression -> expression
(** LOW-LEVEL METHODS *)
method derive_type_ref : loc:location -> label -> longident loc -> core_type list -> expression -> expression
method derive_of_core_type : core_type -> expression -> expression
method derive_of_type_declaration : type_declaration -> value_binding list
end
================================================
FILE: packages/rsc/ppx_common/rsc_deriving_common.ml
================================================
open Ppxlib
open Ppx_deriving_tools.Conv
let get_of_variant_case ?mark_as_seen ~variant ~polyvariant = function
| Vcs_ctx_variant ctx -> Attribute.get ?mark_as_seen variant ctx
| Vcs_ctx_polyvariant ctx -> Attribute.get ?mark_as_seen polyvariant ctx
let get_of_variant ?mark_as_seen ~variant ~polyvariant = function
| Vrt_ctx_variant ctx -> Attribute.get ?mark_as_seen variant ctx
| Vrt_ctx_polyvariant ctx -> Attribute.get ?mark_as_seen polyvariant ctx
let attr_json_name ctx = Attribute.declare "rsc.name" ctx Ast_pattern.(single_expr_payload (estring __')) (fun x -> x)
let vcs_attr_json_name =
let variant = attr_json_name Attribute.Context.constructor_declaration in
let polyvariant = attr_json_name Attribute.Context.rtag in
get_of_variant_case ~variant ~polyvariant
let attr_json_allow_any ctx = Attribute.declare_flag "rsc.allow_any" ctx
let vcs_attr_json_allow_any =
let variant = attr_json_allow_any Attribute.Context.constructor_declaration in
let polyvariant = attr_json_allow_any Attribute.Context.rtag in
fun ?mark_as_seen ctx ->
match get_of_variant_case ~variant ~polyvariant ?mark_as_seen ctx with None -> false | Some () -> true
let ld_attr_json_key =
Attribute.get
(Attribute.declare "rsc.key" Attribute.Context.label_declaration
Ast_pattern.(single_expr_payload (estring __'))
(fun x -> x))
let ld_attr_json_option =
Attribute.get (Attribute.declare "rsc.option" Attribute.Context.label_declaration Ast_pattern.(pstr nil) ())
let attr_json_allow_extra_fields ctx = Attribute.declare "rsc.allow_extra_fields" ctx Ast_pattern.(pstr nil) ()
let td_attr_json_allow_extra_fields = Attribute.get (attr_json_allow_extra_fields Attribute.Context.type_declaration)
let cd_attr_json_allow_extra_fields =
Attribute.get (attr_json_allow_extra_fields Attribute.Context.constructor_declaration)
let ld_attr_json_default =
Attribute.get
(Attribute.declare "rsc.default" Attribute.Context.label_declaration
Ast_pattern.(single_expr_payload __)
(fun x -> x))
let ld_attr_json_drop_default =
Attribute.get (Attribute.declare "rsc.drop_default" Attribute.Context.label_declaration Ast_pattern.(pstr nil) ())
let ld_attr_default ld =
match ld_attr_json_default ld with
| Some e -> Some e
| None -> (
match ld_attr_json_option ld with
| Some () ->
let loc = ld.pld_loc in
Some [%expr Stdlib.Option.None]
| None -> None)
let ld_drop_default ld =
let loc = ld.pld_loc in
match (ld_attr_json_drop_default ld, ld_attr_json_option ld) with
| Some (), None -> Location.raise_errorf ~loc "found [@drop_default] attribute without [@option]"
| Some (), Some () -> `Drop_option
| None, _ -> `No
================================================
FILE: packages/rsc/ppx_js/dune
================================================
(library
(name rsc_js_ppx)
(public_name server-reason-react.rsc.ppx)
(kind ppx_rewriter)
(ppx_runtime_libraries server-reason-react.rsc)
(wrapped false)
(libraries ppxlib rsc_ppx_common)
(preprocess
(pps ppxlib.metaquot)))
================================================
FILE: packages/rsc/ppx_js/ppx_deriving_rsc_js.ml
================================================
open Printf
open StdLabels
open Ppxlib
open Ast_builder.Default
open Ppx_deriving_tools
open Ppx_deriving_tools.Conv
open Rsc_deriving_common
module Of_rsc = struct
let build_tuple ~loc derive si (ts : core_type list) e =
pexp_tuple ~loc (List.mapi ts ~f:(fun i t -> derive t [%expr Js.Array.unsafe_get [%e e] [%e eint ~loc (si + i)]]))
let build_js_type ~loc (fs : label_declaration list) =
let f ld =
let n = ld.pld_name in
let n = Option.value ~default:n (ld_attr_json_key ld) in
let pof_desc = Otag (n, [%type: RSC.t Js.undefined]) in
{ pof_loc = loc; pof_attributes = []; pof_desc }
in
let row = ptyp_object ~loc (List.map fs ~f) Closed in
[%type: [%t row] Js.t]
let build_record ~loc derive (fs : label_declaration list) x make =
let handle_field fs ld =
( map_loc lident ld.pld_name,
let n = ld.pld_name in
let n = Option.value ~default:n (ld_attr_json_key ld) in
[%expr
match Js.Undefined.toOption [%e fs]##[%e pexp_ident ~loc:n.loc (map_loc lident n)] with
| Stdlib.Option.Some v -> [%e derive ld.pld_type [%expr v]]
| Stdlib.Option.None ->
[%e
match ld_attr_default ld with
| Some default -> default
| None ->
[%expr
RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc (sprintf "expected field %S to be present" n.txt)]]]]
)
in
[%expr
let fs = (Obj.magic [%e x] : [%t build_js_type ~loc fs]) in
[%e make (pexp_record ~loc (List.map fs ~f:(handle_field [%expr fs])) None)]]
let is_object ~loc x =
[%expr
Stdlib.( && )
(Stdlib.( = ) (Js.typeof [%e x]) "object")
(Stdlib.( && )
(Stdlib.not (Js.Array.isArray [%e x]))
(Stdlib.not (Stdlib.( == ) (Obj.magic [%e x] : 'a Js.null) Js.null)))]
let ensure_object ~loc x =
[%expr if Stdlib.not [%e is_object ~loc x] then RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc "expected an object"]]
let ensure_array_len ~loc ~allow_any_constr ~else_ n len x =
[%expr
if Stdlib.( <> ) [%e len] [%e eint ~loc n] then
[%e
match allow_any_constr with
| Some allow_any_constr -> allow_any_constr x
| None -> [%expr RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc (sprintf "expected an array of length %i" n)]]]
else [%e else_]]
let derive_of_tuple derive t x =
let loc = t.tpl_loc in
let n = List.length t.tpl_types in
[%expr
if
Stdlib.( && )
(Js.Array.isArray [%e x])
(Stdlib.( = ) (Js.Array.length (Obj.magic [%e x] : RSC.t array)) [%e eint ~loc n])
then
let es = (Obj.magic [%e x] : RSC.t array) in
[%e build_tuple ~loc derive 0 t.tpl_types [%expr es]]
else RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc (sprintf "expected an array of length %i" n)]]
let derive_of_record derive t x =
let loc = t.rcd_loc in
[%expr
[%e ensure_object ~loc x];
[%e build_record ~loc derive t.rcd_fields x Fun.id]]
let derive_of_variant _derive t ~allow_any_constr body x =
let loc = t.vrt_loc in
[%expr
if Js.Array.isArray [%e x] then
let array = (Obj.magic [%e x] : RSC.t array) in
let len = Js.Array.length array in
if Stdlib.( > ) len 0 then
let tag = Js.Array.unsafe_get array 0 in
if Stdlib.( = ) (Js.typeof tag) "string" then
let tag = (Obj.magic tag : string) in
[%e body]
else
[%e
match allow_any_constr with
| Some allow_any_constr -> allow_any_constr x
| None -> [%expr RSC.of_rsc_error ~rsc:[%e x] "expected a non-empty tagged array with a string tag"]]
else
[%e
match allow_any_constr with
| Some allow_any_constr -> allow_any_constr x
| None -> [%expr RSC.of_rsc_error ~rsc:[%e x] "expected a non-empty tagged array"]]
else
[%e
match allow_any_constr with
| Some allow_any_constr -> allow_any_constr x
| None -> [%expr RSC.of_rsc_error ~rsc:[%e x] "expected a non-empty tagged array"]]]
let derive_of_variant_case derive make c ~allow_any_constr next =
match c with
| Vcs_record (n, r) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name r.rcd_ctx) in
[%expr
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then
[%e
ensure_array_len ~loc ~allow_any_constr 2 [%expr len] [%expr x]
~else_:
[%expr
let fs = Js.Array.unsafe_get array 1 in
[%e ensure_object ~loc [%expr fs]];
[%e build_record ~loc derive r.rcd_fields [%expr fs] (fun e -> make (Some e))]]]
else [%e next]]
| Vcs_tuple (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
let arity = List.length t.tpl_types in
[%expr
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then
[%e
ensure_array_len ~loc ~allow_any_constr (arity + 1) [%expr len] [%expr x]
~else_:
(if Stdlib.( = ) arity 0 then make None
else make (Some (build_tuple ~loc derive 1 t.tpl_types [%expr array])))]
else [%e next]]
let is_allow_any_constr vcs = vcs_attr_json_allow_any vcs
let deriving : Ppx_deriving_tools.deriving =
deriving_of () ~name:"of_rsc"
~of_t:(fun ~loc -> [%type: RSC.t])
~is_allow_any_constr ~derive_of_tuple ~derive_of_record ~derive_of_variant ~derive_of_variant_case
end
module To_rsc = struct
let derive_of_tuple derive t es =
let loc = t.tpl_loc in
[%expr RSC.Primitives.list_values_to_rsc [%e elist ~loc (List.map2 t.tpl_types es ~f:derive)]]
let derive_of_record derive t es =
let loc = t.rcd_loc in
let ebnds, pbnds =
let n = gen_symbol ~prefix:"bnds" () in
(evar ~loc n, pvar ~loc n)
in
let e =
List.combine t.rcd_fields es
|> List.fold_left ~init:ebnds ~f:(fun acc (ld, x) ->
let key = Option.value ~default:ld.pld_name (ld_attr_json_key ld) in
let k = estring ~loc:key.loc key.txt in
let v = derive ld.pld_type x in
let ebnds =
match ld_drop_default ld with
| `No -> [%expr ([%e k], [%e v]) :: [%e ebnds]]
| `Drop_option ->
[%expr
match [%e x] with
| Stdlib.Option.None -> [%e ebnds]
| Stdlib.Option.Some _ -> ([%e k], [%e v]) :: [%e ebnds]]
in
[%expr
let [%p pbnds] = [%e ebnds] in
[%e acc]])
in
[%expr
RSC.Primitives.assoc_to_rsc
(let [%p pbnds] = [] in
[%e e])]
let derive_of_variant_case derive c es =
match c with
| Vcs_record (n, r) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name r.rcd_ctx) in
[%expr
RSC.Primitives.list_values_to_rsc
[ RSC.Primitives.string_to_rsc [%e estring ~loc:n.loc n.txt]; [%e derive_of_record derive r es] ]]
| Vcs_tuple (_n, t) when vcs_attr_json_allow_any t.tpl_ctx -> (
match es with [ x ] -> x | xs -> failwith (sprintf "expected a tuple of length 1, got %i" (List.length xs)))
| Vcs_tuple (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
[%expr
RSC.Primitives.list_values_to_rsc
(RSC.Primitives.string_to_rsc [%e estring ~loc:n.loc n.txt]
:: [%e elist ~loc (List.map2 t.tpl_types es ~f:derive)])]
let deriving : Ppx_deriving_tools.deriving =
deriving_to () ~name:"to_rsc"
~t_to:(fun ~loc -> [%type: RSC.t])
~derive_of_tuple ~derive_of_record ~derive_of_variant_case
end
let () =
let _of_rsc = Ppx_deriving_tools.register Of_rsc.deriving in
let _to_rsc = Ppx_deriving_tools.register To_rsc.deriving in
let (_ : Deriving.t) = Ppx_deriving_tools.register_combined "rsc" [ To_rsc.deriving; Of_rsc.deriving ] in
()
================================================
FILE: packages/rsc/ppx_native/dune
================================================
(library
(name rsc_native_ppx)
(public_name server-reason-react.rsc-native.ppx)
(kind ppx_rewriter)
(ppx_runtime_libraries server-reason-react.rsc-native)
(wrapped false)
(libraries ppxlib rsc_ppx_common)
(preprocess
(pps ppxlib.metaquot)))
================================================
FILE: packages/rsc/ppx_native/ppx_deriving_rsc_native.ml
================================================
open Printf
open StdLabels
open Ppxlib
open Ast_builder.Default
open Ppx_deriving_tools
open Ppx_deriving_tools.Conv
open Rsc_deriving_common
module Of_rsc = struct
let with_refs ~loc prefix fs inner =
let gen_name n = sprintf "%s_%s" prefix n in
let gen_expr (n : label loc) = pexp_ident ~loc:n.loc { loc = n.loc; txt = lident (gen_name n.txt) } in
List.fold_left (List.rev fs) ~init:(inner gen_expr) ~f:(fun next ld ->
let n = ld.pld_name in
let patt = ppat_var ~loc:n.loc { loc = n.loc; txt = gen_name n.txt } in
[%expr
let [%p patt] =
ref
[%e
match ld_attr_default ld with
| Some default -> [%expr Stdlib.Option.Some [%e default]]
| None -> [%expr Stdlib.Option.None]]
in
[%e next]])
let build_tuple ~loc derive es ts =
let args =
List.fold_left
(List.rev (List.combine es ts))
~init:[]
~f:(fun prev (x, t) ->
let this = derive t [%expr RSC.of_model [%e x]] in
this :: prev)
in
pexp_tuple ~loc args
let build_record ~allow_extra_fields ~loc derive fs x fields make =
with_refs ~loc "x" fs @@ fun ename ->
let handle_field k v =
let fail_case =
[%pat? name]
-->
if allow_extra_fields then [%expr ()]
else [%expr RSC.of_rsc_error ~rsc:[%e x] (Stdlib.Printf.sprintf {|did not expect field "%s"|} name)]
in
let cases =
List.fold_left (List.rev fs) ~init:[ fail_case ] ~f:(fun next ld ->
let key = Option.value ~default:ld.pld_name (ld_attr_json_key ld) in
pstring ~loc:key.loc key.txt
--> [%expr [%e ename ld.pld_name] := Stdlib.Option.Some [%e derive ld.pld_type [%expr RSC.of_model [%e v]]]]
:: next)
in
pexp_match ~loc k cases
in
let build =
let fields =
List.map fs ~f:(fun ld ->
let key = Option.value ~default:ld.pld_name (ld_attr_json_key ld) in
let default = ld_attr_default ld in
( map_loc lident ld.pld_name,
[%expr
match Stdlib.( ! ) [%e ename ld.pld_name] with
| Stdlib.Option.Some v -> v
| Stdlib.Option.None ->
[%e
match default with
| Some default -> default
| None ->
[%expr
RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc:key.loc (sprintf "expected field %S" key.txt)]]]]
))
in
pexp_record ~loc fields None
in
[%expr
let rec iter = function
| [] -> ()
| (n', v) :: rest ->
[%e handle_field [%expr n'] [%expr v]];
iter rest
in
iter [%e fields];
[%e make build]]
let derive_of_tuple derive t x =
let loc = t.tpl_loc in
let n = List.length t.tpl_types in
let xpatt, xexprs = gen_pat_list ~loc "x" n in
let model = [%expr RSC.to_model [%e x]] in
pexp_match ~loc model
[
[%pat? React.Model.List [%p xpatt]] --> build_tuple ~loc derive xexprs t.tpl_types;
[%pat? _]
--> [%expr RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc (sprintf "expected an array of length %i" n)]];
]
let derive_of_record derive t x =
let loc = t.rcd_loc in
let allow_extra_fields = Option.is_some (td_attr_json_allow_extra_fields t.rcd_ctx) in
let model = [%expr RSC.to_model [%e x]] in
pexp_match ~loc model
[
[%pat? React.Model.Assoc fs] --> build_record ~allow_extra_fields ~loc derive t.rcd_fields x [%expr fs] Fun.id;
[%pat? _] --> [%expr RSC.of_rsc_error ~rsc:[%e x] [%e estring ~loc "expected an object"]];
]
let derive_of_variant_case derive make vcs =
match vcs with
| Vcs_tuple (n, t) when vcs_attr_json_allow_any t.tpl_ctx ->
let loc = n.loc in
[%pat? _] --> make (Some [%expr x])
| Vcs_tuple (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
let arity = List.length t.tpl_types in
if arity = 0 then
[%pat? React.Model.List [ React.Model.Json (`String [%p pstring ~loc:n.loc n.txt]) ]] --> make None
else
let xpatt, xexprs = gen_pat_list ~loc "x" arity in
[%pat? React.Model.List (React.Model.Json (`String [%p pstring ~loc:n.loc n.txt]) :: [%p xpatt])]
--> make (Some (build_tuple ~loc derive xexprs t.tpl_types))
| Vcs_record (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.rcd_ctx) in
let allow_extra_fields =
match t.rcd_ctx with
| Vcs_ctx_variant cd -> Option.is_some (cd_attr_json_allow_extra_fields cd)
| Vcs_ctx_polyvariant _ -> false
in
[%pat? React.Model.List [ React.Model.Json (`String [%p pstring ~loc:n.loc n.txt]); React.Model.Assoc fs ]]
--> build_record ~allow_extra_fields ~loc derive t.rcd_fields [%expr x] [%expr fs] (fun e -> make (Some e))
let cmp_sort_vcs vcs1 vcs2 =
let allow_any_1 = vcs_attr_json_allow_any vcs1 and allow_any_2 = vcs_attr_json_allow_any vcs2 in
match (allow_any_1, allow_any_2) with true, true | false, false -> 0 | true, false -> -1 | false, true -> 1
let deriving : Ppx_deriving_tools.deriving =
deriving_of_match () ~name:"of_rsc"
~of_t:(fun ~loc -> [%type: RSC.t])
~cmp_sort_vcs ~derive_of_tuple ~derive_of_record ~derive_of_variant_case
end
module To_rsc = struct
let gen_exp_pat ~loc prefix =
let n = gen_symbol ~prefix () in
(evar ~loc n, pvar ~loc n)
let derive_of_tuple derive t es =
let loc = t.tpl_loc in
[%expr RSC.Primitives.list_values_to_rsc [%e elist ~loc (List.map2 t.tpl_types es ~f:derive)]]
let derive_of_record derive t es =
let loc = t.rcd_loc in
let ebnds, pbnds = gen_exp_pat ~loc "bnds" in
let e =
List.combine t.rcd_fields es
|> List.fold_left ~init:ebnds ~f:(fun acc (ld, x) ->
let key = Option.value ~default:ld.pld_name (ld_attr_json_key ld) in
let k = estring ~loc:key.loc key.txt in
let v = derive ld.pld_type x in
let ebnds =
match ld_drop_default ld with
| `No -> [%expr ([%e k], [%e v]) :: [%e ebnds]]
| `Drop_option ->
[%expr
match [%e x] with
| Stdlib.Option.None -> [%e ebnds]
| Stdlib.Option.Some _ -> ([%e k], [%e v]) :: [%e ebnds]]
in
[%expr
let [%p pbnds] = [%e ebnds] in
[%e acc]])
in
[%expr
RSC.Primitives.assoc_to_rsc
(let [%p pbnds] = [] in
[%e e])]
let derive_of_variant_case derive vcs es =
match vcs with
| Vcs_tuple (_n, t) when vcs_attr_json_allow_any t.tpl_ctx -> (
match es with [ x ] -> x | xs -> failwith (sprintf "expected a tuple of length 1, got %i" (List.length xs)))
| Vcs_tuple (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
[%expr
RSC.Primitives.list_values_to_rsc
(RSC.Primitives.string_to_rsc [%e estring ~loc:n.loc n.txt]
:: [%e elist ~loc (List.map2 t.tpl_types es ~f:derive)])]
| Vcs_record (n, t) ->
let loc = n.loc in
let n = Option.value ~default:n (vcs_attr_json_name t.rcd_ctx) in
[%expr
RSC.Primitives.list_values_to_rsc
[ RSC.Primitives.string_to_rsc [%e estring ~loc:n.loc n.txt]; [%e derive_of_record derive t es] ]]
let deriving : Ppx_deriving_tools.deriving =
deriving_to () ~name:"to_rsc"
~t_to:(fun ~loc -> [%type: RSC.t])
~derive_of_tuple ~derive_of_record ~derive_of_variant_case
end
let () =
let _of_rsc = Ppx_deriving_tools.register Of_rsc.deriving in
let _to_rsc = Ppx_deriving_tools.register To_rsc.deriving in
let (_ : Deriving.t) = Ppx_deriving_tools.register_combined "rsc" [ To_rsc.deriving; Of_rsc.deriving ] in
()
================================================
FILE: packages/runtime/Runtime.ml
================================================
exception Impossible_in_ssr of string
let fail_impossible_action_in_ssr fn =
let backtrace = Printexc.get_callstack 8 in
let raw_callstack = Printexc.raw_backtrace_to_string backtrace in
let () =
Printf.printf
{|'%s' should only run on the client. Make sure you aren't accidentally calling this function in a server-side context.
Here's the raw callstack:
%s
|}
fn raw_callstack
in
raise (Impossible_in_ssr (Printf.sprintf {|'%s' shouldn't run on the server|} fn))
type platform = Server | Client
(* QUESTION: Can we create a lint ensuring that the callback function is uncurried? *)
type 'callback server_function = { id : string; call : 'callback }
================================================
FILE: packages/runtime/Runtime.mli
================================================
(** A small utility to raise issues with SSR
Mostly used internally by the ppxes *)
exception Impossible_in_ssr of string
(** Exception to throw when operations aren't meant to be running on native, mostly used by browser_ppx or ReactDOM *)
val fail_impossible_action_in_ssr : string -> 'a
type platform =
| Server
| Client
(** `Runtime.platform` is required to use switch%platform. It's a simple variant that expresses the 2 platforms *)
type 'callback server_function = { id : string; call : 'callback }
(** Type for server actions contract.
The 'callback function must to be uncurried as we don't know the amount of arguments.
- [id]: Server Function ID, this will be used on server only
- [call]: The Server function implementation
E.g. React.server_function(. ~name: string, ~age: int) => Js.Promise.t(string) *)
================================================
FILE: packages/runtime/dune
================================================
(library
(name runtime)
(modes :standard melange)
(public_name server-reason-react.runtime))
================================================
FILE: packages/server-reason-react-ppx/DomProps.ml
================================================
[@@@ocamlformat "disable"]
(* This file is more like a spreadsheet, prefer to keep it with margin=300.
Since @@@ocamlformat "margin=300" isn't possible, we disable it *)
type attributeType =
| Action
| String
| Int
| Bool
| BooleanishString (* `Booleanish_string` are JSX attributes represented as boolean values but rendered as strings on HTML https://github.com/facebook/react/blob/a17467e7e2cd8947c595d1834889b5d184459f12/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js#L1165-L1176 *)
| Style
| Ref
| InnerHtml
type eventType =
| Clipboard
| Composition
| Keyboard
| Focus
| Form
| Mouse
| Selection
| Touch
| UI
| Wheel
| Media
| Image
| Animation
| Transition
| Pointer
| Inline
| Drag
(* In React, all DOM properties and attributes (including event handlers) should be camelCased. For example, the HTML attribute tabindex corresponds to the attribute tabIndex in React. The exception is aria-* and data-* attributes, which should be lowercased. For example, you can keep aria-label as aria-label.
More info about it: https://legacy.reactjs.org/docs/dom-elements.html *)
(* In `attribute` we store the 3 formats for DOM (HTML and SVG) attributes, JSX props and Reason's JSX props *)
type attribute = {
type_ : attributeType;
name : string; (* HTML name *)
jsxName : string; (* JSX name *)
reasonJsxName : string; (* Reason's JSX name is the format that appears on Reason/OCaml files, which are must not
match with reserved keywords from OCaml (https://ocaml.org/manual/5.2/lex.html#sss:keywords) or Reason syntax (https://github.com/reasonml/reason/blob/master/src/reason-parser/reason_declarative_lexer.mll#L85-L144).
Currently all reserved words used in HTML come from OCaml (and inheritly in Reason), but there's none that comes from Reason. *)
}
type event = {
type_ : eventType;
(* event handlers should be camelCased and they don't collied with any reserved words from the language.
also we don't use the HTML format in the ppx, neither int the runtime *)
jsxName : string;
}
type prop =
| Attribute of attribute
| Event of event
type element = {
tag : string;
attributes : prop list;
}
let attributeReferrerPolicy = String
(* | Empty | NoReferrer | NoReferrerWhenDowngrade | Origin |
OriginWhenCrossOrigin | SameOrigin | StrictOrigin |
StrictOriginWhenCrossOrigin | UnsafeUrl *)
let attributeAnchorTarget = String
(* | Self | Blank | Parent | Top | Custom of String *)
let globalEventHandlers =
(* https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers *)
[
Event { jsxName = "onCopy"; type_ = Clipboard };
Event { jsxName = "onCopyCapture"; type_ = Clipboard };
Event { jsxName = "onCut"; type_ = Clipboard };
Event { jsxName = "onCutCapture"; type_ = Clipboard };
Event { jsxName = "onPaste"; type_ = Clipboard };
Event { jsxName = "onPasteCapture"; type_ = Clipboard };
Event { jsxName = "onCompositionEnd"; type_ = Composition };
Event { jsxName = "onCompositionEndCapture"; type_ = Composition };
Event { jsxName = "onCompositionStart"; type_ = Composition };
Event { jsxName = "onCompositionStartCapture"; type_ = Composition };
Event { jsxName = "onCompositionUpdate"; type_ = Composition };
Event { jsxName = "onCompositionUpdateCapture"; type_ = Composition };
Event { jsxName = "onFocus"; type_ = Focus };
Event { jsxName = "onFocusCapture"; type_ = Focus };
Event { jsxName = "onBlur"; type_ = Focus };
Event { jsxName = "onBlurCapture"; type_ = Focus };
Event { jsxName = "onChange"; type_ = Form };
Event { jsxName = "onChangeCapture"; type_ = Form };
Event { jsxName = "onBeforeInput"; type_ = Form };
Event { jsxName = "onBeforeInputCapture"; type_ = Form };
Event { jsxName = "onInput"; type_ = Form };
Event { jsxName = "onInputCapture"; type_ = Form };
Event { jsxName = "onReset"; type_ = Form };
Event { jsxName = "onResetCapture"; type_ = Form };
Event { jsxName = "onSubmit"; type_ = Form };
Event { jsxName = "onSubmitCapture"; type_ = Form };
Event { jsxName = "onInvalid"; type_ = Form };
Event { jsxName = "onInvalidCapture"; type_ = Form };
Event { jsxName = "onLoad"; type_ = Media };
Event { jsxName = "onLoadCapture"; type_ = Media };
Event { jsxName = "onError"; type_ = Media };
Event { jsxName = "onErrorCapture"; type_ = Media };
Event { jsxName = "onKeyDown"; type_ = Keyboard };
Event { jsxName = "onKeyDownCapture"; type_ = Keyboard };
Event { jsxName = "onKeyPress"; type_ = Keyboard };
Event { jsxName = "onKeyPressCapture"; type_ = Keyboard };
Event { jsxName = "onKeyUp"; type_ = Keyboard };
Event { jsxName = "onKeyUpCapture"; type_ = Keyboard };
Event { jsxName = "onAbort"; type_ = Media };
Event { jsxName = "onAbortCapture"; type_ = Media };
Event { jsxName = "onCanPlay"; type_ = Media };
Event { jsxName = "onCanPlayCapture"; type_ = Media };
Event { jsxName = "onCanPlayThrough"; type_ = Media };
Event { jsxName = "onCanPlayThroughCapture"; type_ = Media };
Event { jsxName = "onDurationChange"; type_ = Media };
Event { jsxName = "onDurationChangeCapture"; type_ = Media };
Event { jsxName = "onEmptied"; type_ = Media };
Event { jsxName = "onEmptiedCapture"; type_ = Media };
Event { jsxName = "onEncrypted"; type_ = Media };
Event { jsxName = "onEncryptedCapture"; type_ = Media };
Event { jsxName = "onEnded"; type_ = Media };
Event { jsxName = "onEndedCapture"; type_ = Media };
Event { jsxName = "onLoadedData"; type_ = Media };
Event { jsxName = "onLoadedDataCapture"; type_ = Media };
Event { jsxName = "onLoadedMetadata"; type_ = Media };
Event { jsxName = "onLoadedMetadataCapture"; type_ = Media };
Event { jsxName = "onLoadStart"; type_ = Media };
Event { jsxName = "onLoadStartCapture"; type_ = Media };
Event { jsxName = "onPause"; type_ = Media };
Event { jsxName = "onPauseCapture"; type_ = Media };
Event { jsxName = "onPlay"; type_ = Media };
Event { jsxName = "onPlayCapture"; type_ = Media };
Event { jsxName = "onPlaying"; type_ = Media };
Event { jsxName = "onPlayingCapture"; type_ = Media };
Event { jsxName = "onProgress"; type_ = Media };
Event { jsxName = "onProgressCapture"; type_ = Media };
Event { jsxName = "onRateChange"; type_ = Media };
Event { jsxName = "onRateChangeCapture"; type_ = Media };
Event { jsxName = "onSeeked"; type_ = Media };
Event { jsxName = "onSeekedCapture"; type_ = Media };
Event { jsxName = "onSeeking"; type_ = Media };
Event { jsxName = "onSeekingCapture"; type_ = Media };
Event { jsxName = "onStalled"; type_ = Media };
Event { jsxName = "onStalledCapture"; type_ = Media };
Event { jsxName = "onSuspend"; type_ = Media };
Event { jsxName = "onSuspendCapture"; type_ = Media };
Event { jsxName = "onTimeUpdate"; type_ = Media };
Event { jsxName = "onTimeUpdateCapture"; type_ = Media };
Event { jsxName = "onVolumeChange"; type_ = Media };
Event { jsxName = "onVolumeChangeCapture"; type_ = Media };
Event { jsxName = "onWaiting"; type_ = Media };
Event { jsxName = "onWaitingCapture"; type_ = Media };
Event { jsxName = "onAuxClick"; type_ = Mouse };
Event { jsxName = "onAuxClickCapture"; type_ = Mouse };
Event { jsxName = "onClick"; type_ = Mouse };
Event { jsxName = "onClickCapture"; type_ = Mouse };
Event { jsxName = "onContextMenu"; type_ = Mouse };
Event { jsxName = "onContextMenuCapture"; type_ = Mouse };
Event { jsxName = "onDoubleClick"; type_ = Mouse };
Event { jsxName = "onDoubleClickCapture"; type_ = Mouse };
Event { jsxName = "onDrag"; type_ = Drag };
Event { jsxName = "onDragCapture"; type_ = Drag };
Event { jsxName = "onDragEnd"; type_ = Drag };
Event { jsxName = "onDragEndCapture"; type_ = Drag };
Event { jsxName = "onDragEnter"; type_ = Drag };
Event { jsxName = "onDragEnterCapture"; type_ = Drag };
Event { jsxName = "onDragExit"; type_ = Drag };
Event { jsxName = "onDragExitCapture"; type_ = Drag };
Event { jsxName = "onDragLeave"; type_ = Drag };
Event { jsxName = "onDragLeaveCapture"; type_ = Drag };
Event { jsxName = "onDragOver"; type_ = Drag };
Event { jsxName = "onDragOverCapture"; type_ = Drag };
Event { jsxName = "onDragStart"; type_ = Drag };
Event { jsxName = "onDragStartCapture"; type_ = Drag };
Event { jsxName = "onDrop"; type_ = Drag };
Event { jsxName = "onDropCapture"; type_ = Drag };
Event { jsxName = "onMouseDown"; type_ = Mouse };
Event { jsxName = "onMouseDownCapture"; type_ = Mouse };
Event { jsxName = "onMouseEnter"; type_ = Mouse };
Event { jsxName = "onMouseLeave"; type_ = Mouse };
Event { jsxName = "onMouseMove"; type_ = Mouse };
Event { jsxName = "onMouseMoveCapture"; type_ = Mouse };
Event { jsxName = "onMouseOut"; type_ = Mouse };
Event { jsxName = "onMouseOutCapture"; type_ = Mouse };
Event { jsxName = "onMouseOver"; type_ = Mouse };
Event { jsxName = "onMouseOverCapture"; type_ = Mouse };
Event { jsxName = "onMouseUp"; type_ = Mouse };
Event { jsxName = "onMouseUpCapture"; type_ = Mouse };
Event { jsxName = "onSelect"; type_ = Selection };
Event { jsxName = "onSelectCapture"; type_ = Selection };
Event { jsxName = "onTouchCancel"; type_ = Touch };
Event { jsxName = "onTouchCancelCapture"; type_ = Touch };
Event { jsxName = "onTouchEnd"; type_ = Touch };
Event { jsxName = "onTouchEndCapture"; type_ = Touch };
Event { jsxName = "onTouchMove"; type_ = Touch };
Event { jsxName = "onTouchMoveCapture"; type_ = Touch };
Event { jsxName = "onTouchStart"; type_ = Touch };
Event { jsxName = "onTouchStartCapture"; type_ = Touch };
Event { jsxName = "onPointerDown"; type_ = Pointer };
Event { jsxName = "onPointerDownCapture"; type_ = Pointer };
Event { jsxName = "onPointerMove"; type_ = Pointer };
Event { jsxName = "onPointerMoveCapture"; type_ = Pointer };
Event { jsxName = "onPointerUp"; type_ = Pointer };
Event { jsxName = "onPointerUpCapture"; type_ = Pointer };
Event { jsxName = "onPointerCancel"; type_ = Pointer };
Event { jsxName = "onPointerCancelCapture"; type_ = Pointer };
Event { jsxName = "onPointerEnter"; type_ = Pointer };
Event { jsxName = "onPointerEnterCapture"; type_ = Pointer };
Event { jsxName = "onPointerLeave"; type_ = Pointer };
Event { jsxName = "onPointerLeaveCapture"; type_ = Pointer };
Event { jsxName = "onPointerOver"; type_ = Pointer };
Event { jsxName = "onPointerOverCapture"; type_ = Pointer };
Event { jsxName = "onPointerOut"; type_ = Pointer };
Event { jsxName = "onPointerOutCapture"; type_ = Pointer };
Event { jsxName = "onGotPointerCapture"; type_ = Pointer };
Event { jsxName = "onGotPointerCaptureCapture"; type_ = Pointer };
Event { jsxName = "onLostPointerCapture"; type_ = Pointer };
Event { jsxName = "onLostPointerCaptureCapture"; type_ = Pointer };
Event { jsxName = "onScroll"; type_ = UI };
Event { jsxName = "onScrollCapture"; type_ = UI };
Event { jsxName = "onWheel"; type_ = Wheel };
Event { jsxName = "onWheelCapture"; type_ = Wheel };
Event { jsxName = "onAnimationStart"; type_ = Animation };
Event { jsxName = "onAnimationStartCapture"; type_ = Animation };
Event { jsxName = "onAnimationEnd"; type_ = Animation };
Event { jsxName = "onAnimationEndCapture"; type_ = Animation };
Event { jsxName = "onAnimationIteration"; type_ = Animation };
Event { jsxName = "onAnimationIterationCapture"; type_ = Animation };
Event { jsxName = "onTransitionEnd"; type_ = Transition };
Event { jsxName = "onTransitionEndCapture"; type_ = Transition };
]
(* All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ *)
let ariaAttributes =
[
(* Identifies the currently active element when DOM focus is on a composite
widget, textbox, group, or application. *)
Attribute { name = "aria-activedescendant"; jsxName = "aria-activedescendant"; reasonJsxName = "ariaActivedescendant"; type_ = String };
(* Indicates whether assistive technologies will present all, or only parts
of, the changed region based on the change notifications defined by the
aria-relevant attribute. *)
Attribute { name = "aria-atomic"; jsxName = "aria-atomic"; reasonJsxName = "ariaAtomic"; type_ = BooleanishString };
(* Indicates whether inputting text could trigger display of one or more predictions of the user's intended value for an input and specifies how predictions would be
* presented if they are made.
*)
Attribute { name = "aria-autocomplete"; jsxName = "aria-autocomplete"; reasonJsxName = "ariaAutocomplete"; type_ = String (* 'none' | 'inline' | 'list' | 'both' *) };
(* Indicates an element is being modified and that assistive technologies
MAY want to wait until the modifications are complete before exposing
them to the user. *)
Attribute { name = "aria-busy"; jsxName = "aria-busy"; reasonJsxName = "ariaBusy"; type_ = BooleanishString };
(* Indicates the current "checked" state of checkboxes, radio buttons, and other
widgets.
* @see aria-pressed @see aria-selected.
*)
Attribute { name = "aria-checked"; jsxName = "aria-checked"; reasonJsxName = "ariaChecked"; type_ = String (* Bool | 'false' | 'mixed' | 'true' *) };
(* Defines the total number of columns in a table, grid, or treegrid.
* @see aria-colindex.
*)
Attribute { name = "aria-colcount"; jsxName = "aria-colcount"; reasonJsxName = "ariaColcount"; type_ = Int };
(* Defines an element's column index or position with respect to the total number of columns within a table,
grid, or treegrid.
* @see aria-colcount @see aria-colspan.
*)
Attribute { name = "aria-colindex"; jsxName = "aria-colindex"; reasonJsxName = "ariaColindex"; type_ = Int };
(* Defines the number of columns spanned by a cell or gridcell within a table, grid, or treegrid.
* @see aria-colindex @see aria-rowspan.
*)
Attribute { name = "aria-colspan"; jsxName = "aria-colspan"; reasonJsxName = "ariaColspan"; type_ = Int };
(* Identifies the element (or elements) whose contents or presence are controlled by the current element.
* @see aria-owns.
*)
Attribute { name = "aria-controls"; jsxName = "aria-controls"; reasonJsxName = "ariaControls"; type_ = String };
(* Indicates the element that represents the current item within a container
or set of related elements. *)
Attribute { name = "aria-current"; jsxName = "ariaCurrent"; reasonJsxName = "ariaCurrent"; type_ = String (* Bool | 'false' | 'true' | 'page' | 'step' | 'location' | 'date' | 'time' *) };
(* Identifies the element (or elements) that describes the object.
* @see aria-labelledby
*)
Attribute { name = "aria-describedby"; jsxName = "aria-describedby"; reasonJsxName = "ariaDescribedby"; type_ = String };
(* Identifies the element that provides a detailed, extended description for
the object. * @see aria-describedby. *)
Attribute { name = "aria-details"; jsxName = "aria-details"; reasonJsxName = "ariaDetails"; type_ = String };
(* Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable.
* @see aria-hidden @see aria-readonly.
*)
Attribute { name = "aria-disabled"; jsxName = "aria-disabled"; reasonJsxName = "ariaDisabled"; type_ = BooleanishString };
(* Identifies the element that provides an error message for the object.
* @see aria-invalid @see aria-describedby.
*)
Attribute { name = "aria-errormessage"; jsxName = "aria-errormessage"; reasonJsxName = "ariaErrormessage"; type_ = String };
(* Indicates whether the element, or another grouping element it controls,
is currently expanded or collapsed. *)
Attribute { name = "aria-expanded"; jsxName = "aria-expanded"; reasonJsxName = "ariaExpanded"; type_ = BooleanishString };
(* Identifies the next element (or elements) in an alternate reading order of content which, at the user's discretion,
* allows assistive technology to override the general default of reading in document source order.
*)
Attribute { name = "aria-flowto"; jsxName = "aria-flowto"; reasonJsxName = "ariaFlowto"; type_ = String };
(* Indicates the availability and type of interactive popup element, such as
menu or dialog, that can be triggered by an element. *)
Attribute { name = "aria-haspopup"; jsxName = "aria-haspopup"; reasonJsxName = "ariaHaspopup"; type_ = String (* Bool | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'; *)};
(* Indicates whether the element is exposed to an accessibility API.
* @see aria-disabled.
*)
Attribute { name = "aria-hidden"; jsxName = "aria-hidden"; reasonJsxName = "ariaHidden"; type_ = BooleanishString };
(* Indicates the entered value does not conform to the format expected by the
application.
* @see aria-errormessage.
*)
Attribute { name = "aria-invalid"; jsxName = "aria-invalid"; reasonJsxName = "ariaInvalid"; type_ = String (* Bool | 'false' | 'true' | 'grammar' | 'spelling'; *) };
(* Indicates keyboard shortcuts that an author has implemented to activate
or give focus to an element. *)
Attribute { name = "aria-keyshortcuts"; jsxName = "aria-keyshortcuts"; reasonJsxName = "ariaKeyshortcuts"; type_ = String };
(* Defines a String value that labels the current element.
* @see aria-labelledby.
*)
Attribute { name = "aria-label"; jsxName = "aria-label"; reasonJsxName = "ariaLabel"; type_ = String };
(* Identifies the element (or elements) that labels the current element.
* @see aria-describedby.
*)
Attribute { name = "aria-labelledby"; jsxName = "aria-labelledby"; reasonJsxName = "ariaLabelledby"; type_ = String };
(* Defines the hierarchical level of an element within a structure. *)
Attribute { name = "aria-level"; jsxName = "aria-level"; reasonJsxName = "ariaLevel"; type_ = Int };
(* Indicates that an element will be updated, and describes the types of
updates the user agents, assistive technologies, and user can expect ;rom
the live region. *)
Attribute { name = "aria-live"; jsxName = "aria-live"; reasonJsxName = "ariaLive"; type_ = String (* 'off' | 'assertive' | 'polite' *) };
(* Indicates whether an element is modal when displayed. *)
Attribute { name = "aria-modal"; jsxName = "aria-modal"; reasonJsxName = "ariaModal"; type_ = BooleanishString };
(* Indicates whether a text box accepts multiple lines of input or only a
single line. *)
Attribute { name = "aria-multiline"; jsxName = "aria-multiline"; reasonJsxName = "ariaMultiline"; type_ = BooleanishString };
(* Indicates that the user may select more than one item from the current
selectable descendants. *)
Attribute { name = "aria-multiselectable"; jsxName = "aria-multiselectable"; reasonJsxName = "ariaMultiselectable"; type_ = BooleanishString };
(* Indicates whether the element's orientation is horizontal, vertical, or
unknown/ambiguous. *)
Attribute { name = "aria-orientation"; jsxName = "aria-orientation"; reasonJsxName = "ariaOrientation"; type_ = String (* 'horizontal' | 'vertical' *) };
(* Identifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship
* between DOM elements where the DOM hierarchy cannot be used to represent the relationship.
* @see aria-controls.
*)
Attribute { name = "aria-owns"; jsxName = "aria-owns"; reasonJsxName = "ariaOwns"; type_ = String };
(* Defines a short hint (a word or short phrase) intended to aid the user with data entry when the control has no
value.
* A hint could be a sample value or a brief description of the expected format.
*)
Attribute { name = "aria-placeholder"; jsxName = "aria-placeholder"; reasonJsxName = "ariaPlaceholder"; type_ = String };
(* Defines an element's number or position in the current set of listitems
or treeitems. Not required if all elements in the set are present in the
DOM. * @see aria-setsize. *)
Attribute { name = "aria-posinset"; jsxName = "aria-posinset"; reasonJsxName = "ariaPosinset"; type_ = Int };
(* Indicates the current "pressed" state of toggle buttons.
* @see aria-checked @see aria-selected.
*)
Attribute { name = "aria-pressed"; jsxName = "aria-pressed"; reasonJsxName = "ariaPressed"; type_ = String (* Bool | 'false' | 'mixed' | 'true' *) };
(* Indicates that the element is not editable, but is otherwise
operable.
* @see aria-disabled.
*)
Attribute { name = "aria-readonly"; jsxName = "aria-readonly"; reasonJsxName = "ariaReadonly"; type_ = BooleanishString };
(* Indicates what notifications the user agent will trigger when the
accessibility tree within a live region is modified.
* @see aria-atomic.
*)
Attribute { name = "aria-relevant"; jsxName = "aria-relevant"; reasonJsxName = "ariaRelevant"; type_ = String (* 'additions' | 'additions removals' | 'additions text' | 'all' | 'removals' | 'removals additions' | 'removals text' | 'text' | 'text additions' | 'text removals' *) };
(* Indicates that user input is required on the element before a form may be
submitted. *)
Attribute { name = "aria-required"; jsxName = "aria-required"; reasonJsxName = "ariaRequired"; type_ = BooleanishString };
(* Defines a human-readable, author-localized description for the role of an element. *)
Attribute { name = "aria-roledescription"; jsxName = "aria-roledescription"; reasonJsxName = "ariaRoledescription"; type_ = String };
(* Defines the total number of rows in a table, grid, or treegrid.
* @see aria-rowindex.
*)
Attribute { name = "aria-rowcount"; jsxName = "aria-rowcount"; reasonJsxName = "ariaRowcount"; type_ = Int };
(* Defines an element's row index or position with respect to the total number of rows within a table, grid, or
treegrid.
* @see aria-rowcount @see aria-rowspan.
*)
Attribute { name = "aria-rowindex"; jsxName = "aria-rowindex"; reasonJsxName = "ariaRowindex"; type_ = Int };
(* *)
Attribute { name = "aria-rowindextext"; jsxName = "aria-rowindextext"; reasonJsxName = "ariaRowindextext"; type_ = String };
(* Defines the number of rows spanned by a cell or gridcell within a table, grid, or treegrid.
* @see aria-rowindex @see aria-colspan.
*)
Attribute { name = "aria-rowspan"; jsxName = "aria-rowspan"; reasonJsxName = "ariaRowspan"; type_ = Int };
(* Indicates the current "selected" state of various widgets.
* @see aria-checked @see aria-pressed.
*)
Attribute { name = "aria-selected"; jsxName = "aria-selected"; reasonJsxName = "ariaSelected"; type_ = BooleanishString };
(* Defines the number of items in the current set of listitems or treeitems.
Not required if all elements in the set are present in the DOM.
* @see aria-posinset.
*)
Attribute { name = "aria-setsize"; jsxName = "aria-setsize"; reasonJsxName = "ariaSetsize"; type_ = Int };
(* Indicates if items in a table or grid are sorted in ascending or
descending order. *)
Attribute { name = "aria-sort"; jsxName = "aria-sort"; reasonJsxName = "ariaSort"; type_ = String (* 'none' | 'ascending' | 'descending' | 'other' *) };
(* Defines the maximum allowed value for a range widget. *)
Attribute { name = "aria-valuemax"; jsxName = "aria-valuemax"; reasonJsxName = "ariaValuemax"; type_ = Int };
(* Defines the minimum allowed value for a range widget. *)
Attribute { name = "aria-valuemin"; jsxName = "aria-valuemin"; reasonJsxName = "ariaValuemin"; type_ = Int };
(* Defines the current value for a range widget.
* @see aria-valuetext.
*)
Attribute { name = "aria-valuenow"; jsxName = "aria-valuenow"; reasonJsxName = "ariaValuenow"; type_ = Int };
(* Defines the human readable text alternative of aria-valuenow for a range
widget. *)
Attribute { name = "aria-valuetext"; jsxName = "aria-valuetext"; reasonJsxName = "ariaValuetext"; type_ = String };
]
(* All the WAI-ARIA 1.1 role attribute values from
https://www.w3.org/TR/wai-aria-1.1/#role_definitions *)
let ariaRole = String
(* | Alert | Alertdialog | Application | Article | Banner | Button | Cell |
Checkbox | Columnheader | Combobox | Complementary | Contentinfo | Definition
| Dialog | Directory | Document | Feed | Figure | Form | Grid | Gridcell |
Group | Heading | Img | Link | List | Listbox | Listitem | Log | Main |
Marquee | Math | Menu | Menubar | Menuitem | Menuitemcheckbox | Menuitemradio
| Navigation | None | Note | Option | Presentation | Progressbar | Radio |
Radiogroup | Region | Row | Rowgroup | Rowheader | Scrollbar | Search |
Searchbox | Separator | Slider | Spinbutton | Status | Switch | Tab | Table |
Tablist | Tabpanel | Term | Textbox | Timer | Toolbar | Tooltip | Tree |
Treegrid | Treeitem | Custom of String *)
let reactAttributes =
[
Attribute { name = "class"; jsxName = "className"; reasonJsxName = "className"; type_ = String };
Attribute { name = "defaultChecked"; jsxName = "defaultChecked"; reasonJsxName = "defaultChecked"; type_ = Bool };
Attribute { name = "defaultValue"; jsxName = "defaultValue"; reasonJsxName = "defaultValue"; type_ = String (* | number | ReadonlyArray *) };
(* https://reactjs.org/docs/dom-elements.html *)
Attribute { name = "dangerouslySetInnerHTML"; jsxName = "dangerouslySetInnerHTML"; reasonJsxName = "dangerouslySetInnerHTML"; type_ = InnerHtml };
Attribute { name = "ref"; jsxName = "ref"; reasonJsxName = "ref"; type_ = Ref };
Attribute { name = "key"; jsxName = "key"; reasonJsxName = "key"; type_ = String };
Attribute { name = "suppressContentEditableWarning"; jsxName = "suppressContentEditableWarning"; reasonJsxName = "suppressContentEditableWarning"; type_ = Bool };
Attribute { name = "suppressHydrationWarning"; jsxName = "suppressHydrationWarning"; reasonJsxName = "suppressHydrationWarning"; type_ = Bool };
]
let globalAttributes =
[
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes *)
(* Standard HTML Attributes *)
Attribute { name = "accesskey"; jsxName = "accessKey"; reasonJsxName = "accessKey"; type_ = String };
Attribute { name = "autocapitalize"; jsxName = "autoCapitalize"; reasonJsxName = "autoCapitalize"; type_ = String };
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "contextmenu"; jsxName = "contextMenu"; reasonJsxName = "contextMenu"; type_ = String };
Attribute { name = "contenteditable"; jsxName = "contentEditable"; reasonJsxName = "contentEditable"; type_ = BooleanishString };
Attribute { name = "dir"; jsxName = "dir"; reasonJsxName = "dir"; type_ = String };
Attribute { name = "draggable"; jsxName = "draggable"; reasonJsxName = "draggable"; type_ = BooleanishString };
Attribute { name = "hidden"; jsxName = "hidden"; reasonJsxName = "hidden"; type_ = Bool };
Attribute { name = "id"; jsxName = "id"; reasonJsxName = "id"; type_ = String };
Attribute { name = "itemprop"; jsxName = "itemProp"; reasonJsxName = "itemProp"; type_ = String };
Attribute { name = "itemscope"; jsxName = "itemScope"; reasonJsxName = "itemScope"; type_ = Bool };
Attribute { name = "itemtype"; jsxName = "itemType"; reasonJsxName = "itemType"; type_ = String };
Attribute { name = "itemid"; jsxName = "itemID"; reasonJsxName = "itemID"; type_ = String };
Attribute { name = "itemref"; jsxName = "itemRef"; reasonJsxName = "itemRef"; type_ = String };
Attribute { name = "lang"; jsxName = "lang"; reasonJsxName = "lang"; type_ = String };
Attribute { name = "placeholder"; jsxName = "placeholder"; reasonJsxName = "placeholder"; type_ = String };
Attribute { name = "part"; jsxName = "part"; reasonJsxName = "part"; type_ = String };
Attribute { name = "nonce"; jsxName = "nonce"; reasonJsxName = "nonce"; type_ = String };
Attribute { name = "slot"; jsxName = "slot"; reasonJsxName = "slot"; type_ = String };
Attribute { name = "spellcheck"; jsxName = "spellCheck"; reasonJsxName = "spellCheck"; type_ = BooleanishString };
Attribute { name = "style"; jsxName = "style"; reasonJsxName = "style"; type_ = Style };
Attribute { name = "tabindex"; jsxName = "tabIndex"; reasonJsxName = "tabIndex"; type_ = Int };
Attribute { name = "enterkeyhint"; jsxName = "enterKeyHint"; reasonJsxName = "enterKeyHint"; type_ = Int };
(* data-* attributes are globaly available *)
(* Experimental ; Attribute {name= "exportParts"; jsxName= "exportParts";
type_= Int} *)
Attribute { name = "title"; jsxName = "title"; reasonJsxName = "title"; type_ = String };
Attribute { name = "translate"; jsxName = "translate"; reasonJsxName = "translate"; type_ = String (* 'yes' | 'no' *) };
(* Living Standard * Hints at the type of data that might be entered by the
user while editing the element or its contents * @see
https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute *)
Attribute { name = "inputmode"; jsxName = "inputMode"; reasonJsxName = "inputMode"; type_ = String (* 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' *) };
(* Specify that a standard HTML element should behave like a defined custom
built-in element * @see
https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is *)
Attribute { name = "is"; jsxName = "is"; reasonJsxName = "is"; type_ = String };
]
let elementAttributes =
[
(* Attribute { name = "radioGroup"; jsxName = "radioGroup"; reasonJsxName = "radioGroup"; type_ = String }; Does it exist? *)
(* WAI-ARIA *)
Attribute { name = "role"; jsxName = "role"; reasonJsxName = "role"; type_ = ariaRole };
(* RDFa Attributes *)
Attribute { name = "about"; jsxName = "about"; reasonJsxName = "about"; type_ = String };
(* Attribute { name = "dataType"; jsxName = "dataType"; reasonJsxName = "dataType"; type_ = String }; *)
Attribute { name = "inlist"; jsxName = "inlist"; reasonJsxName = "inlist"; type_ = String (* any *) };
Attribute { name = "prefix"; jsxName = "prefix"; reasonJsxName = "prefix"; type_ = String };
Attribute { name = "property"; jsxName = "property"; reasonJsxName = "property"; type_ = String };
Attribute { name = "resource"; jsxName = "resource"; reasonJsxName = "resource"; type_ = String };
Attribute { name = "typeof"; jsxName = "typeof"; reasonJsxName = "typeof"; type_ = String };
Attribute { name = "vocab"; jsxName = "vocab"; reasonJsxName = "vocab"; type_ = String };
(* Non-standard Attributes *)
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#autocorrect *)
Attribute { name = "autocorrect"; jsxName = "autoCorrect"; reasonJsxName = "autoCorrect"; type_ = String };
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#attr-autosave *)
Attribute { name = "autosave"; jsxName = "autoSave"; reasonJsxName = "autoSave"; type_ = String };
Attribute { name = "color"; jsxName = "color"; reasonJsxName = "color"; type_ = String };
Attribute { name = "results"; jsxName = "results"; reasonJsxName = "results"; type_ = Int };
Attribute { name = "security"; jsxName = "security"; reasonJsxName = "security"; type_ = String };
]
let anchorHTMLAttributes =
[
Attribute { name = "download"; jsxName = "download"; reasonJsxName = "download"; type_ = String (* any *) };
Attribute { name = "href"; jsxName = "href"; reasonJsxName = "href"; type_ = String };
Attribute { name = "hrefLang"; jsxName = "hrefLang"; reasonJsxName = "hrefLang"; type_ = String };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "ping"; jsxName = "ping"; reasonJsxName = "ping"; type_ = String };
Attribute { name = "rel"; jsxName = "rel"; reasonJsxName = "rel"; type_ = String };
Attribute { name = "target"; jsxName = "target"; reasonJsxName = "target"; type_ = attributeAnchorTarget };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
Attribute { name = "referrerpolicy"; jsxName = "referrerPolicy"; reasonJsxName = "referrerPolicy"; type_ = attributeReferrerPolicy };
]
let areaHTMLAttributes =
[
Attribute { name = "alt"; jsxName = "alt"; reasonJsxName = "alt"; type_ = String };
Attribute { name = "coords"; jsxName = "coords"; reasonJsxName = "coords"; type_ = String };
Attribute { name = "download"; jsxName = "download"; reasonJsxName = "download"; type_ = String (* any *) };
Attribute { name = "href"; jsxName = "href"; reasonJsxName = "href"; type_ = String };
Attribute { name = "hreflang"; jsxName = "hrefLang"; reasonJsxName = "hrefLang"; type_ = String };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "referrerpolicy"; jsxName = "referrerPolicy"; reasonJsxName = "referrerPolicy"; type_ = attributeReferrerPolicy };
Attribute { name = "rel"; jsxName = "rel"; reasonJsxName = "rel"; type_ = String };
Attribute { name = "shape"; jsxName = "shape"; reasonJsxName = "shape"; type_ = String };
Attribute { name = "target"; jsxName = "target"; reasonJsxName = "target"; type_ = String };
]
let baseHTMLAttributes =
[
Attribute { name = "href"; jsxName = "href"; reasonJsxName = "href"; type_ = String };
Attribute { name = "target"; jsxName = "target"; reasonJsxName = "target"; type_ = String };
]
let blockquoteHTMLAttributes =
[
Attribute { name = "cite"; jsxName = "cite"; reasonJsxName = "cite"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button *)
let buttonHTMLAttributes =
[
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "autocomplete"; jsxName = "autoComplete"; reasonJsxName = "autoComplete"; type_ = String };
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "formaction"; jsxName = "formAction"; reasonJsxName = "formAction"; type_ = Action };
Attribute { name = "formenctype"; jsxName = "formEncType"; reasonJsxName = "formEncType"; type_ = String };
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method *)
Attribute { name = "formmethod"; jsxName = "formMethod"; reasonJsxName = "formMethod"; type_ = String };
Attribute { name = "formnovalidate"; jsxName = "formNoValidate"; reasonJsxName = "formNoValidate"; type_ = Bool };
Attribute { name = "formtarget"; jsxName = "formTarget"; reasonJsxName = "formTarget"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "popovertarget"; jsxName = "popoverTarget"; reasonJsxName = "popoverTarget"; type_ = String };
Attribute { name = "popovertargetaction"; jsxName = "popoverTargetAction"; reasonJsxName = "popoverTargetAction"; type_ = String };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String (* 'submit' | 'reset' | 'button' *) };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
]
let canvasHTMLAttributes =
[
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) }
]
let colHTMLAttributes =
[
Attribute { name = "span"; jsxName = "span"; reasonJsxName = "span"; type_ = Int (* number *) };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) }
]
let colgroupHTMLAttributes =
[
Attribute { name = "span"; jsxName = "span"; reasonJsxName = "span"; type_ = Int (* number *) }
]
let dataHTMLAttributes =
[
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) }
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details *)
let detailsHTMLAttributes =
[
Attribute { name = "open"; jsxName = "open"; reasonJsxName = "open_"; type_ = Bool }; Event { jsxName = "onToggle"; type_ = Media }
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del *)
let delHTMLAttributes =
[
Attribute { name = "cite"; type_ = String; jsxName = "cite"; reasonJsxName = "cite" };
Attribute { name = "datetime"; type_ = String; jsxName = "dateTime"; reasonJsxName = "dateTime" };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog *)
let dialogHTMLAttributes =
[
Attribute { name = "open"; jsxName = "open"; reasonJsxName = "open_"; type_ = Bool }
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed *)
let embedHTMLAttributes =
[
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *); };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String; };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String; };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String; (* number | *) };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset *)
let fieldsetHTMLAttributes =
[
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form *)
let formHTMLAttributes =
[
Attribute { name = "accept"; jsxName = "accept"; reasonJsxName = "accept"; type_ = String };
Attribute { name = "accept-charset"; jsxName = "acceptCharset"; reasonJsxName = "acceptCharset"; type_ = String };
Attribute { name = "autocapitalize"; jsxName = "autoCapitalize"; reasonJsxName = "autoCapitalize"; type_ = String };
Attribute { name = "autocomplete"; jsxName = "autoComplete"; reasonJsxName = "autoComplete"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "rel"; jsxName = "rel"; reasonJsxName = "rel"; type_ = String };
Attribute { name = "enctype"; jsxName = "encType"; reasonJsxName = "encType"; type_ = String };
Attribute { name = "action"; jsxName = "action"; reasonJsxName = "action"; type_ = Action };
Attribute { name = "method"; jsxName = "method"; reasonJsxName = "method_"; type_ = String };
Attribute { name = "novalidate"; jsxName = "noValidate"; reasonJsxName = "noValidate"; type_ = Bool };
Attribute { name = "target"; jsxName = "target"; reasonJsxName = "target"; type_ = String };
]
let htmlHTMLAttributes =
[
Attribute { name = "manifest"; jsxName = "manifest"; reasonJsxName = "manifest"; type_ = String };
]
let iframeHTMLAttributes =
[
Attribute { name = "allow"; jsxName = "allow"; reasonJsxName = "allow"; type_ = String };
Attribute { name = "allowfullscreen"; jsxName = "allowFullScreen"; reasonJsxName = "allowFullScreen"; type_ = Bool };
Attribute { name = "allowtransparency"; jsxName = "allowTransparency"; reasonJsxName = "allowTransparency"; type_ = Bool };
Attribute { name = "csp"; jsxName = "csp"; reasonJsxName = "csp"; type_ = String };
Attribute { name = "credentialless"; jsxName = "credentialLess"; reasonJsxName = "credentialLess"; type_ = String };
Attribute { name = "loading"; jsxName = "loading"; reasonJsxName = "loading"; type_ = String };
Attribute { name = "sandbox"; jsxName = "sandbox"; reasonJsxName = "sandbox"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "sandbox"; jsxName = "sandbox"; reasonJsxName = "sandbox"; type_ = String };
Attribute { name = "seamless"; jsxName = "seamless"; reasonJsxName = "seamless"; type_ = Bool };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "srcdoc"; jsxName = "srcDoc"; reasonJsxName = "srcDoc"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
(* Deprecated attributes *)
Attribute { name = "align"; jsxName = "align"; reasonJsxName = "align"; type_ = String };
Attribute { name = "longdesc"; jsxName = "longDesc"; reasonJsxName = "longDesc"; type_ = String };
Attribute { name = "frameborder"; jsxName = "frameBorder"; reasonJsxName = "frameBorder"; type_ = String (* number | *) };
Attribute { name = "marginheight"; jsxName = "marginHeight"; reasonJsxName = "marginHeight"; type_ = Int (* number *) };
Attribute { name = "marginwidth"; jsxName = "marginWidth"; reasonJsxName = "marginWidth"; type_ = Int (* number *) };
Attribute { name = "scrolling"; jsxName = "scrolling"; reasonJsxName = "scrolling"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img *)
let imgHTMLAttributes =
[
Attribute { name = "alt"; jsxName = "alt"; reasonJsxName = "alt"; type_ = String };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String (* "anonymous" | "use-credentials" | "" *) };
Attribute { name = "elementtiming"; jsxName = "elementTiming"; reasonJsxName = "elementTiming"; type_ = String };
Attribute { name = "fetchpriority"; jsxName = "fetchPriority"; reasonJsxName = "fetchPriority"; type_ = String };
Attribute { name = "loading"; jsxName = "loading"; reasonJsxName = "loading"; type_ = String };
Attribute { name = "ismap"; jsxName = "isMap"; reasonJsxName = "isMap"; type_ = Bool };
Attribute { name = "decoding"; jsxName = "decoding"; reasonJsxName = "decoding"; type_ = String (* "async" | "auto" | "sync" *) };
Attribute { name = "sizes"; jsxName = "sizes"; reasonJsxName = "sizes"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "srcset"; jsxName = "srcset"; reasonJsxName = "srcset"; type_ = String };
Attribute { name = "usemap"; jsxName = "usemap"; reasonJsxName = "usemap"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
(* Deprecated *)
(* align, border, hspace, longdesc, name, vspace *)
]
let insHTMLAttributes =
[
Attribute { name = "cite"; jsxName = "cite"; reasonJsxName = "cite"; type_ = String };
Attribute { name = "datetime"; jsxName = "datetime"; reasonJsxName = "datetime"; type_ = String };
]
let inputTypeAttribute = String
(* | 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' |
'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' |
'range' | 'reset' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' |
'week' | (String @ {}); *)
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input *)
let inputHTMLAttributes =
[
Attribute { name = "accept"; jsxName = "accept"; reasonJsxName = "accept"; type_ = String };
Attribute { name = "alt"; jsxName = "alt"; reasonJsxName = "alt"; type_ = String };
Attribute { name = "autocomplete"; jsxName = "autoComplete"; reasonJsxName = "autoComplete"; type_ = String };
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "capture"; jsxName = "capture"; reasonJsxName = "capture"; type_ = String (* Bool | *) (* https://www.w3.org/TR/html-media-capture/ *) };
Attribute { name = "checked"; jsxName = "checked"; reasonJsxName = "checked"; type_ = Bool };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String };
Attribute { name = "dirname"; jsxName = "dirname"; reasonJsxName = "dirname"; type_ = String };
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "formaction"; jsxName = "formAction"; reasonJsxName = "formAction"; type_ = String };
Attribute { name = "formenctype"; jsxName = "formEncType"; reasonJsxName = "formEncType"; type_ = String };
Attribute { name = "method"; jsxName = "formMethod"; reasonJsxName = "formMethod"; type_ = String };
Attribute { name = "novalidate"; jsxName = "formNoValidate"; reasonJsxName = "formNoValidate"; type_ = Bool };
Attribute { name = "target"; jsxName = "formTarget"; reasonJsxName = "formTarget"; type_ = String };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "list"; jsxName = "list"; reasonJsxName = "list"; type_ = String };
Attribute { name = "max"; jsxName = "max"; reasonJsxName = "max"; type_ = String (* number | *) };
Attribute { name = "maxlength"; jsxName = "maxLength"; reasonJsxName = "maxLength"; type_ = Int (* number *) };
Attribute { name = "min"; jsxName = "min"; reasonJsxName = "min"; type_ = String (* number | *) };
Attribute { name = "minlength"; jsxName = "minLength"; reasonJsxName = "minLength"; type_ = Int (* number *) };
Attribute { name = "multiple"; jsxName = "multiple"; reasonJsxName = "multiple"; type_ = Bool };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "pattern"; jsxName = "pattern"; reasonJsxName = "pattern"; type_ = String };
Attribute { name = "placeholder"; jsxName = "placeholder"; reasonJsxName = "placeholder"; type_ = String };
Attribute { name = "readonly"; jsxName = "readOnly"; reasonJsxName = "readOnly"; type_ = Bool };
Attribute { name = "required"; jsxName = "required"; reasonJsxName = "required"; type_ = Bool };
Attribute { name = "size"; jsxName = "size"; reasonJsxName = "size"; type_ = Int (* number *) };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "step"; jsxName = "step"; reasonJsxName = "step"; type_ = String (* number | *) };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = inputTypeAttribute };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
(* Added by React, oninput is the HTML *)
Event { jsxName = "onChange"; type_ = Form };
]
let keygenHTMLAttributes =
[
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "challenge"; jsxName = "challenge"; reasonJsxName = "challenge"; type_ = String };
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "keytype"; jsxName = "keyType"; reasonJsxName = "keyType"; type_ = String };
Attribute { name = "keyparams"; jsxName = "keyParams"; reasonJsxName = "keyParams"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
]
let labelHTMLAttributes =
[
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "for"; jsxName = "htmlFor"; reasonJsxName = "htmlFor"; type_ = String };
]
let liHTMLAttributes =
[
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) }
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link *)
let linkHTMLAttributes =
[
Attribute { name = "as"; jsxName = "as"; reasonJsxName = "as_"; type_ = String };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String };
Attribute { name = "blocking"; jsxName = "blocking"; reasonJsxName = "blocking"; type_ = Bool }; (* Experimental *)
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool }; (* Deprecated *)
Attribute { name = "fetchpriority"; jsxName = "fetchPriority"; reasonJsxName = "fetchPriority"; type_ = String }; (* Experimental *)
Attribute { name = "referrerpolicy"; jsxName = "referrerPolicy"; reasonJsxName = "referrerPolicy"; type_ = attributeReferrerPolicy };
Attribute { name = "href"; jsxName = "href"; reasonJsxName = "href"; type_ = String };
Attribute { name = "hreflang"; jsxName = "hrefLang"; reasonJsxName = "hrefLang"; type_ = String };
Attribute { name = "integrity"; jsxName = "integrity"; reasonJsxName = "integrity"; type_ = String };
Attribute { name = "imagesizes"; jsxName = "imageSizes"; reasonJsxName = "imageSizes"; type_ = String };
Attribute { name = "imagesrcset"; jsxName = "imageSrcSet"; reasonJsxName = "imageSrcSet"; type_ = String };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "rel"; jsxName = "rel"; reasonJsxName = "rel"; type_ = String };
Attribute { name = "title"; jsxName = "title"; reasonJsxName = "title"; type_ = String };
Attribute { name = "sizes"; jsxName = "sizes"; reasonJsxName = "sizes"; type_ = String };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
Attribute { name = "charset"; jsxName = "charSet"; reasonJsxName = "charSet"; type_ = String }; (* non standard *)
]
let mapHTMLAttributes =
[
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
]
let menuHTMLAttributes =
[
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
]
(* isn't validated with mdn *)
let mediaHTMLAttributes =
[
Attribute { name = "autoplay"; jsxName = "autoPlay"; reasonJsxName = "autoPlay"; type_ = Bool };
Attribute { name = "controls"; jsxName = "controls"; reasonJsxName = "controls"; type_ = Bool };
Attribute { name = "controlslist"; jsxName = "controlsList"; reasonJsxName = "controlsList"; type_ = String };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String };
Attribute { name = "loop"; jsxName = "loop"; reasonJsxName = "loop"; type_ = Bool };
(* deprecated *)
Attribute { name = "mediagroup"; jsxName = "mediaGroup"; reasonJsxName = "mediaGroup"; type_ = String };
Attribute { name = "muted"; jsxName = "muted"; reasonJsxName = "muted"; type_ = Bool };
Attribute { name = "playsinline"; jsxName = "playsInline"; reasonJsxName = "playsInline"; type_ = Bool };
Attribute { name = "preload"; jsxName = "preload"; reasonJsxName = "preload"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta *)
let metaHTMLAttributes =
[
Attribute { name = "charset"; jsxName = "charSet"; reasonJsxName = "charSet"; type_ = String };
Attribute { name = "content"; jsxName = "content"; reasonJsxName = "content"; type_ = String };
Attribute { name = "http-equiv"; jsxName = "httpEquiv"; reasonJsxName = "httpEquiv"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
]
let meterHTMLAttributes =
[
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "high"; jsxName = "high"; reasonJsxName = "high"; type_ = Int (* number *) };
Attribute { name = "low"; jsxName = "low"; reasonJsxName = "low"; type_ = Int (* number *) };
Attribute { name = "max"; jsxName = "max"; reasonJsxName = "max"; type_ = String (* number | *) };
Attribute { name = "min"; jsxName = "min"; reasonJsxName = "min"; type_ = String (* number | *) };
Attribute { name = "optimum"; jsxName = "optimum"; reasonJsxName = "optimum"; type_ = Int (* number *) };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
]
let quoteHTMLAttributes =
[
Attribute { name = "cite"; jsxName = "cite"; reasonJsxName = "cite"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object *)
(* TODO: lacks a few *)
let objectHTMLAttributes =
[
Attribute { name = "classid"; jsxName = "classID"; reasonJsxName = "classID"; type_ = String };
Attribute { name = "data"; jsxName = "data"; reasonJsxName = "data"; type_ = String };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
Attribute { name = "usemap"; jsxName = "useMap"; reasonJsxName = "useMap"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
Attribute { name = "wmode"; jsxName = "wmode"; reasonJsxName = "wmode"; type_ = String };
]
let olHTMLAttributes =
[
Attribute { name = "reversed"; jsxName = "reversed"; reasonJsxName = "reversed"; type_ = Bool };
Attribute { name = "start"; jsxName = "start"; reasonJsxName = "start"; type_ = Int (* number *) };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String (* '1' | 'a' | 'A' | 'i' | 'I' *) };
]
let optgroupHTMLAttributes =
[
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "label"; jsxName = "label"; reasonJsxName = "label"; type_ = String };
]
let optionHTMLAttributes =
[
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "label"; jsxName = "label"; reasonJsxName = "label"; type_ = String };
Attribute { name = "selected"; jsxName = "selected"; reasonJsxName = "selected"; type_ = Bool };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
]
let outputHTMLAttributes =
[
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "for"; jsxName = "htmlFor"; reasonJsxName = "htmlFor"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
]
let paramHTMLAttributes =
[
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
]
let progressHTMLAttributes =
[
Attribute { name = "max"; jsxName = "max"; reasonJsxName = "max"; type_ = String (* number | *) };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
]
let slotHTMLAttributes =
[
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script *)
let scriptHTMLAttributes =
[
Attribute { name = "async"; jsxName = "async"; reasonJsxName = "async"; type_ = Bool };
Attribute { name = "charset"; jsxName = "charSet"; reasonJsxName = "charSet"; type_ = String };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String };
Attribute { name = "defer"; jsxName = "defer"; reasonJsxName = "defer"; type_ = Bool };
Attribute { name = "integrity"; jsxName = "integrity"; reasonJsxName = "integrity"; type_ = String };
Attribute { name = "nomodule"; jsxName = "noModule"; reasonJsxName = "noModule"; type_ = Bool };
Attribute { name = "nonce"; jsxName = "nonce"; reasonJsxName = "nonce"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
]
let selectHTMLAttributes =
[
Attribute { name = "autocomplete"; jsxName = "autoComplete"; reasonJsxName = "autoComplete"; type_ = String };
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "multiple"; jsxName = "multiple"; reasonJsxName = "multiple"; type_ = Bool };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "required"; jsxName = "required"; reasonJsxName = "required"; type_ = Bool };
Attribute { name = "size"; jsxName = "size"; reasonJsxName = "size"; type_ = Int (* number *) };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
Event { jsxName = "onChange"; type_ = Form };
]
let sourceHTMLAttributes =
[
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "sizes"; jsxName = "sizes"; reasonJsxName = "sizes"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "srcset"; jsxName = "srcSet"; reasonJsxName = "srcSet"; type_ = String };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
]
let styleHTMLAttributes =
[
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "nonce"; jsxName = "nonce"; reasonJsxName = "nonce"; type_ = String };
Attribute { name = "scoped"; jsxName = "scoped"; reasonJsxName = "scoped"; type_ = Bool };
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String }
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table *)
(* All attributes are deprecated *)
let tableHTMLAttributes =
[
Attribute { name = "cellpadding"; jsxName = "cellPadding"; reasonJsxName = "cellPadding"; type_ = String (* number | *) };
Attribute { name = "cellspacing"; jsxName = "cellSpacing"; reasonJsxName = "cellSpacing"; type_ = String (* number | *) };
Attribute { name = "summary"; jsxName = "summary"; reasonJsxName = "summary"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea *)
let textareaHTMLAttributes =
[
Attribute { name = "autocomplete"; jsxName = "autoComplete"; reasonJsxName = "autoComplete"; type_ = String };
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "cols"; jsxName = "cols"; reasonJsxName = "cols"; type_ = Int (* number *) };
Attribute { name = "dirName"; jsxName = "dirName"; reasonJsxName = "dirName"; type_ = String };
Attribute { name = "disabled"; jsxName = "disabled"; reasonJsxName = "disabled"; type_ = Bool };
Attribute { name = "form"; jsxName = "form"; reasonJsxName = "form"; type_ = String };
Attribute { name = "maxlength"; jsxName = "maxLength"; reasonJsxName = "maxLength"; type_ = Int (* number *) };
Attribute { name = "minlength"; jsxName = "minLength"; reasonJsxName = "minLength"; type_ = Int (* number *) };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "placeholder"; jsxName = "placeholder"; reasonJsxName = "placeholder"; type_ = String };
Attribute { name = "readonly"; jsxName = "readOnly"; reasonJsxName = "readOnly"; type_ = Bool };
Attribute { name = "required"; jsxName = "required"; reasonJsxName = "required"; type_ = Bool };
Attribute { name = "rows"; jsxName = "rows"; reasonJsxName = "rows"; type_ = Int (* number *) };
Attribute { name = "value"; jsxName = "value"; reasonJsxName = "value"; type_ = String (* | ReadonlyArray | number *) };
Attribute { name = "wrap"; jsxName = "wrap"; reasonJsxName = "wrap"; type_ = String };
Event { jsxName = "onChange"; type_ = Form };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td *)
(* TODO: Add a few deprecated attrs *)
let tdHTMLAttributes =
[
Attribute { name = "align"; jsxName = "align"; reasonJsxName = "align"; type_ = String (* type_= "left" | "center" | "right" | "justify" | "char" *) };
Attribute { name = "colspan"; jsxName = "colSpan"; reasonJsxName = "colSpan"; type_ = Int (* number *) };
Attribute { name = "headers"; jsxName = "headers"; reasonJsxName = "headers"; type_ = String };
Attribute { name = "rowspan"; jsxName = "rowspan"; reasonJsxName = "rowspan"; type_ = Int (* number *) };
Attribute { name = "scope"; jsxName = "scope"; reasonJsxName = "scope"; type_ = String };
Attribute { name = "abbr"; jsxName = "abbr"; reasonJsxName = "abbr"; type_ = String };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
Attribute { name = "valign"; jsxName = "valign"; reasonJsxName = "valign"; type_ = String (* "top" | "middle" | "bottom" | "baseline" *) };
]
let thHTMLAttributes =
[
Attribute { name = "align"; jsxName = "align"; reasonJsxName = "align"; type_ = String (* "left" | "center" | "right" | "justify" | "char" *) };
Attribute { name = "colspan"; jsxName = "colSpan"; reasonJsxName = "colSpan"; type_ = Int (* number *) };
Attribute { name = "headers"; jsxName = "headers"; reasonJsxName = "headers"; type_ = String };
Attribute { name = "rowspan"; jsxName = "rowSpan"; reasonJsxName = "rowSpan"; type_ = Int (* number *) };
Attribute { name = "scope"; jsxName = "scope"; reasonJsxName = "scope"; type_ = String };
Attribute { name = "abbr"; jsxName = "abbr"; reasonJsxName = "abbr"; type_ = String };
]
let timeHTMLAttributes =
[
Attribute { name = "datetime"; jsxName = "datetime"; reasonJsxName = "datetime"; type_ = String };
]
let trackHTMLAttributes =
[
Attribute { name = "default"; jsxName = "default"; reasonJsxName = "default"; type_ = Bool };
Attribute { name = "kind"; jsxName = "kind"; reasonJsxName = "kind"; type_ = String };
Attribute { name = "label"; jsxName = "label"; reasonJsxName = "label"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "srclang"; jsxName = "srclang"; reasonJsxName = "srclang"; type_ = String };
]
(* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video *)
let videoHTMLAttributes =
[
Attribute { name = "autoplay"; jsxName = "autoPlay"; reasonJsxName = "autoPlay"; type_ = Bool };
Attribute { name = "controls"; jsxName = "controls"; reasonJsxName = "controls"; type_ = Bool };
Attribute { name = "controlslist"; jsxName = "controlsList"; reasonJsxName = "controlsList"; type_ = String };
Attribute { name = "crossorigin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String };
Attribute { name = "disablepictureinpicture"; jsxName = "disablePictureInPicture"; reasonJsxName = "disablePictureInPicture"; type_ = Bool };
Attribute { name = "disableremoteplayback"; jsxName = "disableRemotePlayback"; reasonJsxName = "disableRemotePlayback"; type_ = Bool };
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "loop"; jsxName = "loop"; reasonJsxName = "loop"; type_ = Bool };
Attribute { name = "muted"; jsxName = "muted"; reasonJsxName = "muted"; type_ = Bool };
Attribute { name = "playsinline"; jsxName = "playsInline"; reasonJsxName = "playsInline"; type_ = Bool };
Attribute { name = "poster"; jsxName = "poster"; reasonJsxName = "poster"; type_ = String };
Attribute { name = "preload"; jsxName = "preload"; reasonJsxName = "preload"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
]
module SVG = struct
(* "https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/" *)
let coreAttributes =
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Core *)
[
Attribute { name = "id"; jsxName = "id"; reasonJsxName = "id"; type_ = String };
Attribute { name = "lang"; jsxName = "lang"; reasonJsxName = "lang"; type_ = String };
Attribute { name = "tabindex"; jsxName = "tabIndex"; reasonJsxName = "tabIndex"; type_ = Int };
Attribute { name = "xml:base"; jsxName = "xmlBase"; reasonJsxName = "xmlBase"; type_ = String };
Attribute { name = "xml:lang"; jsxName = "xmlLang"; reasonJsxName = "xmlLang"; type_ = String };
Attribute { name = "xml:space"; jsxName = "xmlSpace"; reasonJsxName = "xmlSpace"; type_ = String };
]
let stylingAttributes =
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Styling *)
[
Attribute { name = "class"; jsxName = "className"; reasonJsxName = "className"; type_ = String };
Attribute { name = "style"; jsxName = "style"; reasonJsxName = "style"; type_ = Style }
]
let presentationAttributes =
(* Presentation attributes *)
[
Attribute { name = "alignment-baseline"; jsxName = "alignmentBaseline"; reasonJsxName = "alignmentBaseline"; type_ = String (* "auto" | "baseline" | "before-edge" | "text-before-edge" | "middle" | "central" | "after-edge" "text-after-edge" | "ideographic" | "alphabetic" | "hanging" | "mathematical" | "inherit" *) };
Attribute { name = "baseline-shift"; jsxName = "baselineShift"; reasonJsxName = "baselineShift"; type_ = String (* "auto" | "baseline" | "before-edge" | "text-before-edge" | "middle" | "central" | "after-edge" "text-after-edge" | "ideographic" | "alphabetic" | "hanging" | "mathematical" | "inherit" *) };
Attribute { name = "clip"; jsxName = "clip"; reasonJsxName = "clip"; type_ = String (* number | *) };
Attribute { name = "clip-path"; jsxName = "clipPath"; reasonJsxName = "clipPath"; type_ = (* none||inherit *) String };
Attribute { name = "clip-rule"; jsxName = "clipRule"; reasonJsxName = "clipRule"; type_ = (* number | "linearRGB" | "inherit" *) String };
Attribute { name = "color"; jsxName = "color"; reasonJsxName = "color"; type_ = String (* number | *) };
Attribute { name = "color-interpolation"; jsxName = "colorInterpolation"; reasonJsxName = "colorInterpolation"; type_ = String };
Attribute { name = "color-interpolation-filters"; jsxName = "colorInterpolationFilters"; reasonJsxName = "colorInterpolationFilters"; type_ = String };
Attribute { name = "color-profile"; jsxName = "colorProfile"; reasonJsxName = "colorProfile"; type_ = String (* number | *) };
Attribute { name = "color-rendering"; jsxName = "colorRendering"; reasonJsxName = "colorRendering"; type_ = String (* number | *) };
Attribute { name = "cursor"; jsxName = "cursor"; reasonJsxName = "cursor"; type_ = String (* number | *) };
Attribute { name = "direction"; jsxName = "direction"; reasonJsxName = "direction"; type_ = String (* number | *) };
Attribute { name = "display"; jsxName = "display"; reasonJsxName = "display"; type_ = String (* number | *) };
Attribute { name = "divisor"; jsxName = "divisor"; reasonJsxName = "divisor"; type_ = String (* number | *) };
Attribute { name = "dominant-baseline"; jsxName = "dominantBaseline"; reasonJsxName = "dominantBaseline"; type_ = String };
Attribute { name = "enable-background"; jsxName = "enableBackground"; reasonJsxName = "enableBackground"; type_ = String };
Attribute { name = "fill"; jsxName = "fill"; reasonJsxName = "fill"; type_ = String };
Attribute { name = "fill-opacity"; jsxName = "fillOpacity"; reasonJsxName = "fillOpacity"; type_ = String (* number | *) };
Attribute { name = "fill-rule"; jsxName = "fillRule"; reasonJsxName = "fillRule"; type_ = String (* type_= "nonzero" | "evenodd" | "inherit" *) };
Attribute { name = "filter"; jsxName = "filter"; reasonJsxName = "filter"; type_ = String };
Attribute { name = "flood-color"; jsxName = "floodColor"; reasonJsxName = "floodColor"; type_ = String (* number | *) };
Attribute { name = "flood-opacity"; jsxName = "floodOpacity"; reasonJsxName = "floodOpacity"; type_ = String (* number | *) };
Attribute { name = "font-family"; jsxName = "fontFamily"; reasonJsxName = "fontFamily"; type_ = String };
Attribute { name = "font-size"; jsxName = "fontSize"; reasonJsxName = "fontSize"; type_ = String (* number | *) };
Attribute { name = "font-size-adjust"; jsxName = "fontSizeAdjust"; reasonJsxName = "fontSizeAdjust"; type_ = String };
Attribute { name = "font-stretch"; jsxName = "fontStretch"; reasonJsxName = "fontStretch"; type_ = String (* number | *) };
Attribute { name = "font-style"; jsxName = "fontStyle"; reasonJsxName = "fontStyle"; type_ = String (* number | *) };
Attribute { name = "font-variant"; jsxName = "fontVariant"; reasonJsxName = "fontVariant"; type_ = String (* number | *) };
Attribute { name = "font-weight"; jsxName = "fontWeight"; reasonJsxName = "fontWeight"; type_ = String (* number | *) };
Attribute { name = "glyph-orientation-horizontal"; jsxName = "glyphOrientationHorizontal"; reasonJsxName = "glyphOrientationHorizontal"; type_ = String (* number | *) };
Attribute { name = "glyph-orientation-vertical"; jsxName = "glyphOrientationVertical"; reasonJsxName = "glyphOrientationVertical"; type_ = String (* number | *) };
Attribute { name = "image-rendering"; jsxName = "imageRendering"; reasonJsxName = "imageRendering"; type_ = String };
Attribute { name = "kerning"; jsxName = "kerning"; reasonJsxName = "kerning"; type_ = String (* number | *) };
Attribute { name = "letter-spacing"; jsxName = "letterSpacing"; reasonJsxName = "letterSpacing"; type_ = String };
Attribute { name = "lighting-color"; jsxName = "lightingColor"; reasonJsxName = "lightingColor"; type_ = String };
Attribute { name = "marker-end"; jsxName = "markerEnd"; reasonJsxName = "markerEnd"; type_ = String };
Attribute { name = "marker-mid"; jsxName = "markerMid"; reasonJsxName = "markerMid"; type_ = String };
Attribute { name = "marker-start"; jsxName = "markerStart"; reasonJsxName = "markerStart"; type_ = String };
Attribute { name = "mask"; jsxName = "mask"; reasonJsxName = "mask"; type_ = String };
Attribute { name = "opacity"; jsxName = "opacity"; reasonJsxName = "opacity"; type_ = String (* number | *) };
Attribute { name = "operator"; jsxName = "operator"; reasonJsxName = "operator"; type_ = String (* number | *) };
Attribute { name = "overflow"; jsxName = "overflow"; reasonJsxName = "overflow"; type_ = String (* number | *) };
Attribute { name = "pointer-events"; jsxName = "pointerEvents"; reasonJsxName = "pointerEvents"; type_ = String };
Attribute { name = "shape-rendering"; jsxName = "shapeRendering"; reasonJsxName = "shapeRendering"; type_ = String };
Attribute { name = "specularConstant"; jsxName = "specularConstant"; reasonJsxName = "specularConstant"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularConstant *)
Attribute { name = "specularExponent"; jsxName = "specularExponent"; reasonJsxName = "specularExponent"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularExponent *)
Attribute { name = "solid-color"; jsxName = "solidColor"; reasonJsxName = "solidColor"; type_ = String };
Attribute { name = "solid-opacity"; jsxName = "solidOpacity"; reasonJsxName = "solidOpacity"; type_ = String };
Attribute { name = "stop-color"; jsxName = "stopColor"; reasonJsxName = "stopColor"; type_ = String };
Attribute { name = "stop-opacity"; jsxName = "stopOpacity"; reasonJsxName = "stopOpacity"; type_ = String (* number | *) };
Attribute { name = "stroke"; jsxName = "stroke"; reasonJsxName = "stroke"; type_ = String };
Attribute { name = "stroke-dasharray"; jsxName = "strokeDasharray"; reasonJsxName = "strokeDasharray"; type_ = String };
Attribute { name = "stroke-opacity"; jsxName = "strokeOpacity"; reasonJsxName = "strokeOpacity"; type_ = String };
Attribute { name = "stroke-miterlimit"; jsxName = "strokeMiterlimit"; reasonJsxName = "strokeMiterlimit"; type_ = String };
Attribute { name = "stroke-dashoffset"; jsxName = "strokeDashoffset"; reasonJsxName = "strokeDashoffset"; type_ = String };
Attribute { name = "stroke-linecap"; jsxName = "strokeLinecap"; reasonJsxName = "strokeLinecap"; type_ = String (* type_= "butt" | "round" | "square" | "inherit" *) };
Attribute { name = "stroke-linejoin"; jsxName = "strokeLinejoin"; reasonJsxName = "strokeLinejoin"; type_ = String (* type_= "arcs" | "bevel" | "miter" | "miter-clip" | "round" *) };
Attribute { name = "text-anchor"; jsxName = "textAnchor"; reasonJsxName = "textAnchor"; type_ = String };
Attribute { name = "text-decoration"; jsxName = "textDecoration"; reasonJsxName = "textDecoration"; type_ = String };
Attribute { name = "text-rendering"; jsxName = "textRendering"; reasonJsxName = "textRendering"; type_ = String };
Attribute { name = "transform"; jsxName = "transform"; reasonJsxName = "transform"; type_ = String };
Attribute { name = "transform-origin"; jsxName = "transformOrigin"; reasonJsxName = "transformOrigin"; type_ = String }; (* Does it exist? *)
Attribute { name = "unicode-bidi"; jsxName = "unicodeBidi"; reasonJsxName = "unicodeBidi"; type_ = String (* number | *) };
Attribute { name = "vector-effect"; jsxName = "vectorEffect"; reasonJsxName = "vectorEffect"; type_ = String (* number | *) };
Attribute { name = "word-spacing"; jsxName = "wordSpacing"; reasonJsxName = "wordSpacing"; type_ = String (* number | *) };
Attribute { name = "writing-mode"; jsxName = "writingMode"; reasonJsxName = "writingMode"; type_ = String (* number | *) };
]
let filtersAttributes =
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute#filters_attributes *)
[
(* Filter primitive attributes *)
Attribute { name = "height"; jsxName = "height"; reasonJsxName = "height"; type_ = String (* number | *) };
Attribute { name = "width"; jsxName = "width"; reasonJsxName = "width"; type_ = String (* number | *) };
Attribute { name = "result"; jsxName = "result"; reasonJsxName = "result"; type_ = String };
Attribute { name = "x"; jsxName = "x"; reasonJsxName = "x"; type_ = String (* number | *) };
Attribute { name = "y"; jsxName = "y"; reasonJsxName = "y"; type_ = String (* number | *) };
(* Transfer function attributes type, tableValues, slope, intercept,
amplitude, exponent, offset *)
Attribute { name = "type"; jsxName = "type"; reasonJsxName = "type_"; type_ = String };
Attribute { name = "exponent"; jsxName = "exponent"; reasonJsxName = "exponent"; type_ = String (* number | *) };
Attribute { name = "slope"; jsxName = "slope"; reasonJsxName = "slope"; type_ = String (* number | *) };
Attribute { name = "amplitude"; jsxName = "amplitude"; reasonJsxName = "amplitude"; type_ = String (* number | *) };
Attribute { name = "intercept"; jsxName = "intercept"; reasonJsxName = "intercept"; type_ = String (* number | *) };
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tableValues *)
Attribute { name = "tableValues"; jsxName = "tableValues"; reasonJsxName = "tableValues"; type_ = String (* number | *) };
(* Animation target element attributes *)
Attribute { name = "href"; jsxName = "href"; reasonJsxName = "href"; type_ = String };
(* Animation attribute target attributes*)
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeName *)
Attribute { name = "attributeName"; jsxName = "attributeName"; reasonJsxName = "attributeName"; type_ = String };
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeType *)
Attribute { name = "attributeType"; jsxName = "attributeType"; reasonJsxName = "attributeType"; type_ = String };
(* Animation timing attributes begin, dur, end, min, max, restart,
repeatCount, repeatDur, fill *)
Attribute { name = "begin"; jsxName = "begin"; reasonJsxName = "begin_"; type_ = String (* number | *) };
Attribute { name = "dur"; jsxName = "dur"; reasonJsxName = "dur"; type_ = String (* number | *) };
Attribute { name = "end"; jsxName = "end"; reasonJsxName = "end_"; type_ = String (* number | *) };
Attribute { name = "max"; jsxName = "max"; reasonJsxName = "max"; type_ = String (* number | *) };
Attribute { name = "min"; jsxName = "min"; reasonJsxName = "min"; type_ = String (* number | *) };
Attribute { name = "repeatCount"; jsxName = "repeatCount"; reasonJsxName = "repeatCount"; type_ = String (* number | *) };
Attribute { name = "restart"; jsxName = "restart"; reasonJsxName = "restart"; type_ = String (* number | *) };
Attribute { name = "repeatDur"; jsxName = "repeatDur"; reasonJsxName = "repeatDur"; type_ = String (* number | *) };
Attribute { name = "fill"; jsxName = "fill"; reasonJsxName = "fill"; type_ = String };
(* Animation value attributes *)
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode *)
Attribute { name = "calcMode"; jsxName = "calcMode"; reasonJsxName = "calcMode"; type_ = String (* number | *) };
Attribute { name = "values"; jsxName = "values"; reasonJsxName = "values"; type_ = String };
Attribute { name = "keySplines"; jsxName = "keySplines"; reasonJsxName = "keySplines"; type_ = String (* number | *) };
Attribute { name = "keyTimes"; jsxName = "keyTimes"; reasonJsxName = "keyTimes"; type_ = String (* number | *) };
Attribute { name = "from"; jsxName = "from"; reasonJsxName = "from"; type_ = String (* number | *) };
Attribute { name = "to"; jsxName = "to"; reasonJsxName = "to_"; type_ = String (* number | *) };
Attribute { name = "by"; jsxName = "by"; reasonJsxName = "by"; type_ = String (* number | *) };
(* Animation addition attributes *)
Attribute { name = "accumulate"; jsxName = "accumulate"; reasonJsxName = "accumulate"; type_ = String (* type_= "none" | "sum" *) };
Attribute { name = "additive"; jsxName = "additive"; reasonJsxName = "additive"; type_ = String (* type_= "replace" | "sum" *) };
]
let htmlAttributes =
(* These are valid SVG attributes which are HTML Attributes as well *)
[
Attribute { name = "color"; jsxName = "color"; reasonJsxName = "color"; type_ = String };
Attribute { name = "id"; jsxName = "id"; reasonJsxName = "id"; type_ = String };
Attribute { name = "lang"; jsxName = "lang"; reasonJsxName = "lang"; type_ = String };
Attribute { name = "media"; jsxName = "media"; reasonJsxName = "media"; type_ = String };
Attribute { name = "method"; jsxName = "method"; reasonJsxName = "method_"; type_ = String };
Attribute { name = "name"; jsxName = "name"; reasonJsxName = "name"; type_ = String };
Attribute { name = "style"; jsxName = "style"; reasonJsxName = "style"; type_ = Style };
Attribute { name = "target"; jsxName = "target"; reasonJsxName = "target"; type_ = String };
(* Other HTML properties supported by SVG elements in browsers *)
Attribute { name = "role"; jsxName = "role"; reasonJsxName = "role"; type_ = ariaRole };
Attribute { name = "tabindex"; jsxName = "tabIndex"; reasonJsxName = "tabIndex"; type_ = Int (* number *) };
Attribute { name = "cross-origin"; jsxName = "crossOrigin"; reasonJsxName = "crossOrigin"; type_ = String (* "anonymous" | "use-credentials" | "" *) };
(* SVG Specific attributes *)
Attribute { name = "accent-height"; jsxName = "accentHeight"; reasonJsxName = "accentHeight"; type_ = String (* number | *) };
(* Attribute { name = "allowReorder"; jsxName = "allowReorder"; reasonJsxName = "allowReorder"; type_ = String (* type_= "no" | "yes" *) }; Does it exist? *)
Attribute { name = "alphabetic"; jsxName = "alphabetic"; reasonJsxName = "alphabetic"; type_ = String (* number | *) };
Attribute { name = "arabic-form"; jsxName = "arabicForm"; reasonJsxName = "arabicForm"; type_ = String (* type_= "initial" | "medial" | "terminal" | "isolated" *) };
Attribute { name = "ascent"; jsxName = "ascent"; reasonJsxName = "ascent"; type_ = String (* number | *) };
(* Attribute { name = "autoReverse"; jsxName = "autoReverse"; reasonJsxName = "autoReverse"; type_ = BooleanishString }; Does it exists? *)
Attribute { name = "azimuth"; jsxName = "azimuth"; reasonJsxName = "azimuth"; type_ = String (* number | *) };
Attribute { name = "baseProfile"; jsxName = "baseProfile"; reasonJsxName = "baseProfile"; type_ = String (* number | *) };
Attribute { name = "baseFrequency"; jsxName = "baseFrequency"; reasonJsxName = "baseFrequency"; type_ = String (* number | *) };
Attribute { name = "bbox"; jsxName = "bbox"; reasonJsxName = "bbox"; type_ = String (* number | *) };
Attribute { name = "bias"; jsxName = "bias"; reasonJsxName = "bias"; type_ = String (* number | *) };
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cap-height *)
Attribute { name = "cap-height"; jsxName = "capHeight"; reasonJsxName = "capHeight"; type_ = String (* number | *) };
Attribute { name = "cx"; jsxName = "cx"; reasonJsxName = "cx"; type_ = String (* number | *) };
Attribute { name = "cy"; jsxName = "cy"; reasonJsxName = "cy"; type_ = String (* number | *) };
Attribute { name = "d"; jsxName = "d"; reasonJsxName = "d"; type_ = String };
Attribute { name = "decelerate"; jsxName = "decelerate"; reasonJsxName = "decelerate"; type_ = String (* number | *) };
Attribute { name = "descent"; jsxName = "descent"; reasonJsxName = "descent"; type_ = String (* number | *) };
Attribute { name = "dx"; jsxName = "dx"; reasonJsxName = "dx"; type_ = String (* number | *) };
Attribute { name = "dy"; jsxName = "dy"; reasonJsxName = "dy"; type_ = String (* number | *) };
Attribute { name = "edgeMode"; jsxName = "edgeMode"; reasonJsxName = "edgeMode"; type_ = String (* number | *) };
Attribute { name = "elevation"; jsxName = "elevation"; reasonJsxName = "elevation"; type_ = String (* number | *) };
(* Attribute { name = "externalResourcesRequired"; jsxName = "externalResourcesRequired"; reasonJsxName = "externalResourcesRequired"; type_ = BooleanishString }; Does it exists? *)
Attribute { name = "filterRes"; jsxName = "filterRes"; reasonJsxName = "filterRes"; type_ = String (* number | *) };
Attribute { name = "filterUnits"; jsxName = "filterUnits"; reasonJsxName = "filterUnits"; type_ = String (* number | *) };
Attribute { name = "format"; jsxName = "format"; reasonJsxName = "format"; type_ = String (* number | *) };
Attribute { name = "fr"; jsxName = "fr"; reasonJsxName = "fr"; type_ = String (* number | *) };
Attribute { name = "fx"; jsxName = "fx"; reasonJsxName = "fx"; type_ = String (* number | *) };
Attribute { name = "fy"; jsxName = "fy"; reasonJsxName = "fy"; type_ = String (* number | *) };
Attribute { name = "g1"; jsxName = "g1"; reasonJsxName = "g1"; type_ = String (* number | *) };
Attribute { name = "g2"; jsxName = "g2"; reasonJsxName = "g2"; type_ = String (* number | *) };
Attribute { name = "glyph-name"; jsxName = "glyphName"; reasonJsxName = "glyphName"; type_ = String (* number | *) }; (* Deprecated *)
Attribute { name = "glyphRef"; jsxName = "glyphRef"; reasonJsxName = "glyphRef"; type_ = String (* number | *) }; (* Deprecated *)
Attribute { name = "gradientTransform"; jsxName = "gradientTransform"; reasonJsxName = "gradientTransform"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform *)
Attribute { name = "gradientUnits"; jsxName = "gradientUnits"; reasonJsxName = "gradientUnits"; type_ = String };
Attribute { name = "hanging"; jsxName = "hanging"; reasonJsxName = "hanging"; type_ = String (* number | *) };
Attribute { name = "horiz-adv-x"; jsxName = "horizAdvX"; reasonJsxName = "horizAdvX"; type_ = String (* number | *) };
Attribute { name = "horiz-origin-x"; jsxName = "horizOriginX"; reasonJsxName = "horizOriginX"; type_ = String (* number | *) };
Attribute { name = "horiz-origin-y"; jsxName = "horizOriginY"; reasonJsxName = "horizOriginY"; type_ = String (* number | *) };
Attribute { name = "ideographic"; jsxName = "ideographic"; reasonJsxName = "ideographic"; type_ = String (* number | *) };
Attribute { name = "in2"; jsxName = "in2"; reasonJsxName = "in2"; type_ = String (* number | *) };
Attribute { name = "in"; jsxName = "in"; reasonJsxName = "in_"; type_ = String };
Attribute { name = "k1"; jsxName = "k1"; reasonJsxName = "k1"; type_ = String (* number | *) };
Attribute { name = "k2"; jsxName = "k2"; reasonJsxName = "k2"; type_ = String (* number | *) };
Attribute { name = "k3"; jsxName = "k3"; reasonJsxName = "k3"; type_ = String (* number | *) };
Attribute { name = "k4"; jsxName = "k4"; reasonJsxName = "k4"; type_ = String (* number | *) };
Attribute { name = "k"; jsxName = "k"; reasonJsxName = "k"; type_ = String (* number | *) };
Attribute { name = "kernelMatrix"; jsxName = "kernelMatrix"; reasonJsxName = "kernelMatrix"; type_ = String (* number | *) };
Attribute { name = "limitingConeAngle"; jsxName = "limitingConeAngle"; reasonJsxName = "limitingConeAngle"; type_ = String };
Attribute { name = "lengthAdjust"; jsxName = "lengthAdjust"; reasonJsxName = "lengthAdjust"; type_ = String (* number | *) };
Attribute { name = "local"; jsxName = "local"; reasonJsxName = "local"; type_ = String (* number | *) };
Attribute { name = "marker-mid"; jsxName = "markerMid"; reasonJsxName = "markerMid"; type_ = String };
Attribute { name = "marker-start"; jsxName = "markerStart"; reasonJsxName = "markerStart"; type_ = String };
Attribute { name = "marker-units"; jsxName = "markerUnits"; reasonJsxName = "markerUnits"; type_ = String (* number | *) };
Attribute { name = "markerWidth"; jsxName = "markerWidth"; reasonJsxName = "markerWidth"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerWidth *)
Attribute { name = "markerHeight"; jsxName = "markerHeight"; reasonJsxName = "markerHeight"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerHeight *)
Attribute { name = "maskUnits"; jsxName = "maskUnits"; reasonJsxName = "maskUnits"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskUnits *)
Attribute { name = "maskContentUnits"; jsxName = "maskContentUnits"; reasonJsxName = "maskContentUnits"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskContentUnits *)
Attribute { name = "mathematical"; jsxName = "mathematical"; reasonJsxName = "mathematical"; type_ = String (* number | *) };
Attribute { name = "mode"; jsxName = "mode"; reasonJsxName = "mode"; type_ = String (* number | *) };
Attribute { name = "numOctaves"; jsxName = "numOctaves"; reasonJsxName = "numOctaves"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/numOctaves *)
Attribute { name = "offset"; jsxName = "offset"; reasonJsxName = "offset"; type_ = String (* number | *) };
Attribute { name = "order"; jsxName = "order"; reasonJsxName = "order"; type_ = String (* number | *) };
Attribute { name = "orient"; jsxName = "orient"; reasonJsxName = "orient"; type_ = String (* number | *) };
Attribute { name = "orientation"; jsxName = "orientation"; reasonJsxName = "orientation"; type_ = String (* number | *) };
Attribute { name = "origin"; jsxName = "origin"; reasonJsxName = "origin"; type_ = String (* number | *) };
Attribute { name = "overline-thickness"; jsxName = "overlineThickness"; reasonJsxName = "overlineThickness"; type_ = String };
Attribute { name = "overline-position"; jsxName = "overlinePosition"; reasonJsxName = "overlinePosition"; type_ = String };
Attribute { name = "paint-order"; jsxName = "paintOrder"; reasonJsxName = "paintOrder"; type_ = String (* number | *) };
Attribute { name = "panose1"; jsxName = "panose1"; reasonJsxName = "panose1"; type_ = String (* number | *) };
Attribute { name = "path"; jsxName = "path"; reasonJsxName = "path"; type_ = String };
Attribute { name = "pathLength"; jsxName = "pathLength"; reasonJsxName = "pathLength"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pathLength *)
Attribute { name = "patternContentUnits"; jsxName = "patternContentUnits"; reasonJsxName = "patternContentUnits"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternContentUnits *)
Attribute { name = "patternUnits"; jsxName = "patternUnits"; reasonJsxName = "patternUnits"; type_ = String };
Attribute { name = "points"; jsxName = "points"; reasonJsxName = "points"; type_ = String };
Attribute { name = "pointsAtX"; jsxName = "pointsAtX"; reasonJsxName = "pointsAtX"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtX *)
Attribute { name = "pointsAtY"; jsxName = "pointsAtY"; reasonJsxName = "pointsAtY"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtY *)
Attribute { name = "pointsAtZ"; jsxName = "pointsAtZ"; reasonJsxName = "pointsAtZ"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtZ *)
Attribute { name = "preserveAspectRatio"; jsxName = "preserveAspectRatio"; reasonJsxName = "preserveAspectRatio"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio *)
Attribute { name = "r"; jsxName = "r"; reasonJsxName = "r"; type_ = String (* number | *) };
Attribute { name = "radius"; jsxName = "radius"; reasonJsxName = "radius"; type_ = String (* number | *) };
Attribute { name = "requiredFeatures"; jsxName = "requiredFeatures"; reasonJsxName = "requiredFeatures"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredFeatures *)
Attribute { name = "refX"; jsxName = "refX"; reasonJsxName = "refX"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX *)
Attribute { name = "refY"; jsxName = "refY"; reasonJsxName = "refY"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refY *)
Attribute { name = "rotate"; jsxName = "rotate"; reasonJsxName = "rotate"; type_ = String (* number | *) };
Attribute { name = "rx"; jsxName = "rx"; reasonJsxName = "rx"; type_ = String (* number | *) };
Attribute { name = "ry"; jsxName = "ry"; reasonJsxName = "ry"; type_ = String (* number | *) };
Attribute { name = "scale"; jsxName = "scale"; reasonJsxName = "scale"; type_ = String (* number | *) };
Attribute { name = "seed"; jsxName = "seed"; reasonJsxName = "seed"; type_ = String (* number | *) };
Attribute { name = "spacing"; jsxName = "spacing"; reasonJsxName = "spacing"; type_ = String (* number | *) };
Attribute { name = "speed"; jsxName = "speed"; reasonJsxName = "speed"; type_ = String (* number | *) };
Attribute { name = "spreadMethod"; jsxName = "spreadMethod"; reasonJsxName = "spreadMethod"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod *)
Attribute { name = "startOffset"; jsxName = "startOffset"; reasonJsxName = "startOffset"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/startOffset *)
Attribute { name = "stdDeviation"; jsxName = "stdDeviation"; reasonJsxName = "stdDeviation"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stdDeviation *)
Attribute { name = "stemh"; jsxName = "stemh"; reasonJsxName = "stemh"; type_ = String (* number | *) };
Attribute { name = "stemv"; jsxName = "stemv"; reasonJsxName = "stemv"; type_ = String (* number | *) };
Attribute { name = "stitchTiles"; jsxName = "stitchTiles"; reasonJsxName = "stitchTiles"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stitchTiles *)
Attribute { name = "strikethrough-position"; jsxName = "strikethroughPosition"; reasonJsxName = "strikethroughPosition"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/strikethrough-position *)
Attribute { name = "strikethrough-thickness"; jsxName = "strikethroughThickness"; reasonJsxName = "strikethroughThickness"; type_ = String (* number | *) };
Attribute { name = "stroke-width"; jsxName = "strokeWidth"; reasonJsxName = "strokeWidth"; type_ = String (* number | *) };
Attribute { name = "surfaceScale"; jsxName = "surfaceScale"; reasonJsxName = "surfaceScale"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/surfaceScale *)
Attribute { name = "systemLanguage"; jsxName = "systemLanguage"; reasonJsxName = "systemLanguage"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/systemLanguage *)
Attribute { name = "targetX"; jsxName = "targetX"; reasonJsxName = "targetX"; type_ = String (* number | *) };
Attribute { name = "targetY"; jsxName = "targetY"; reasonJsxName = "targetY"; type_ = String (* number | *) };
Attribute { name = "textLength"; jsxName = "textLength"; reasonJsxName = "textLength"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength *)
Attribute { name = "u1"; jsxName = "u1"; reasonJsxName = "u1"; type_ = String (* number | *) };
Attribute { name = "u2"; jsxName = "u2"; reasonJsxName = "u2"; type_ = String (* number | *) };
Attribute { name = "unicode"; jsxName = "unicode"; reasonJsxName = "unicode"; type_ = String (* number | *) };
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/unicode-range *)
Attribute { name = "unicode-range"; jsxName = "unicodeRange"; reasonJsxName = "unicodeRange"; type_ = String (* number | *) };
(* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/units-per-em *)
Attribute { name = "units-per-em"; jsxName = "unitsPerEm"; reasonJsxName = "unitsPerEm"; type_ = String (* number | *) };
Attribute { name = "v-alphabetic"; jsxName = "vAlphabetic"; reasonJsxName = "vAlphabetic"; type_ = String (* number | *) }; (* Deprecated *) (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-alphabetic *)
Attribute { name = "version"; jsxName = "version"; reasonJsxName = "version"; type_ = String };
Attribute { name = "vert-adv-y"; jsxName = "vertAdvY"; reasonJsxName = "vertAdvY"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-adv-y *)
Attribute { name = "vert-origin-x"; jsxName = "vertOriginX"; reasonJsxName = "vertOriginX"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-origin-x *)
Attribute { name = "vert-origin-y"; jsxName = "vertOriginY"; reasonJsxName = "vertOriginY"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vert-origin-y *)
Attribute { name = "v-hanging"; jsxName = "vHanging"; reasonJsxName = "vHanging"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-hanging *)
Attribute { name = "v-ideographic"; jsxName = "vIdeographic"; reasonJsxName = "vIdeographic"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/v-ideographic *)
Attribute { name = "viewBox"; jsxName = "viewBox"; reasonJsxName = "viewBox"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox *)
Attribute { name = "viewTarget"; jsxName = "viewTarget"; reasonJsxName = "viewTarget"; type_ = String (* number | *) }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewTarget *)
Attribute { name = "visibility"; jsxName = "visibility"; reasonJsxName = "visibility"; type_ = String (* number | *) };
Attribute { name = "widths"; jsxName = "widths"; reasonJsxName = "widths"; type_ = String (* number | *) };
Attribute { name = "x1"; jsxName = "x1"; reasonJsxName = "x1"; type_ = String (* number | *) };
Attribute { name = "x2"; jsxName = "x2"; reasonJsxName = "x2"; type_ = String (* number | *) };
Attribute { name = "xChannelSelector"; jsxName = "xChannelSelector"; reasonJsxName = "xChannelSelector"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xChannelSelector *)
Attribute { name = "xHeight"; jsxName = "xHeight"; reasonJsxName = "xHeight"; type_ = String (* number | *) };
(* All xlink: attributes are rendered like this and are deprecated *)
Attribute { name = "xlink:actuate"; jsxName = "xlinkActuate"; reasonJsxName = "xlinkActuate"; type_ = String };
Attribute { name = "xlink:arcrole"; jsxName = "xlinkArcrole"; reasonJsxName = "xlinkArcrole"; type_ = String };
Attribute { name = "xlink:href"; jsxName = "xlinkHref"; reasonJsxName = "xlinkHref"; type_ = String };
Attribute { name = "xlink:role"; jsxName = "xlinkRole"; reasonJsxName = "xlinkRole"; type_ = String };
Attribute { name = "xlink:show"; jsxName = "xlinkShow"; reasonJsxName = "xlinkShow"; type_ = String };
Attribute { name = "xlink:title"; jsxName = "xlinkTitle"; reasonJsxName = "xlinkTitle"; type_ = String };
Attribute { name = "xlink:type"; jsxName = "xlinkType"; reasonJsxName = "xlinkType"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:type *)
Attribute { name = "xml:base"; jsxName = "xmlBase"; reasonJsxName = "xmlBase"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:base *)
Attribute { name = "xml:lang"; jsxName = "xmlLang"; reasonJsxName = "xmlLang"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:lang *)
Attribute { name = "xmlns"; jsxName = "xmlns"; reasonJsxName = "xmlns"; type_ = String };
Attribute { name = "xmlnsXlink"; jsxName = "xmlnsXlink"; reasonJsxName = "xmlnsXlink"; type_ = String };
Attribute { name = "xmlSpace"; jsxName = "xmlSpace"; reasonJsxName = "xmlSpace"; type_ = String };
Attribute { name = "y1"; jsxName = "y1"; reasonJsxName = "y1"; type_ = String (* number | *) };
Attribute { name = "y2"; jsxName = "y2"; reasonJsxName = "y2"; type_ = String (* number | *) };
Attribute { name = "yChannelSelector"; jsxName = "yChannelSelector"; reasonJsxName = "yChannelSelector"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/yChannelSelector *)
Attribute { name = "z"; jsxName = "z"; reasonJsxName = "z"; type_ = String (* number | *) };
Attribute { name = "zoomAndPan"; jsxName = "zoomAndPan"; reasonJsxName = "zoomAndPan"; type_ = String }; (* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/zoomAndPan *) (* Deprecated *)
]
let attributes = htmlAttributes @ filtersAttributes @ presentationAttributes @ stylingAttributes @ coreAttributes
end
let webViewHTMLAttributes =
[
Attribute { name = "allowfullcreen"; jsxName = "allowFullScreen"; reasonJsxName = "allowFullScreen"; type_ = Bool };
Attribute { name = "autofocus"; jsxName = "autoFocus"; reasonJsxName = "autoFocus"; type_ = Bool };
Attribute { name = "autoSize"; jsxName = "autoSize"; reasonJsxName = "autoSize"; type_ = Bool };
Attribute { name = "blinkFeatures"; jsxName = "blinkFeatures"; reasonJsxName = "blinkFeatures"; type_ = String };
Attribute { name = "disableBlinkFeatures"; jsxName = "disableBlinkFeatures"; reasonJsxName = "disableBlinkFeatures"; type_ = String };
Attribute { name = "disableGuestResize"; jsxName = "disableGuestResize"; reasonJsxName = "disableGuestResize"; type_ = Bool };
Attribute { name = "disableWebSecurity"; jsxName = "disableWebSecurity"; reasonJsxName = "disableWebSecurity"; type_ = Bool };
Attribute { name = "guestInstance"; jsxName = "guestInstance"; reasonJsxName = "guestInstance"; type_ = String };
Attribute { name = "httpReferrer"; jsxName = "httpReferrer"; reasonJsxName = "httpReferrer"; type_ = String };
Attribute { name = "nodeIntegration"; jsxName = "nodeIntegration"; reasonJsxName = "nodeIntegration"; type_ = Bool };
Attribute { name = "partition"; jsxName = "partition"; reasonJsxName = "partition"; type_ = String };
Attribute { name = "plugins"; jsxName = "plugins"; reasonJsxName = "plugins"; type_ = Bool };
Attribute { name = "preload"; jsxName = "preload"; reasonJsxName = "preload"; type_ = String };
Attribute { name = "src"; jsxName = "src"; reasonJsxName = "src"; type_ = String };
Attribute { name = "userAgent"; jsxName = "userAgent"; reasonJsxName = "userAgent"; type_ = String };
Attribute { name = "webPreferences"; jsxName = "webPreferences"; reasonJsxName = "webPreferences"; type_ = String };
]
let commonHtmlAttributes = elementAttributes @ reactAttributes @ globalAttributes @ globalEventHandlers @ ariaAttributes
let htmlElements =
[
{ tag = "a"; attributes = commonHtmlAttributes @ anchorHTMLAttributes };
{ tag = "abbr"; attributes = commonHtmlAttributes };
{ tag = "address"; attributes = commonHtmlAttributes };
{ tag = "area"; attributes = commonHtmlAttributes @ areaHTMLAttributes };
{ tag = "article"; attributes = commonHtmlAttributes };
{ tag = "aside"; attributes = commonHtmlAttributes };
{ tag = "audio"; attributes = commonHtmlAttributes @ mediaHTMLAttributes };
{ tag = "b"; attributes = commonHtmlAttributes };
{ tag = "base"; attributes = commonHtmlAttributes @ baseHTMLAttributes };
{ tag = "bdi"; attributes = commonHtmlAttributes };
{ tag = "bdo"; attributes = commonHtmlAttributes };
{ tag = "big"; attributes = commonHtmlAttributes };
{ tag = "blockquote"; attributes = commonHtmlAttributes @ blockquoteHTMLAttributes };
{ tag = "body"; attributes = commonHtmlAttributes };
{ tag = "br"; attributes = commonHtmlAttributes };
{ tag = "button"; attributes = commonHtmlAttributes @ buttonHTMLAttributes };
{ tag = "canvas"; attributes = commonHtmlAttributes @ canvasHTMLAttributes };
{ tag = "caption"; attributes = commonHtmlAttributes };
{ tag = "cite"; attributes = commonHtmlAttributes };
{ tag = "code"; attributes = commonHtmlAttributes };
{ tag = "col"; attributes = commonHtmlAttributes @ colHTMLAttributes };
{ tag = "colgroup"; attributes = commonHtmlAttributes @ colgroupHTMLAttributes };
{ tag = "data"; attributes = commonHtmlAttributes @ dataHTMLAttributes };
{ tag = "datalist"; attributes = commonHtmlAttributes };
{ tag = "dd"; attributes = commonHtmlAttributes };
{ tag = "del"; attributes = commonHtmlAttributes @ delHTMLAttributes };
{ tag = "details"; attributes = commonHtmlAttributes @ detailsHTMLAttributes };
{ tag = "dfn"; attributes = commonHtmlAttributes };
{ tag = "dialog"; attributes = commonHtmlAttributes @ dialogHTMLAttributes };
{ tag = "div"; attributes = commonHtmlAttributes };
{ tag = "dl"; attributes = commonHtmlAttributes };
{ tag = "dt"; attributes = commonHtmlAttributes };
{ tag = "em"; attributes = commonHtmlAttributes };
{ tag = "embed"; attributes = commonHtmlAttributes @ embedHTMLAttributes };
{ tag = "fieldset"; attributes = commonHtmlAttributes @ fieldsetHTMLAttributes };
{ tag = "figcaption"; attributes = commonHtmlAttributes };
{ tag = "figure"; attributes = commonHtmlAttributes };
{ tag = "footer"; attributes = commonHtmlAttributes };
{ tag = "form"; attributes = commonHtmlAttributes @ formHTMLAttributes };
{ tag = "h1"; attributes = commonHtmlAttributes };
{ tag = "h2"; attributes = commonHtmlAttributes };
{ tag = "h3"; attributes = commonHtmlAttributes };
{ tag = "h4"; attributes = commonHtmlAttributes };
{ tag = "h5"; attributes = commonHtmlAttributes };
{ tag = "h6"; attributes = commonHtmlAttributes };
{ tag = "head"; attributes = commonHtmlAttributes };
{ tag = "header"; attributes = commonHtmlAttributes };
{ tag = "hgroup"; attributes = commonHtmlAttributes };
{ tag = "hr"; attributes = commonHtmlAttributes };
{ tag = "html"; attributes = commonHtmlAttributes @ htmlHTMLAttributes };
{ tag = "i"; attributes = commonHtmlAttributes };
{ tag = "iframe"; attributes = commonHtmlAttributes @ iframeHTMLAttributes };
{ tag = "img"; attributes = commonHtmlAttributes @ imgHTMLAttributes };
{ tag = "input"; attributes = commonHtmlAttributes @ inputHTMLAttributes };
{ tag = "ins"; attributes = commonHtmlAttributes @ insHTMLAttributes };
{ tag = "kbd"; attributes = commonHtmlAttributes };
{ tag = "keygen"; attributes = commonHtmlAttributes @ keygenHTMLAttributes };
{ tag = "label"; attributes = commonHtmlAttributes @ labelHTMLAttributes };
{ tag = "legend"; attributes = commonHtmlAttributes };
{ tag = "li"; attributes = commonHtmlAttributes @ liHTMLAttributes };
{ tag = "link"; attributes = commonHtmlAttributes @ linkHTMLAttributes };
{ tag = "main"; attributes = commonHtmlAttributes };
{ tag = "map"; attributes = commonHtmlAttributes @ mapHTMLAttributes };
{ tag = "mark"; attributes = commonHtmlAttributes };
{ tag = "menu"; attributes = commonHtmlAttributes @ menuHTMLAttributes };
{ tag = "menuitem"; attributes = commonHtmlAttributes };
{ tag = "meta"; attributes = commonHtmlAttributes @ metaHTMLAttributes };
{ tag = "meter"; attributes = commonHtmlAttributes @ meterHTMLAttributes };
{ tag = "nav"; attributes = commonHtmlAttributes };
{ tag = "noindex"; attributes = commonHtmlAttributes };
{ tag = "noscript"; attributes = commonHtmlAttributes };
{ tag = "object"; attributes = commonHtmlAttributes @ objectHTMLAttributes };
{ tag = "ol"; attributes = commonHtmlAttributes @ olHTMLAttributes };
{ tag = "optgroup"; attributes = commonHtmlAttributes @ optgroupHTMLAttributes };
{ tag = "option"; attributes = commonHtmlAttributes @ optionHTMLAttributes };
{ tag = "output"; attributes = commonHtmlAttributes @ outputHTMLAttributes };
{ tag = "p"; attributes = commonHtmlAttributes };
{ tag = "param"; attributes = commonHtmlAttributes @ paramHTMLAttributes };
{ tag = "picture"; attributes = commonHtmlAttributes };
{ tag = "pre"; attributes = commonHtmlAttributes };
{ tag = "progress"; attributes = commonHtmlAttributes @ progressHTMLAttributes };
{ tag = "q"; attributes = commonHtmlAttributes @ quoteHTMLAttributes };
{ tag = "rp"; attributes = commonHtmlAttributes };
{ tag = "rt"; attributes = commonHtmlAttributes };
{ tag = "ruby"; attributes = commonHtmlAttributes };
{ tag = "s"; attributes = commonHtmlAttributes };
{ tag = "samp"; attributes = commonHtmlAttributes };
{ tag = "script"; attributes = commonHtmlAttributes @ scriptHTMLAttributes };
{ tag = "section"; attributes = commonHtmlAttributes };
{ tag = "select"; attributes = commonHtmlAttributes @ selectHTMLAttributes };
{ tag = "slot"; attributes = commonHtmlAttributes @ slotHTMLAttributes };
{ tag = "small"; attributes = commonHtmlAttributes };
{ tag = "source"; attributes = commonHtmlAttributes @ sourceHTMLAttributes };
{ tag = "span"; attributes = commonHtmlAttributes };
{ tag = "strong"; attributes = commonHtmlAttributes };
{ tag = "style"; attributes = commonHtmlAttributes @ styleHTMLAttributes };
{ tag = "sub"; attributes = commonHtmlAttributes };
{ tag = "summary"; attributes = commonHtmlAttributes };
{ tag = "sup"; attributes = commonHtmlAttributes };
{ tag = "table"; attributes = commonHtmlAttributes @ tableHTMLAttributes };
{ tag = "tbody"; attributes = commonHtmlAttributes };
{ tag = "td"; attributes = commonHtmlAttributes @ tdHTMLAttributes };
{ tag = "template"; attributes = commonHtmlAttributes };
{ tag = "textarea"; attributes = commonHtmlAttributes @ textareaHTMLAttributes };
{ tag = "tfoot"; attributes = commonHtmlAttributes };
{ tag = "th"; attributes = commonHtmlAttributes @ thHTMLAttributes };
{ tag = "thead"; attributes = commonHtmlAttributes };
{ tag = "time"; attributes = commonHtmlAttributes @ timeHTMLAttributes };
{ tag = "title"; attributes = commonHtmlAttributes };
{ tag = "tr"; attributes = commonHtmlAttributes };
{ tag = "track"; attributes = commonHtmlAttributes @ trackHTMLAttributes };
{ tag = "u"; attributes = commonHtmlAttributes };
{ tag = "ul"; attributes = commonHtmlAttributes };
{ tag = "var"; attributes = commonHtmlAttributes };
{ tag = "video"; attributes = commonHtmlAttributes @ videoHTMLAttributes };
{ tag = "wbr"; attributes = commonHtmlAttributes };
{ tag = "webview"; attributes = commonHtmlAttributes @ webViewHTMLAttributes };
]
let commonSvgAttributes = SVG.attributes @ reactAttributes @ globalEventHandlers @ ariaAttributes
let feConvolveMatrixAttributes = [ Attribute { name = "preserveAlpha"; jsxName = "preserveAlpha"; reasonJsxName = "preserveAlpha"; type_ = BooleanishString } ]
let svgElements =
[
{ tag = "svg"; attributes = commonSvgAttributes };
{ tag = "animate"; attributes = commonSvgAttributes };
{ tag = "animateMotion"; attributes = commonSvgAttributes };
{ tag = "animateTransform"; attributes = commonSvgAttributes };
{ tag = "circle"; attributes = commonSvgAttributes };
{ tag = "clipPath"; attributes = commonSvgAttributes };
{ tag = "defs"; attributes = commonSvgAttributes };
{ tag = "desc"; attributes = commonSvgAttributes };
{ tag = "ellipse"; attributes = commonSvgAttributes };
{ tag = "feBlend"; attributes = commonSvgAttributes };
{ tag = "feColorMatrix"; attributes = commonSvgAttributes };
{ tag = "feComponentTransfer"; attributes = commonSvgAttributes };
{ tag = "feComposite"; attributes = commonSvgAttributes };
{ tag = "feConvolveMatrix"; attributes = commonSvgAttributes @ feConvolveMatrixAttributes };
{ tag = "feDiffuseLighting"; attributes = commonSvgAttributes };
{ tag = "feDisplacementMap"; attributes = commonSvgAttributes };
{ tag = "feDistantLight"; attributes = commonSvgAttributes };
{ tag = "feDropShadow"; attributes = commonSvgAttributes };
{ tag = "feFlood"; attributes = commonSvgAttributes };
{ tag = "feFuncA"; attributes = commonSvgAttributes };
{ tag = "feFuncB"; attributes = commonSvgAttributes };
{ tag = "feFuncG"; attributes = commonSvgAttributes };
{ tag = "feFuncR"; attributes = commonSvgAttributes };
{ tag = "feGaussianBlur"; attributes = commonSvgAttributes };
{ tag = "feImage"; attributes = commonSvgAttributes };
{ tag = "feMerge"; attributes = commonSvgAttributes };
{ tag = "feMergeNode"; attributes = commonSvgAttributes };
{ tag = "feMorphology"; attributes = commonSvgAttributes };
{ tag = "feOffset"; attributes = commonSvgAttributes };
{ tag = "fePointLight"; attributes = commonSvgAttributes };
{ tag = "feSpecularLighting"; attributes = commonSvgAttributes };
{ tag = "feSpotLight"; attributes = commonSvgAttributes };
{ tag = "feTile"; attributes = commonSvgAttributes };
{ tag = "feTurbulence"; attributes = commonSvgAttributes };
{ tag = "filter"; attributes = commonSvgAttributes };
{ tag = "foreignObject"; attributes = commonSvgAttributes };
{ tag = "g"; attributes = commonSvgAttributes };
{ tag = "image"; attributes = commonSvgAttributes };
{ tag = "line"; attributes = commonSvgAttributes };
{ tag = "linearGradient"; attributes = commonSvgAttributes };
{ tag = "marker"; attributes = commonSvgAttributes };
{ tag = "mask"; attributes = commonSvgAttributes };
{ tag = "metadata"; attributes = commonSvgAttributes };
{ tag = "mpath"; attributes = commonSvgAttributes };
{ tag = "path"; attributes = commonSvgAttributes };
{ tag = "pattern"; attributes = commonSvgAttributes };
{ tag = "polygon"; attributes = commonSvgAttributes };
{ tag = "polyline"; attributes = commonSvgAttributes };
{ tag = "radialGradient"; attributes = commonSvgAttributes };
{ tag = "rect"; attributes = commonSvgAttributes };
{ tag = "stop"; attributes = commonSvgAttributes };
{ tag = "switch"; attributes = commonSvgAttributes };
{ tag = "symbol"; attributes = commonSvgAttributes };
{ tag = "text"; attributes = commonSvgAttributes };
{ tag = "textPath"; attributes = commonSvgAttributes };
{ tag = "tspan"; attributes = commonSvgAttributes };
{ tag = "use"; attributes = commonSvgAttributes };
{ tag = "view"; attributes = commonSvgAttributes };
]
[@@@ocamlformat "enable"]
let domAttributes = commonSvgAttributes @ commonHtmlAttributes
let elements = svgElements @ htmlElements
let getReasonJSXName = function Attribute { reasonJsxName; _ } -> reasonJsxName | Event { jsxName; _ } -> jsxName
let getJSXName = function Attribute { jsxName; _ } -> jsxName | Event { jsxName; _ } -> jsxName
let domPropNames = List.map getJSXName domAttributes
type errors = [ `ElementNotFound | `AttributeNotFound ]
let getAttributes tag =
List.find_opt (fun element -> element.tag = tag) elements |> Option.to_result ~none:`ElementNotFound
let isDataAttribute = String.starts_with ~prefix:"data"
let string_of_chars chars =
let buf = Buffer.create 16 in
List.iter (Buffer.add_char buf) chars;
Buffer.contents buf
let chars_of_string str = List.init (String.length str) (String.get str)
let camelcaseToKebabcase str =
let rec loop acc = function
| [] -> acc
| [ x ] -> x :: acc
| x :: y :: xs ->
if Char.uppercase_ascii y == y then loop ('-' :: x :: acc) (Char.lowercase_ascii y :: xs)
else loop (x :: acc) (y :: xs)
in
str |> chars_of_string |> loop [] |> List.rev |> string_of_chars
let findByJsxName ~tag name =
let jsxName = name in
let byReasonName p = getReasonJSXName p = jsxName in
if isDataAttribute jsxName then
let name = camelcaseToKebabcase jsxName in
Ok (Attribute { name; jsxName; reasonJsxName = jsxName; type_ = String })
else if jsxName = "styles" then
(* styles needs to be "valid" for the ppx to validate, but the type isn't important, since it's expanded into className and style *)
Ok (Attribute { name; jsxName; reasonJsxName = jsxName; type_ = String })
else
match getAttributes tag with
| Ok { attributes; _ } -> (
match List.find_opt byReasonName attributes with Some p -> Ok p | None -> Error `AttributeNotFound)
| Error err -> Error err
module Levenshtein = struct
(* Levenshtein distance from
https://rosettacode.org/wiki/Levenshtein_distance *)
let minimum a b c = min a (min b c)
let distance s t =
let first = String.length s and second = String.length t in
let matrix = Array.make_matrix (first + 1) (second + 1) 0 in
for i = 0 to first do
matrix.(i).(0) <- i
done;
for j = 0 to second do
matrix.(0).(j) <- j
done;
for j = 1 to second do
for i = 1 to first do
if s.[i - 1] = t.[j - 1] then matrix.(i).(j) <- matrix.(i - 1).(j - 1)
else matrix.(i).(j) <- minimum (matrix.(i - 1).(j) + 1) (matrix.(i).(j - 1) + 1) (matrix.(i - 1).(j - 1) + 1)
done
done;
matrix.(first).(second)
end
let findClosestName invalid =
let accumulate_distance name (bestName, bestDistance) =
let distance = Levenshtein.distance invalid name in
match distance < bestDistance with true -> (name, distance) | false -> (bestName, bestDistance)
in
let name, distance = List.fold_right accumulate_distance domPropNames ("", max_int) in
if distance > 2 then None else Some name
================================================
FILE: packages/server-reason-react-ppx/DomProps.mli
================================================
type attributeType = Action | String | Int | Bool | BooleanishString | Style | Ref | InnerHtml
type eventType =
| Clipboard
| Composition
| Keyboard
| Focus
| Form
| Mouse
| Selection
| Touch
| UI
| Wheel
| Media
| Image
| Animation
| Transition
| Pointer
| Inline
| Drag
type attribute = { type_ : attributeType; name : string; jsxName : string; reasonJsxName : string }
type event = { type_ : eventType; jsxName : string }
type prop = Attribute of attribute | Event of event
type errors = [ `ElementNotFound | `AttributeNotFound ]
val getJSXName : prop -> string
val findByJsxName : tag:string -> string -> (prop, errors) result
val findClosestName : string -> string option
================================================
FILE: packages/server-reason-react-ppx/Style_rewrite.ml
================================================
(* Rewrites [ReactDOM.Style.make ~foo:a ~bar:b ()] at compile time into a
direct list of [(kebab, camel, value)] tuples, avoiding the 347-optional-arg
calling convention overhead (~1460 words/call on stock OCaml).
The PPX only rewrites calls where:
- the function is literally [ReactDOM.Style.make] (or [Style.make] in the
ReactDOM module namespace), and
- all arguments are labelled (not optional), and
- the final arg is [()].
Calls that don't fit fall through to the runtime function. *)
open Ppxlib
open Ast_builder.Default
(* CamelCase -> kebab-case mapping, kept in sync with ReactDOMStyle.ml. *)
let mapping =
[
("azimuth", "azimuth");
("background", "background");
("backgroundAttachment", "background-attachment");
("backgroundColor", "background-color");
("backgroundImage", "background-image");
("backgroundPosition", "background-position");
("backgroundRepeat", "background-repeat");
("border", "border");
("borderCollapse", "border-collapse");
("borderColor", "border-color");
("borderSpacing", "border-spacing");
("borderStyle", "border-style");
("borderTop", "border-top");
("borderRight", "border-right");
("borderBottom", "border-bottom");
("borderLeft", "border-left");
("borderTopColor", "border-top-color");
("borderRightColor", "border-right-color");
("borderBottomColor", "border-bottom-color");
("borderLeftColor", "border-left-color");
("borderTopStyle", "border-top-style");
("borderRightStyle", "border-right-style");
("borderBottomStyle", "border-bottom-style");
("borderLeftStyle", "border-left-style");
("borderTopWidth", "border-top-width");
("borderRightWidth", "border-right-width");
("borderBottomWidth", "border-bottom-width");
("borderLeftWidth", "border-left-width");
("borderWidth", "border-width");
("bottom", "bottom");
("captionSide", "caption-side");
("clear", "clear");
("color", "color");
("content", "content");
("counterIncrement", "counter-increment");
("counterReset", "counter-reset");
("cue", "cue");
("cueAfter", "cue-after");
("cueBefore", "cue-before");
("cursor", "cursor");
("direction", "direction");
("display", "display");
("elevation", "elevation");
("emptyCells", "empty-cells");
("float", "float");
("font", "font");
("fontFamily", "font-family");
("fontSize", "font-size");
("fontSizeAdjust", "font-size-adjust");
("fontStretch", "font-stretch");
("fontStyle", "font-style");
("fontVariant", "font-variant");
("fontWeight", "font-weight");
("height", "height");
("left", "left");
("letterSpacing", "letter-spacing");
("lineHeight", "line-height");
("listStyle", "list-style");
("listStyleImage", "list-style-image");
("listStylePosition", "list-style-position");
("listStyleType", "list-style-type");
("margin", "margin");
("marginTop", "margin-top");
("marginRight", "margin-right");
("marginBottom", "margin-bottom");
("marginLeft", "margin-left");
("markerOffset", "marker-offset");
("marks", "marks");
("maxHeight", "max-height");
("maxWidth", "max-width");
("minHeight", "min-height");
("minWidth", "min-width");
("orphans", "orphans");
("outline", "outline");
("outlineColor", "outline-color");
("outlineStyle", "outline-style");
("outlineWidth", "outline-width");
("overflow", "overflow");
("overflowX", "overflow-x");
("overflowY", "overflow-y");
("padding", "padding");
("paddingTop", "padding-top");
("paddingRight", "padding-right");
("paddingBottom", "padding-bottom");
("paddingLeft", "padding-left");
("page", "page");
("pageBreakAfter", "page-break-after");
("pageBreakBefore", "page-break-before");
("pageBreakInside", "page-break-inside");
("pause", "pause");
("pauseAfter", "pause-after");
("pauseBefore", "pause-before");
("pitch", "pitch");
("pitchRange", "pitch-range");
("playDuring", "play-during");
("position", "position");
("quotes", "quotes");
("richness", "richness");
("right", "right");
("size", "size");
("speak", "speak");
("speakHeader", "speak-header");
("speakNumeral", "speak-numeral");
("speakPunctuation", "speak-punctuation");
("speechRate", "speech-rate");
("stress", "stress");
("tableLayout", "table-layout");
("textAlign", "text-align");
("textDecoration", "text-decoration");
("textIndent", "text-indent");
("textShadow", "text-shadow");
("textTransform", "text-transform");
("top", "top");
("unicodeBidi", "unicode-bidi");
("verticalAlign", "vertical-align");
("visibility", "visibility");
("voiceFamily", "voice-family");
("volume", "volume");
("whiteSpace", "white-space");
("widows", "widows");
("width", "width");
("wordSpacing", "word-spacing");
("zIndex", "z-index");
("opacity", "opacity");
("backgroundOrigin", "background-origin");
("backgroundSize", "background-size");
("backgroundClip", "background-clip");
("borderRadius", "border-radius");
("borderTopLeftRadius", "border-top-left-radius");
("borderTopRightRadius", "border-top-right-radius");
("borderBottomLeftRadius", "border-bottom-left-radius");
("borderBottomRightRadius", "border-bottom-right-radius");
("borderImage", "border-image");
("borderImageSource", "border-image-source");
("borderImageSlice", "border-image-slice");
("borderImageWidth", "border-image-width");
("borderImageOutset", "border-image-outset");
("borderImageRepeat", "border-image-repeat");
("boxShadow", "box-shadow");
("columns", "columns");
("columnCount", "column-count");
("columnFill", "column-fill");
("columnGap", "column-gap");
("columnRule", "column-rule");
("columnRuleColor", "column-rule-color");
("columnRuleStyle", "column-rule-style");
("columnRuleWidth", "column-rule-width");
("columnSpan", "column-span");
("columnWidth", "column-width");
("breakAfter", "break-after");
("breakBefore", "break-before");
("breakInside", "break-inside");
("rest", "rest");
("restAfter", "rest-after");
("restBefore", "rest-before");
("speakAs", "speak-as");
("voiceBalance", "voice-balance");
("voiceDuration", "voice-duration");
("voicePitch", "voice-pitch");
("voiceRange", "voice-range");
("voiceRate", "voice-rate");
("voiceStress", "voice-stress");
("voiceVolume", "voice-volume");
("objectFit", "object-fit");
("objectPosition", "object-position");
("imageResolution", "image-resolution");
("imageOrientation", "image-orientation");
("alignContent", "align-content");
("alignItems", "align-items");
("alignSelf", "align-self");
("flex", "flex");
("flexBasis", "flex-basis");
("flexDirection", "flex-direction");
("flexFlow", "flex-flow");
("flexGrow", "flex-grow");
("flexShrink", "flex-shrink");
("flexWrap", "flex-wrap");
("justifyContent", "justify-content");
("order", "order");
("textDecorationColor", "text-decoration-color");
("textDecorationLine", "text-decoration-line");
("textDecorationSkip", "text-decoration-skip");
("textDecorationStyle", "text-decoration-style");
("textEmphasis", "text-emphasis");
("textEmphasisColor", "text-emphasis-color");
("textEmphasisPosition", "text-emphasis-position");
("textEmphasisStyle", "text-emphasis-style");
("textUnderlinePosition", "text-underline-position");
("fontFeatureSettings", "font-feature-settings");
("fontKerning", "font-kerning");
("fontLanguageOverride", "font-language-override");
("fontSynthesis", "font-synthesis");
("forntVariantAlternates", "fornt-variant-alternates");
("fontVariantCaps", "font-variant-caps");
("fontVariantEastAsian", "font-variant-east-asian");
("fontVariantLigatures", "font-variant-ligatures");
("fontVariantNumeric", "font-variant-numeric");
("fontVariantPosition", "font-variant-position");
("all", "all");
("textCombineUpright", "text-combine-upright");
("textOrientation", "text-orientation");
("writingMode", "writing-mode");
("shapeImageThreshold", "shape-image-threshold");
("shapeMargin", "shape-margin");
("shapeOutside", "shape-outside");
("mask", "mask");
("maskBorder", "mask-border");
("maskBorderMode", "mask-border-mode");
("maskBorderOutset", "mask-border-outset");
("maskBorderRepeat", "mask-border-repeat");
("maskBorderSlice", "mask-border-slice");
("maskBorderSource", "mask-border-source");
("maskBorderWidth", "mask-border-width");
("maskClip", "mask-clip");
("maskComposite", "mask-composite");
("maskImage", "mask-image");
("maskMode", "mask-mode");
("maskOrigin", "mask-origin");
("maskPosition", "mask-position");
("maskRepeat", "mask-repeat");
("maskSize", "mask-size");
("maskType", "mask-type");
("backgroundBlendMode", "background-blend-mode");
("isolation", "isolation");
("mixBlendMode", "mix-blend-mode");
("boxDecorationBreak", "box-decoration-break");
("boxSizing", "box-sizing");
("caretColor", "caret-color");
("navDown", "nav-down");
("navLeft", "nav-left");
("navRight", "nav-right");
("navUp", "nav-up");
("outlineOffset", "outline-offset");
("resize", "resize");
("textOverflow", "text-overflow");
("grid", "grid");
("gridArea", "grid-area");
("gridAutoColumns", "grid-auto-columns");
("gridAutoFlow", "grid-auto-flow");
("gridAutoRows", "grid-auto-rows");
("gridColumn", "grid-column");
("gridColumnEnd", "grid-column-end");
("gridColumnGap", "grid-column-gap");
("gridColumnStart", "grid-column-start");
("gridGap", "grid-gap");
("gridRow", "grid-row");
("gridRowEnd", "grid-row-end");
("gridRowGap", "grid-row-gap");
("gridRowStart", "grid-row-start");
("gridTemplate", "grid-template");
("gridTemplateAreas", "grid-template-areas");
("gridTemplateColumns", "grid-template-columns");
("gridTemplateRows", "grid-template-rows");
("willChange", "will-change");
("hangingPunctuation", "hanging-punctuation");
("hyphens", "hyphens");
("lineBreak", "line-break");
("overflowWrap", "overflow-wrap");
("tabSize", "tab-size");
("textAlignLast", "text-align-last");
("textJustify", "text-justify");
("wordBreak", "word-break");
("wordWrap", "word-wrap");
("animation", "animation");
("animationDelay", "animation-delay");
("animationDirection", "animation-direction");
("animationDuration", "animation-duration");
("animationFillMode", "animation-fill-mode");
("animationIterationCount", "animation-iteration-count");
("animationName", "animation-name");
("animationPlayState", "animation-play-state");
("animationTimingFunction", "animation-timing-function");
("transition", "transition");
("transitionDelay", "transition-delay");
("transitionDuration", "transition-duration");
("transitionProperty", "transition-property");
("transitionTimingFunction", "transition-timing-function");
("backfaceVisibility", "backface-visibility");
("perspective", "perspective");
("perspectiveOrigin", "perspective-origin");
("transform", "transform");
("transformOrigin", "transform-origin");
("transformStyle", "transform-style");
("justifyItems", "justify-items");
("justifySelf", "justify-self");
("placeContent", "place-content");
("placeItems", "place-items");
("placeSelf", "place-self");
("appearance", "appearance");
("caret", "caret");
("caretAnimation", "caret-animation");
("caretShape", "caret-shape");
("userSelect", "user-select");
("maxLines", "max-lines");
("marqueeDirection", "marquee-direction");
("marqueeLoop", "marquee-loop");
("marqueeSpeed", "marquee-speed");
("marqueeStyle", "marquee-style");
("overflowStyle", "overflow-style");
("rotation", "rotation");
("rotationPoint", "rotation-point");
("alignmentBaseline", "alignment-baseline");
("baselineShift", "baseline-shift");
("clip", "clip");
("clipPath", "clip-path");
("clipRule", "clip-rule");
("colorInterpolation", "color-interpolation");
("colorInterpolationFilters", "color-interpolation-filters");
("colorProfile", "color-profile");
("colorRendering", "color-rendering");
("dominantBaseline", "dominant-baseline");
("fill", "fill");
("fillOpacity", "fill-opacity");
("fillRule", "fill-rule");
("filter", "filter");
("floodColor", "flood-color");
("floodOpacity", "flood-opacity");
("glyphOrientationHorizontal", "glyph-orientation-horizontal");
("glyphOrientationVertical", "glyph-orientation-vertical");
("imageRendering", "image-rendering");
("kerning", "kerning");
("lightingColor", "lighting-color");
("markerEnd", "marker-end");
("markerMid", "marker-mid");
("markerStart", "marker-start");
("pointerEvents", "pointer-events");
("shapeRendering", "shape-rendering");
("stopColor", "stop-color");
("stopOpacity", "stop-opacity");
("stroke", "stroke");
("strokeDasharray", "stroke-dasharray");
("strokeDashoffset", "stroke-dashoffset");
("strokeLinecap", "stroke-linecap");
("strokeLinejoin", "stroke-linejoin");
("strokeMiterlimit", "stroke-miterlimit");
("strokeOpacity", "stroke-opacity");
("strokeWidth", "stroke-width");
("textAnchor", "text-anchor");
("textRendering", "text-rendering");
("rubyAlign", "ruby-align");
("rubyMerge", "ruby-merge");
("rubyPosition", "ruby-position");
]
(* Assoc of camel -> (kebab, signature_index). *)
let indexed_mapping = List.mapi (fun i (camel, kebab) -> (camel, (kebab, i))) mapping
let find_camel camel = List.assoc_opt camel indexed_mapping
(* Returns true if [longident] is [ReactDOM.Style.make] (possibly prefixed). *)
let is_style_make_ident = function
| Ldot (Ldot (Lident "ReactDOM", "Style"), "make") -> true
| Ldot (Lident "Style", "make") -> true (* used inside ReactDOM module *)
| _ -> false
(* Attempt to rewrite a call [ReactDOM.Style.make ~foo:a ~bar:b ()] to a direct
list expression. Returns [Some new_expr] on success, [None] otherwise.
The final arg must be [()] (Nolabel, unit), and there must be no optional
args and no unknown label names.
The output list must match the runtime order produced by [Style.make]: items
appear in reverse-signature-order (because the body prepends in signature
order, last prepend ends up at head). The visible CSS output then reads
reverse-signature-order, which tests and the [style_order_matters]
assertions depend on. *)
let try_rewrite_call ~loc:_ args =
let rec collect acc = function
| [] -> None (* missing the final unit *)
| [ (Nolabel, { pexp_desc = Pexp_construct ({ txt = Lident "()"; _ }, None); _ }) ] -> Some acc
| (Labelled name, expr) :: rest -> (
match find_camel name with
| Some (kebab, idx) -> collect ((idx, name, kebab, expr) :: acc) rest
| None ->
(* unknown style name — fall back *)
None)
| (Optional _, _) :: _ -> None (* optional arg — fall back *)
| (Nolabel, _) :: _ -> None (* unexpected positional — fall back *)
in
match collect [] args with
| None -> None
| Some entries ->
(* Sort by signature index ascending; since [make]'s body prepends in
signature order, the resulting list has the last (highest-index) entry
at the head. We build [head :: ... :: tail] in that order. *)
let sorted = List.sort (fun (i, _, _, _) (j, _, _, _) -> Int.compare j i) entries in
let loc = Location.none in
let list_expr =
List.fold_right
(fun (_, camel, kebab, expr) acc ->
let loc = expr.pexp_loc in
[%expr ([%e estring ~loc kebab], [%e estring ~loc camel], [%e expr]) :: [%e acc]])
sorted
[%expr ([] : (string * string * string) list)]
in
Some [%expr ([%e list_expr] : ReactDOM.Style.t)]
(* Top-level rewriter: looks at any [Pexp_apply] and rewrites eligible ones. *)
let rewrite_expression expr =
match expr.pexp_desc with
| Pexp_apply ({ pexp_desc = Pexp_ident { txt; _ }; _ }, args) when is_style_make_ident txt -> (
match try_rewrite_call ~loc:expr.pexp_loc args with Some new_expr -> new_expr | None -> expr)
| _ -> expr
================================================
FILE: packages/server-reason-react-ppx/cram/client-component-e2e.t/input.re
================================================
[@deriving rsc]
type lola = {name: string};
[@react.client.component]
let make =
(
~initial: int,
~lola: lola,
~default: int=23,
~children: React.element,
~promise: Js.Promise.t(string),
) => {
let value = React.Experimental.usePromise(promise);
{React.string(lola.name)}
{React.int(initial)}
{React.int(default)}
children
{React.string(value)}