Small ocaml/js output rehaul

Print to json directly rather than depend on yojson and a ppx.

Note: this should be tested with the website in order to validate that the Json
output is 1-to-1.
(a second step could be to simplify this output, now that it's manual)
This commit is contained in:
Louis Gesbert 2024-02-15 16:19:25 +01:00
parent 98ad084062
commit e0928677b1
8 changed files with 188 additions and 106 deletions

View File

@ -347,9 +347,7 @@ module Poll = struct
let ocaml_link_flags : string list Lazy.t = let ocaml_link_flags : string list Lazy.t =
lazy lazy
(let link_libs = (let link_libs = ["zarith"; "dates_calc"] in
["yojson"; "ppx_yojson_conv_lib"; "zarith"; "dates_calc"]
in
let link_libs_flags = let link_libs_flags =
List.concat_map List.concat_map
(fun lib -> (fun lib ->

View File

@ -31,8 +31,6 @@ depends: [
"ocaml" {>= "4.14.0"} "ocaml" {>= "4.14.0"}
"ocamlfind" {!= "1.9.5"} "ocamlfind" {!= "1.9.5"}
"ocamlgraph" {>= "1.8.8"} "ocamlgraph" {>= "1.8.8"}
"yojson" {>= "2.0" }
"ppx_yojson_conv" {>= "0.14.0"}
"re" {>= "1.9.0"} "re" {>= "1.9.0"}
"sedlex" {>= "2.4"} "sedlex" {>= "2.4"}
"uutf" {>= "1.0.3"} "uutf" {>= "1.0.3"}

View File

@ -462,7 +462,7 @@ let format_struct_embedding
format_struct_name struct_name format_to_module_name (`Sname struct_name) format_struct_name struct_name format_to_module_name (`Sname struct_name)
else else
Format.fprintf fmt Format.fprintf fmt
"@[<hov 2>let embed_%a (x: %a.t) : runtime_value =@ Struct([\"%a\"],@ \ "@[<hov 2>let embed_%a (x: %a.t) : runtime_value =@ Struct(\"%a\",@ \
@[<hov 2>[%a]@])@]@\n\ @[<hov 2>[%a]@])@]@\n\
@\n" @\n"
format_struct_name struct_name format_to_module_name (`Sname struct_name) format_struct_name struct_name format_to_module_name (`Sname struct_name)
@ -485,7 +485,7 @@ let format_enum_embedding
else else
Format.fprintf fmt Format.fprintf fmt
"@[<hv 2>@[<hov 2>let embed_%a@ @[<hov 2>(x:@ %a.t)@]@ : runtime_value \ "@[<hv 2>@[<hov 2>let embed_%a@ @[<hov 2>(x:@ %a.t)@]@ : runtime_value \
=@]@ Enum([\"%a\"],@ @[<hov 2>match x with@ %a@])@]@\n\ =@]@ Enum(\"%a\",@ @[<hov 2>match x with@ %a@])@]@\n\
@\n" @\n"
format_enum_name enum_name format_to_module_name (`Ename enum_name) format_enum_name enum_name format_to_module_name (`Ename enum_name)
EnumName.format enum_name EnumName.format enum_name

View File

@ -994,7 +994,7 @@ let interpret_program_dcalc p s : (Uid.MarkedString.info * ('a, 'm) gexpr) list
built-in interpreter does not have a way to retrieve input \ built-in interpreter does not have a way to retrieve input \
values from the command line, so it cannot execute this scope. \ values from the command line, so it cannot execute this scope. \
Please create another scope that provides the input arguments \ Please create another scope that provides the input arguments \
to this one and execute it instead. ") to this one and execute it instead.")
taus taus
in in
let to_interpret = let to_interpret =

View File

@ -84,83 +84,75 @@ let event_manager : event_manager Js.t =
method resetLog = Js.wrap_meth_callback R_ocaml.reset_log method resetLog = Js.wrap_meth_callback R_ocaml.reset_log
method retrieveEvents = method retrieveEvents =
Js.wrap_meth_callback (fun () -> Js.wrap_meth_callback
Js.array @@ fun () ->
(Array.of_list R_ocaml.retrieve_log ()
(R_ocaml.retrieve_log () |> R_ocaml.EventParser.parse_raw_events
|> R_ocaml.EventParser.parse_raw_events |> List.map (fun event ->
|> List.map (fun event -> object%js
object%js val mutable data = event |> R_ocaml.Json.event |> Js.string
val mutable data = end)
event |> Array.of_list
|> R_ocaml.yojson_of_event |> Js.array
|> Yojson.Safe.to_string
|> Js.string
end))))
method retrieveRawEvents = method retrieveRawEvents =
Js.wrap_meth_callback (fun () -> let evt_to_js evt =
Js.array (* FIXME: ideally this could be just a Json.parse (R_ocaml.Json.event
(Array.of_list foo) ? *)
(List.map object%js
(fun evt -> val mutable eventType =
object%js (match evt with
val mutable eventType = | R_ocaml.BeginCall _ -> "Begin call"
Js.string | EndCall _ -> "End call"
(match evt with | VariableDefinition _ -> "Variable definition"
| R_ocaml.BeginCall _ -> "Begin call" | DecisionTaken _ -> "Decision taken")
| EndCall _ -> "End call" |> Js.string
| VariableDefinition _ -> "Variable definition"
| DecisionTaken _ -> "Decision taken")
val mutable information = val mutable information =
Js.array (match evt with
(Array.of_list | BeginCall info | EndCall info | VariableDefinition (info, _, _) ->
(match evt with List.map Js.string info
| BeginCall info | DecisionTaken _ -> [])
| EndCall info |> Array.of_list
| VariableDefinition (info, _, _) -> |> Js.array
List.map Js.string info
| DecisionTaken _ -> []))
val mutable loggedIOJson = val mutable loggedIOJson =
match evt with match evt with
| VariableDefinition (_, io, _) -> | VariableDefinition (_, io, _) ->
io io |> R_ocaml.Json.io_log |> Js.string
|> R_ocaml.yojson_of_io_log | EndCall _ | BeginCall _ | DecisionTaken _ ->
|> Yojson.Safe.to_string "unavailable" |> Js.string
|> Js.string
| EndCall _ | BeginCall _ | DecisionTaken _ ->
"unavailable" |> Js.string
val mutable loggedValueJson = val mutable loggedValueJson =
(match evt with (match evt with
| VariableDefinition (_, _, v) -> v | VariableDefinition (_, _, v) -> v
| EndCall _ | BeginCall _ | DecisionTaken _ -> | EndCall _ | BeginCall _ | DecisionTaken _ ->
R_ocaml.unembeddable ()) R_ocaml.unembeddable ())
|> R_ocaml.yojson_of_runtime_value |> R_ocaml.Json.runtime_value
|> Yojson.Safe.to_string |> Js.string
|> Js.string
val mutable sourcePosition = val mutable sourcePosition =
match evt with match evt with
| DecisionTaken pos -> | DecisionTaken pos ->
Js.def Js.def
(object%js (object%js
val mutable fileName = Js.string pos.filename val mutable fileName = Js.string pos.filename
val mutable startLine = pos.start_line val mutable startLine = pos.start_line
val mutable endLine = pos.end_line val mutable endLine = pos.end_line
val mutable startColumn = pos.start_column val mutable startColumn = pos.start_column
val mutable endColumn = pos.end_column val mutable endColumn = pos.end_column
val mutable lawHeadings = val mutable lawHeadings =
Js.array List.map Js.string pos.law_headings
(Array.of_list |> Array.of_list
(List.map Js.string pos.law_headings)) |> Js.array
end) end)
| _ -> Js.undefined | _ -> Js.undefined
end) end
(R_ocaml.retrieve_log ())))) in
Js.wrap_meth_callback
@@ fun () ->
R_ocaml.retrieve_log () |> List.map evt_to_js |> Array.of_list |> Js.array
end end
let execute_or_throw_error f = let execute_or_throw_error f =

View File

@ -1,7 +1,5 @@
(library (library
(name runtime_ocaml) (name runtime_ocaml)
(public_name catala.runtime_ocaml) (public_name catala.runtime_ocaml)
(preprocess
(pps ppx_yojson_conv))
(libraries dates_calc zarith zarith_stubs_js) (libraries dates_calc zarith zarith_stubs_js)
(modules runtime)) (modules runtime))

View File

@ -13,8 +13,6 @@
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under License for the specific language governing permissions and limitations under
the License. *) the License. *)
open Ppx_yojson_conv_lib.Yojson_conv.Primitives
type nonrec unit = unit type nonrec unit = unit
type nonrec bool = bool type nonrec bool = bool
@ -30,8 +28,8 @@ module Eoption = struct
type 'a t = ENone of unit | ESome of 'a type 'a t = ENone of unit | ESome of 'a
end end
type io_input = NoInput | OnlyInput | Reentrant [@@deriving yojson_of] type io_input = NoInput | OnlyInput | Reentrant
type io_log = { io_input : io_input; io_output : bool } [@@deriving yojson_of] type io_log = { io_input : io_input; io_output : bool }
type source_position = { type source_position = {
filename : string; filename : string;
@ -41,7 +39,6 @@ type source_position = {
end_column : int; end_column : int;
law_headings : string list; law_headings : string list;
} }
[@@deriving yojson_of]
exception EmptyError exception EmptyError
exception AssertionFailed of source_position exception AssertionFailed of source_position
@ -190,12 +187,6 @@ let duration_to_string (d : duration) : string =
let duration_to_years_months_days (d : duration) : int * int * int = let duration_to_years_months_days (d : duration) : int * int * int =
Dates_calc.Dates.period_to_ymds d Dates_calc.Dates.period_to_ymds d
let yojson_of_money (m : money) = `Float (money_to_float m)
let yojson_of_integer (i : integer) = `Int (integer_to_int i)
let yojson_of_decimal (d : decimal) = `Float (decimal_to_float d)
let yojson_of_date (d : date) = `String (date_to_string d)
let yojson_of_duration (d : duration) = `String (duration_to_string d)
type runtime_value = type runtime_value =
| Unit | Unit
| Bool of bool | Bool of bool
@ -204,11 +195,10 @@ type runtime_value =
| Decimal of decimal | Decimal of decimal
| Date of date | Date of date
| Duration of duration | Duration of duration
| Enum of string list * (string * runtime_value) | Enum of string * (string * runtime_value)
| Struct of string list * (string * runtime_value) list | Struct of string * (string * runtime_value) list
| Array of runtime_value array | Array of runtime_value array
| Unembeddable | Unembeddable
[@@deriving yojson_of]
let unembeddable _ = Unembeddable let unembeddable _ = Unembeddable
let embed_unit () = Unit let embed_unit () = Unit
@ -220,7 +210,7 @@ let embed_date x = Date x
let embed_duration x = Duration x let embed_duration x = Duration x
let embed_array f x = Array (Array.map f x) let embed_array f x = Array (Array.map f x)
type information = string list [@@deriving yojson_of] type information = string list
type raw_event = type raw_event =
| BeginCall of information | BeginCall of information
@ -236,7 +226,6 @@ type event =
inputs : var_def list; inputs : var_def list;
body : event list; body : event list;
} }
[@@deriving yojson_of]
and var_def = { and var_def = {
pos : source_position option; pos : source_position option;
@ -253,6 +242,108 @@ and fun_call = {
output : var_def; output : var_def;
} }
module BufferedJson = struct
let rec list f buf = function
| [] -> ()
| [x] -> f buf x
| x :: r ->
f buf x;
Buffer.add_char buf ',';
list f buf r
let quote buf str =
Buffer.add_char buf '"';
String.iter
(function
| ('"' | '\\') as c ->
Buffer.add_char buf '\\';
Buffer.add_char buf c
| '\n' -> Buffer.add_string buf "\\n"
| '\t' -> Buffer.add_string buf "\\t"
| '\r' -> Buffer.add_string buf "\\r"
| '\x00' .. '\x1F' as c -> Printf.bprintf buf "\\u%04x" (int_of_char c)
| c -> Buffer.add_char buf c)
str;
Buffer.add_char buf '"'
(* Note: the output format is made for transition with what Yojson gave us,
but we could change it to something nicer (e.g. objects for structures) *)
let rec runtime_value buf = function
| Unit -> Buffer.add_string buf {|"Unit"|}
| Bool b -> Buffer.add_string buf (string_of_bool b)
| Money m -> Buffer.add_string buf (money_to_string m)
| Integer i -> Buffer.add_string buf (integer_to_string i)
| Decimal d ->
Buffer.add_string buf (decimal_to_string ~max_prec_digits:10 d)
| Date d -> quote buf (date_to_string d)
| Duration d -> quote buf (duration_to_string d)
| Enum (name, (constr, v)) ->
Printf.bprintf buf {|[["%s"],["%s",%a]]|} name constr runtime_value v
| Struct (name, elts) ->
Printf.bprintf buf {|["%s",[%a]]|} name
(list (fun buf (cstr, v) ->
Printf.bprintf buf {|"%s":%a|} cstr runtime_value v))
elts
| Array elts ->
Printf.bprintf buf "[%a]" (list runtime_value) (Array.to_list elts)
| Unembeddable -> Buffer.add_string buf {|"unembeddable"|}
let information buf info = Printf.bprintf buf "[%a]" (list quote) info
let source_position buf pos =
Printf.bprintf buf {|{"filename":%a|} quote pos.filename;
Printf.bprintf buf {|,"start_line":%d|} pos.start_line;
Printf.bprintf buf {|,"start_column":%d|} pos.start_column;
Printf.bprintf buf {|,"end_line":%d|} pos.end_line;
Printf.bprintf buf {|,"end_column":%d|} pos.end_column;
Printf.bprintf buf {|,"law_headings":[%a]}|} (list quote) pos.law_headings
let io_input buf = function
| NoInput -> quote buf "NoInput"
| OnlyInput -> quote buf "OnlyInput"
| Reentrant -> quote buf "Reentrant"
let io_log buf iol =
Printf.bprintf buf {|{"io_input":%a|} io_input iol.io_input;
Printf.bprintf buf {|,"io_output":%b}|} iol.io_output
let rec event buf = function
| VarComputation vd ->
Printf.bprintf buf {|"VarComputation",%a]|} var_def vd
| FunCall fc -> Printf.bprintf buf {|"FunCall",%a]|} fun_call fc
| SubScopeCall { name; inputs; body } ->
Printf.bprintf buf {|{"name":%a,"inputs":[%a],"body":[%a]}|} information
name (list var_def) inputs (list event) body
and var_def buf def =
Option.iter (Printf.bprintf buf {|{"pos":%a|} source_position) def.pos;
Printf.bprintf buf {|,"name":%a|} information def.name;
Printf.bprintf buf {|,"io":%a|} io_log def.io;
Printf.bprintf buf {|,"value":%a|} runtime_value def.value;
Option.iter
(Printf.bprintf buf {|,"fun_calls":[%a]}|} (list fun_call))
def.fun_calls
and fun_call buf fc =
Printf.bprintf buf {|{"fun_name":%a|} information fc.fun_name;
Printf.bprintf buf {|,"fun_inputs":[%a]|} (list var_def) fc.fun_inputs;
Printf.bprintf buf {|,"body":[%a]|} (list event) fc.body;
Printf.bprintf buf {|,"output":%a}|} var_def fc.output
end
module Json = struct
let str f x =
let buf = Buffer.create 800 in
f buf x;
Buffer.contents buf
open BufferedJson
let runtime_value = str runtime_value
let io_log = str io_log
let event = str event
end
let log_ref : raw_event list ref = ref [] let log_ref : raw_event list ref = ref []
let reset_log () = log_ref := [] let reset_log () = log_ref := []
let retrieve_log () = List.rev !log_ref let retrieve_log () = List.rev !log_ref
@ -313,8 +404,7 @@ let rec pp_events ?(is_first_call = true) ppf events =
| Duration x -> Format.fprintf ppf "%s" (duration_to_string x) | Duration x -> Format.fprintf ppf "%s" (duration_to_string x)
| Enum (_, (name, _)) -> Format.fprintf ppf "%s" name | Enum (_, (name, _)) -> Format.fprintf ppf "%s" name
| Struct (name, attrs) -> | Struct (name, attrs) ->
Format.fprintf ppf "@[<hv 2>%s = {@ %a@;<1 -2>}@]" Format.fprintf ppf "@[<hv 2>%s = {@ %a@;<1 -2>}@]" name
(String.concat "." name)
(Format.pp_print_list (Format.pp_print_list
~pp_sep:(fun ppf () -> Format.fprintf ppf ",@,") ~pp_sep:(fun ppf () -> Format.fprintf ppf ",@,")
(fun fmt (name, value) -> (fun fmt (name, value) ->
@ -414,7 +504,7 @@ module EventParser = struct
| VariableDefinition (name, io, value) -> | VariableDefinition (name, io, value) ->
Printf.sprintf "VariableDefinition([ %s ], %s, %s)" Printf.sprintf "VariableDefinition([ %s ], %s, %s)"
(String.concat ", " name) (io_log_to_string io) (String.concat ", " name) (io_log_to_string io)
(yojson_of_runtime_value value |> Yojson.Safe.to_string) (Json.runtime_value value)
| DecisionTaken pos -> | DecisionTaken pos ->
Printf.sprintf "DecisionTaken(%s:%d.%d-%d.%d)" pos.filename pos.start_line Printf.sprintf "DecisionTaken(%s:%d.%d-%d.%d)" pos.filename pos.start_line
pos.start_column pos.end_line pos.end_column pos.start_column pos.end_line pos.end_column

View File

@ -57,13 +57,11 @@ type io_input =
| Reentrant | Reentrant
(** For variables defined in the scope that can also be redefined by the (** For variables defined in the scope that can also be redefined by the
caller as they appear in the input. *) caller as they appear in the input. *)
[@@deriving yojson_of]
type io_log = { type io_log = {
io_input : io_input; io_input : io_input;
io_output : bool; (** [true] if the variable is an output *) io_output : bool; (** [true] if the variable is an output *)
} }
[@@deriving yojson_of]
(** {1 Exceptions} *) (** {1 Exceptions} *)
@ -85,11 +83,10 @@ type runtime_value =
| Decimal of decimal | Decimal of decimal
| Date of date | Date of date
| Duration of duration | Duration of duration
| Enum of string list * (string * runtime_value) | Enum of string * (string * runtime_value)
| Struct of string list * (string * runtime_value) list | Struct of string * (string * runtime_value) list
| Array of runtime_value Array.t | Array of runtime_value Array.t
| Unembeddable | Unembeddable
[@@deriving yojson_of]
val unembeddable : 'a -> runtime_value val unembeddable : 'a -> runtime_value
val embed_unit : unit -> runtime_value val embed_unit : unit -> runtime_value
@ -115,7 +112,7 @@ val embed_array : ('a -> runtime_value) -> 'a Array.t -> runtime_value
(** {2 Data structures} *) (** {2 Data structures} *)
type information = string list [@@deriving yojson_of] type information = string list
(** Represents information about a name in the code -- i.e. variable name, (** Represents information about a name in the code -- i.e. variable name,
subscope name, etc... subscope name, etc...
@ -188,7 +185,6 @@ type event =
inputs : var_def list; inputs : var_def list;
body : event list; body : event list;
} }
[@@deriving yojson_of]
and var_def = { and var_def = {
pos : source_position option; pos : source_position option;
@ -230,6 +226,16 @@ val log_decision_taken : source_position -> bool -> bool
(** {3 Pretty printers} *) (** {3 Pretty printers} *)
(** {4 Conversions to JSON} *)
module Json : sig
(* val io_input: io_input -> string *)
val io_log : io_log -> string
val runtime_value : runtime_value -> string
(* val information: information -> string *)
val event : event -> string
end
val pp_events : ?is_first_call:bool -> Format.formatter -> event list -> unit val pp_events : ?is_first_call:bool -> Format.formatter -> event list -> unit
(** [pp_events ~is_first_call ppf events] pretty prints in [ppf] the string (** [pp_events ~is_first_call ppf events] pretty prints in [ppf] the string
representation of [events]. representation of [events].