Rework module includes CLI in Catala

Rather than require all files to be listed on the command-line (and having to
check consistency with `> Using` directives), the main catala CLI is now a bit
more clever.

⇒ There is a new assumption that a module name definition must match the file
name (up to case and extension) — with appropriate error handling to enforce it.

In exchange, `> Using` directives are now used to more transparently lookup the
appropriate `.catala_*` interfaces and the compiled artifacts for the used modules (handling transitive dependencies), with just standard `-I` flags for when they need to be looked up in different places.
This commit is contained in:
Louis Gesbert 2023-09-22 15:37:58 +02:00
parent 4bce4e6322
commit dbe0990163
18 changed files with 321 additions and 436 deletions

View File

@ -39,7 +39,6 @@ type options = {
mutable message_format : message_format_enum; mutable message_format : message_format_enum;
mutable trace : bool; mutable trace : bool;
mutable plugins_dirs : string list; mutable plugins_dirs : string list;
mutable build_dir : string option;
mutable disable_warnings : bool; mutable disable_warnings : bool;
mutable max_prec_digits : int; mutable max_prec_digits : int;
} }
@ -58,7 +57,6 @@ let globals =
message_format = Human; message_format = Human;
trace = false; trace = false;
plugins_dirs = []; plugins_dirs = [];
build_dir = None;
disable_warnings = false; disable_warnings = false;
max_prec_digits = 20; max_prec_digits = 20;
} }
@ -71,7 +69,6 @@ let enforce_globals
?message_format ?message_format
?trace ?trace
?plugins_dirs ?plugins_dirs
?build_dir
?disable_warnings ?disable_warnings
?max_prec_digits ?max_prec_digits
() = () =
@ -82,7 +79,6 @@ let enforce_globals
Option.iter (fun x -> globals.message_format <- x) message_format; Option.iter (fun x -> globals.message_format <- x) message_format;
Option.iter (fun x -> globals.trace <- x) trace; Option.iter (fun x -> globals.trace <- x) trace;
Option.iter (fun x -> globals.plugins_dirs <- x) plugins_dirs; Option.iter (fun x -> globals.plugins_dirs <- x) plugins_dirs;
Option.iter (fun x -> globals.build_dir <- x) build_dir;
Option.iter (fun x -> globals.disable_warnings <- x) disable_warnings; Option.iter (fun x -> globals.disable_warnings <- x) disable_warnings;
Option.iter (fun x -> globals.max_prec_digits <- x) max_prec_digits; Option.iter (fun x -> globals.max_prec_digits <- x) max_prec_digits;
globals globals
@ -112,7 +108,7 @@ module Flags = struct
| _ -> assert false ) | _ -> assert false )
in in
required required
& pos 0 (some converter) None & pos ~rev:true 0 (some converter) None
& Arg.info [] ~docv:"FILE" ~docs:Manpage.s_arguments & Arg.info [] ~docv:"FILE" ~docs:Manpage.s_arguments
~doc:"Catala master file to be compiled." ~doc:"Catala master file to be compiled."
@ -191,15 +187,6 @@ module Flags = struct
in in
value & opt_all string default & info ["plugin-dir"] ~docv:"DIR" ~env ~doc value & opt_all string default & info ["plugin-dir"] ~docv:"DIR" ~env ~doc
let build_dir =
value
& opt (some string) None
& info ["build-dir"] ~docv:"DIR"
~doc:
"Directory where build artefacts are expected to be found. This \
doesn't affect outptuts, but is used when looking up compiled \
modules."
let disable_warnings = let disable_warnings =
value value
& flag & flag
@ -223,14 +210,13 @@ module Flags = struct
message_format message_format
trace trace
plugins_dirs plugins_dirs
build_dir
disable_warnings disable_warnings
max_prec_digits : options = max_prec_digits : options =
if debug then Printexc.record_backtrace true; if debug then Printexc.record_backtrace true;
(* This sets some global refs for convenience, but most importantly (* This sets some global refs for convenience, but most importantly
returns the options record. *) returns the options record. *)
enforce_globals ~language ~debug ~color ~message_format ~trace enforce_globals ~language ~debug ~color ~message_format ~trace
~plugins_dirs ~build_dir ~disable_warnings ~max_prec_digits () ~plugins_dirs ~disable_warnings ~max_prec_digits ()
in in
Term.( Term.(
const make const make
@ -240,7 +226,6 @@ module Flags = struct
$ message_format $ message_format
$ trace $ trace
$ plugins_dirs $ plugins_dirs
$ build_dir
$ disable_warnings $ disable_warnings
$ max_prec_digits) $ max_prec_digits)
@ -253,6 +238,13 @@ module Flags = struct
Term.(const make $ input_file $ flags) Term.(const make $ input_file $ flags)
end end
let include_dirs =
value
& opt_all string []
& info ["I";"include"] ~docv:"DIR"
~doc:
"Include directory to lookup for compiled module files."
let check_invariants = let check_invariants =
value value
& flag & flag
@ -314,17 +306,6 @@ module Flags = struct
"Performs closure conversion on the lambda calculus. Implies \ "Performs closure conversion on the lambda calculus. Implies \
$(b,--avoid-exceptions) and $(b,--optimize)." $(b,--avoid-exceptions) and $(b,--optimize)."
let link_modules =
value
& opt_all file []
& info ["use"; "u"] ~docv:"FILE"
~doc:
"Specifies an additional module to be linked to the Catala program. \
$(i,FILE) must be a catala file with a metadata section expressing \
what is exported ; for interpretation, a compiled OCaml shared \
module by the same basename (either .cmo or .cmxs) will be \
expected."
let disable_counterexamples = let disable_counterexamples =
value value
& flag & flag
@ -334,6 +315,14 @@ module Flags = struct
"Disables the search for counterexamples. Useful when you want a \ "Disables the search for counterexamples. Useful when you want a \
deterministic output from the Catala compiler, since provers can \ deterministic output from the Catala compiler, since provers can \
have some randomness in them." have some randomness in them."
let build_dirs =
value
& opt_all string ["."; "_build"]
& info ["build-dir"] ~docv:"DIR"
~env:(Cmd.Env.info "CATALA_BUILD_DIR")
~doc:
"Directory where compiled modules are expected to be found (this option does not affect catala outputs)"
end end
(* Retrieve current version from dune *) (* Retrieve current version from dune *)

View File

@ -43,7 +43,6 @@ type options = private {
mutable message_format : message_format_enum; mutable message_format : message_format_enum;
mutable trace : bool; mutable trace : bool;
mutable plugins_dirs : string list; mutable plugins_dirs : string list;
mutable build_dir : string option;
mutable disable_warnings : bool; mutable disable_warnings : bool;
mutable max_prec_digits : int; mutable max_prec_digits : int;
} }
@ -63,7 +62,6 @@ val enforce_globals :
?message_format:message_format_enum -> ?message_format:message_format_enum ->
?trace:bool -> ?trace:bool ->
?plugins_dirs:string list -> ?plugins_dirs:string list ->
?build_dir:string option ->
?disable_warnings:bool -> ?disable_warnings:bool ->
?max_prec_digits:int -> ?max_prec_digits:int ->
unit -> unit ->
@ -101,8 +99,9 @@ module Flags : sig
val optimize : bool Term.t val optimize : bool Term.t
val avoid_exceptions : bool Term.t val avoid_exceptions : bool Term.t
val closure_conversion : bool Term.t val closure_conversion : bool Term.t
val link_modules : string list Term.t val include_dirs : string list Term.t
val disable_counterexamples : bool Term.t val disable_counterexamples : bool Term.t
val build_dirs : string list Term.t
end end
(** {2 Command-line application} *) (** {2 Command-line application} *)

