diff --git a/CHANGES.md b/CHANGES.md index 6c665e2ce0..2634e1aa91 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Features/Changes * Misc: drop support for OCaml 4.12 and bellow * Misc: switch to dune.3.19 +* Misc: initial support for ocaml 5.4 (#2030) * Compiler: use a Wasm text files preprocessor (#1822) * Compiler: support for OCaml 4.14.3+trunk (#1844) * Compiler: optimize compilation of switches diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index d06aca727e..505c4d2a2b 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -663,6 +663,7 @@ module Generate (Target : Target_sig.S) = struct | Constant c -> Constant.translate c | Special (Alias_prim _) -> assert false | Prim (Extern "caml_alloc_dummy_function", [ _; Pc (Int arity) ]) -> + (* Removed in OCaml 5.2 *) Closure.dummy ~cps:(effects_cps ()) ~arity:(Targetint.to_int_exn arity) | Prim (Extern "caml_alloc_dummy_infix", _) -> Closure.dummy ~cps:(effects_cps ()) ~arity:1 diff --git a/compiler/lib/effects.ml b/compiler/lib/effects.ml index 8bec7e0a7e..449216f3b1 100644 --- a/compiler/lib/effects.ml +++ b/compiler/lib/effects.ml @@ -530,6 +530,7 @@ let rewrite_instr ~st (instr : instr) : instr = st.in_cps := Var.Set.add x !(st.in_cps); Let (x, Closure (cps_params, cps_cont, None)) | Let (x, Prim (Extern "caml_alloc_dummy_function", [ size; arity ])) -> ( + (* Removed in OCaml 5.2 *) match arity with | Pc (Int a) -> Let diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 5ec43d2cb3..27c78c6f35 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -558,6 +558,10 @@ let eval_instr update_count inline_constant ~target info i = in Specialize_js, which would make the call to [the_const_of] below fail. *) [ i ] + | Let (x, Prim (Extern "caml_atomic_load_field", [ Pv o; f ])) -> ( + match the_int info f with + | None -> [ i ] + | Some i -> [ Let (x, Field (o, Targetint.to_int_exn i, Non_float)) ]) | Let (x, Prim (IsInt, [ y ])) -> ( match is_int info y with | Unknown -> [ i ] diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index aaf394c337..6a6f729c72 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -1497,6 +1497,7 @@ let rec translate_expr ctx loc x e level : (_ * J.statement_list) Expr_builder.t let* fields = build_fields fields in return (J.EObj fields) | Extern "caml_alloc_dummy_function", [ _; size ] -> + (* Removed in Ocaml 5.2 *) let* i = let* cx = access' ~ctx size in return diff --git a/compiler/lib/magic_number.ml b/compiler/lib/magic_number.ml index a5e11d1f76..693430bc5e 100644 --- a/compiler/lib/magic_number.ml +++ b/compiler/lib/magic_number.ml @@ -73,11 +73,12 @@ let v = | 5 :: 01 :: _ -> 33 | 5 :: 02 :: _ -> 34 | 5 :: 03 :: _ -> 35 + | 5 :: 04 :: _ -> 36 | _ -> if Ocaml_version.compare current [ 4; 13 ] < 0 then failwith "OCaml version unsupported. Upgrade to OCaml 4.13 or newer." else ( - assert (Ocaml_version.compare current [ 5; 4 ] >= 0); + assert (Ocaml_version.compare current [ 5; 5 ] >= 0); failwith "OCaml version unsupported. Upgrade js_of_ocaml.") let current_exe = "Caml1999X", v diff --git a/compiler/tests-check-prim/dune.inc b/compiler/tests-check-prim/dune.inc index a6dc98022b..f587188599 100644 --- a/compiler/tests-check-prim/dune.inc +++ b/compiler/tests-check-prim/dune.inc @@ -133,3 +133,48 @@ +toplevel.js %{dep:unix.bc})))) +(rule + (targets main.5.4.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:main.bc})))) + +(rule + (targets unix-Win32.5.4.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Win32))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:unix.bc})))) + +(rule + (targets unix-Unix.5.4.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Unix))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:unix.bc})))) + diff --git a/compiler/tests-check-prim/gen_dune.ml b/compiler/tests-check-prim/gen_dune.ml index 401df00106..e6cd42c2e4 100644 --- a/compiler/tests-check-prim/gen_dune.ml +++ b/compiler/tests-check-prim/gen_dune.ml @@ -8,6 +8,7 @@ type version = | `V5_2 | `V5_3 | `V5_4 + | `V5_5 ] let string_of_version : version -> string = function @@ -18,6 +19,7 @@ let string_of_version : version -> string = function | `V5_2 -> "5.2" | `V5_3 -> "5.3" | `V5_4 -> "5.4" + | `V5_5 -> "5.5" let next_version : version -> version option = function | `V4_13 -> Some `V4_14 @@ -26,7 +28,8 @@ let next_version : version -> version option = function | `V5_1 -> Some `V5_2 | `V5_2 -> Some `V5_3 | `V5_3 -> Some `V5_4 - | `V5_4 -> None + | `V5_4 -> Some `V5_5 + | `V5_5 -> None type os_type = | Unix @@ -82,7 +85,7 @@ let rule bc ocaml_version os_type = bc let () = - let versions : version list = [ `V4_14; `V5_2; `V5_3 ] in + let versions : version list = [ `V4_14; `V5_2; `V5_3; `V5_4 ] in List.iter (fun ocaml_version -> List.iter diff --git a/compiler/tests-check-prim/main.5.4.output b/compiler/tests-check-prim/main.5.4.output new file mode 100644 index 0000000000..ba68876cd2 --- /dev/null +++ b/compiler/tests-check-prim/main.5.4.output @@ -0,0 +1,199 @@ +Missing +------- + +From main.bc: +caml_assume_no_perform +caml_continuation_use +caml_int_as_pointer +caml_reset_afl_instrumentation +debugger + +Unused +------- + +From +array.js: +caml_check_bound + +From +bigarray.js: +caml_ba_create_from +caml_ba_init + +From +bigstring.js: +caml_bigstring_blit_ba_to_ba +caml_bigstring_blit_ba_to_bytes +caml_bigstring_blit_bytes_to_ba +caml_bigstring_blit_string_to_ba +caml_bigstring_memcmp +caml_hash_mix_bigstring + +From +effect.js: +jsoo_effect_not_supported + +From +fs.js: +caml_ba_map_file +caml_ba_map_file_bytecode +caml_fs_init +jsoo_create_file +jsoo_create_file_extern + +From +graphics.js: +caml_gr_arc_aux +caml_gr_blit_image +caml_gr_clear_graph +caml_gr_close_graph +caml_gr_close_subwindow +caml_gr_create_image +caml_gr_current_x +caml_gr_current_y +caml_gr_display_mode +caml_gr_doc_of_state +caml_gr_draw_arc +caml_gr_draw_char +caml_gr_draw_image +caml_gr_draw_rect +caml_gr_draw_str +caml_gr_draw_string +caml_gr_dump_image +caml_gr_fill_arc +caml_gr_fill_poly +caml_gr_fill_rect +caml_gr_lineto +caml_gr_make_image +caml_gr_moveto +caml_gr_open_graph +caml_gr_open_subwindow +caml_gr_plot +caml_gr_point_color +caml_gr_remember_mode +caml_gr_resize_window +caml_gr_set_color +caml_gr_set_font +caml_gr_set_line_width +caml_gr_set_text_size +caml_gr_set_window_title +caml_gr_sigio_handler +caml_gr_sigio_signal +caml_gr_size_x +caml_gr_size_y +caml_gr_state +caml_gr_state_create +caml_gr_state_get +caml_gr_state_init +caml_gr_state_set +caml_gr_synchronize +caml_gr_text_size +caml_gr_wait_event +caml_gr_window_id + +From +hash.js: +caml_hash_mix_int64 + +From +ints.js: +caml_div +caml_mod + +From +jslib.js: +caml_is_js +caml_trampoline +caml_trampoline_return +caml_wrap_exception + +From +marshal.js: +caml_marshal_constants + +From +mlBytes.js: +caml_array_of_bytes (deprecated) +caml_array_of_string (deprecated) +caml_bytes_of_utf16_jsstring +caml_new_string (deprecated) +caml_string_concat +caml_to_js_string (deprecated) + +From +runtime_events.js: +caml_runtime_events_create_cursor +caml_runtime_events_free_cursor +caml_runtime_events_read_poll +caml_runtime_events_user_resolve + +From +stdlib.js: +caml_build_symbols +caml_is_printable +caml_maybe_print_stats +caml_register_global +jsoo_toplevel_reloc + +From +sys.js: +caml_fatal_uncaught_exception +caml_format_exception +caml_is_special_exception +caml_set_static_env +caml_sys_const_naked_pointers_checked + +From +toplevel.js: +jsoo_get_runtime_aliases +jsoo_toplevel_init_compile +jsoo_toplevel_init_reloc + +From +unix.js: +caml_strerror +caml_unix_access +caml_unix_chdir +caml_unix_chmod +caml_unix_cleanup +caml_unix_close +caml_unix_closedir +caml_unix_fchmod +caml_unix_filedescr_of_fd +caml_unix_findclose +caml_unix_findfirst +caml_unix_findnext +caml_unix_fstat +caml_unix_fstat_64 +caml_unix_fsync +caml_unix_ftruncate +caml_unix_ftruncate_64 +caml_unix_getegid +caml_unix_geteuid +caml_unix_getgid +caml_unix_getpwnam +caml_unix_gettimeofday +caml_unix_getuid +caml_unix_gmtime +caml_unix_has_symlink +caml_unix_inchannel_of_filedescr +caml_unix_inet_addr_of_string +caml_unix_isatty +caml_unix_link +caml_unix_localtime +caml_unix_lookup_file +caml_unix_lseek +caml_unix_lseek_64 +caml_unix_lstat +caml_unix_lstat_64 +caml_unix_mkdir +caml_unix_mktime +caml_unix_open +caml_unix_opendir +caml_unix_outchannel_of_filedescr +caml_unix_read +caml_unix_read_bigarray +caml_unix_readdir +caml_unix_readlink +caml_unix_rename +caml_unix_rewinddir +caml_unix_rmdir +caml_unix_single_write +caml_unix_startup +caml_unix_stat +caml_unix_stat_64 +caml_unix_symlink +caml_unix_time +caml_unix_times +caml_unix_truncate +caml_unix_truncate_64 +caml_unix_unlink +caml_unix_utimes +caml_unix_write +caml_unix_write_bigarray +unix_error_message + diff --git a/compiler/tests-check-prim/unix-Unix.5.4.output b/compiler/tests-check-prim/unix-Unix.5.4.output new file mode 100644 index 0000000000..92dd7fd24e --- /dev/null +++ b/compiler/tests-check-prim/unix-Unix.5.4.output @@ -0,0 +1,223 @@ +Missing +------- + +From unix.bc: +caml_assume_no_perform +caml_continuation_use +caml_int_as_pointer +caml_reset_afl_instrumentation +caml_unix_accept +caml_unix_alarm +caml_unix_bind +caml_unix_chown +caml_unix_chroot +caml_unix_clear_close_on_exec +caml_unix_clear_nonblock +caml_unix_connect +caml_unix_dup +caml_unix_dup2 +caml_unix_environment +caml_unix_environment_unsafe +caml_unix_execv +caml_unix_execve +caml_unix_execvp +caml_unix_execvpe +caml_unix_fchown +caml_unix_fork +caml_unix_getaddrinfo +caml_unix_getgroups +caml_unix_gethostbyaddr +caml_unix_gethostbyname +caml_unix_gethostname +caml_unix_getitimer +caml_unix_getlogin +caml_unix_getnameinfo +caml_unix_getpeername +caml_unix_getpid +caml_unix_getppid +caml_unix_getprotobyname +caml_unix_getprotobynumber +caml_unix_getservbyname +caml_unix_getservbyport +caml_unix_getsockname +caml_unix_getsockopt +caml_unix_initgroups +caml_unix_kill +caml_unix_listen +caml_unix_lockf +caml_unix_map_file_bytecode +caml_unix_mkfifo +caml_unix_nice +caml_unix_pipe +caml_unix_putenv +caml_unix_realpath +caml_unix_recv +caml_unix_recvfrom +caml_unix_select +caml_unix_send +caml_unix_sendto +caml_unix_set_close_on_exec +caml_unix_set_nonblock +caml_unix_setgid +caml_unix_setgroups +caml_unix_setitimer +caml_unix_setsid +caml_unix_setsockopt +caml_unix_setuid +caml_unix_shutdown +caml_unix_sigpending +caml_unix_sigprocmask +caml_unix_sigsuspend +caml_unix_sigwait +caml_unix_sleep +caml_unix_socket +caml_unix_socketpair +caml_unix_spawn +caml_unix_string_of_inet_addr +caml_unix_tcdrain +caml_unix_tcflow +caml_unix_tcflush +caml_unix_tcgetattr +caml_unix_tcsendbreak +caml_unix_tcsetattr +caml_unix_umask +caml_unix_wait +caml_unix_waitpid +debugger + +Unused +------- + +From +array.js: +caml_check_bound + +From +bigarray.js: +caml_ba_create_from +caml_ba_init + +From +bigstring.js: +caml_bigstring_blit_ba_to_ba +caml_bigstring_blit_ba_to_bytes +caml_bigstring_blit_bytes_to_ba +caml_bigstring_blit_string_to_ba +caml_bigstring_memcmp +caml_hash_mix_bigstring + +From +effect.js: +jsoo_effect_not_supported + +From +fs.js: +caml_ba_map_file +caml_ba_map_file_bytecode +caml_fs_init +jsoo_create_file +jsoo_create_file_extern + +From +graphics.js: +caml_gr_arc_aux +caml_gr_blit_image +caml_gr_clear_graph +caml_gr_close_graph +caml_gr_close_subwindow +caml_gr_create_image +caml_gr_current_x +caml_gr_current_y +caml_gr_display_mode +caml_gr_doc_of_state +caml_gr_draw_arc +caml_gr_draw_char +caml_gr_draw_image +caml_gr_draw_rect +caml_gr_draw_str +caml_gr_draw_string +caml_gr_dump_image +caml_gr_fill_arc +caml_gr_fill_poly +caml_gr_fill_rect +caml_gr_lineto +caml_gr_make_image +caml_gr_moveto +caml_gr_open_graph +caml_gr_open_subwindow +caml_gr_plot +caml_gr_point_color +caml_gr_remember_mode +caml_gr_resize_window +caml_gr_set_color +caml_gr_set_font +caml_gr_set_line_width +caml_gr_set_text_size +caml_gr_set_window_title +caml_gr_sigio_handler +caml_gr_sigio_signal +caml_gr_size_x +caml_gr_size_y +caml_gr_state +caml_gr_state_create +caml_gr_state_get +caml_gr_state_init +caml_gr_state_set +caml_gr_synchronize +caml_gr_text_size +caml_gr_wait_event +caml_gr_window_id + +From +hash.js: +caml_hash_mix_int64 + +From +ints.js: +caml_div +caml_mod + +From +jslib.js: +caml_is_js +caml_trampoline +caml_trampoline_return +caml_wrap_exception + +From +marshal.js: +caml_marshal_constants + +From +mlBytes.js: +caml_array_of_bytes (deprecated) +caml_array_of_string (deprecated) +caml_bytes_of_utf16_jsstring +caml_new_string (deprecated) +caml_string_concat +caml_to_js_string (deprecated) + +From +runtime_events.js: +caml_runtime_events_create_cursor +caml_runtime_events_free_cursor +caml_runtime_events_read_poll +caml_runtime_events_user_resolve + +From +stdlib.js: +caml_build_symbols +caml_is_printable +caml_maybe_print_stats +caml_register_global +jsoo_toplevel_reloc + +From +sys.js: +caml_fatal_uncaught_exception +caml_format_exception +caml_is_special_exception +caml_set_static_env +caml_sys_const_naked_pointers_checked + +From +toplevel.js: +jsoo_get_runtime_aliases +jsoo_toplevel_init_compile +jsoo_toplevel_init_reloc + +From +unix.js: +caml_strerror +caml_unix_cleanup +caml_unix_filedescr_of_fd +caml_unix_findclose +caml_unix_findfirst +caml_unix_findnext +caml_unix_startup +unix_error_message + diff --git a/lib/js_of_ocaml/dom.ml b/lib/js_of_ocaml/dom.ml index 5dac937369..82e1198a97 100644 --- a/lib/js_of_ocaml/dom.ml +++ b/lib/js_of_ocaml/dom.ml @@ -333,7 +333,7 @@ class type event_listener_options = object method passive : bool t writeonly_prop end -let addEventListenerWithOptions (e : (< .. > as 'a) t) typ ?capture ?once ?passive h = +let addEventListenerWithOptions (e : < .. > t) typ ?capture ?once ?passive h = if not (Js.Optdef.test (Js.Unsafe.coerce e)##.addEventListener) then let ev = (Js.string "on")##concat typ in @@ -353,7 +353,7 @@ let addEventListenerWithOptions (e : (< .. > as 'a) t) typ ?capture ?once ?passi let () = (Js.Unsafe.coerce e)##addEventListener typ h opts in fun () -> (Js.Unsafe.coerce e)##removeEventListener typ h opts -let addEventListener (e : (< .. > as 'a) t) typ h capt = +let addEventListener (e : < .. > t) typ h capt = addEventListenerWithOptions e typ ~capture:capt h let removeEventListener id = id () diff --git a/lib/js_of_ocaml/dom_html.mli b/lib/js_of_ocaml/dom_html.mli index 1fcf0cc7e8..59480e01bf 100644 --- a/lib/js_of_ocaml/dom_html.mli +++ b/lib/js_of_ocaml/dom_html.mli @@ -2593,7 +2593,7 @@ val removeEventListener : event_listener_id -> unit (** Remove the given event listener. *) val addMousewheelEventListenerWithOptions : - (#eventTarget t as 'a) + #eventTarget t -> ?capture:bool t -> ?once:bool t -> ?passive:bool t @@ -2605,7 +2605,7 @@ val addMousewheelEventListenerWithOptions : means down / right. *) val addMousewheelEventListener : - (#eventTarget t as 'a) + #eventTarget t -> (mouseEvent t -> dx:int -> dy:int -> bool t) -> bool t -> event_listener_id diff --git a/runtime/js/array.js b/runtime/js/array.js index e0bd1a9c60..76e0773a0c 100644 --- a/runtime/js/array.js +++ b/runtime/js/array.js @@ -80,6 +80,30 @@ function caml_array_concat(l) { return a; } +//Provides: caml_floatarray_concat mutable +//Version: >= 5.4 +function caml_floatarray_concat(l) { + var a = [0]; + while (l !== 0) { + var b = l[1]; + for (var i = 1; i < b.length; i++) a.push(b[i]); + l = l[2]; + } + return a; +} + +//Provides: caml_uniform_array_concat mutable +//Version: >= 5.4 +function caml_uniform_array_concat(l) { + var a = [0]; + while (l !== 0) { + var b = l[1]; + for (var i = 1; i < b.length; i++) a.push(b[i]); + l = l[2]; + } + return a; +} + //Provides: caml_array_blit function caml_array_blit(a1, i1, a2, i2, len) { if (i2 <= i1) { diff --git a/runtime/js/domain.js b/runtime/js/domain.js index e2808c3063..442b65a7f4 100644 --- a/runtime/js/domain.js +++ b/runtime/js/domain.js @@ -31,6 +31,12 @@ function caml_atomic_load(ref) { return ref[1]; } +//Provides: caml_atomic_load_field +//Version: >= 5.4 +function caml_atomic_load_field(b, i) { + return b[i + 1]; +} + //Provides: caml_atomic_cas //Version: >= 5 function caml_atomic_cas(ref, o, n) { @@ -41,6 +47,16 @@ function caml_atomic_cas(ref, o, n) { return 0; } +//Provides: caml_atomic_cas_field +//Version: >= 5.4 +function caml_atomic_cas_field(b, i, o, n) { + if (b[i + 1] === o) { + b[i + 1] = n; + return 1; + } + return 0; +} + //Provides: caml_atomic_fetch_add //Version: >= 5 function caml_atomic_fetch_add(ref, i) { @@ -49,6 +65,14 @@ function caml_atomic_fetch_add(ref, i) { return old; } +//Provides: caml_atomic_fetch_add_field +//Version: >= 5.4 +function caml_atomic_fetch_add_field(b, i, n) { + var old = b[i + 1]; + b[i + 1] += n; + return old; +} + //Provides: caml_atomic_exchange //Version: >= 5 function caml_atomic_exchange(ref, v) { @@ -57,6 +81,14 @@ function caml_atomic_exchange(ref, v) { return r; } +//Provides: caml_atomic_exchange_field +//Version: >= 5.4 +function caml_atomic_exchange_field(b, i, v) { + var r = b[i + 1]; + b[i + 1] = v; + return r; +} + //Provides: caml_atomic_make_contended //Version: >= 5.2 function caml_atomic_make_contended(a) { diff --git a/runtime/js/io.js b/runtime/js/io.js index 4498cd1a03..e34fde72ff 100644 --- a/runtime/js/io.js +++ b/runtime/js/io.js @@ -194,6 +194,7 @@ function caml_ml_out_channels_list() { //Requires: caml_ml_channels, caml_sys_fds //Requires: caml_raise_sys_error //Requires: caml_sys_open +//Requires: caml_io_buffer_size function caml_ml_open_descriptor_out(fd) { var fd_desc = caml_sys_fds[fd]; if (fd_desc === undefined) @@ -208,7 +209,7 @@ function caml_ml_open_descriptor_out(fd) { opened: true, out: true, buffer_curr: 0, - buffer: new Uint8Array(65536), + buffer: new Uint8Array(caml_io_buffer_size), buffered: buffered, }; caml_ml_channels.set(chanid, channel); @@ -219,6 +220,7 @@ function caml_ml_open_descriptor_out(fd) { //Requires: caml_ml_channels, caml_sys_fds //Requires: caml_raise_sys_error //Requires: caml_sys_open +//Requires: caml_io_buffer_size function caml_ml_open_descriptor_in(fd) { var fd_desc = caml_sys_fds[fd]; if (fd_desc === undefined) @@ -234,7 +236,7 @@ function caml_ml_open_descriptor_in(fd) { out: false, buffer_curr: 0, buffer_max: 0, - buffer: new Uint8Array(65536), + buffer: new Uint8Array(caml_io_buffer_size), refill: refill, }; caml_ml_channels.set(chanid, channel); diff --git a/runtime/js/obj.js b/runtime/js/obj.js index 2d2ab48b0d..e6578211c9 100644 --- a/runtime/js/obj.js +++ b/runtime/js/obj.js @@ -32,12 +32,38 @@ function caml_update_dummy(x, y) { //Provides: caml_alloc_dummy_infix //Requires: caml_call_gen +//Version: < 5.4 function caml_alloc_dummy_infix() { return function f(x) { return caml_call_gen(f.fun, [x]); }; } +//Provides: caml_alloc_dummy_lazy +//Version: >= 5.4 +function caml_alloc_dummy_lazy(_unit) { + return [0, 0]; +} + +//Provides: caml_update_dummy_lazy +//Requires: caml_obj_tag +//Requires: caml_update_dummy +//Version: >= 5.4 +function caml_update_dummy_lazy(dummy, newval) { + switch (caml_obj_tag(newval)) { + case 246: // Lazy + case 244: // Forcing + case 250: // Forward + caml_update_dummy(dummy, newval); + break; + default: + dummy[1] = newval; + dummy[0] = 250; + break; + } + return 0; +} + //Provides: caml_obj_tag //Requires: caml_is_ml_bytes, caml_is_ml_string function caml_obj_tag(x) { @@ -244,3 +270,18 @@ function caml_is_continuation_tag(t) { function caml_custom_identifier(o) { return caml_string_of_jsstring(o.caml_custom); } + +//Provides: caml_ml_gc_ramp_up +//Requires: caml_callback +//Version: >= 5.4 +function caml_ml_gc_ramp_up(f) { + var a = caml_callback(f, [0]); + var suspended = 0; + return [0, a, suspended]; +} + +//Provides: caml_ml_gc_ramp_down +//Version: >= 5.4 +function caml_ml_gc_ramp_down(_suspended_collection_work) { + return 0; +} diff --git a/runtime/js/sys.js b/runtime/js/sys.js index 16ecee6ad6..836153d0ec 100644 --- a/runtime/js/sys.js +++ b/runtime/js/sys.js @@ -133,6 +133,17 @@ function caml_sys_getenv(name) { return caml_string_of_jsstring(r); } +//Provides: caml_sys_getenv_opt (const) +//Requires: caml_string_of_jsstring +//Requires: caml_jsstring_of_string +//Requires: jsoo_sys_getenv +//Version: >= 5.4 +function caml_sys_getenv_opt(name) { + var r = jsoo_sys_getenv(caml_jsstring_of_string(name)); + if (r === undefined) return 0; + return [0, caml_string_of_jsstring(r)]; +} + //Provides: caml_sys_unsafe_getenv //Requires: caml_sys_getenv function caml_sys_unsafe_getenv(name) { @@ -350,6 +361,41 @@ function caml_sys_is_regular_file(name) { var root = resolve_fs_device(name); return root.device.isFile(root.rest); } + +//Provides: caml_io_buffer_size +var caml_io_buffer_size = 65536; + +//Provides: caml_sys_io_buffer_size +//Requires: caml_io_buffer_size +//Version: >= 5.4 +function caml_sys_io_buffer_size(_unit) { + return caml_io_buffer_size; +} + +//Provides: caml_sys_temp_dir_name +//Requires: os_type +//Requires: caml_string_of_jsstring +//Version: >= 5.4 +function caml_sys_temp_dir_name(_unit) { + if (os_type === "Win32") { + return caml_string_of_jsstring(require("node:os").tmpdir()); + } else { + return caml_string_of_jsstring(""); + } +} + +//Provides: caml_sys_convert_signal_number +//Version: >= 5.4 +function caml_sys_convert_signal_number(signo) { + return signo; +} + +//Provides: caml_sys_rev_convert_signal_number +//Version: >= 5.4 +function caml_sys_rev_convert_signal_number(signo) { + return signo; +} + //Always //Requires: caml_fatal_uncaught_exception //If: !wasm diff --git a/runtime/wasm/array.wat b/runtime/wasm/array.wat index 7b01ab84ad..235a9aa52b 100644 --- a/runtime/wasm/array.wat +++ b/runtime/wasm/array.wat @@ -315,6 +315,90 @@ (br $fill)))) (local.get $a)))) + (func (export "caml_float_array_concat") (param (ref eq)) (result (ref eq)) + (local $i i32) (local $len i32) + (local $l (ref eq)) (local $v (ref eq)) + (local $b (ref $block)) + (local $fa (ref $float_array)) (local $fa' (ref $float_array)) + (local.set $l (local.get 0)) + (local.set $len (i32.const 0)) + (loop $compute_length + (drop (block $exit (result (ref eq)) + (local.set $b + (br_on_cast_fail $exit (ref eq) (ref $block) (local.get $l))) + (local.set $v (array.get $block (local.get $b) (i32.const 1))) + (local.set $len + (i32.add (local.get $len) + (array.len (ref.cast (ref $float_array) (local.get $v))))) + (local.set $l (array.get $block (local.get $b) (i32.const 2))) + (br $compute_length)))) + (local.set $fa + (array.new $float_array (f64.const 0) (local.get $len))) + (local.set $l (local.get 0)) + (local.set $i (i32.const 0)) + (loop $fill + (drop (block $exit (result (ref eq)) + (local.set $b + (br_on_cast_fail $exit (ref eq) (ref $block) + (local.get $l))) + (local.set $l (array.get $block (local.get $b) (i32.const 2))) + (drop (block $not_float (result (ref eq)) + (local.set $fa' + (br_on_cast_fail $not_float (ref eq) (ref $float_array) + (array.get $block (local.get $b) (i32.const 1)))) + (local.set $len (array.len (local.get $fa'))) + (array.copy $float_array $float_array + (local.get $fa) (local.get $i) + (local.get $fa') (i32.const 0) + (local.get $len)) + (local.set $i (i32.add (local.get $i) (local.get $len))) + (br $fill))) + (br $fill)))) + (local.get $fa)) + + (func (export "caml_uniform_array_concat") (param (ref eq)) (result (ref eq)) + (local $i i32) (local $len i32) + (local $l (ref eq)) (local $v (ref eq)) + (local $b (ref $block)) + (local $a (ref $block)) (local $a' (ref $block)) + (local.set $l (local.get 0)) + (local.set $len (i32.const 0)) + (loop $compute_length + (drop (block $exit (result (ref eq)) + (local.set $b + (br_on_cast_fail $exit (ref eq) (ref $block) (local.get $l))) + (local.set $v (array.get $block (local.get $b) (i32.const 1))) + (local.set $len + (i32.add (local.get $len) + (i32.sub + (array.len (ref.cast (ref $block) (local.get $v))) + (i32.const 1)))) + (local.set $l (array.get $block (local.get $b) (i32.const 2))) + (br $compute_length)))) + (local.set $a + (array.new $block (ref.i31 (i32.const 0)) + (i32.add (local.get $len) (i32.const 1)))) + (local.set $l (local.get 0)) + (local.set $i (i32.const 1)) + (loop $fill + (drop (block $exit (result (ref eq)) + (local.set $b + (br_on_cast_fail $exit (ref eq) (ref $block) + (local.get $l))) + (local.set $a' + (ref.cast (ref $block) + (array.get $block (local.get $b) (i32.const 1)))) + (local.set $len + (i32.sub (array.len (local.get $a')) (i32.const 1))) + (array.copy $block $block + (local.get $a) (local.get $i) + (local.get $a') (i32.const 1) + (local.get $len)) + (local.set $i (i32.add (local.get $i) (local.get $len))) + (local.set $l (array.get $block (local.get $b) (i32.const 2))) + (br $fill)))) + (local.get $a)) + (func $caml_floatarray_blit (export "caml_floatarray_blit") (param $a1 (ref eq)) (param $i1 (ref eq)) (param $a2 (ref eq)) (param $i2 (ref eq)) diff --git a/runtime/wasm/domain.wat b/runtime/wasm/domain.wat index d4a832ed40..712486bbdd 100644 --- a/runtime/wasm/domain.wat +++ b/runtime/wasm/domain.wat @@ -38,9 +38,31 @@ (else (ref.i31 (i32.const 0))))) + (func (export "caml_atomic_cas_field") + (param $ref (ref eq)) (param $i (ref eq)) (param $o (ref eq)) + (param $n (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $j i32) + (local.set $j + (i32.add (i31.get_u (ref.cast (ref i31) (local.get $i))) (i32.const 1))) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (if (result (ref eq)) + (ref.eq (array.get $block (local.get $b) (local.get $j)) + (local.get $o)) + (then + (array.set $block (local.get $b) (local.get $j) (local.get $n)) + (ref.i31 (i32.const 1))) + (else + (ref.i31 (i32.const 0))))) + (func (export "caml_atomic_load") (param (ref eq)) (result (ref eq)) (array.get $block (ref.cast (ref $block) (local.get 0)) (i32.const 1))) + (func (export "caml_atomic_load_field") + (param $b (ref eq)) (param $i (ref eq)) (result (ref eq)) + (array.get $block (ref.cast (ref $block) (local.get $b)) + (i32.add (i31.get_u (ref.cast (ref i31) (local.get $i))) (i32.const 1)))) + (func (export "caml_atomic_fetch_add") (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $b (ref $block)) @@ -52,6 +74,21 @@ (i31.get_s (ref.cast (ref i31) (local.get $i)))))) (local.get $old)) + (func (export "caml_atomic_fetch_add_field") + (param $ref (ref eq)) (param $i (ref eq)) (param $n (ref eq)) + (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local $j i32) + (local.set $j + (i32.add (i31.get_u (ref.cast (ref i31) (local.get $i))) (i32.const 1))) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (local.get $j))) + (array.set $block (local.get $b) (local.get $j) + (ref.i31 (i32.add (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $n)))))) + (local.get $old)) + (func (export "caml_atomic_exchange") (param $ref (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $b (ref $block)) @@ -61,6 +98,19 @@ (array.set $block (local.get $b) (i32.const 1) (local.get $v)) (local.get $r)) + (func (export "caml_atomic_exchange_field") + (param $ref (ref eq)) (param $i (ref eq)) (param $v (ref eq)) + (result (ref eq)) + (local $b (ref $block)) + (local $r (ref eq)) + (local $j i32) + (local.set $j + (i32.add (i31.get_u (ref.cast (ref i31) (local.get $i))) (i32.const 1))) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $r (array.get $block (local.get $b) (local.get $j))) + (array.set $block (local.get $b) (local.get $j) (local.get $v)) + (local.get $r)) + (func (export "caml_atomic_make_contended") (param $v (ref eq)) (result (ref eq)) (array.new_fixed $block 2 (ref.i31 (i32.const 0)) (local.get $v))) diff --git a/runtime/wasm/fs.wat b/runtime/wasm/fs.wat index 89903e2c92..898dabc6a6 100644 --- a/runtime/wasm/fs.wat +++ b/runtime/wasm/fs.wat @@ -16,11 +16,13 @@ ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. (module + (import "bindings" "on_windows" (global $on_windows i32)) (import "bindings" "getcwd" (func $getcwd (result anyref))) (import "bindings" "chdir" (func $chdir (param anyref))) (import "bindings" "mkdir" (func $mkdir (param anyref) (param i32))) (import "bindings" "rmdir" (func $rmdir (param anyref))) (import "bindings" "unlink" (func $unlink (param anyref))) + (import "bindings" "tmpdir" (func $tmpdir (result anyref))) (import "bindings" "read_dir" (func $read_dir (param anyref) (result (ref extern)))) (import "bindings" "file_exists" @@ -172,6 +174,12 @@ (call $caml_handle_sys_error (pop externref)) (return (ref.i31 (i32.const 0)))))) + (func (export "caml_sys_temp_dir_name") (param (ref eq)) (result (ref eq)) + (if (global.get $on_windows) + (then + (return_call $caml_string_of_jsstring (call $wrap (call $tmpdir))))) + (@string "")) + (func (export "caml_mount_autoload") (param (ref eq) (ref eq)) (result (ref eq)) (ref.i31 (i32.const 0))) diff --git a/runtime/wasm/gc.wat b/runtime/wasm/gc.wat index e50f805d71..3fba5782d3 100644 --- a/runtime/wasm/gc.wat +++ b/runtime/wasm/gc.wat @@ -16,6 +16,10 @@ ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. (module + (import "obj" "caml_callback_1" + (func $caml_callback_1 + (param (ref eq)) (param (ref eq)) (result (ref eq)))) + (type $float (struct (field f64))) (type $block (array (mut (ref eq)))) @@ -118,4 +122,12 @@ (func (export "caml_eventlog_resume") (param (ref eq)) (result (ref eq)) (ref.i31 (i32.const 0))) + + (func (export "caml_ml_gc_ramp_up") (param $f (ref eq)) (result (ref eq)) + (array.new_fixed $block 3 (ref.i31 (i32.const 0)) + (call $caml_callback_1 (local.get $f) (ref.i31 (i32.const 0))) + (ref.i31 (i32.const 0)))) + + (func (export "caml_ml_gc_ramp_down") (param (ref eq)) (result (ref eq)) + (ref.i31 (i32.const 0))) ) diff --git a/runtime/wasm/io.wat b/runtime/wasm/io.wat index deff2a6d40..adf0112f64 100644 --- a/runtime/wasm/io.wat +++ b/runtime/wasm/io.wat @@ -260,6 +260,9 @@ (call $caml_handle_sys_error (pop externref)))) (ref.i31 (i32.const 0))) + (func (export "caml_sys_io_buffer_size") (param (ref eq)) (result (ref eq)) + (ref.i31 (global.get $IO_BUFFER_SIZE))) + (func (export "caml_ml_set_channel_name") (param (ref eq)) (param (ref eq)) (result (ref eq)) (ref.i31 (i32.const 0))) diff --git a/runtime/wasm/obj.wat b/runtime/wasm/obj.wat index b33d32bd18..59edeae660 100644 --- a/runtime/wasm/obj.wat +++ b/runtime/wasm/obj.wat @@ -176,6 +176,26 @@ (return (ref.i31 (i32.const 0))))) (unreachable)) + (func (export "caml_alloc_dummy_lazy") (param (ref eq)) (result (ref eq)) + (array.new_fixed $block 2 (ref.i31 (i32.const 0)) (ref.i31 (i32.const 0)))) + + (func (export "caml_update_dummy_lazy") + (param $dummy (ref eq)) (param $newval (ref eq)) (result (ref eq)) + (local $tag i32) + (local $b (ref $block)) + (local.set $tag + (i31.get_s (ref.cast (ref i31) (call $caml_obj_tag (local.get $newval))))) + (block $update + (br_if $update (i32.eq (local.get $tag) (global.get $lazy_tag))) + (br_if $update (i32.eq (local.get $tag) (global.get $forcing_tag))) + (br_if $update (i32.eq (local.get $tag) (global.get $forward_tag))) + (local.set $b (ref.cast (ref $block) (local.get $dummy))) + (array.set $block (local.get $b) (i32.const 0) + (ref.i31 (global.get $forward_tag))) + (array.set $block (local.get $b) (i32.const 1) (local.get $newval)) + (return (ref.i31 (i32.const 0)))) + (return_call $caml_update_dummy (local.get $dummy) (local.get $newval))) + (func $caml_obj_dup (export "caml_obj_dup") (param (ref eq)) (result (ref eq)) (local $orig (ref $block)) (local $res (ref $block)) diff --git a/runtime/wasm/runtime.js b/runtime/wasm/runtime.js index 3dfcaa4b89..0794c0e939 100644 --- a/runtime/wasm/runtime.js +++ b/runtime/wasm/runtime.js @@ -538,6 +538,7 @@ } fs.renameSync(o, n); }, + tmpdir: () => require("node:os").tmpdir(), start_fiber: (x) => start_fiber(x), suspend_fiber: make_suspending((f, env) => new Promise((k) => f(k, env))), resume_fiber: (k, v) => k(v), diff --git a/runtime/wasm/sys.wat b/runtime/wasm/sys.wat index 788e0ee478..7a2a582499 100644 --- a/runtime/wasm/sys.wat +++ b/runtime/wasm/sys.wat @@ -78,6 +78,18 @@ (call $caml_raise_not_found))) (return_call $caml_string_of_jsstring (call $wrap (local.get $res)))) + (func (export "caml_sys_getenv_opt") + (param (ref eq)) (result (ref eq)) + (local $res anyref) + (local.set $res + (call $getenv + (call $unwrap (call $caml_jsstring_of_string (local.get 0))))) + (if (i32.eqz (call $jsstring_test (local.get $res))) + (then + (return (ref.i31 (i32.const 0))))) + (array.new_fixed $block 2 (ref.i31 (i32.const 0)) + (call $caml_string_of_jsstring (call $wrap (local.get $res))))) + (func (export "caml_sys_argv") (param (ref eq)) (result (ref eq)) ;; ZZZ (call $caml_js_to_string_array (call $argv))) @@ -179,6 +191,14 @@ (param (ref eq) (ref eq)) (result (ref eq)) (ref.i31 (i32.const 0))) + (func (export "caml_sys_convert_signal_number") + (param $signo (ref eq)) (result (ref eq)) + (local.get $signo)) + + (func (export "caml_sys_rev_convert_signal_number") + (param $signo (ref eq)) (result (ref eq)) + (local.get $signo)) + (global $caml_runtime_warnings (mut i32) (i32.const 0)) (func (export "caml_ml_enable_runtime_warnings") diff --git a/tools/toplevel_expect/gen.ml b/tools/toplevel_expect/gen.ml index a905d5e575..7bc9db25be 100644 --- a/tools/toplevel_expect/gen.ml +++ b/tools/toplevel_expect/gen.ml @@ -53,5 +53,6 @@ let () = assert (min >= 11); dump_file "toplevel_expect_test.ml-4.11" | 5, 0 | 5, 1 | 5, 2 -> dump_file "toplevel_expect_test.ml-4.11" - | 5, _ -> dump_file "toplevel_expect_test.ml-5.3" + | 5, 3 -> dump_file "toplevel_expect_test.ml-5.3" + | 5, 4 -> dump_file "toplevel_expect_test.ml-5.4" | _ -> failwith ("unsupported version " ^ Sys.ocaml_version) diff --git a/tools/toplevel_expect/toplevel_expect_test.ml-5.4 b/tools/toplevel_expect/toplevel_expect_test.ml-5.4 new file mode 100644 index 0000000000..909bfb4495 --- /dev/null +++ b/tools/toplevel_expect/toplevel_expect_test.ml-5.4 @@ -0,0 +1,390 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* Jeremie Dimino, Jane Street Europe *) +(* *) +(* Copyright 2016 Jane Street Group LLC *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +(* Execute a list of phrases from a .ml file and compare the result to the + expected output, written inside [%%expect ...] nodes. At the end, create + a .corrected file containing the corrected expectations. The test is + successful if there is no differences between the two files. + + An [%%expect] node always contains both the expected outcome with and + without -principal. When the two differ the expectation is written as + follows: + + {[ + [%%expect {| + output without -principal + |}, Principal{| + output with -principal + |}] + ]} +*) + +[@@@ocaml.warning "-40"] + +open StdLabels + +(* representation of: {tag|str|tag} *) +type string_constant = + { str : string + ; tag : string + } + +type expectation = + { extid_loc : Location.t (* Location of "expect" in "[%%expect ...]" *) + ; payload_loc : Location.t (* Location of the whole payload *) + ; normal : string_constant (* expectation without -principal *) + ; principal : string_constant (* expectation with -principal *) + } + +(* A list of phrases with the expected toplevel output *) +type chunk = + { phrases : Parsetree.toplevel_phrase list + ; expectation : expectation + } + +type correction = + { corrected_expectations : expectation list + ; trailing_output : string + } + +let match_expect_extension (ext : Parsetree.extension) = + match ext with + | ({Asttypes.txt="expect"|"ocaml.expect"; loc = extid_loc}, payload) -> + let invalid_payload () = + Location.raise_errorf ~loc:extid_loc + "invalid [%%%%expect payload]" + in + let string_constant (e : Parsetree.expression) = + match e.pexp_desc with + | Pexp_constant {pconst_desc=Pconst_string (str, _, Some tag); _} -> + { str; tag } + | _ -> invalid_payload () + in + let expectation = + match payload with + | PStr [{ pstr_desc = Pstr_eval (e, []); _ }] -> + let normal, principal = + match e.pexp_desc with + | Pexp_tuple + [ None, a + ; None, { pexp_desc = Pexp_construct + ({ txt = Lident "Principal"; _ }, Some b); _ } + ] -> + (string_constant a, string_constant b) + | _ -> let s = string_constant e in (s, s) + in + { extid_loc + ; payload_loc = e.pexp_loc + ; normal + ; principal + } + | PStr [] -> + let s = { tag = ""; str = "" } in + { extid_loc + ; payload_loc = { extid_loc with loc_start = extid_loc.loc_end } + ; normal = s + ; principal = s + } + | _ -> invalid_payload () + in + Some expectation + | _ -> + None + +(* Split a list of phrases from a .ml file *) +let split_chunks phrases = + let rec loop (phrases : Parsetree.toplevel_phrase list) code_acc acc = + match phrases with + | [] -> + if code_acc = [] then + (List.rev acc, None) + else + (List.rev acc, Some (List.rev code_acc)) + | phrase :: phrases -> + match phrase with + | Ptop_def [] -> loop phrases code_acc acc + | Ptop_def [{pstr_desc = Pstr_extension(ext, []); _}] -> begin + match match_expect_extension ext with + | None -> loop phrases (phrase :: code_acc) acc + | Some expectation -> + let chunk = + { phrases = List.rev code_acc + ; expectation + } + in + loop phrases [] (chunk :: acc) + end + | _ -> loop phrases (phrase :: code_acc) acc + in + loop phrases [] [] + +module Compiler_messages = struct + let print_loc ppf (loc : Location.t) = + let startchar = loc.loc_start.pos_cnum - loc.loc_start.pos_bol in + let endchar = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum + startchar in + Format.fprintf ppf "Line _"; + if startchar >= 0 then + Format.fprintf ppf ", characters %d-%d" startchar endchar; + Format.fprintf ppf ":@." + + let () = + let default = !Location.report_printer () in + Location.report_printer := (fun _ -> + { default with + Location.pp_main_loc = (fun _ _ fmt loc -> print_loc fmt loc); + Location.pp_submsg_loc = (fun _ _ fmt loc -> print_loc fmt loc); + }) + + let capture ppf ~f = + Misc.protect_refs + [ R (Location.formatter_for_warnings , ppf ) + ] + f +end + +let collect_formatters buf pps ~f = + List.iter ~f:(fun pp -> Format.pp_print_flush pp ()) pps; + let save = + List.map ~f:(fun pp -> Format.pp_get_formatter_out_functions pp ()) pps + in + let restore () = + List.iter2 + ~f:(fun pp out_functions -> + Format.pp_print_flush pp (); + Format.pp_set_formatter_out_functions pp out_functions) + pps save + in + let out_string str ofs len = Buffer.add_substring buf str ofs len + and out_flush = ignore + and out_newline () = Buffer.add_char buf '\n' + and out_spaces n = for _i = 1 to n do Buffer.add_char buf ' ' done + and out_indent n = for _i = 1 to n do Buffer.add_char buf ' ' done + and out_width = Format.utf_8_scalar_width + in + let out_functions = + { Format.out_string; out_flush; out_newline; out_spaces; out_indent; out_width } + in + List.iter + ~f:(fun pp -> Format.pp_set_formatter_out_functions pp out_functions) + pps; + match f () with + | x -> restore (); x + | exception exn -> restore (); raise exn + +(* Invariant: ppf = Format.formatter_of_buffer buf *) +let capture_everything buf ppf ~f = + collect_formatters buf [Format.std_formatter; Format.err_formatter] + ~f:(fun () -> Compiler_messages.capture ppf ~f) + +let exec_phrase ppf phrase = + if !Clflags.dump_parsetree then Printast. top_phrase ppf phrase; + if !Clflags.dump_source then Pprintast.top_phrase ppf phrase; + Toploop.execute_phrase true ppf phrase + +let parse_contents ~fname contents = + let lexbuf = Lexing.from_string contents in + Location.init lexbuf fname; + Location.input_name := fname; + Parse.use_file lexbuf + +let eval_expectation expectation ~output = + let s = + if !Clflags.principal then + expectation.principal + else + expectation.normal + in + if s.str = output then + None + else + let trimmed = String.trim output in + let normalized = if String.exists ~f:(function '\n' -> true | _ -> false) output + then "\n" ^ trimmed ^ "\n" + else trimmed + in + let s = { s with str = normalized } in + Some ( + if !Clflags.principal then + { expectation with principal = s } + else + { expectation with normal = s } + ) + +let preprocess_structure mappers str = + let open Ast_mapper in + List.fold_right + ~f:(fun ppx_rewriter str -> + let mapper : Ast_mapper.mapper = ppx_rewriter [] in + mapper.structure mapper str) + mappers + ~init:str + +let preprocess_phrase mappers phrase = + let open Parsetree in + match phrase with + | Ptop_def str -> Ptop_def (preprocess_structure mappers str) + | Ptop_dir _ as x -> x + + +let shift_lines delta = + let position (pos : Lexing.position) = + { pos with pos_lnum = pos.pos_lnum + delta } + in + let location _this (loc : Location.t) = + { loc with + loc_start = position loc.loc_start + ; loc_end = position loc.loc_end + } + in + fun _ -> { Ast_mapper.default_mapper with location } + +let rec min_line_number : Parsetree.toplevel_phrase list -> int option = +function + | [] -> None + | (Ptop_dir _ | Ptop_def []) :: l -> min_line_number l + | Ptop_def (st :: _) :: _ -> Some st.pstr_loc.loc_start.pos_lnum + +let eval_expect_file mapper fname ~file_contents = + Warnings.reset_fatal (); + let chunks, trailing_code = + parse_contents ~fname:fname file_contents |> split_chunks + in + let buf = Buffer.create 1024 in + let ppf = Format.formatter_of_buffer buf in + let out_fun = Format.pp_get_formatter_out_functions ppf () in + Format.pp_set_formatter_out_functions Format.std_formatter out_fun; + + let exec_phrases phrases = + + let mappers = + match min_line_number phrases with + | None -> [] + | Some lnum -> [shift_lines (1 - lnum)] + in + let mappers = mapper :: mappers in + let phrases = List.map ~f:(preprocess_phrase mappers) phrases in + + (* For formatting purposes *) + Buffer.add_char buf '\n'; + let _ : bool = + List.fold_left phrases ~init:true ~f:(fun acc phrase -> + acc && + try + Location.reset (); + exec_phrase ppf phrase + with exn -> + Location.report_exception ppf exn; + false) + in + Format.pp_print_flush ppf (); + let len = Buffer.length buf in + if len > 0 && Buffer.nth buf (len - 1) <> '\n' then + (* For formatting purposes *) + Buffer.add_char buf '\n'; + let s = Buffer.contents buf in + Buffer.clear buf; + Misc.delete_eol_spaces s + in + let corrected_expectations = + capture_everything buf ppf ~f:(fun () -> + List.fold_left chunks ~init:[] ~f:(fun acc chunk -> + let output = exec_phrases chunk.phrases in + match eval_expectation chunk.expectation ~output with + | None -> acc + | Some correction -> correction :: acc) + |> List.rev) + in + let trailing_output = + match trailing_code with + | None -> "" + | Some phrases -> + capture_everything buf ppf ~f:(fun () -> exec_phrases phrases) + in + { corrected_expectations; trailing_output } + +let output_slice oc s a b = + output_string oc (String.sub s ~pos:a ~len:(b - a)) + +let output_corrected oc ~file_contents correction = + let output_body oc { str; tag } = + Printf.fprintf oc "{%s|%s|%s}" tag str tag + in + let ofs = + List.fold_left correction.corrected_expectations ~init:0 + ~f:(fun ofs c -> + output_slice oc file_contents ofs c.payload_loc.loc_start.pos_cnum; + output_body oc c.normal; + if !Clflags.principal && c.normal.str <> c.principal.str then begin + output_string oc ", Principal"; + output_body oc c.principal + end; + c.payload_loc.loc_end.pos_cnum) + in + output_slice oc file_contents ofs (String.length file_contents); + match correction.trailing_output with + | "" -> () + | s -> Printf.fprintf oc "\n[%%%%expect{|%s|}]\n" s + +let write_corrected ~file ~file_contents correction = + let oc = open_out file in + output_corrected oc ~file_contents correction; + close_out oc + +let process_expect_file mapper fname = + let corrected_fname = fname ^ ".corrected" in + let file_contents = + let ic = open_in_bin fname in + match really_input_string ic (in_channel_length ic) with + | s -> close_in ic; Misc.normalise_eol s + | exception e -> close_in ic; raise e + in + let correction = eval_expect_file mapper fname ~file_contents in + write_corrected ~file:corrected_fname ~file_contents correction + +let repo_root = ref "" + +let main mapper fname = + Toploop.override_sys_argv + (Array.sub Sys.argv ~pos:!Arg.current + ~len:(Array.length Sys.argv - !Arg.current)); + (* Ignore OCAMLRUNPARAM=b to be reproducible *) + Printexc.record_backtrace false; + List.iter [ "stdlib" ] ~f:(fun s -> + Topdirs.dir_directory (Filename.concat !repo_root s)); + Toploop.initialize_toplevel_env (); + Sys.interactive := false; + process_expect_file mapper fname; + exit 0 + +let args = + Arg.align + [ "-repo-root", Set_string repo_root, + " root of the OCaml repository" + ; "-principal", Set Clflags.principal, + " Evaluate the file with -principal set" + ] + +let usage = "Usage: expect_test [script-file [arguments]]\n\ + options are:" + +let run mapper = + Toploop.set_paths (); + Clflags.error_style := Some Misc.Error_style.Short; + try + Arg.parse args (main mapper) usage; + Printf.eprintf "expect_test: no input file\n"; + exit 2 + with exn -> + Location.report_exception Format.err_formatter exn; + exit 2