From 061180917410e3a9648529d8ec67bf3203dfd3a8 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Mon, 16 Dec 2024 10:27:16 +0100 Subject: [PATCH 01/19] Add trace format CLI flag --- compiler/catala_utils/cli.ml | 19 ++++++++++++++++--- compiler/catala_utils/global.ml | 5 +++++ compiler/catala_utils/global.mli | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index 92762f5cc..f727df63f 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -26,7 +26,8 @@ let language_code = let rl = List.map (fun (a, b) -> b, a) languages in fun l -> List.assoc l rl -let message_format_opt = ["human", Human; "gnu", GNU] +let message_format_opt = ["human", (Human : message_format_enum); "gnu", GNU] +let trace_format_opt = ["human", (Human : trace_format_enum); "json", JSON] open Cmdliner @@ -154,6 +155,16 @@ module Flags = struct "Displays a trace of the interpreter's computation or generates \ logging instructions in translate programs." + let trace_format = + value + & opt (enum trace_format_opt) Human + & info ["trace-format"] + ~doc: + "Selects the format of trace logs emitted by the interpreter. If \ + set to $(i,human), the messages will be nicely displayed and \ + meant to be read by a human. If set to $(i, json), the messages \ + will be emitted as a JSON structured object." + let plugins_dirs = let doc = "Set the given directory to be searched for backend plugins." in let env = Cmd.Env.info "CATALA_PLUGINS" in @@ -223,6 +234,7 @@ module Flags = struct color message_format trace + trace_format plugins_dirs disable_warnings max_prec_digits @@ -242,8 +254,8 @@ module Flags = struct (* This sets some global refs for convenience, but most importantly returns the options record. *) Global.enforce_options ~language ~debug ~color ~message_format ~trace - ~plugins_dirs ~disable_warnings ~max_prec_digits ~path_rewrite - ~stop_on_error ~no_fail_on_assert () + ~trace_format ~plugins_dirs ~disable_warnings ~max_prec_digits + ~path_rewrite ~stop_on_error ~no_fail_on_assert () in Term.( const make @@ -252,6 +264,7 @@ module Flags = struct $ color $ message_format $ trace + $ trace_format $ plugins_dirs $ disable_warnings $ max_prec_digits diff --git a/compiler/catala_utils/global.ml b/compiler/catala_utils/global.ml index 104e8357d..f7d9eb9bc 100644 --- a/compiler/catala_utils/global.ml +++ b/compiler/catala_utils/global.ml @@ -19,6 +19,7 @@ type raw_file = file type backend_lang = En | Fr | Pl type when_enum = Auto | Always | Never type message_format_enum = Human | GNU | Lsp +type trace_format_enum = Human | JSON type 'file input_src = | FileName of 'file @@ -33,6 +34,7 @@ type options = { mutable color : when_enum; mutable message_format : message_format_enum; mutable trace : bool; + mutable trace_format : trace_format_enum; mutable plugins_dirs : file list; mutable disable_warnings : bool; mutable max_prec_digits : int; @@ -54,6 +56,7 @@ let options = color = Auto; message_format = Human; trace = false; + trace_format = Human; plugins_dirs = []; disable_warnings = false; max_prec_digits = 20; @@ -69,6 +72,7 @@ let enforce_options ?color ?message_format ?trace + ?trace_format ?plugins_dirs ?disable_warnings ?max_prec_digits @@ -82,6 +86,7 @@ let enforce_options Option.iter (fun x -> options.color <- x) color; Option.iter (fun x -> options.message_format <- x) message_format; Option.iter (fun x -> options.trace <- x) trace; + Option.iter (fun x -> options.trace_format <- x) trace_format; Option.iter (fun x -> options.plugins_dirs <- x) plugins_dirs; Option.iter (fun x -> options.disable_warnings <- x) disable_warnings; Option.iter (fun x -> options.max_prec_digits <- x) max_prec_digits; diff --git a/compiler/catala_utils/global.mli b/compiler/catala_utils/global.mli index 2502beedf..88b496d75 100644 --- a/compiler/catala_utils/global.mli +++ b/compiler/catala_utils/global.mli @@ -32,6 +32,9 @@ type when_enum = Auto | Always | Never (** Format of error and warning messages output by the compiler. *) type message_format_enum = Human | GNU | Lsp +(** Format of trace logs *) +type trace_format_enum = Human | JSON + (** Sources for program input *) type 'file input_src = | FileName of 'file (** A file path to read from disk *) @@ -51,6 +54,7 @@ type options = private { mutable color : when_enum; mutable message_format : message_format_enum; mutable trace : bool; + mutable trace_format : trace_format_enum; mutable plugins_dirs : file list; mutable disable_warnings : bool; mutable max_prec_digits : int; @@ -73,6 +77,7 @@ val enforce_options : ?color:when_enum -> ?message_format:message_format_enum -> ?trace:bool -> + ?trace_format:trace_format_enum -> ?plugins_dirs:file list -> ?disable_warnings:bool -> ?max_prec_digits:int -> From 5ed499e92511f852a428d880a6f7cc5a272a87ed Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Mon, 16 Dec 2024 11:02:25 +0100 Subject: [PATCH 02/19] Two tracks --- compiler/shared_ast/interpreter.ml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 21b5306c0..80ebea04c 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -917,9 +917,13 @@ let evaluate_expr_trace : ~finally:(fun () -> if Global.options.trace then let trace = Runtime.retrieve_log () in - List.iter (print_log lang) trace - (* TODO: [Runtime.pp_events ~is_first_call:true Format.err_formatter - (Runtime.EventParser.parse_raw_events trace)] fais here, check why *)) + match Global.options.trace_format with + | Human -> + List.iter (print_log lang) trace + (* TODO: [Runtime.pp_events ~is_first_call:true Format.err_formatter + (Runtime.EventParser.parse_raw_events trace)] fais here, check + why *) + | JSON -> assert false (*TODO*)) let evaluate_expr_safe : type d. From 3f1826d976f664cc03f5a7682841c0ffb688b075 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 16 Dec 2024 11:01:31 +0100 Subject: [PATCH 03/19] draft --- runtimes/ocaml/runtime.ml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index 6ce3c86c0..eb77b6abb 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -380,6 +380,17 @@ module BufferedJson = struct 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 + + and raw_event buf = function + | BeginCall name -> Printf.bprintf buf {|{"event": "BeginCall", "name": "%s"}|} (String.concat "." name) + | EndCall name -> Printf.bprintf buf {|{"event": "EndCall", "name": "%s"}|} (String.concat "." name) + | VariableDefinition (name, _io, _value) -> + Printf.bprintf buf {|{ + "event": "VariableDefinition", + "name": "%s", + "value": "TODO" + }|} (String.concat "." name) + | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} end module Json = struct From 88e40d67bfc7c18ce196024be4e7ad77793e3b21 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 16 Dec 2024 09:36:18 +0100 Subject: [PATCH 04/19] interim commit --- runtimes/ocaml/runtime.ml | 7 +++++++ runtimes/ocaml/runtime.mli | 1 + 2 files changed, 8 insertions(+) diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index eb77b6abb..00abb8f72 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -366,6 +366,12 @@ module BufferedJson = struct Printf.bprintf buf {|{"name":%a,"inputs":[%a],"body":[%a]}|} information name (list var_def) inputs (list event) body + and raw_event buf = function + | BeginCall _bcall -> Printf.bprintf buf {|BeginCall|} + | EndCall _ecall -> Printf.bprintf buf {|EndCall|} + | VariableDefinition (_infos, _io, _value) -> Printf.bprintf buf {|VariableDefinition|} + | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} + and var_def buf def = Option.iter (Printf.bprintf buf {|{"pos":%a|} source_position) def.pos; Printf.bprintf buf {|,"name":%a|} information def.name; @@ -404,6 +410,7 @@ module Json = struct let runtime_value = str runtime_value let io_log = str io_log let event = str event + let raw_event = str raw_event end let log_ref : raw_event list ref = ref [] diff --git a/runtimes/ocaml/runtime.mli b/runtimes/ocaml/runtime.mli index 47f856f94..3a3f1276e 100644 --- a/runtimes/ocaml/runtime.mli +++ b/runtimes/ocaml/runtime.mli @@ -252,6 +252,7 @@ module Json : sig (* val information: information -> string *) val event : event -> string + val raw_event : raw_event -> string end val pp_events : ?is_first_call:bool -> Format.formatter -> event list -> unit From f83279d96bb01929eec63cb7c73e04ab63a56de8 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Mon, 16 Dec 2024 11:13:01 +0100 Subject: [PATCH 05/19] Print JSON trace but bug on implementation mismatch --- compiler/shared_ast/interpreter.ml | 6 +++++- runtimes/ocaml/runtime.ml | 27 ++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 80ebea04c..a5e5329e9 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -923,7 +923,11 @@ let evaluate_expr_trace : (* TODO: [Runtime.pp_events ~is_first_call:true Format.err_formatter (Runtime.EventParser.parse_raw_events trace)] fais here, check why *) - | JSON -> assert false (*TODO*)) + | JSON -> + List.iter + (fun raw_event -> + Format.printf "%s" (Runtime.Json.raw_event raw_event)) + trace) let evaluate_expr_safe : type d. diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index 00abb8f72..fcf89b40b 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -366,12 +366,6 @@ module BufferedJson = struct Printf.bprintf buf {|{"name":%a,"inputs":[%a],"body":[%a]}|} information name (list var_def) inputs (list event) body - and raw_event buf = function - | BeginCall _bcall -> Printf.bprintf buf {|BeginCall|} - | EndCall _ecall -> Printf.bprintf buf {|EndCall|} - | VariableDefinition (_infos, _io, _value) -> Printf.bprintf buf {|VariableDefinition|} - | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} - and var_def buf def = Option.iter (Printf.bprintf buf {|{"pos":%a|} source_position) def.pos; Printf.bprintf buf {|,"name":%a|} information def.name; @@ -388,15 +382,22 @@ module BufferedJson = struct Printf.bprintf buf {|,"output":%a}|} var_def fc.output and raw_event buf = function - | BeginCall name -> Printf.bprintf buf {|{"event": "BeginCall", "name": "%s"}|} (String.concat "." name) - | EndCall name -> Printf.bprintf buf {|{"event": "EndCall", "name": "%s"}|} (String.concat "." name) - | VariableDefinition (name, _io, _value) -> - Printf.bprintf buf {|{ + | BeginCall name -> + Printf.bprintf buf {|{"event": "BeginCall", "name": "%s"}|} + (String.concat "." name) + | EndCall name -> + Printf.bprintf buf {|{"event": "EndCall", "name": "%s"}|} + (String.concat "." name) + | VariableDefinition (name, io, value) -> + Printf.bprintf buf + {|{ "event": "VariableDefinition", "name": "%s", - "value": "TODO" - }|} (String.concat "." name) - | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} + "io": %a, + "value": "%a" + }|} + (String.concat "." name) io_log io runtime_value value + | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} end module Json = struct From 6ff4a5d93ae851aee4ba3639acec734753bc697f Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Mon, 16 Dec 2024 11:35:45 +0100 Subject: [PATCH 06/19] Debug of JSON trace formatting OK --- compiler/shared_ast/interpreter.ml | 11 +++++++---- runtimes/ocaml/runtime.ml | 26 ++++++++++++++++++-------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index a5e5329e9..e4e8f59f7 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -924,10 +924,13 @@ let evaluate_expr_trace : (Runtime.EventParser.parse_raw_events trace)] fais here, check why *) | JSON -> - List.iter - (fun raw_event -> - Format.printf "%s" (Runtime.Json.raw_event raw_event)) - trace) + Format.printf "["; + Format.pp_print_list + ~pp_sep:(fun fmt () -> Format.fprintf fmt ",") + (fun fmt -> Format.fprintf fmt "%s") + Format.std_formatter + (List.map Runtime.Json.raw_event trace); + Format.printf "]\n") let evaluate_expr_safe : type d. diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index fcf89b40b..74b34bee6 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -320,7 +320,7 @@ module BufferedJson = struct (* 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"|} + | Unit -> Buffer.add_string buf {|{}|} | 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) @@ -329,14 +329,22 @@ module BufferedJson = struct | 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 + Printf.bprintf buf + {|{"kind": "enum", "name": "%s", "constructor": "%s", "value": %a}|} + name constr runtime_value v | Struct (name, elts) -> - Printf.bprintf buf {|["%s",[%a]]|} name + Printf.bprintf buf {|{"kind": "struct", "name": "%s", "fields": {%a}}|} + name (list (fun buf (cstr, v) -> - Printf.bprintf buf {|"%s":%a|} cstr runtime_value v)) + Printf.bprintf buf {|"%s": %a|} cstr runtime_value v)) elts - | Array elts | Tuple elts -> - Printf.bprintf buf "[%a]" (list runtime_value) (Array.to_list elts) + | (Array elts | Tuple elts) as v -> + Printf.bprintf buf {|{"kind": %s, "value":[%a]}|} + (match v with + | Array _ -> "\"array\"" + | Tuple _ -> "\"tuple\"" + | _ -> assert false) + (list runtime_value) (Array.to_list elts) | Unembeddable -> Buffer.add_string buf {|"unembeddable"|} let information buf info = Printf.bprintf buf "[%a]" (list quote) info @@ -394,10 +402,12 @@ module BufferedJson = struct "event": "VariableDefinition", "name": "%s", "io": %a, - "value": "%a" + "value": %a }|} (String.concat "." name) io_log io runtime_value value - | DecisionTaken _dectaken -> Printf.bprintf buf {|DecisionTaken|} + | DecisionTaken source_pos -> + Printf.bprintf buf {|{"event": "DecisionTaken", "pos": %a}|} + source_position source_pos end module Json = struct From c86a831d999c1d0915e27ec53542cf4d2dc6286a Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 6 Jan 2025 10:33:38 +0100 Subject: [PATCH 07/19] this dotfile should not have been added --- .envrc | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .envrc diff --git a/.envrc b/.envrc deleted file mode 100644 index 1e71ffbc6..000000000 --- a/.envrc +++ /dev/null @@ -1,4 +0,0 @@ -# shellcheck shell=bash -# For use with direnv. -# Installing nix-direnv will ensure a smoother experience. -use flake \ No newline at end of file From b9638822b4a1bd57268fe98f3cbd6941312b5ae2 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 6 Jan 2025 11:43:59 +0100 Subject: [PATCH 08/19] output trace to file --- compiler/catala_utils/cli.ml | 12 +++++++++- compiler/catala_utils/global.ml | 4 ++++ compiler/catala_utils/global.mli | 2 ++ compiler/shared_ast/interpreter.ml | 37 +++++++++++++++++++----------- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index f727df63f..96e812d4c 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -155,6 +155,14 @@ module Flags = struct "Displays a trace of the interpreter's computation or generates \ logging instructions in translate programs." + let trace_output = + value + & opt (some string) None + & info ["trace-output"] ~docv:"FILE" + ~doc: + "Output trace logs to the specified file instead of stdout. Works \ + with both human-readable and JSON formats." + let trace_format = value & opt (enum trace_format_opt) Human @@ -235,6 +243,7 @@ module Flags = struct message_format trace trace_format + trace_output plugins_dirs disable_warnings max_prec_digits @@ -254,7 +263,7 @@ module Flags = struct (* This sets some global refs for convenience, but most importantly returns the options record. *) Global.enforce_options ~language ~debug ~color ~message_format ~trace - ~trace_format ~plugins_dirs ~disable_warnings ~max_prec_digits + ~trace_format ~trace_output ~plugins_dirs ~disable_warnings ~max_prec_digits ~path_rewrite ~stop_on_error ~no_fail_on_assert () in Term.( @@ -265,6 +274,7 @@ module Flags = struct $ message_format $ trace $ trace_format + $ trace_output $ plugins_dirs $ disable_warnings $ max_prec_digits diff --git a/compiler/catala_utils/global.ml b/compiler/catala_utils/global.ml index f7d9eb9bc..44879b0cf 100644 --- a/compiler/catala_utils/global.ml +++ b/compiler/catala_utils/global.ml @@ -41,6 +41,7 @@ type options = { mutable path_rewrite : raw_file -> file; mutable stop_on_error : bool; mutable no_fail_on_assert : bool; + mutable trace_output : string option; } (* Note: we force that the global options (ie options common to all commands) @@ -63,6 +64,7 @@ let options = path_rewrite = (fun _ -> assert false); stop_on_error = false; no_fail_on_assert = false; + trace_output = None; } let enforce_options @@ -79,6 +81,7 @@ let enforce_options ?path_rewrite ?stop_on_error ?no_fail_on_assert + ?trace_output () = Option.iter (fun x -> options.input_src <- x) input_src; Option.iter (fun x -> options.language <- x) language; @@ -93,6 +96,7 @@ let enforce_options Option.iter (fun x -> options.path_rewrite <- x) path_rewrite; Option.iter (fun x -> options.stop_on_error <- x) stop_on_error; Option.iter (fun x -> options.no_fail_on_assert <- x) no_fail_on_assert; + Option.iter (fun x -> options.trace_output <- x) trace_output; options let input_src_file = function FileName f | Contents (_, f) | Stdin f -> f diff --git a/compiler/catala_utils/global.mli b/compiler/catala_utils/global.mli index 88b496d75..60ca5272f 100644 --- a/compiler/catala_utils/global.mli +++ b/compiler/catala_utils/global.mli @@ -61,6 +61,7 @@ type options = private { mutable path_rewrite : raw_file -> file; mutable stop_on_error : bool; mutable no_fail_on_assert : bool; + mutable trace_output : string option; } (** Global options, common to all subcommands (note: the fields are internally mutable only for purposes of the [globals] toplevel value defined below) *) @@ -84,6 +85,7 @@ val enforce_options : ?path_rewrite:(raw_file -> file) -> ?stop_on_error:bool -> ?no_fail_on_assert:bool -> + ?trace_output:string option -> unit -> options (** Sets up the global options (side-effect); for specific use-cases only, this diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index e4e8f59f7..e142e1690 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -917,20 +917,29 @@ let evaluate_expr_trace : ~finally:(fun () -> if Global.options.trace then let trace = Runtime.retrieve_log () in - match Global.options.trace_format with - | Human -> - List.iter (print_log lang) trace - (* TODO: [Runtime.pp_events ~is_first_call:true Format.err_formatter - (Runtime.EventParser.parse_raw_events trace)] fais here, check - why *) - | JSON -> - Format.printf "["; - Format.pp_print_list - ~pp_sep:(fun fmt () -> Format.fprintf fmt ",") - (fun fmt -> Format.fprintf fmt "%s") - Format.std_formatter - (List.map Runtime.Json.raw_event trace); - Format.printf "]\n") + let output_trace fmt = + match Global.options.trace_format with + | Human -> List.iter (print_log lang) trace + | JSON -> + Format.fprintf fmt "["; + Format.pp_print_list + ~pp_sep:(fun fmt () -> Format.fprintf fmt ",") + (fun fmt -> Format.fprintf fmt "%s") + fmt + (List.map Runtime.Json.raw_event trace); + Format.fprintf fmt "]@." + in + match Global.options.trace_output with + | None -> output_trace Format.std_formatter + | Some filename -> + let oc = open_out filename in + let fmt = Format.formatter_of_out_channel oc in + Fun.protect + (fun () -> output_trace fmt) + ~finally:(fun () -> + close_out oc; + Format.pp_print_flush fmt ()) + ) let evaluate_expr_safe : type d. From 770c842d2d6f62924082c912151a8b7a30e94810 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 6 Jan 2025 14:19:28 +0100 Subject: [PATCH 09/19] remove unneeded flush --- compiler/shared_ast/interpreter.ml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index e142e1690..db76f486c 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -937,8 +937,7 @@ let evaluate_expr_trace : Fun.protect (fun () -> output_trace fmt) ~finally:(fun () -> - close_out oc; - Format.pp_print_flush fmt ()) + close_out oc) ) let evaluate_expr_safe : From c094ac6caf0bec8252d4ba0c87e0de6342d685fd Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Mon, 6 Jan 2025 14:36:33 +0100 Subject: [PATCH 10/19] formatting --- compiler/shared_ast/interpreter.ml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index db76f486c..ed414b9b5 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -936,8 +936,7 @@ let evaluate_expr_trace : let fmt = Format.formatter_of_out_channel oc in Fun.protect (fun () -> output_trace fmt) - ~finally:(fun () -> - close_out oc) + ~finally:(fun () -> close_out oc) ) let evaluate_expr_safe : From c9ce9fe613c706d01351fb950cc1968cb6674442 Mon Sep 17 00:00:00 2001 From: Louis Gesbert Date: Wed, 8 Jan 2025 18:31:19 +0100 Subject: [PATCH 11/19] trace output: use a file argument instead of string --- compiler/catala_utils/cli.ml | 7 ++++--- compiler/catala_utils/global.ml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index 96e812d4c..05c996422 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -157,7 +157,7 @@ module Flags = struct let trace_output = value - & opt (some string) None + & opt (some raw_file) None & info ["trace-output"] ~docv:"FILE" ~doc: "Output trace logs to the specified file instead of stdout. Works \ @@ -260,11 +260,12 @@ module Flags = struct | "-" -> "-" | f -> File.reverse_path ~to_dir f) in + let trace_output = Option.map path_rewrite trace_output in (* This sets some global refs for convenience, but most importantly returns the options record. *) Global.enforce_options ~language ~debug ~color ~message_format ~trace - ~trace_format ~trace_output ~plugins_dirs ~disable_warnings ~max_prec_digits - ~path_rewrite ~stop_on_error ~no_fail_on_assert () + ~trace_format ~trace_output ~plugins_dirs ~disable_warnings + ~max_prec_digits ~path_rewrite ~stop_on_error ~no_fail_on_assert () in Term.( const make diff --git a/compiler/catala_utils/global.ml b/compiler/catala_utils/global.ml index 44879b0cf..b3800de5f 100644 --- a/compiler/catala_utils/global.ml +++ b/compiler/catala_utils/global.ml @@ -41,7 +41,7 @@ type options = { mutable path_rewrite : raw_file -> file; mutable stop_on_error : bool; mutable no_fail_on_assert : bool; - mutable trace_output : string option; + mutable trace_output : file option; } (* Note: we force that the global options (ie options common to all commands) From cd99504e761c221a04c60a52ced2b2354236f973 Mon Sep 17 00:00:00 2001 From: Louis Gesbert Date: Thu, 9 Jan 2025 21:51:36 +0100 Subject: [PATCH 12/19] Json: print decimals according to the standard --- compiler/shared_ast/interpreter.ml | 3 +-- runtimes/ocaml/runtime.ml | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index ed414b9b5..463f018a4 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -936,8 +936,7 @@ let evaluate_expr_trace : let fmt = Format.formatter_of_out_channel oc in Fun.protect (fun () -> output_trace fmt) - ~finally:(fun () -> close_out oc) - ) + ~finally:(fun () -> close_out oc)) let evaluate_expr_safe : type d. diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index 74b34bee6..8738ba9a7 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -317,6 +317,24 @@ module BufferedJson = struct str; Buffer.add_char buf '"' + let decimal buf d = + let max_decimals = 6 in + let dec_str = + let open Z in + let sign = Q.sign d in + let n = abs (Q.num d) in + let d = abs (Q.den d) in + let int_part, dec_part = div_rem n d in + bprint buf (~$sign * int_part); + Buffer.add_char buf '.'; + let dec_part = (((~$10 ** max_decimals) * dec_part) + (d / ~$2)) / d in + format ("%0" ^ string_of_int max_decimals ^ "d") dec_part + in + let rec last_non0 n = + if n <= 1 || dec_str.[n - 1] <> '0' then n else last_non0 (n - 1) + in + Buffer.add_substring buf dec_str 0 (last_non0 max_decimals) + (* 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 @@ -324,8 +342,7 @@ module BufferedJson = struct | 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) + | Decimal d -> decimal buf d | Date d -> quote buf (date_to_string d) | Duration d -> quote buf (duration_to_string d) | Enum (name, (constr, v)) -> From dbbf551c45c42ef9a4c7a267c8b4fb87da99df94 Mon Sep 17 00:00:00 2001 From: Romain Primet Date: Wed, 15 Jan 2025 14:15:51 +0100 Subject: [PATCH 13/19] Update runtimes/ocaml/runtime.ml Co-authored-by: Louis Gesbert --- runtimes/ocaml/runtime.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtimes/ocaml/runtime.ml b/runtimes/ocaml/runtime.ml index 8738ba9a7..6761e64a9 100644 --- a/runtimes/ocaml/runtime.ml +++ b/runtimes/ocaml/runtime.ml @@ -338,7 +338,7 @@ module BufferedJson = struct (* 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 -> Buffer.add_string buf "{}" | 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) From 83cdbe21572e9814297d0c09d3c45a90c0a9df96 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Wed, 15 Jan 2025 17:42:53 +0100 Subject: [PATCH 14/19] refactor command line options --- compiler/catala_utils/cli.ml | 34 ++++++++++++++++------------ compiler/catala_utils/global.ml | 8 ++----- compiler/catala_utils/global.mli | 6 ++--- compiler/catala_utils/hash.ml | 2 +- compiler/catala_utils/message.mli | 1 + compiler/catala_web_interpreter.ml | 2 +- compiler/dcalc/from_scopelang.ml | 2 +- compiler/lcalc/to_ocaml.ml | 12 +++++----- compiler/plugins/api_web.ml | 2 +- compiler/scalc/to_python.ml | 8 +++---- compiler/scopelang/from_desugared.ml | 2 +- compiler/shared_ast/interpreter.ml | 27 ++++++++++------------ 12 files changed, 52 insertions(+), 54 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index 05c996422..fe8072a41 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -147,21 +147,25 @@ module Flags = struct standards." let trace = + let converter = + conv ~docv:"FILE" + ( (fun s -> + if s = "-" then Ok (`Stdout) + else Ok (`FileName (Global.raw_file s))), + fun ppf -> function + | `Stdout -> Format.pp_print_string ppf "-" + | `FileName f -> Format.pp_print_string ppf (f:>string) ) + in value - & flag + & opt (some converter) None ~vopt:(Some `Stdout) & info ["trace"; "t"] + ~docv: "FILE" ~env:(Cmd.Env.info "CATALA_TRACE") ~doc: "Displays a trace of the interpreter's computation or generates \ - logging instructions in translate programs." - - let trace_output = - value - & opt (some raw_file) None - & info ["trace-output"] ~docv:"FILE" - ~doc: - "Output trace logs to the specified file instead of stdout. Works \ - with both human-readable and JSON formats." + logging instructions in translate programs. If set as a flag, outputs + trace to stdout. If $(docv) is defined, outputs the trace to a file while interpreting. + Defining a filename does not affect code generation." let trace_format = value @@ -243,7 +247,6 @@ module Flags = struct message_format trace trace_format - trace_output plugins_dirs disable_warnings max_prec_digits @@ -260,11 +263,15 @@ module Flags = struct | "-" -> "-" | f -> File.reverse_path ~to_dir f) in - let trace_output = Option.map path_rewrite trace_output in + let trace = match trace with + | None -> None + | Some `Stdout -> Some (lazy (Message.std_ppf ())) + | Some `FileName f -> Some (lazy (Message.formatter_of_out_channel (open_out (path_rewrite f)) () )) + in (* This sets some global refs for convenience, but most importantly returns the options record. *) Global.enforce_options ~language ~debug ~color ~message_format ~trace - ~trace_format ~trace_output ~plugins_dirs ~disable_warnings + ~trace_format ~plugins_dirs ~disable_warnings ~max_prec_digits ~path_rewrite ~stop_on_error ~no_fail_on_assert () in Term.( @@ -275,7 +282,6 @@ module Flags = struct $ message_format $ trace $ trace_format - $ trace_output $ plugins_dirs $ disable_warnings $ max_prec_digits diff --git a/compiler/catala_utils/global.ml b/compiler/catala_utils/global.ml index b3800de5f..795fb2176 100644 --- a/compiler/catala_utils/global.ml +++ b/compiler/catala_utils/global.ml @@ -33,7 +33,7 @@ type options = { mutable debug : bool; mutable color : when_enum; mutable message_format : message_format_enum; - mutable trace : bool; + mutable trace : Format.formatter Lazy.t option; mutable trace_format : trace_format_enum; mutable plugins_dirs : file list; mutable disable_warnings : bool; @@ -41,7 +41,6 @@ type options = { mutable path_rewrite : raw_file -> file; mutable stop_on_error : bool; mutable no_fail_on_assert : bool; - mutable trace_output : file option; } (* Note: we force that the global options (ie options common to all commands) @@ -56,7 +55,7 @@ let options = debug = false; color = Auto; message_format = Human; - trace = false; + trace = None; trace_format = Human; plugins_dirs = []; disable_warnings = false; @@ -64,7 +63,6 @@ let options = path_rewrite = (fun _ -> assert false); stop_on_error = false; no_fail_on_assert = false; - trace_output = None; } let enforce_options @@ -81,7 +79,6 @@ let enforce_options ?path_rewrite ?stop_on_error ?no_fail_on_assert - ?trace_output () = Option.iter (fun x -> options.input_src <- x) input_src; Option.iter (fun x -> options.language <- x) language; @@ -96,7 +93,6 @@ let enforce_options Option.iter (fun x -> options.path_rewrite <- x) path_rewrite; Option.iter (fun x -> options.stop_on_error <- x) stop_on_error; Option.iter (fun x -> options.no_fail_on_assert <- x) no_fail_on_assert; - Option.iter (fun x -> options.trace_output <- x) trace_output; options let input_src_file = function FileName f | Contents (_, f) | Stdin f -> f diff --git a/compiler/catala_utils/global.mli b/compiler/catala_utils/global.mli index 60ca5272f..7edd22bb0 100644 --- a/compiler/catala_utils/global.mli +++ b/compiler/catala_utils/global.mli @@ -53,7 +53,7 @@ type options = private { mutable debug : bool; mutable color : when_enum; mutable message_format : message_format_enum; - mutable trace : bool; + mutable trace : Format.formatter Lazy.t option; mutable trace_format : trace_format_enum; mutable plugins_dirs : file list; mutable disable_warnings : bool; @@ -61,7 +61,6 @@ type options = private { mutable path_rewrite : raw_file -> file; mutable stop_on_error : bool; mutable no_fail_on_assert : bool; - mutable trace_output : string option; } (** Global options, common to all subcommands (note: the fields are internally mutable only for purposes of the [globals] toplevel value defined below) *) @@ -77,7 +76,7 @@ val enforce_options : ?debug:bool -> ?color:when_enum -> ?message_format:message_format_enum -> - ?trace:bool -> + ?trace:Format.formatter Lazy.t option -> ?trace_format:trace_format_enum -> ?plugins_dirs:file list -> ?disable_warnings:bool -> @@ -85,7 +84,6 @@ val enforce_options : ?path_rewrite:(raw_file -> file) -> ?stop_on_error:bool -> ?no_fail_on_assert:bool -> - ?trace_output:string option -> unit -> options (** Sets up the global options (side-effect); for specific use-cases only, this diff --git a/compiler/catala_utils/hash.ml b/compiler/catala_utils/hash.ml index 59fd8f359..61260e387 100644 --- a/compiler/catala_utils/hash.ml +++ b/compiler/catala_utils/hash.ml @@ -49,7 +49,7 @@ end = struct % !(monomorphize_types : bool) % (* The following may not affect the call convention, but we want it set in an homogeneous way *) - !(Global.options.trace : bool) + !(Global.options.trace <> None: bool) % !(Global.options.max_prec_digits : int) |> k diff --git a/compiler/catala_utils/message.mli b/compiler/catala_utils/message.mli index 3a61da557..b70edd7c6 100644 --- a/compiler/catala_utils/message.mli +++ b/compiler/catala_utils/message.mli @@ -93,6 +93,7 @@ val pad : int -> string -> Format.formatter -> unit (* {1 More general color-enabled formatting helpers}*) +val std_ppf : unit -> Format.formatter val formatter_of_out_channel : out_channel -> unit -> Format.formatter (** Creates a new formatter from the given out channel, with correct handling of the ocolor tags. Actual use of escape codes in the output depends on diff --git a/compiler/catala_web_interpreter.ml b/compiler/catala_web_interpreter.ml index fa0f9b738..9e2bae7d7 100644 --- a/compiler/catala_web_interpreter.ml +++ b/compiler/catala_web_interpreter.ml @@ -21,7 +21,7 @@ let () = let options = Global.enforce_options ~input_src:(Contents (contents, "-inline-")) - ~language:(Some language) ~debug:false ~color:Never ~trace () + ~language:(Some language) ~debug:false ~color:Never ~trace: (if trace then Some (lazy Format.std_formatter) else None) () in let prg, _type_order = Passes.dcalc options ~includes:[] ~optimize:false diff --git a/compiler/dcalc/from_scopelang.ml b/compiler/dcalc/from_scopelang.ml index 051539fd2..d3b4c9ca3 100644 --- a/compiler/dcalc/from_scopelang.ml +++ b/compiler/dcalc/from_scopelang.ml @@ -127,7 +127,7 @@ let tag_with_log_entry (markings : Uid.MarkedString.info list) : 'm Ast.expr boxed = let m = mark_tany (Mark.get e) (Expr.pos e) in - if Global.options.trace then + if Global.options.trace <> None then let pos = Expr.pos e in Expr.eappop ~op:(Log (l, markings), pos) ~tys:[TAny, pos] ~args:[e] m else e diff --git a/compiler/lcalc/to_ocaml.ml b/compiler/lcalc/to_ocaml.ml index 87c7de090..aaffbfb55 100644 --- a/compiler/lcalc/to_ocaml.ml +++ b/compiler/lcalc/to_ocaml.ml @@ -340,11 +340,11 @@ let rec format_expr (ctx : decl_ctx) (fmt : Format.formatter) (e : 'm expr) : args = [arg]; _; } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "(log_begin_call@ %a@ %a)@ %a" format_uid_list info format_with_parens f format_with_parens arg | EAppOp { op = Log (VarDef var_def_info, info), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "(log_variable_definition@ %a@ {io_input=%s;@ io_output=%b}@ (%a)@ %a)" format_uid_list info @@ -356,7 +356,7 @@ let rec format_expr (ctx : decl_ctx) (fmt : Format.formatter) (e : 'm expr) : (var_def_info.log_typ, Pos.no_pos) format_with_parens arg1 | EAppOp { op = Log (PosRecordIfTrueBool, _), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> let pos = Expr.pos e in Format.fprintf fmt "(log_decision_taken@ @[{filename = \"%s\";@ start_line=%d;@ \ @@ -365,7 +365,7 @@ let rec format_expr (ctx : decl_ctx) (fmt : Format.formatter) (e : 'm expr) : (Pos.get_end_line pos) (Pos.get_end_column pos) format_string_list (Pos.get_law_info pos) format_with_parens arg1 | EAppOp { op = Log (EndCall, info), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "(log_end_call@ %a@ %a)" format_uid_list info format_with_parens arg1 | EAppOp { op = Log _, _; args = [arg1]; _ } -> @@ -482,7 +482,7 @@ let format_ctx Format.fprintf fmt "@[%a:@ %a@]" format_struct_field_name (None, struct_field) format_typ struct_field_type)) (StructField.Map.bindings struct_fields); - if Global.options.trace then + if Global.options.trace <> None then format_struct_embedding fmt (struct_name, struct_fields) in let format_enum_decl fmt (enum_name, enum_cons) = @@ -495,7 +495,7 @@ let format_ctx Format.fprintf fmt "@[| %a@ of@ %a@]" format_enum_cons_name enum_cons format_typ enum_cons_type)) (EnumConstructor.Map.bindings enum_cons); - if Global.options.trace then format_enum_embedding fmt (enum_name, enum_cons) + if Global.options.trace <> None then format_enum_embedding fmt (enum_name, enum_cons) in let is_in_type_ordering s = List.exists diff --git a/compiler/plugins/api_web.ml b/compiler/plugins/api_web.ml index d29552170..978d98bdc 100644 --- a/compiler/plugins/api_web.ml +++ b/compiler/plugins/api_web.ml @@ -475,7 +475,7 @@ let run keep_special_ops monomorphize_types _options = - let options = Global.enforce_options ~trace:true () in + let options = Global.enforce_options ~trace:(Some (lazy Format.std_formatter)) () in let prg, type_ordering, _ = Driver.Passes.lcalc options ~includes ~optimize ~check_invariants ~autotest:false ~closure_conversion ~keep_special_ops ~typed:Expr.typed diff --git a/compiler/scalc/to_python.ml b/compiler/scalc/to_python.ml index 19053e811..d77658539 100644 --- a/compiler/scalc/to_python.ml +++ b/compiler/scalc/to_python.ml @@ -297,11 +297,11 @@ let rec format_expression ctx (fmt : Format.formatter) (e : expr) : unit = f = EAppOp { op = Log (BeginCall, info), _; args = [f]; _ }, _; args = [arg]; } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "log_begin_call(%a,@ %a,@ %a)" format_uid_list info (format_expression ctx) f (format_expression ctx) arg | EAppOp { op = Log (VarDef var_def_info, info), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "log_variable_definition(%a,@ LogIO(input_io=InputIO.%s,@ \ output_io=%s),@ %a)" @@ -313,7 +313,7 @@ let rec format_expression ctx (fmt : Format.formatter) (e : expr) : unit = (if var_def_info.log_io_output then "True" else "False") (format_expression ctx) arg1 | EAppOp { op = Log (PosRecordIfTrueBool, _), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> let pos = Mark.get e in Format.fprintf fmt "log_decision_taken(SourcePosition(filename=\"%s\",@ start_line=%d,@ \ @@ -322,7 +322,7 @@ let rec format_expression ctx (fmt : Format.formatter) (e : expr) : unit = (Pos.get_end_line pos) (Pos.get_end_column pos) format_string_list (Pos.get_law_info pos) (format_expression ctx) arg1 | EAppOp { op = Log (EndCall, info), _; args = [arg1]; _ } - when Global.options.trace -> + when Global.options.trace <> None -> Format.fprintf fmt "log_end_call(%a,@ %a)" format_uid_list info (format_expression ctx) arg1 | EAppOp { op = Log _, _; args = [arg1]; _ } -> diff --git a/compiler/scopelang/from_desugared.ml b/compiler/scopelang/from_desugared.ml index 30afdaf8d..5037d1fcd 100644 --- a/compiler/scopelang/from_desugared.ml +++ b/compiler/scopelang/from_desugared.ml @@ -37,7 +37,7 @@ let tag_with_log_entry (e : untyped Ast.expr boxed) (l : log_entry) (markings : Uid.MarkedString.info list) : untyped Ast.expr boxed = - if Global.options.trace then + if Global.options.trace <> None then Expr.eappop ~op:(Log (l, markings), Expr.pos e) ~tys:[TAny, Expr.pos e] diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 463f018a4..5e402a950 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -68,7 +68,7 @@ let rec format_runtime_value lang ppf = function (Array.to_list elts) | Runtime.Unembeddable -> Format.pp_print_string ppf "" -let print_log lang entry = +let print_log ppf lang entry = let pp_infos = Format.( pp_print_list @@ -77,13 +77,13 @@ let print_log lang entry = in match entry with | Runtime.BeginCall infos -> - Message.log "%s%a %a" !indent_str Print.log_entry BeginCall pp_infos infos; + Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry BeginCall pp_infos infos; indent_str := !indent_str ^ " " | Runtime.EndCall infos -> indent_str := String.sub !indent_str 0 (String.length !indent_str - 2); - Message.log "%s%a %a" !indent_str Print.log_entry EndCall pp_infos infos + Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry EndCall pp_infos infos | Runtime.VariableDefinition (infos, io, value) -> - Message.log "%s%a %a: @{%s@}" !indent_str Print.log_entry + Format.fprintf ppf "%s%a %a: @{%s@}" !indent_str Print.log_entry (VarDef { log_typ = TAny; @@ -94,7 +94,7 @@ let print_log lang entry = (Message.unformat (fun ppf -> format_runtime_value lang ppf value)) | Runtime.DecisionTaken rtpos -> let pos = Expr.runtime_to_pos rtpos in - Message.log "%s@[%a@{Definition applied@}:@,%a@]" !indent_str + Format.fprintf ppf "%s@[%a@{Definition applied@}:@,%a@]" !indent_str Print.log_entry PosRecordIfTrueBool Pos.format_loc_text pos let rec value_to_runtime_embedded = function @@ -248,7 +248,7 @@ let rec evaluate_operator match op, args with | Length, [(EArray es, _)] -> ELit (LInt (Runtime.integer_of_int (List.length es))) - | Log (entry, infos), [(e, _)] when Global.options.trace -> ( + | Log (entry, infos), [(e, _)] when Global.options.trace <> None -> ( let rtinfos = List.map Uid.MarkedString.to_string infos in match entry with | BeginCall -> Runtime.log_begin_call rtinfos e @@ -915,11 +915,13 @@ let evaluate_expr_trace : Fun.protect (fun () -> evaluate_expr ctx lang e) ~finally:(fun () -> - if Global.options.trace then + match Global.options.trace with + | None -> () + | Some (lazy ppf) -> let trace = Runtime.retrieve_log () in let output_trace fmt = match Global.options.trace_format with - | Human -> List.iter (print_log lang) trace + | Human -> List.iter (print_log ppf lang) trace | JSON -> Format.fprintf fmt "["; Format.pp_print_list @@ -929,14 +931,9 @@ let evaluate_expr_trace : (List.map Runtime.Json.raw_event trace); Format.fprintf fmt "]@." in - match Global.options.trace_output with - | None -> output_trace Format.std_formatter - | Some filename -> - let oc = open_out filename in - let fmt = Format.formatter_of_out_channel oc in Fun.protect - (fun () -> output_trace fmt) - ~finally:(fun () -> close_out oc)) + (fun () -> output_trace ppf) + ~finally:(fun () -> (Format.pp_print_flush ppf ()))) let evaluate_expr_safe : type d. From 8f1764f250005328144af05f4812b5a2ab442e6c Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Fri, 17 Jan 2025 10:12:48 +0100 Subject: [PATCH 15/19] temporarily protect indent --- compiler/shared_ast/interpreter.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 5e402a950..809e0922f 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -80,7 +80,7 @@ let print_log ppf lang entry = Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry BeginCall pp_infos infos; indent_str := !indent_str ^ " " | Runtime.EndCall infos -> - indent_str := String.sub !indent_str 0 (String.length !indent_str - 2); + indent_str := if String.length !indent_str >= 2 then String.sub !indent_str 0 (String.length !indent_str - 2) else !indent_str; Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry EndCall pp_infos infos | Runtime.VariableDefinition (infos, io, value) -> Format.fprintf ppf "%s%a %a: @{%s@}" !indent_str Print.log_entry From d673ea443c065aa21ce1ae20b8a32cec068a3034 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Fri, 17 Jan 2025 11:39:27 +0100 Subject: [PATCH 16/19] infer json trace_format if the output filename has a .json extension (thanks @vbot) --- compiler/catala_utils/cli.ml | 48 +++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index fe8072a41..d48a7e1dc 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -150,26 +150,26 @@ module Flags = struct let converter = conv ~docv:"FILE" ( (fun s -> - if s = "-" then Ok (`Stdout) - else Ok (`FileName (Global.raw_file s))), + if s = "-" then Ok `Stdout else Ok (`FileName (Global.raw_file s))), fun ppf -> function | `Stdout -> Format.pp_print_string ppf "-" - | `FileName f -> Format.pp_print_string ppf (f:>string) ) + | `FileName f -> Format.pp_print_string ppf (f :> string) ) in value & opt (some converter) None ~vopt:(Some `Stdout) - & info ["trace"; "t"] - ~docv: "FILE" + & info ["trace"; "t"] ~docv:"FILE" ~env:(Cmd.Env.info "CATALA_TRACE") ~doc: "Displays a trace of the interpreter's computation or generates \ - logging instructions in translate programs. If set as a flag, outputs - trace to stdout. If $(docv) is defined, outputs the trace to a file while interpreting. - Defining a filename does not affect code generation." + logging instructions in translate programs. If set as a flag, \ + outputs\n\ + \ trace to stdout. If $(docv) is defined, outputs the \ + trace to a file while interpreting.\n\ + \ Defining a filename does not affect code generation." let trace_format = value - & opt (enum trace_format_opt) Human + & opt (some (enum trace_format_opt)) None & info ["trace-format"] ~doc: "Selects the format of trace logs emitted by the interpreter. If \ @@ -263,16 +263,34 @@ module Flags = struct | "-" -> "-" | f -> File.reverse_path ~to_dir f) in - let trace = match trace with - | None -> None - | Some `Stdout -> Some (lazy (Message.std_ppf ())) - | Some `FileName f -> Some (lazy (Message.formatter_of_out_channel (open_out (path_rewrite f)) () )) + let trace, trace_format = + match trace, trace_format with + | None, _ -> None, trace_format + | Some `Stdout, _ -> Some (lazy (Message.std_ppf ())), trace_format + | Some (`FileName f), Some _ -> + ( Some + (lazy + (Message.formatter_of_out_channel + (open_out (path_rewrite f)) + ())), + trace_format ) + | Some (`FileName f), None -> + let trace_format = + if Filename.extension (f :> file) = ".json" then JSON else Human + in + ( Some + (lazy + (Message.formatter_of_out_channel + (open_out (path_rewrite f)) + ())), + Some trace_format ) in + let trace_format = Option.value trace_format ~default:Human in (* This sets some global refs for convenience, but most importantly returns the options record. *) Global.enforce_options ~language ~debug ~color ~message_format ~trace - ~trace_format ~plugins_dirs ~disable_warnings - ~max_prec_digits ~path_rewrite ~stop_on_error ~no_fail_on_assert () + ~trace_format ~plugins_dirs ~disable_warnings ~max_prec_digits + ~path_rewrite ~stop_on_error ~no_fail_on_assert () in Term.( const make From 5a26d13d1ba9d07fa166df32c6163e873d8398c8 Mon Sep 17 00:00:00 2001 From: PRIMET Romain Date: Fri, 17 Jan 2025 18:24:50 +0100 Subject: [PATCH 17/19] reject trace file names whose extension start in .catala --- compiler/catala_utils/cli.ml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/catala_utils/cli.ml b/compiler/catala_utils/cli.ml index d48a7e1dc..a996761cf 100644 --- a/compiler/catala_utils/cli.ml +++ b/compiler/catala_utils/cli.ml @@ -150,7 +150,12 @@ module Flags = struct let converter = conv ~docv:"FILE" ( (fun s -> - if s = "-" then Ok `Stdout else Ok (`FileName (Global.raw_file s))), + if s = "-" then Ok `Stdout + else if + Filename.extension s |> String.starts_with ~prefix:".catala" + then + Error (`Msg "Output trace file cannot have a .catala extension") + else Ok (`FileName (Global.raw_file s))), fun ppf -> function | `Stdout -> Format.pp_print_string ppf "-" | `FileName f -> Format.pp_print_string ppf (f :> string) ) @@ -165,7 +170,8 @@ module Flags = struct outputs\n\ \ trace to stdout. If $(docv) is defined, outputs the \ trace to a file while interpreting.\n\ - \ Defining a filename does not affect code generation." + \ Defining a filename does not affect code generation. \ + Cannot use .catala extension." let trace_format = value From 2eecc0456e3d5834864f9e532114cce794be0f12 Mon Sep 17 00:00:00 2001 From: Louis Gesbert Date: Mon, 20 Jan 2025 18:04:57 +0100 Subject: [PATCH 18/19] Interpreter: restore human printing format --- compiler/shared_ast/interpreter.ml | 44 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 809e0922f..034626faa 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -31,8 +31,6 @@ let is_empty_error : type a. (a, 'm) gexpr -> bool = (* TODO: we should provide a generic way to print logs, that work across the different backends: python, ocaml, javascript, and interpreter *) -let indent_str = ref "" - (** {1 Evaluation} *) let rec format_runtime_value lang ppf = function @@ -68,34 +66,43 @@ let rec format_runtime_value lang ppf = function (Array.to_list elts) | Runtime.Unembeddable -> Format.pp_print_string ppf "" -let print_log ppf lang entry = +let print_log ppf lang level entry = let pp_infos = Format.( pp_print_list - ~pp_sep:(fun ppf () -> Format.fprintf ppf ".@,") + ~pp_sep:(fun ppf () -> fprintf ppf ".@,") pp_print_string) in + let logprintf entry fmt = + if ppf == Message.std_ppf () then Format.fprintf ppf "[@{LOG@}] "; + Format.fprintf ppf ("%s@[%a " ^^ fmt ^^ "@]@,") (String.make (level * 2) ' ') Print.log_entry entry + in match entry with | Runtime.BeginCall infos -> - Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry BeginCall pp_infos infos; - indent_str := !indent_str ^ " " + logprintf BeginCall "%a" pp_infos infos; + level + 1 | Runtime.EndCall infos -> - indent_str := if String.length !indent_str >= 2 then String.sub !indent_str 0 (String.length !indent_str - 2) else !indent_str; - Format.fprintf ppf "%s%a %a" !indent_str Print.log_entry EndCall pp_infos infos + let level = max 0 (level - 1) in + logprintf EndCall "%a" pp_infos infos; + level | Runtime.VariableDefinition (infos, io, value) -> - Format.fprintf ppf "%s%a %a: @{%s@}" !indent_str Print.log_entry + logprintf (VarDef { log_typ = TAny; log_io_input = io.Runtime.io_input; log_io_output = io.Runtime.io_output; }) + "%a: @{%s@}" pp_infos infos - (Message.unformat (fun ppf -> format_runtime_value lang ppf value)) + (Message.unformat (fun ppf -> format_runtime_value lang ppf value)); + level | Runtime.DecisionTaken rtpos -> let pos = Expr.runtime_to_pos rtpos in - Format.fprintf ppf "%s@[%a@{Definition applied@}:@,%a@]" !indent_str - Print.log_entry PosRecordIfTrueBool Pos.format_loc_text pos + logprintf PosRecordIfTrueBool + "@[@{Definition applied@}:@,%a@]@," + Pos.format_loc_text pos; + level let rec value_to_runtime_embedded = function | ELit LUnit -> Runtime.Unit @@ -921,15 +928,18 @@ let evaluate_expr_trace : let trace = Runtime.retrieve_log () in let output_trace fmt = match Global.options.trace_format with - | Human -> List.iter (print_log ppf lang) trace + | Human -> + Format.pp_open_vbox ppf 0; + ignore @@ List.fold_left (print_log ppf lang) 0 trace; + Format.pp_close_box ppf () | JSON -> - Format.fprintf fmt "["; + Format.fprintf fmt "@[[@,"; Format.pp_print_list - ~pp_sep:(fun fmt () -> Format.fprintf fmt ",") - (fun fmt -> Format.fprintf fmt "%s") + ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@,") + Format.pp_print_string fmt (List.map Runtime.Json.raw_event trace); - Format.fprintf fmt "]@." + Format.fprintf fmt "]@]@." in Fun.protect (fun () -> output_trace ppf) From 0bf89666c7f4841ef07d7d99089f04fa4734dc76 Mon Sep 17 00:00:00 2001 From: Louis Gesbert Date: Tue, 21 Jan 2025 15:05:21 +0100 Subject: [PATCH 19/19] Fix trace printing - don't duplicate Message.*ppf printers - adjust some formatting details --- compiler/catala_utils/message.ml | 9 +++++++-- compiler/catala_utils/message.mli | 1 + compiler/shared_ast/interpreter.ml | 18 +++++++++--------- tests/scope/good/scope_call3.catala_en | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/compiler/catala_utils/message.ml b/compiler/catala_utils/message.ml index e7b00ccdf..e2c8ec9ae 100644 --- a/compiler/catala_utils/message.ml +++ b/compiler/catala_utils/message.ml @@ -66,8 +66,13 @@ let formatter_of_out_channel oc = if Lazy.force tty then Format.pp_set_margin ppf (terminal_columns ()); ppf -let std_ppf = formatter_of_out_channel stdout -let err_ppf = formatter_of_out_channel stderr +let std_ppf = + let ppf = lazy (formatter_of_out_channel stdout ()) in + fun () -> Lazy.force ppf + +let err_ppf = + let ppf = lazy (formatter_of_out_channel stderr ()) in + fun () -> Lazy.force ppf let ignore_ppf = let ppf = lazy (Format.make_formatter (fun _ _ _ -> ()) (fun () -> ())) in diff --git a/compiler/catala_utils/message.mli b/compiler/catala_utils/message.mli index b70edd7c6..5bd5eab40 100644 --- a/compiler/catala_utils/message.mli +++ b/compiler/catala_utils/message.mli @@ -94,6 +94,7 @@ val pad : int -> string -> Format.formatter -> unit (* {1 More general color-enabled formatting helpers}*) val std_ppf : unit -> Format.formatter +val err_ppf : unit -> Format.formatter val formatter_of_out_channel : out_channel -> unit -> Format.formatter (** Creates a new formatter from the given out channel, with correct handling of the ocolor tags. Actual use of escape codes in the output depends on diff --git a/compiler/shared_ast/interpreter.ml b/compiler/shared_ast/interpreter.ml index 034626faa..8aa9aecab 100644 --- a/compiler/shared_ast/interpreter.ml +++ b/compiler/shared_ast/interpreter.ml @@ -73,34 +73,34 @@ let print_log ppf lang level entry = ~pp_sep:(fun ppf () -> fprintf ppf ".@,") pp_print_string) in - let logprintf entry fmt = - if ppf == Message.std_ppf () then Format.fprintf ppf "[@{LOG@}] "; - Format.fprintf ppf ("%s@[%a " ^^ fmt ^^ "@]@,") (String.make (level * 2) ' ') Print.log_entry entry + let logprintf level entry fmt = + if ppf == Message.std_ppf () then Format.fprintf ppf "[@{LOG@}] "; + Format.fprintf ppf ("@[%*s%a" ^^ fmt ^^ "@]@,") (level * 2) "" Print.log_entry entry in match entry with | Runtime.BeginCall infos -> - logprintf BeginCall "%a" pp_infos infos; + logprintf level BeginCall " %a" pp_infos infos; level + 1 | Runtime.EndCall infos -> let level = max 0 (level - 1) in - logprintf EndCall "%a" pp_infos infos; + logprintf level EndCall " %a" pp_infos infos; level | Runtime.VariableDefinition (infos, io, value) -> - logprintf + logprintf level (VarDef { log_typ = TAny; log_io_input = io.Runtime.io_input; log_io_output = io.Runtime.io_output; }) - "%a: @{%s@}" + " %a: @{%s@}" pp_infos infos (Message.unformat (fun ppf -> format_runtime_value lang ppf value)); level | Runtime.DecisionTaken rtpos -> let pos = Expr.runtime_to_pos rtpos in - logprintf PosRecordIfTrueBool - "@[@{Definition applied@}:@,%a@]@," + logprintf level PosRecordIfTrueBool + "@[@{Definition applied@}:@,%a@]@," Pos.format_loc_text pos; level diff --git a/tests/scope/good/scope_call3.catala_en b/tests/scope/good/scope_call3.catala_en index 2cd176529..d615ca94f 100644 --- a/tests/scope/good/scope_call3.catala_en +++ b/tests/scope/good/scope_call3.catala_en @@ -67,7 +67,7 @@ $ catala Interpret -t -s HousingComputation --debug 7 │ definition f of x equals (output of RentComputation).f of x │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ [LOG] ≔ RentComputation.direct. - output: RentComputation { -- f: } + output: RentComputation { -- f: } [LOG] ← RentComputation.direct [LOG] → RentComputation.f [LOG] ≔ RentComputation.f.input0: 1