View File

@ -117,12 +117,27 @@ let check_directory d =
if Sys.is_directory d then Some d else None if Sys.is_directory d then Some d else None
with Unix.Unix_error _ | Sys_error _ -> None with Unix.Unix_error _ | Sys_error _ -> None
let ( / ) = Filename.concat let check_file f =
try if Sys.is_directory f then None else Some f
with Unix.Unix_error _ | Sys_error _ -> None
let ( / ) a b =
if a = "" || a = Filename.current_dir_name then b
else Filename.concat a b
let dirname = Filename.dirname let dirname = Filename.dirname
let ( /../ ) a b = dirname a / b let ( /../ ) a b = dirname a / b
let ( -.- ) file ext = Filename.chop_extension file ^ "." ^ ext let ( -.- ) file ext = Filename.chop_extension file ^ "." ^ ext
let equal = String.equal
let compare = String.compare let path_to_list path =
String.split_on_char Filename.dir_sep.[0] path
|> List.filter (fun d -> d <> "")
let equal a b =
String.equal (String.lowercase_ascii a) (String.lowercase_ascii b)
let compare a b =
String.compare (String.lowercase_ascii a) (String.lowercase_ascii b)
let format ppf t = Format.fprintf ppf "\"@{<cyan>%s@}\"" t let format ppf t = Format.fprintf ppf "\"@{<cyan>%s@}\"" t
module Set = Set.Make (struct module Set = Set.Make (struct
@ -131,6 +146,13 @@ module Set = Set.Make (struct
let compare = compare let compare = compare
end) end)
module Map = Map.Make (struct
type nonrec t = t
let compare = compare
let format = format
end)
let scan_tree f t = let scan_tree f t =
let is_dir t = let is_dir t =
try Sys.is_directory t try Sys.is_directory t
@ -143,7 +165,7 @@ let scan_tree f t =
Sys.readdir d Sys.readdir d
|> Array.to_list |> Array.to_list
|> List.filter not_hidden |> List.filter not_hidden
|> (if d = "." then fun t -> t else List.map (fun t -> d / t)) |> List.map (fun t -> d / t)
|> do_files |> do_files
and do_files flist = and do_files flist =
let dirs, files = let dirs, files =
@ -154,3 +176,42 @@ let scan_tree f t =
(Seq.filter_map f (List.to_seq files)) (Seq.filter_map f (List.to_seq files))
in in
do_files [t] do_files [t]
module Tree = struct
type path = t
type item = F | D of t
and t = (path * item) Map.t Lazy.t
let empty = lazy Map.empty
let rec build path = lazy
(Array.fold_left
(fun m f ->
let path = path / f in
match Sys.is_directory path with
| true -> Map.add f (path, D (build path)) m
| false -> Map.add f (path, F) m
| exception Sys_error _ -> m)
Map.empty
(Sys.readdir path))
let subtree t path =
let rec aux t = function
| [] -> t
| dir :: path ->
match Map.find_opt dir (Lazy.force t) with
| Some (_, D sub) -> aux sub path
| Some (_, F) | None -> raise Not_found
in
aux t (path_to_list path)
let lookup t path =
try
let t = subtree t (dirname path) in
match Map.find_opt (Filename.basename path) (Lazy.force t) with
| Some (path, F) -> Some path
| Some (_, D _) | None -> None
with Not_found -> None
end

View File

@ -85,9 +85,12 @@ val check_directory : t -> t option
(** Checks if the given directory exists and returns it normalised (as per (** Checks if the given directory exists and returns it normalised (as per
[Unix.realpath]). *) [Unix.realpath]). *)
val check_file : t -> t option
(** Returns its argument if it exists and is a plain file, [None] otherwise. Does not do resolution like [check_directory]. *)
val ( / ) : t -> t -> t val ( / ) : t -> t -> t
(** [Filename.concat]: Sugar to allow writing (** [Filename.concat]: Sugar to allow writing
[File.("some" / "relative" / "path")] *) [File.("some" / "relative" / "path")]. As an exception, if the lhs is [.], returns the rhs unchanged. *)
val dirname : t -> t val dirname : t -> t
(** [Filename.dirname], re-exported for convenience *) (** [Filename.dirname], re-exported for convenience *)
@ -100,18 +103,42 @@ val ( -.- ) : t -> string -> t
with the given one (which shouldn't contain a dot) *) with the given one (which shouldn't contain a dot) *)
val equal : t -> t -> bool val equal : t -> t -> bool
(** String comparison no fancy file resolution *) (** Case-insensitive string comparison (no file resolution whatsoever) *)
val compare : t -> t -> int val compare : t -> t -> int
(** String comparison no fancy file resolution *) (** Case-insensitive string comparison (no file resolution whatsoever) *)
val format : Format.formatter -> t -> unit val format : Format.formatter -> t -> unit
(** Formats a filename in a consistent style, with double-quotes and color (when (** Formats a filename in a consistent style, with double-quotes and color (when
the output supports) *) the output supports) *)
module Set : Set.S with type elt = t module Set : Set.S with type elt = t
module Map : Map.S with type key = t
val scan_tree : (t -> 'a option) -> t -> 'a Seq.t val scan_tree : (t -> 'a option) -> t -> 'a Seq.t
(** Recursively scans a directory for files. Directories or files matching ".*" (** Recursively scans a directory for files. Directories or files matching ".*"
or "_*" are ignored. Unreadable files or subdirectories are ignored with a or "_*" are ignored. Unreadable files or subdirectories are ignored with a
debug message. If [t] is a plain file, scan just that non-recursively. *) debug message. If [t] is a plain file, scan just that non-recursively. *)
module Tree: sig
(** A lazy tree structure mirroring the filesystem ; uses the comparison from File, so paths are case-insensitive. *)
type path = t (** Alias for [File.t] *)
type item =
| F (** Plain file *)
| D of t (** Directory with subtree *)
and t = (path * item) Map.t Lazy.t
(** Contents of a directory, lazily loaded. The map keys are the basenames of the files and subdirectories, while the values contain the original path (with correct capitalisation) *)
val empty: t
val build: path -> t
(** Lazily builds a [Tree.path] from the files read at [path]. The names in the maps are qualified (i.e. they all start with ["path/"]) *)
val subtree: t -> path -> t
(** Looks up a path within a lazy tree *)
val lookup: t -> path -> path option
(** Checks if there is a matching plain file (case-insensitively) ; and returns its path with the correct case if so *)
end

View File

@ -23,7 +23,7 @@ let () =
~language:(Some language) ~debug:false ~color:Never ~trace () ~language:(Some language) ~debug:false ~color:Never ~trace ()
in in
let prg, ctx, _type_order = let prg, ctx, _type_order =
Passes.dcalc options ~link_modules:[] ~optimize:false Passes.dcalc options ~includes:File.Tree.empty ~optimize:false
~check_invariants:false ~check_invariants:false
in in
Shared_ast.Interpreter.interpret_program_dcalc prg Shared_ast.Interpreter.interpret_program_dcalc prg

View File

@ -1527,7 +1527,16 @@ let translate_program (ctxt : Name_resolution.context) (surface : S.program) :
(fun prgm child -> process_structure prgm child) (fun prgm child -> process_structure prgm child)
prgm children prgm children
| S.CodeBlock (block, _, _) -> process_code_block ctxt prgm block | S.CodeBlock (block, _, _) -> process_code_block ctxt prgm block
| S.LawInclude _ | S.LawText _ | S.ModuleUse _ | S.ModuleDef _ -> prgm | S.ModuleDef ((name, pos) as mname) ->
let file = Filename.basename (Pos.get_file pos) in
if not File.(equal name (Filename.remove_extension file))
then
Message.raise_spanned_error pos
"Module declared as %a, which does not match the file name %a"
ModuleName.format (ModuleName.of_string mname)
File.format file
else prgm
| S.LawInclude _ | S.LawText _ | S.ModuleUse _ -> prgm
in in
let desugared = let desugared =
List.fold_left List.fold_left

View File

@ -42,63 +42,68 @@ let get_lang options file =
@{<yellow>%s@}, and @{<bold>--language@} was not specified" @{<yellow>%s@}, and @{<bold>--language@} was not specified"
filename) filename)
let load_module_interfaces options program files = let load_module_interfaces includes program =
let module MS = ModuleName.Set in (* Recurse into program modules, looking up files in [using] and loading them *)
let to_set intf_list = let err_req_pos chain =
MS.of_list List.map (fun m -> Some "Module required from", ModuleName.pos m) chain
(List.map (fun (mname, _) -> ModuleName.of_string mname) intf_list) in
let find_module req_chain m =
let fname_base = ModuleName.to_string m in
let required_from_file = Pos.get_file (ModuleName.pos m) in
match
Option.to_list (File.check_file File.(required_from_file /../ fname_base)) @
List.filter_map
(fun (ext, _) ->
File.Tree.lookup includes (fname_base ^ ext))
extensions
with
| [] ->
Message.raise_multispanned_error (err_req_pos (m::req_chain))
"Required module not found: %a"
ModuleName.format m
| [f] ->
f
| ms ->
Message.raise_multispanned_error
(err_req_pos (m::req_chain))
"Required module %a matches multiple files: %a"
ModuleName.format m
(Format.pp_print_list ~pp_sep:Format.pp_print_space File.format)
ms
in in
let used_modules = to_set program.Surface.Ast.program_modules in
let load_file f = let load_file f =
let lang = get_lang options (FileName f) in let lang =
List.assoc
(List.assoc (Filename.extension f) extensions)
Cli.languages
in
let (mname, intf), using = let (mname, intf), using =
Surface.Parser_driver.load_interface (FileName f) lang Surface.Parser_driver.load_interface (Cli.FileName f) lang
in in
(ModuleName.of_string mname, intf), using (ModuleName.of_string mname, intf), using
in in
let module_interfaces = List.map load_file files in let rec aux req_chain acc modules =
let rec check (required, acc) interfaces = List.fold_left (fun acc mname ->
let required, acc, remaining = let m = ModuleName.of_string mname in
List.fold_left if List.mem_assoc m acc then acc else
(fun (required, acc, skipped) (((modname, intf), using) as modl) -> let f = find_module req_chain m in
if MS.mem modname required then let (m', intf), using = load_file f in
let required = if not (ModuleName.equal m m') then
List.fold_left Message.raise_multispanned_error
(fun req m -> MS.add (ModuleName.of_string m) req) ((Some "Module name declaration", ModuleName.pos m') ::
required using err_req_pos (m::req_chain))
in "Mismatching module name declaration:";
required, ((modname :> string Mark.pos), intf) :: acc, skipped let acc = (m', intf) :: acc in
else required, acc, modl :: skipped) aux (m::req_chain) acc using
(required, acc, []) interfaces )
in acc modules
if List.length remaining < List.length interfaces then
(* Loop until fixpoint *)
check (required, acc) remaining
else required, acc, remaining
in in
let required, loaded, unused = check (used_modules, []) module_interfaces in let program_modules =
let missing = aux [] [] (List.map fst program.Surface.Ast.program_modules)
MS.diff required |> List.map (fun (m, i) -> (m : ModuleName.t :> string Mark.pos), i)
(MS.of_list (List.map (fun (m, _) -> ModuleName.of_string m) loaded))
in in
if (not (MS.is_empty missing)) || unused <> [] then { program with
Message.raise_multispanned_error Surface.Ast.program_modules }
(List.map
(fun m ->
( Some
(Format.asprintf "Required module not found: %a"
ModuleName.format m),
ModuleName.pos m ))
(ModuleName.Set.elements missing)
@ List.map
(fun ((m, _), _) ->
( Some
(Format.asprintf "No use was found for this module: %a"
ModuleName.format m),
ModuleName.pos m ))
unused)
"Modules used from the program don't match the command-line";
loaded
module Passes = struct module Passes = struct
(* Each pass takes only its cli options, then calls upon its dependent passes (* Each pass takes only its cli options, then calls upon its dependent passes
@ -108,19 +113,19 @@ module Passes = struct
Message.emit_debug "@{<bold;magenta>=@} @{<bold>%s@} @{<bold;magenta>=@}" Message.emit_debug "@{<bold;magenta>=@} @{<bold>%s@} @{<bold;magenta>=@}"
(String.uppercase_ascii s) (String.uppercase_ascii s)
let surface options ~link_modules : Surface.Ast.program * Cli.backend_lang = let surface options ~includes : Surface.Ast.program * Cli.backend_lang =
debug_pass_name "surface"; debug_pass_name "surface";
let language = get_lang options options.input_file in let language = get_lang options options.input_file in
let prg = let prg =
Surface.Parser_driver.parse_top_level_file options.input_file language Surface.Parser_driver.parse_top_level_file options.input_file language
in in
let prg = Surface.Fill_positions.fill_pos_with_legislative_info prg in let prg = Surface.Fill_positions.fill_pos_with_legislative_info prg in
let program_modules = load_module_interfaces options prg link_modules in let prg = load_module_interfaces includes prg in
{ prg with program_modules }, language prg, language
let desugared options ~link_modules : let desugared options ~includes :
Desugared.Ast.program * Desugared.Name_resolution.context = Desugared.Ast.program * Desugared.Name_resolution.context =
let prg, _ = surface options ~link_modules in let prg, _ = surface options ~includes in
debug_pass_name "desugared"; debug_pass_name "desugared";
Message.emit_debug "Name resolution..."; Message.emit_debug "Name resolution...";
let ctx = Desugared.Name_resolution.form_context prg in let ctx = Desugared.Name_resolution.form_context prg in
@ -138,12 +143,12 @@ module Passes = struct
uids from strings. Maybe a reduced form should be included directly in uids from strings. Maybe a reduced form should be included directly in
[prg] for that purpose *) [prg] for that purpose *)
let scopelang options ~link_modules : let scopelang options ~includes :
untyped Scopelang.Ast.program untyped Scopelang.Ast.program
* Desugared.Name_resolution.context * Desugared.Name_resolution.context
* Desugared.Dependency.ExceptionsDependencies.t * Desugared.Dependency.ExceptionsDependencies.t
Desugared.Ast.ScopeDef.Map.t = Desugared.Ast.ScopeDef.Map.t =
let prg, ctx = desugared options ~link_modules in let prg, ctx = desugared options ~includes in
debug_pass_name "scopelang"; debug_pass_name "scopelang";
let exceptions_graphs = let exceptions_graphs =
Scopelang.From_desugared.build_exceptions_graph prg Scopelang.From_desugared.build_exceptions_graph prg
@ -153,11 +158,11 @@ module Passes = struct
in in
prg, ctx, exceptions_graphs prg, ctx, exceptions_graphs
let dcalc options ~link_modules ~optimize ~check_invariants : let dcalc options ~includes ~optimize ~check_invariants :
typed Dcalc.Ast.program typed Dcalc.Ast.program
* Desugared.Name_resolution.context * Desugared.Name_resolution.context
* Scopelang.Dependency.TVertex.t list = * Scopelang.Dependency.TVertex.t list =
let prg, ctx, _ = scopelang options ~link_modules in let prg, ctx, _ = scopelang options ~includes in
debug_pass_name "dcalc"; debug_pass_name "dcalc";
let type_ordering = let type_ordering =
Scopelang.Dependency.check_type_cycles prg.program_ctx.ctx_structs Scopelang.Dependency.check_type_cycles prg.program_ctx.ctx_structs
@ -193,7 +198,7 @@ module Passes = struct
let lcalc let lcalc
options options
~link_modules ~includes
~optimize ~optimize
~check_invariants ~check_invariants
~avoid_exceptions ~avoid_exceptions
@ -202,7 +207,7 @@ module Passes = struct
* Desugared.Name_resolution.context * Desugared.Name_resolution.context
* Scopelang.Dependency.TVertex.t list = * Scopelang.Dependency.TVertex.t list =
let prg, ctx, type_ordering = let prg, ctx, type_ordering =
dcalc options ~link_modules ~optimize ~check_invariants dcalc options ~includes ~optimize ~check_invariants
in in
debug_pass_name "lcalc"; debug_pass_name "lcalc";
let avoid_exceptions = avoid_exceptions || closure_conversion in let avoid_exceptions = avoid_exceptions || closure_conversion in
@ -243,7 +248,7 @@ module Passes = struct
let scalc let scalc
options options
~link_modules ~includes
~optimize ~optimize
~check_invariants ~check_invariants
~avoid_exceptions ~avoid_exceptions
@ -252,7 +257,7 @@ module Passes = struct
* Desugared.Name_resolution.context * Desugared.Name_resolution.context
* Scopelang.Dependency.TVertex.t list = * Scopelang.Dependency.TVertex.t list =
let prg, ctx, type_ordering = let prg, ctx, type_ordering =
lcalc options ~link_modules ~optimize ~check_invariants ~avoid_exceptions lcalc options ~includes ~optimize ~check_invariants ~avoid_exceptions
~closure_conversion ~closure_conversion
in in
debug_pass_name "scalc"; debug_pass_name "scalc";
@ -353,6 +358,14 @@ module Commands = struct
second_part first_part ScopeName.format scope_uid) second_part first_part ScopeName.format scope_uid)
second_part ) second_part )
let include_flags =
let mk dirs =
lazy (dirs
|> List.map (fun d -> Lazy.force (File.Tree.build d))
|> List.fold_left (File.Map.union (fun _ x _ -> Some x)) File.Map.empty)
in
Term.(const mk $ Cli.Flags.include_dirs)
let get_output ?ext options output_file = let get_output ?ext options output_file =
File.get_out_channel ~source_file:options.Cli.input_file ~output_file ?ext File.get_out_channel ~source_file:options.Cli.input_file ~output_file ?ext
() ()
@ -362,7 +375,7 @@ module Commands = struct
~output_file ?ext () ~output_file ?ext ()
let makefile options output = let makefile options output =
let prg, _ = Passes.surface options ~link_modules:[] in let prg, _ = Passes.surface options ~includes:File.Tree.empty in
let backend_extensions_list = [".tex"] in let backend_extensions_list = [".tex"] in
let source_file = let source_file =
match options.Cli.input_file with match options.Cli.input_file with
@ -393,7 +406,7 @@ module Commands = struct
Term.(const makefile $ Cli.Flags.Global.options $ Cli.Flags.output) Term.(const makefile $ Cli.Flags.Global.options $ Cli.Flags.output)
let html options output print_only_law wrap_weaved_output = let html options output print_only_law wrap_weaved_output =
let prg, language = Passes.surface options ~link_modules:[] in let prg, language = Passes.surface options ~includes:File.Tree.empty in
Message.emit_debug "Weaving literate program into HTML"; Message.emit_debug "Weaving literate program into HTML";
let output_file, with_output = let output_file, with_output =
get_output_format options ~ext:".html" output get_output_format options ~ext:".html" output
@ -421,7 +434,7 @@ module Commands = struct
$ Cli.Flags.wrap_weaved_output) $ Cli.Flags.wrap_weaved_output)
let latex options output print_only_law wrap_weaved_output = let latex options output print_only_law wrap_weaved_output =
let prg, language = Passes.surface options ~link_modules:[] in let prg, language = Passes.surface options ~includes:File.Tree.empty in
Message.emit_debug "Weaving literate program into LaTeX"; Message.emit_debug "Weaving literate program into LaTeX";
let output_file, with_output = let output_file, with_output =
get_output_format options ~ext:".tex" output get_output_format options ~ext:".tex" output
@ -448,8 +461,8 @@ module Commands = struct
$ Cli.Flags.print_only_law $ Cli.Flags.print_only_law
$ Cli.Flags.wrap_weaved_output) $ Cli.Flags.wrap_weaved_output)
let exceptions options link_modules ex_scope ex_variable = let exceptions options includes ex_scope ex_variable =
let _, ctxt, exceptions_graphs = Passes.scopelang options ~link_modules in let _, ctxt, exceptions_graphs = Passes.scopelang options ~includes in
let scope_uid = get_scope_uid ctxt ex_scope in let scope_uid = get_scope_uid ctxt ex_scope in
let variable_uid = get_variable_uid ctxt scope_uid ex_variable in let variable_uid = get_variable_uid ctxt scope_uid ex_variable in
Desugared.Print.print_exceptions_graph scope_uid variable_uid Desugared.Print.print_exceptions_graph scope_uid variable_uid
@ -467,12 +480,12 @@ module Commands = struct
Term.( Term.(
const exceptions const exceptions
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.ex_scope $ Cli.Flags.ex_scope
$ Cli.Flags.ex_variable) $ Cli.Flags.ex_variable)
let scopelang options link_modules output ex_scope_opt = let scopelang options includes output ex_scope_opt =
let prg, ctx, _ = Passes.scopelang options ~link_modules in let prg, ctx, _ = Passes.scopelang options ~includes in
let _output_file, with_output = get_output_format options output in let _output_file, with_output = get_output_format options output in
with_output with_output
@@ fun fmt -> @@ fun fmt ->
@ -496,12 +509,12 @@ module Commands = struct
Term.( Term.(
const scopelang const scopelang
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.ex_scope_opt) $ Cli.Flags.ex_scope_opt)
let typecheck options link_modules = let typecheck options includes =
let prg, _, _ = Passes.scopelang options ~link_modules in let prg, _, _ = Passes.scopelang options ~includes in
Message.emit_debug "Typechecking..."; Message.emit_debug "Typechecking...";
let _type_ordering = let _type_ordering =
Scopelang.Dependency.check_type_cycles prg.program_ctx.ctx_structs Scopelang.Dependency.check_type_cycles prg.program_ctx.ctx_structs
@ -519,11 +532,11 @@ module Commands = struct
Cmd.v Cmd.v
(Cmd.info "typecheck" (Cmd.info "typecheck"
~doc:"Parses and typechecks a Catala program, without interpreting it.") ~doc:"Parses and typechecks a Catala program, without interpreting it.")
Term.(const typecheck $ Cli.Flags.Global.options $ Cli.Flags.link_modules) Term.(const typecheck $ Cli.Flags.Global.options $ include_flags)
let dcalc options link_modules output optimize ex_scope_opt check_invariants = let dcalc options includes output optimize ex_scope_opt check_invariants =
let prg, ctx, _ = let prg, ctx, _ =
Passes.dcalc options ~link_modules ~optimize ~check_invariants Passes.dcalc options ~includes ~optimize ~check_invariants
in in
let _output_file, with_output = get_output_format options output in let _output_file, with_output = get_output_format options output in
with_output with_output
@ -560,7 +573,7 @@ module Commands = struct
Term.( Term.(
const dcalc const dcalc
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.ex_scope_opt $ Cli.Flags.ex_scope_opt
@ -568,13 +581,13 @@ module Commands = struct
let proof let proof
options options
link_modules includes
optimize optimize
ex_scope_opt ex_scope_opt
check_invariants check_invariants
disable_counterexamples = disable_counterexamples =
let prg, ctx, _ = let prg, ctx, _ =
Passes.dcalc options ~link_modules ~optimize ~check_invariants Passes.dcalc options ~includes ~optimize ~check_invariants
in in
Verification.Globals.setup ~optimize ~disable_counterexamples; Verification.Globals.setup ~optimize ~disable_counterexamples;
let vcs = let vcs =
@ -592,7 +605,7 @@ module Commands = struct
Term.( Term.(
const proof const proof
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.ex_scope_opt $ Cli.Flags.ex_scope_opt
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
@ -621,11 +634,11 @@ module Commands = struct
result) result)
results results
let interpret_dcalc options link_modules optimize check_invariants ex_scope = let interpret_dcalc options includes optimize check_invariants build_dirs ex_scope =
let prg, ctx, _ = let prg, ctx, _ =
Passes.dcalc options ~link_modules ~optimize ~check_invariants Passes.dcalc options ~includes ~optimize ~check_invariants
in in
Interpreter.load_runtime_modules prg; Interpreter.load_runtime_modules ~build_dirs prg;
print_interpretation_results options Interpreter.interpret_program_dcalc prg print_interpretation_results options Interpreter.interpret_program_dcalc prg
(get_scope_uid ctx ex_scope) (get_scope_uid ctx ex_scope)
@ -639,14 +652,15 @@ module Commands = struct
Term.( Term.(
const interpret_dcalc const interpret_dcalc
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
$ Cli.Flags.build_dirs
$ Cli.Flags.ex_scope) $ Cli.Flags.ex_scope)
let lcalc let lcalc
options options
link_modules includes
output output
optimize optimize
check_invariants check_invariants
@ -654,7 +668,7 @@ module Commands = struct
closure_conversion closure_conversion
ex_scope_opt = ex_scope_opt =
let prg, ctx, _ = let prg, ctx, _ =
Passes.lcalc options ~link_modules ~optimize ~check_invariants Passes.lcalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
let _output_file, with_output = get_output_format options output in let _output_file, with_output = get_output_format options output in
@ -680,7 +694,7 @@ module Commands = struct
Term.( Term.(
const lcalc const lcalc
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
@ -690,16 +704,18 @@ module Commands = struct
let interpret_lcalc let interpret_lcalc
options options
link_modules includes
optimize optimize
check_invariants check_invariants
avoid_exceptions avoid_exceptions
closure_conversion closure_conversion
build_dirs
ex_scope = ex_scope =
let prg, ctx, _ = let prg, ctx, _ =
Passes.lcalc options ~link_modules ~optimize ~check_invariants Passes.lcalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
Interpreter.load_runtime_modules ~build_dirs prg;
print_interpretation_results options Interpreter.interpret_program_lcalc prg print_interpretation_results options Interpreter.interpret_program_lcalc prg
(get_scope_uid ctx ex_scope) (get_scope_uid ctx ex_scope)
@ -713,23 +729,24 @@ module Commands = struct
Term.( Term.(
const interpret_lcalc const interpret_lcalc
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
$ Cli.Flags.avoid_exceptions $ Cli.Flags.avoid_exceptions
$ Cli.Flags.closure_conversion $ Cli.Flags.closure_conversion
$ Cli.Flags.build_dirs
$ Cli.Flags.ex_scope) $ Cli.Flags.ex_scope)
let ocaml let ocaml
options options
link_modules includes
output output
optimize optimize
check_invariants check_invariants
avoid_exceptions avoid_exceptions
closure_conversion = closure_conversion =
let prg, _, type_ordering = let prg, _, type_ordering =
Passes.lcalc options ~link_modules ~optimize ~check_invariants Passes.lcalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
let output_file, with_output = let output_file, with_output =
@ -749,7 +766,7 @@ module Commands = struct
Term.( Term.(
const ocaml const ocaml
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
@ -758,7 +775,7 @@ module Commands = struct
let scalc let scalc
options options
link_modules includes
output output
optimize optimize
check_invariants check_invariants
@ -766,7 +783,7 @@ module Commands = struct
closure_conversion closure_conversion
ex_scope_opt = ex_scope_opt =
let prg, ctx, _ = let prg, ctx, _ =
Passes.scalc options ~link_modules ~optimize ~check_invariants Passes.scalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
let _output_file, with_output = get_output_format options output in let _output_file, with_output = get_output_format options output in
@ -795,7 +812,7 @@ module Commands = struct
Term.( Term.(
const scalc const scalc
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
@ -805,14 +822,14 @@ module Commands = struct
let python let python
options options
link_modules includes
output output
optimize optimize
check_invariants check_invariants
avoid_exceptions avoid_exceptions
closure_conversion = closure_conversion =
let prg, _, type_ordering = let prg, _, type_ordering =
Passes.scalc options ~link_modules ~optimize ~check_invariants Passes.scalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
@ -832,17 +849,17 @@ module Commands = struct
Term.( Term.(
const python const python
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
$ Cli.Flags.avoid_exceptions $ Cli.Flags.avoid_exceptions
$ Cli.Flags.closure_conversion) $ Cli.Flags.closure_conversion)
let r options link_modules output optimize check_invariants closure_conversion let r options includes output optimize check_invariants closure_conversion
= =
let prg, _, type_ordering = let prg, _, type_ordering =
Passes.scalc options ~link_modules ~optimize ~check_invariants Passes.scalc options ~includes ~optimize ~check_invariants
~avoid_exceptions:false ~closure_conversion ~avoid_exceptions:false ~closure_conversion
in in
@ -858,7 +875,7 @@ module Commands = struct
Term.( Term.(
const r const r
$ Cli.Flags.Global.options $ Cli.Flags.Global.options
$ Cli.Flags.link_modules $ include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants

View File

@ -27,24 +27,24 @@ val main : unit -> unit
module Passes : sig module Passes : sig
val surface : val surface :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
Surface.Ast.program * Cli.backend_lang Surface.Ast.program * Cli.backend_lang
val desugared : val desugared :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
Desugared.Ast.program * Desugared.Name_resolution.context Desugared.Ast.program * Desugared.Name_resolution.context
val scopelang : val scopelang :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
Shared_ast.untyped Scopelang.Ast.program Shared_ast.untyped Scopelang.Ast.program
* Desugared.Name_resolution.context * Desugared.Name_resolution.context
* Desugared.Dependency.ExceptionsDependencies.t Desugared.Ast.ScopeDef.Map.t * Desugared.Dependency.ExceptionsDependencies.t Desugared.Ast.ScopeDef.Map.t
val dcalc : val dcalc :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
optimize:bool -> optimize:bool ->
check_invariants:bool -> check_invariants:bool ->
Shared_ast.typed Dcalc.Ast.program Shared_ast.typed Dcalc.Ast.program
@ -53,7 +53,7 @@ module Passes : sig
val lcalc : val lcalc :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
optimize:bool -> optimize:bool ->
check_invariants:bool -> check_invariants:bool ->
avoid_exceptions:bool -> avoid_exceptions:bool ->
@ -64,7 +64,7 @@ module Passes : sig
val scalc : val scalc :
Cli.options -> Cli.options ->
link_modules:string list -> includes:File.Tree.t ->
optimize:bool -> optimize:bool ->
check_invariants:bool -> check_invariants:bool ->
avoid_exceptions:bool -> avoid_exceptions:bool ->
@ -99,6 +99,8 @@ module Commands : sig
string -> string ->
Desugared.Ast.ScopeDef.t Desugared.Ast.ScopeDef.t
val include_flags : File.Tree.t Cmdliner.Term.t
val commands : unit Cmdliner.Cmd.t list val commands : unit Cmdliner.Cmd.t list
(** The list of built-in catala subcommands, as expected by (** The list of built-in catala subcommands, as expected by
[Cmdliner.Cmd.group] *) [Cmdliner.Cmd.group] *)

View File

@ -428,7 +428,7 @@ module To_jsoo = struct
end end
let run let run
link_modules includes
output output
optimize optimize
check_invariants check_invariants
@ -438,7 +438,7 @@ let run
if not options.Cli.trace then if not options.Cli.trace then
Message.raise_error "This plugin requires the --trace flag."; Message.raise_error "This plugin requires the --trace flag.";
let prg, _, type_ordering = let prg, _, type_ordering =
Driver.Passes.lcalc options ~link_modules ~optimize ~check_invariants Driver.Passes.lcalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
let modname = let modname =
@ -472,7 +472,7 @@ let run
let term = let term =
let open Cmdliner.Term in let open Cmdliner.Term in
const run const run
$ Cli.Flags.link_modules $ Driver.Commands.include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants

View File

@ -38,13 +38,6 @@
(flags (-linkall)) (flags (-linkall))
(libraries shared_ast catala.driver ocamlgraph)) (libraries shared_ast catala.driver ocamlgraph))
(library
(name modules)
(public_name catala.plugins.modules)
(synopsis "Catala plugin for experimental module handling tooling")
(modules modules)
(libraries shared_ast catala.driver))
(documentation (documentation
(package catala) (package catala)
(mld_files plugins)) (mld_files plugins))

View File

@ -1385,12 +1385,12 @@ let options =
$ Cli.Flags.output $ Cli.Flags.output
$ base_src_url) $ base_src_url)
let run link_modules optimize ex_scope explain_options global_options = let run includes optimize ex_scope explain_options global_options =
let prg, ctx, _ = let prg, ctx, _ =
Driver.Passes.dcalc global_options ~link_modules ~optimize Driver.Passes.dcalc global_options ~includes ~optimize
~check_invariants:false ~check_invariants:false
in in
Interpreter.load_runtime_modules prg; Interpreter.load_runtime_modules ~includes prg;
let scope = Driver.Commands.get_scope_uid ctx ex_scope in let scope = Driver.Commands.get_scope_uid ctx ex_scope in
(* let result_expr, env = interpret_program prg scope in *) (* let result_expr, env = interpret_program prg scope in *)
let g, base_vars, env = program_to_graph explain_options prg scope in let g, base_vars, env = program_to_graph explain_options prg scope in
@ -1436,7 +1436,7 @@ let run link_modules optimize ex_scope explain_options global_options =
let term = let term =
let open Cmdliner.Term in let open Cmdliner.Term in
const run const run
$ Cli.Flags.link_modules $ Driver.Commands.include_flags
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.ex_scope $ Cli.Flags.ex_scope
$ options $ options

View File

@ -206,7 +206,7 @@ module To_json = struct
end end
let run let run
link_modules includes
output output
optimize optimize
check_invariants check_invariants
@ -215,7 +215,7 @@ let run
ex_scope ex_scope
options = options =
let prg, ctx, _ = let prg, ctx, _ =
Driver.Passes.lcalc options ~link_modules ~optimize ~check_invariants Driver.Passes.lcalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
let output_file, with_output = let output_file, with_output =
@ -233,7 +233,7 @@ let run
let term = let term =
let open Cmdliner.Term in let open Cmdliner.Term in
const run const run
$ Cli.Flags.link_modules $ Driver.Commands.include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants

View File

@ -257,11 +257,11 @@ let interpret_program (prg : ('dcalc, 'm) gexpr program) (scope : ScopeName.t) :
(* -- Plugin registration -- *) (* -- Plugin registration -- *)
let run link_modules optimize check_invariants ex_scope options = let run includes optimize check_invariants ex_scope options =
let prg, ctx, _ = let prg, ctx, _ =
Driver.Passes.dcalc options ~link_modules ~optimize ~check_invariants Driver.Passes.dcalc options ~includes ~optimize ~check_invariants
in in
Interpreter.load_runtime_modules prg; Interpreter.load_runtime_modules ~includes prg;
let scope = Driver.Commands.get_scope_uid ctx ex_scope in let scope = Driver.Commands.get_scope_uid ctx ex_scope in
let result_expr, _env = interpret_program prg scope in let result_expr, _env = interpret_program prg scope in
let fmt = Format.std_formatter in let fmt = Format.std_formatter in
@ -270,7 +270,7 @@ let run link_modules optimize check_invariants ex_scope options =
let term = let term =
let open Cmdliner.Term in let open Cmdliner.Term in
const run const run
$ Cli.Flags.link_modules $ Driver.Commands.include_flags
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants
$ Cli.Flags.ex_scope $ Cli.Flags.ex_scope

View File

@ -1,233 +0,0 @@
(* This file is part of the Catala compiler, a specification language for tax
and social benefits computation rules. Copyright (C) 2020 Inria, contributor:
Louis Gesbert <louis.gesbert@inria.fr>.
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License. *)
open Catala_utils
let action_flag =
let docs = "ACTIONS" in
let open Cmdliner.Arg in
required
& vflag None
[
( Some `Compile,
info ["compile"] ~docs
~doc:
"Compiles a Catala file into a module: a $(b,.cmxs) file that \
can be used by the Catala interpreter."
(* "and $(b,cmo) and $(b,cmx) files that can be linked into an OCaml
program" *) );
( Some `Link,
info ["link"] ~docs
~doc:
"Compiles and links a catala program into a binary (using the \
ocaml backend). Specify a main scope using the $(b,--scope) \
flag to be run upon execution. This is still pretty useless at \
the moment besides for testing purposes, as there is no way to \
feed input to the generated program, and the output will be \
silent. Assertions will be checked, though." );
]
let gen_ocaml options link_modules optimize check_invariants main =
let prg, ctx, type_ordering =
Driver.Passes.lcalc options ~link_modules ~optimize ~check_invariants
~avoid_exceptions:false ~closure_conversion:false
in
let exec_scope = Option.map (Driver.Commands.get_scope_uid ctx) main in
let filename, with_output =
Driver.Commands.get_output_format options ~ext:".ml" None
in
with_output
@@ fun ppf ->
Lcalc.To_ocaml.format_program ppf ?exec_scope prg type_ordering;
Option.get filename
let run_process cmd args =
Message.emit_debug "Running @[<hov 4>@{<yellow>@{<bold>%s@} %a@}@}@]" cmd
(Format.pp_print_list ~pp_sep:Format.pp_print_space Format.pp_print_string)
args;
match
Unix.waitpid []
(Unix.create_process cmd
(Array.of_list (cmd :: args))
Unix.stdin Unix.stdout Unix.stderr)
with
| _, Unix.WEXITED 0 -> ()
| _, _ -> Message.raise_error "Child process @{<bold>%s@} failed" cmd
let with_flag flag args =
List.fold_right (fun p acc -> flag :: p :: acc) args []
let ocaml_libdir =
lazy
(try String.trim (File.process_out "opam" ["var"; "lib"])
with Failure _ -> (
try String.trim (File.process_out "ocamlc" ["-where"])
with Failure _ -> (
match File.(check_directory (dirname Sys.argv.(0) /../ "lib")) with
| Some d -> d
| None ->
Message.raise_error
"Could not locate the OCaml library directory, make sure OCaml or \
opam is installed")))
let rec find_catala_project_root dir =
if Sys.file_exists File.(dir / "catala.opam") then Some dir
else
let dir' = File.dirname dir in
if dir' = dir then None else find_catala_project_root dir'
let runtime_dir =
lazy
(let d =
match find_catala_project_root (Sys.getcwd ()) with
| Some root ->
(* Relative dir when running from catala source *)
File.(
root
/ "_build"
/ "install"
/ "default"
/ "lib"
/ "catala"
/ "runtime_ocaml")
| None -> (
match
File.check_directory
File.(dirname Sys.argv.(0) /../ "lib" / "catala" / "runtime_ocaml")
with
| Some d -> d
| None -> File.(Lazy.force ocaml_libdir / "catala" / "runtime"))
in
match File.check_directory d with
| Some dir ->
Message.emit_debug "Catala runtime libraries found at @{<bold>%s@}." dir;
dir
| None ->
Message.raise_error
"@[<hov>Could not locate the Catala runtime library.@ Make sure that \
either catala is correctly installed,@ or you are running from the \
root of a compiled source tree.@]")
let compile options link_modules optimize check_invariants =
let modname =
match options.Cli.input_file with
(* TODO: extract module name from directives *)
| FileName n -> Driver.modname_of_file n
| _ -> Message.raise_error "Input must be a file name for this command"
in
let basename = String.uncapitalize_ascii modname in
let ml_file = gen_ocaml options link_modules optimize check_invariants None in
let flags = ["-I"; Lazy.force runtime_dir] in
let shared_out = File.((ml_file /../ basename) ^ ".cmxs") in
Message.emit_debug "Compiling OCaml shared object file @{<bold>%s@}..."
shared_out;
run_process "ocamlopt" ("-shared" :: ml_file :: "-o" :: shared_out :: flags);
(* let byte_out = basename ^ ".cmo" in
* Message.emit_debug "Compiling OCaml byte-code object file @{<bold>%s@}..." byte_out;
* run_process "ocamlc" ("-c" :: ml_file :: "-o" :: byte_out :: flags);
* let native_out = basename ^ ".cmx" in
* Message.emit_debug "Compiling OCaml native object file @{<bold>%s@}..." native_out;
* run_process "ocamlopt" ("-c" :: ml_file :: "-o" :: native_out ::flags); *)
Message.emit_debug "Done."
let link options link_modules optimize check_invariants output ex_scope_opt =
let ml_file =
gen_ocaml options link_modules optimize check_invariants ex_scope_opt
in
(* NOTE: assuming native target at the moment *)
let cmd = "ocamlopt" in
let ocaml_libdir = Lazy.force ocaml_libdir in
let runtime_dir = Lazy.force runtime_dir in
(* Recursive dependencies are expanded manually here. A shorter version would
use [ocamlfind ocalmopt -linkpkg -package] with just ppx_yojson_conv_lib,
zarith and dates_calc *)
let link_libs =
[
"biniou";
"easy-format";
"yojson";
"ppx_yojson_conv_lib";
"zarith";
"dates_calc";
]
in
let link_libdirs =
List.map
(fun lib ->
match File.(check_directory (ocaml_libdir / lib)) with
| None ->
Message.raise_error
"Required OCaml library not found at @{<bold>%s@}.@ Try `opam \
install %s'"
File.(ocaml_libdir / lib)
lib
| Some l -> l)
link_libs
in
let runtime_lib = File.(runtime_dir / "runtime_ocaml.cmxa") in
let modules =
List.map (fun m -> Filename.remove_extension m ^ ".cmx") link_modules
in
let output =
match output with
| Some o -> o
| None -> Filename.remove_extension ml_file ^ ".exe"
in
let args =
with_flag "-I" link_libdirs
@ with_flag "-I"
(List.sort_uniq compare (List.map Filename.dirname modules))
@ List.map
(fun lib -> String.map (function '-' -> '_' | c -> c) lib ^ ".cmxa")
link_libs
@ ("-I" :: runtime_dir :: runtime_lib :: modules)
@ [ml_file; "-o"; output]
in
run_process cmd args;
Message.emit_result "Successfully generated @{<bold>%s@}" output
(* Compile from ml and link the modules cmx. => ocamlfind ocamlopt -linkpkg
-package ppx_yojson_conv_lib -package zarith -package dates_calc -I
_build/default/runtimes/ocaml/.runtime_ocaml.objs/byte
_build/default/runtimes/ocaml/runtime_ocaml.cmxa ext.cmx extuse.ml *)
let run
action
link_modules
optimize
check_invariants
output
ex_scope_opt
options =
match action with
| `Compile -> compile options link_modules optimize check_invariants
| `Link ->
link options link_modules optimize check_invariants ex_scope_opt output
let term =
let open Cmdliner.Term in
const run
$ action_flag
$ Cli.Flags.link_modules
$ Cli.Flags.optimize
$ Cli.Flags.check_invariants
$ Cli.Flags.ex_scope_opt
$ Cli.Flags.output
let () =
Driver.Plugin.register "module" term
~doc:
"This plugin provides a few experimental tools related to module \
generation and compilation"

View File

@ -23,7 +23,7 @@
open Catala_utils open Catala_utils
let run let run
link_modules includes
output output
optimize optimize
check_invariants check_invariants
@ -32,7 +32,7 @@ let run
options = options =
let open Driver.Commands in let open Driver.Commands in
let prg, _, type_ordering = let prg, _, type_ordering =
Driver.Passes.scalc options ~link_modules ~optimize ~check_invariants Driver.Passes.scalc options ~includes ~optimize ~check_invariants
~avoid_exceptions ~closure_conversion ~avoid_exceptions ~closure_conversion
in in
@ -45,7 +45,7 @@ let run
let term = let term =
let open Cmdliner.Term in let open Cmdliner.Term in
const run const run
$ Cli.Flags.link_modules $ Driver.Commands.include_flags
$ Cli.Flags.output $ Cli.Flags.output
$ Cli.Flags.optimize $ Cli.Flags.optimize
$ Cli.Flags.check_invariants $ Cli.Flags.check_invariants

View File

@ -940,29 +940,47 @@ let interpret_program_dcalc p s : (Uid.MarkedString.info * ('a, 'm) gexpr) list
reflect that. *) reflect that. *)
let evaluate_expr ctx lang e = delcustom (evaluate_expr ctx lang (addcustom e)) let evaluate_expr ctx lang e = delcustom (evaluate_expr ctx lang (addcustom e))
let load_runtime_modules prg = let load_runtime_modules ~build_dirs prg =
match ModuleName.Map.keys prg.decl_ctx.ctx_modules with let load m =
| [] -> () let obj_base =
| modules -> Dynlink.adapt_filename File.(Pos.get_file (ModuleName.pos m) /../ ModuleName.to_string m ^ ".cmo")
Message.emit_debug "Loading shared modules... %a" in
(fun ppf -> ModuleName.Map.format_keys ppf) let possible_files = List.map File.(fun d -> d / obj_base) build_dirs in
prg.decl_ctx.ctx_modules; match List.filter Sys.file_exists possible_files with
List.iter | [] ->
(fun m -> Message.raise_spanned_error
let srcfile = Pos.get_file (ModuleName.pos m) in ~span_msg:(fun ppf -> Format.pp_print_string ppf "Module defined here")
let obj_file = (ModuleName.pos m)
File.((srcfile /../ ModuleName.to_string m) ^ ".cmo") "Compiled OCaml object %a not found. Make sure it has been suitably compiled, and use @{<blue>-I DIR@} if necessary." File.format obj_base
|> Dynlink.adapt_filename | [f] ->
in (try Dynlink.loadfile f
let obj_file = with Dynlink.Error dl_err ->
match Cli.globals.build_dir with Message.raise_error
| None -> obj_file "Error loading compiled module from %a:@;\
| Some d -> File.(d / obj_file) <1 2>@[<hov>%a@]" File.format f
in Format.pp_print_text
try Dynlink.loadfile obj_file (Dynlink.error_message dl_err))
with Dynlink.Error dl_err -> | fs ->
Message.raise_error Message.raise_spanned_error
"Could not load module %a, has it been suitably compiled?@;\ ~span_msg:(fun ppf -> Format.pp_print_string ppf "Module defined here")
<1 2>@[<hov>%a@]" ModuleName.format m Format.pp_print_text (ModuleName.pos m)
(Dynlink.error_message dl_err)) "@[<v>Multiple compiled OCaml objects for %a found:@,- %a@]"
modules ModuleName.format m
(Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "@,- ")
File.format)
fs
in
let rec aux loaded decl_ctx =
ModuleName.Map.fold (fun mname sub_decl_ctx loaded ->
if ModuleName.Set.mem mname loaded then loaded else
let loaded = ModuleName.Set.add mname loaded in
let loaded = aux loaded sub_decl_ctx in
load mname;
loaded)
decl_ctx.ctx_modules loaded
in
Message.emit_debug "Loading shared modules... %a"
(fun ppf -> ModuleName.Map.format_keys ppf)
prg.decl_ctx.ctx_modules;
let (_loaded: ModuleName.Set.t) = aux ModuleName.Set.empty prg.decl_ctx in
()

View File

@ -72,8 +72,8 @@ val interpret_program_lcalc :
providing for each argument a thunked empty default. Returns a list of all providing for each argument a thunked empty default. Returns a list of all
the computed values for the scope variables of the executed scope. *) the computed values for the scope variables of the executed scope. *)
val load_runtime_modules : _ program -> unit val load_runtime_modules : build_dirs:File.t list -> _ program -> unit
(** Dynlink the runtime modules required by the given program, in order to make (** Dynlink the runtime modules required by the given program, in order to make
them callable by the interpreter. If Cli.globals.build_dir is specified, the them callable by the interpreter.
runtime module names (as obtained by looking up the positions in the
program's module bindings) are assumed to be relative and looked up there. *) The specified build dirs are used as prefixes to the catala files defining the modules: with {[["."; "_build"]]}, this means that the compiled artifact of [foo/bar.catala_en] will be searched in [foo/bar.cmxs] and [_build/foo/bar.cmxs] *)

View File

@ -273,7 +273,10 @@ and expand_includes
match command with match command with
| Ast.ModuleDef id -> ( | Ast.ModuleDef id -> (
match acc.Ast.program_module_name with match acc.Ast.program_module_name with
| None -> { acc with Ast.program_module_name = Some id } | None ->
{ acc with Ast.program_module_name = Some id;
Ast.program_items = command :: acc.Ast.program_items;
}
| Some id2 -> | Some id2 ->
Message.raise_multispanned_error Message.raise_multispanned_error
[None, Mark.get id; None, Mark.get id2] [None, Mark.get id; None, Mark.get id2]