Skip to content

Commit 6965ce1

Browse files
vouillonOlivierNicole
authored andcommitted
Runtime: implement Json.output
1 parent 3204c33 commit 6965ce1

File tree

5 files changed

+157
-26
lines changed

5 files changed

+157
-26
lines changed

lib/js_of_ocaml/js_of_ocaml_stubs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ void caml_bytes_of_array () {
44
caml_fatal_error("Unimplemented Javascript primitive caml_bytes_of_array!");
55
}
66

7+
void caml_custom_identifier () {
8+
caml_fatal_error("Unimplemented Javascript primitive caml_custom_identifier!");
9+
}
10+
711
void caml_js_error_of_exception () {
812
caml_fatal_error("Unimplemented Javascript primitive caml_js_error_of_exception!");
913
}

lib/js_of_ocaml/json.ml

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,93 @@
2020
open Js
2121
open! Import
2222

23+
(****)
24+
25+
(* The writing logic for basic types is copied from [lib/deriving_json]. *)
26+
27+
let write_string buffer s =
28+
Buffer.add_char buffer '"';
29+
for i = 0 to String.length s - 1 do
30+
match s.[i] with
31+
| '"' -> Buffer.add_string buffer {|\"|}
32+
| '\\' -> Buffer.add_string buffer {|\\|}
33+
| '\b' -> Buffer.add_string buffer {|\b|}
34+
| '\x0C' -> Buffer.add_string buffer {|\f|}
35+
| '\n' -> Buffer.add_string buffer {|\n|}
36+
| '\r' -> Buffer.add_string buffer {|\r|}
37+
| '\t' -> Buffer.add_string buffer {|\t|}
38+
| c when Poly.(c <= '\x1F') ->
39+
(* Other control characters are escaped. *)
40+
Printf.bprintf buffer {|\u%04X|} (int_of_char c)
41+
| c when Poly.(c < '\x80') -> Buffer.add_char buffer s.[i]
42+
| _c (* >= '\x80' *) ->
43+
(* Bytes greater than 127 are embedded in a UTF-8 sequence. *)
44+
Buffer.add_char buffer (Char.chr (0xC2 lor (Char.code s.[i] lsr 6)));
45+
Buffer.add_char buffer (Char.chr (0x80 lor (Char.code s.[i] land 0x3F)))
46+
done;
47+
Buffer.add_char buffer '"'
48+
49+
let write_float buffer f =
50+
(* "%.15g" can be (much) shorter; "%.17g" is round-trippable *)
51+
let s = Printf.sprintf "%.15g" f in
52+
if Poly.(float_of_string s = f)
53+
then Buffer.add_string buffer s
54+
else Printf.bprintf buffer "%.17g" f
55+
56+
let write_int64 buffer i =
57+
let mask16 = Int64.of_int 0xffff in
58+
let mask24 = Int64.of_int 0xffffff in
59+
Printf.bprintf
60+
buffer
61+
"[255,%Ld,%Ld,%Ld]"
62+
(Int64.logand i mask24)
63+
(Int64.logand (Int64.shift_right i 24) mask24)
64+
(Int64.logand (Int64.shift_right i 48) mask16)
65+
66+
external custom_identifier : Obj.t -> string = "caml_custom_identifier"
67+
68+
let rec write b v =
69+
if Obj.is_int v
70+
then Printf.bprintf b "%d" (Obj.obj v : int)
71+
else
72+
let t = Obj.tag v in
73+
if t <= Obj.last_non_constant_constructor_tag
74+
then (
75+
Printf.bprintf b "[%d" t;
76+
for i = 0 to Obj.size v - 1 do
77+
Buffer.add_char b ',';
78+
write b (Obj.field v i)
79+
done;
80+
Buffer.add_char b ']')
81+
else if t = Obj.string_tag
82+
then write_string b (Obj.obj v : string)
83+
else if t = Obj.double_tag
84+
then write_float b (Obj.obj v : float)
85+
else if t = Obj.double_array_tag
86+
then (
87+
Printf.bprintf b "[%d" t;
88+
for i = 0 to Obj.size v - 1 do
89+
Buffer.add_char b ',';
90+
write_float b (Obj.double_field v i)
91+
done;
92+
Buffer.add_char b ']')
93+
else if t = Obj.custom_tag
94+
then
95+
match custom_identifier v with
96+
| "_i" -> Printf.bprintf b "%ld" (Obj.obj v : int32)
97+
| "_j" ->
98+
let i : int64 = Obj.obj v in
99+
write_int64 b i
100+
| id -> failwith (Printf.sprintf "Json.output: unsupported custom value %s " id)
101+
else failwith (Printf.sprintf "Json.output: unsupported tag %d " t)
102+
103+
let to_json v =
104+
let buf = Buffer.create 50 in
105+
write buf v;
106+
Buffer.contents buf
107+
108+
(****)
109+
23110
class type json = object
24111
method parse : 'a. js_string t -> 'a meth
25112

@@ -51,13 +138,18 @@ let input_reviver =
51138
in
52139
wrap_meth_callback reviver
53140

54-
let unsafe_input s = json##parse_ s input_reviver
141+
let unsafe_input s =
142+
match Sys.backend_type with
143+
| Other "wasm_of_ocaml" -> failwith "Json.unsafe_input: not implemented"
144+
| _ -> json##parse_ s input_reviver
55145

56146
class type obj = object
57147
method constructor : 'a. 'a constr Js.readonly_prop
58148
end
59149

60150
let mlInt64_constr =
151+
Js.Unsafe.pure_expr
152+
@@ fun () ->
61153
let dummy_int64 = 1L in
62154
let dummy_obj : obj t = Obj.magic dummy_int64 in
63155
dummy_obj##.constructor
@@ -71,4 +163,18 @@ let output_reviver _key (value : Unsafe.any) : Obj.t =
71163
Obj.repr (array [| 255; value##.lo; value##.mi; value##.hi |])
72164
else Obj.repr value
73165

74-
let output obj = json##stringify_ obj (Js.wrap_callback output_reviver)
166+
let use_native_stringify_ =
167+
ref (
168+
match Sys.backend_type with
169+
| Other "js_of_ocaml" -> true
170+
| Native | Bytecode | Other _ -> false)
171+
172+
let use_native_stringify () = !use_native_stringify_
173+
174+
let set_use_native_stringify b = use_native_stringify_ := b
175+
176+
let output obj =
177+
match Sys.backend_type with
178+
| Other "js_of_ocaml" when use_native_stringify () ->
179+
json##stringify_ obj (Js.wrap_callback output_reviver)
180+
| _ -> Js.string (to_json (Obj.repr obj))

lib/js_of_ocaml/json.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,12 @@ val output : 'a -> Js.js_string Js.t
2525
val unsafe_input : Js.js_string Js.t -> 'a
2626
(** Unmarshal a string in JSON format as an OCaml value (unsafe but
2727
fast !). *)
28+
29+
val set_use_native_stringify : bool -> unit
30+
(** Only affects js_of_ocaml. Whether to use native Javascript [stringify] to
31+
turn a value into JSON in {!val:output}. Otherwise, fall back to the slower
32+
method used by other backends, such as wasm_of_ocaml. *)
33+
34+
val use_native_stringify : unit -> bool
35+
(** Whether js_of_ocaml is using [stringify] in {!val:output}. See
36+
{!val:set_use_native_stringify}. *)

lib/tests/test_json.ml

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,35 @@ open Js_of_ocaml
2323
let round_trip x =
2424
let s = Json.output x in
2525
Printf.printf "%s\n" (Js.to_bytestring s);
26-
let y = Json.unsafe_input s in
27-
Printf.printf "%b\n" (x = y)
26+
(* Other direction of the round-trip (unmarshalling from JSON) is only
27+
available with js_of_ocaml *)
28+
match Sys.backend_type with
29+
| Other "js_of_ocaml" when Json.use_native_stringify () ->
30+
let y = Json.unsafe_input s in
31+
if not (x = y) then Printf.printf "not invariant by round-trip\n"
32+
| _ -> ()
2833

2934
let%expect_test _ =
30-
round_trip 123L;
31-
[%expect {|
32-
[255,123,0,0]
33-
true |}];
34-
round_trip "asd";
35-
[%expect {|
36-
"asd"
37-
true |}];
38-
round_trip "\000\255\254";
39-
[%expect {|
40-
"\u0000ÿþ"
41-
true |}];
42-
round_trip (2, 3);
43-
round_trip (2., 3.);
44-
round_trip (2.2, 3.3);
45-
[%expect {|
46-
[0,2,3]
47-
true
48-
[0,2,3]
49-
true
50-
[0,2.2,3.3]
51-
true |}]
35+
let tests ~use_native_stringify =
36+
let () = Json.set_use_native_stringify use_native_stringify in
37+
round_trip 123L;
38+
[%expect {|
39+
[255,123,0,0] |}];
40+
round_trip "asd";
41+
[%expect {|
42+
"asd" |}];
43+
round_trip "\000\255\254";
44+
[%expect {|
45+
"\u0000ÿþ" |}];
46+
round_trip (2, 3);
47+
round_trip (2., 3.);
48+
round_trip (2.2, 3.3);
49+
[%expect {|
50+
[0,2,3]
51+
[0,2,3]
52+
[0,2.2,3.3] |}]
53+
in
54+
tests ~use_native_stringify:false;
55+
match Sys.backend_type with
56+
| Other "js_of_ocaml" -> tests ~use_native_stringify:true
57+
| _ -> ()

runtime/obj.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,9 @@ function caml_is_continuation_tag(t) {
214214
function caml_is_continuation_tag(t) {
215215
return (t == 245) ? 1 : 0;
216216
}
217+
218+
//Provides: caml_custom_identifier
219+
//Requires: caml_string_of_jsstring
220+
function caml_custom_identifier (o) {
221+
return caml_string_of_jsstring(o.caml_custom);
222+
}

0 commit comments

Comments
 (0)