diff --git a/CHANGES.md b/CHANGES.md index 7c0d422f..4eec8a42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,9 +12,10 @@ details. - Add initial OCaml 5.3 support (#487, @NathanReb, @hhugo, @nojb) -- Initialise OCaml 5.3's lexer with the `keywords` setting from `OCAMLPARAM` to - allow the standalone ppx driver to process old packages using `effect` as an - identifier (#535, @dra27) +- Initialise OCaml 5.3's lexer with the `keywords` setting from `OCAMLPARAM` or + the new `-keywords` driver's CLI option to allow the standalone ppx driver to + process old packages using `effect` as an identifier + (#535, @dra27, @NathanReb) ### Other changes diff --git a/astlib/keyword.ml b/astlib/keyword.ml index e2beb24a..7822e4e0 100644 --- a/astlib/keyword.ml +++ b/astlib/keyword.ml @@ -58,26 +58,43 @@ let is_keyword = function | "asr" -> true | _ -> false -let apply_keyword_edition () = - match Sys.getenv "OCAMLPARAM" with - | s -> - let items = - if String.equal s "" then [] - else - (* cf. Compenv.parse_args *) - match s.[0] with - | (':' | '|' | ';' | ' ' | ',') as c -> - List.tl (String.split_on_char c s) - | _ -> String.split_on_char ',' s - in - let fold_settings acc item = - let len = String.length item in - if len >= 9 && String.sub item 0 9 = "keywords=" then - Some (String.sub item 9 (len - 9)) - else acc - in - let keyword_edition = List.fold_left fold_settings None items in - (*IF_AT_LEAST 503 let () = if Option.is_some keyword_edition then Clflags.keyword_edition := keyword_edition in*) - (*IF_NOT_AT_LEAST 503 let () = ignore keyword_edition in*) - () - | exception Not_found -> () +let apply_keyword_edition ~cli () = + let from_ocaml_param = + match Sys.getenv "OCAMLPARAM" with + | s -> + let items = + if String.equal s "" then [] + else + (* cf. Compenv.parse_args *) + match s.[0] with + | (':' | '|' | ';' | ' ' | ',') as c -> + List.tl (String.split_on_char c s) + | _ -> String.split_on_char ',' s + in + let fold_settings (acc, after_cli) item = + match (item, acc) with + | "_", None -> (acc, true) + | _ -> + let len = String.length item in + if len >= 9 && String.sub item 0 9 = "keywords=" then + (Some (String.sub item 9 (len - 9)), after_cli) + else (acc, after_cli) + in + let from_ocaml_param, after_cli = + List.fold_left fold_settings (None, false) items + in + (match from_ocaml_param with + | None -> None + | Some s -> Some (s, after_cli)) + | exception Not_found -> None + in + let keyword_edition = + match cli, from_ocaml_param with + | None, None -> None + | None, Some (s, _) + | Some _, Some (s, true) -> Some s + | _ -> cli + in + (*IF_AT_LEAST 503 let () = if Option.is_some keyword_edition then Clflags.keyword_edition := keyword_edition in*) + (*IF_NOT_AT_LEAST 503 let () = ignore keyword_edition in*) + () diff --git a/astlib/keyword.mli b/astlib/keyword.mli index a7977796..00dd5067 100644 --- a/astlib/keyword.mli +++ b/astlib/keyword.mli @@ -1,6 +1,7 @@ val is_keyword : string -> bool (** Check if a string is an OCaml keyword. *) -val apply_keyword_edition : unit -> unit +val apply_keyword_edition : cli:string option -> unit -> unit (** Processes any keywords= sections from the OCAMLPARAM environment variable - and initialises the compiler's lexer with the correct keyword set. *) + and CLI option and initialises the compiler's lexer with the correct keyword + set. *) diff --git a/src/driver.ml b/src/driver.ml index ac20acf0..47614851 100644 --- a/src/driver.ml +++ b/src/driver.ml @@ -24,6 +24,7 @@ let pretty = ref false let styler = ref None let output_metadata_filename = ref None let corrected_suffix = ref ".ppx-corrected" +let keywords = ref None let ghost = object @@ -1409,6 +1410,14 @@ let standalone_args = ( "-corrected-suffix", Arg.Set_string corrected_suffix, "SUFFIX Suffix to append to corrected files" ); + ( "-keywords", + Arg.String (fun s -> keywords := Some s), + " Set keywords according to the version+list \ + specification. Allows using a set of keywords different from the one of \ + the current compiler for backword compatibility." ); + ( "--keywords", + Arg.String (fun s -> keywords := Some s), + " Same as -keywords" ); ] let get_args ?(standalone_args = standalone_args) () = @@ -1417,8 +1426,8 @@ let get_args ?(standalone_args = standalone_args) () = let standalone_main () = let usage = Printf.sprintf "%s [extra_args] []" exe_name in let args = get_args () in - Astlib.Keyword.apply_keyword_edition (); Arg.parse (Arg.align args) set_input usage; + Astlib.Keyword.apply_keyword_edition ~cli:(!keywords) (); interpret_mask (); if !request_print_transformations then ( print_transformations (); diff --git a/test/driver/keywords-option/driver.ml b/test/driver/keywords-option/driver.ml new file mode 100644 index 00000000..e3cba404 --- /dev/null +++ b/test/driver/keywords-option/driver.ml @@ -0,0 +1 @@ +let () = Ppxlib.Driver.standalone () diff --git a/test/driver/keywords-option/dune b/test/driver/keywords-option/dune new file mode 100644 index 00000000..d0360a7f --- /dev/null +++ b/test/driver/keywords-option/dune @@ -0,0 +1,10 @@ +(executable + (name driver) + (enabled_if + (>= %{ocaml_version} "5.3")) + (libraries ppxlib)) + +(cram + (enabled_if + (>= %{ocaml_version} "5.3")) + (deps driver.exe)) diff --git a/test/driver/keywords-option/run.t b/test/driver/keywords-option/run.t new file mode 100644 index 00000000..df14e651 --- /dev/null +++ b/test/driver/keywords-option/run.t @@ -0,0 +1,38 @@ +This test can only work with OCaml 5.3 or higher. + +OCaml 5.3 introduced the new `effect` keyword. To allow old code to compile +under 5.3 it also introduced a `-keyword=version+list` CLI option, allowing one to +override the set of keywords. + +The ppxlib driver also has such an option now to properly configure the lexer before +attempting to parse source code. + +Let's consider the following source file: + + $ cat > test.ml << EOF + > let effect = 1 + > EOF + +If passed to the driver as is, it will trigger a parse error: + + $ ./driver.exe --impl test.ml -o ignore.ml + File "test.ml", line 1, characters 4-10: + 1 | let effect = 1 + ^^^^^^ + Error: Syntax error + [1] + +Now, if we use the 5.2 set of keywords, it should happily handle the file: + + $ ./driver.exe --keywords 5.2 --impl test.ml -o ignore.ml + +It can also be set using OCAMLPARAM: + + $ OCAMLPARAM=_,keywords=5.2 ./driver.exe --impl test.ml -o ignore.ml + +The priority between the CLI option and OCAMLPARAM must be respected, therefore +both of the following invocation should parse: + + $ OCAMLPARAM=_,keywords=5.2 ./driver.exe --keywords 5.3 --impl test.ml -o ignore.ml + + $ OCAMLPARAM=keywords=5.3,_ ./driver.exe --keywords 5.2 --impl test.ml -o ignore.ml