diff --git a/CHANGELOG.md b/CHANGELOG.md index 2679c6e207d..35327ab57ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ #### :boom: Breaking Change +- Remove `%re`; use regexp literals instead. https://github.com/rescript-lang/rescript/pull/8634 - Reject malformed UTF-8 in documentation comments and invalid string or template literal escapes that were previously accepted, including empty or out-of-range braced Unicode escapes (`\u{}`, `\u{110000}`) and legacy decimal or octal escapes in templates (`\1`, `\01`, `\8`). These inputs now produce syntax diagnostics instead of compiling to invalid or inconsistent JavaScript. https://github.com/rescript-lang/rescript/pull/8606 - Reject tagged template literals in patterns. Patterns cannot invoke their tag; previously their raw payload was compiled as a plain string comparison. https://github.com/rescript-lang/rescript/pull/8606 - Remove runtime APIs that were deprecated for removal in ReScript 13, including the `Char` module, unsafe `Obj` operations, legacy `Pervasives` helpers, and `Array.unsafe_get`. https://github.com/rescript-lang/rescript/pull/8564 @@ -85,6 +86,7 @@ #### :house: Internal - Refactor parser token handling to separate inspection (`peek`/`peek2`) from consumption, removing Diamond mode and `prev_end_pos` bookkeeping and moving missing JSX prop recovery from an editor-analysis regex heuristic into the parser. https://github.com/rescript-lang/rescript/pull/8633 +- Give regexp literals a dedicated AST node. https://github.com/rescript-lang/rescript/pull/8634 - Developer playground: Make panes resizable with wrapping text. https://github.com/rescript-lang/rescript/pull/8628 - Normalize Lambda terms where they are built: a match guard stays structured data until its fallthrough is known, and `apply` and `mk_builtin` go through the folding constructors. https://github.com/rescript-lang/rescript/pull/8615 - Replace non-escaping local mutable blocks with scalar bindings when all uses are direct field accesses, generalizing reference unboxing to multi-field records and references captured by JavaScript closures. https://github.com/rescript-lang/rescript/pull/8617 diff --git a/analysis/src/dump_ast.ml b/analysis/src/dump_ast.ml index aa30c5d4610..2819297b174 100644 --- a/analysis/src/dump_ast.ml +++ b/analysis/src/dump_ast.ml @@ -267,6 +267,8 @@ and print_expr_item expr ~pos ~indentation = ^ ")" | Pexp_extension (({txt} as loc), _) -> "Pexp_extension(%" ^ (loc |> print_loc_denominator_loc ~pos) ^ txt ^ ")" + | Pexp_regexp {pattern; flags} -> + "Pexp_regexp(/" ^ pattern ^ "/" ^ flags ^ ")" | Pexp_template {source_segments; values} -> "Pexp_template(source_segments=[" ^ String.concat ", " (List.map (fun {Asttypes.txt} -> txt) source_segments) diff --git a/analysis/src/utils.ml b/analysis/src/utils.ml index 4850604eb5d..be405cb20ed 100644 --- a/analysis/src/utils.ml +++ b/analysis/src/utils.ml @@ -118,6 +118,7 @@ let identify_pexp pexp = | Pexp_open _ -> "Pexp_open" | Pexp_await _ -> "Pexp_await" | Pexp_jsx_element _ -> "Pexp_jsx_element" + | Pexp_regexp _ -> "Pexp_regexp" | Pexp_template _ -> "Pexp_template" | Pexp_tagged_template _ -> "Pexp_tagged_template" diff --git a/compiler/ext/config.ml b/compiler/ext/config.ml index 5a1bef759ae..be12e47763a 100644 --- a/compiler/ext/config.ml +++ b/compiler/ext/config.ml @@ -2,9 +2,9 @@ let cmi_magic_number = "Caml1999I034" (* Magic numbers for marshaled values of the *current* parsetree, whose layout changes across compiler versions. *) -and ast_impl_magic_number = "ResImpl01307" +and ast_impl_magic_number = "ResImpl01308" -and ast_intf_magic_number = "ResIntf01307" +and ast_intf_magic_number = "ResIntf01308" (* Magic numbers of the frozen Parsetree0 (OCaml 4.06) layout used on the external-PPX wire. They must never be written in front of a diff --git a/compiler/frontend/ast_exp_extension.ml b/compiler/frontend/ast_exp_extension.ml index bc51df89a96..072d5d0a78c 100644 --- a/compiler/frontend/ast_exp_extension.ml +++ b/compiler/frontend/ast_exp_extension.ml @@ -56,11 +56,7 @@ let handle_extension e (_self : Ast_mapper.mapper) | Some msg -> " - Todo: " ^ msg)) ); ] | "ffi" -> Ast_exp_handle_external.handle_ffi ~loc ~payload - | "raw" -> Ast_exp_handle_external.handle_raw ~kind:Raw_exp loc payload - | "re" -> - Exp.constraint_ ~loc - (Ast_exp_handle_external.handle_raw ~kind:Raw_re loc payload) - (Ast_comb.to_regexp_type loc) + | "raw" -> Ast_exp_handle_external.handle_raw loc payload | "debugger" -> {e with pexp_desc = Ast_exp_handle_external.handle_debugger loc payload} | _ -> e diff --git a/compiler/frontend/ast_exp_handle_external.ml b/compiler/frontend/ast_exp_handle_external.ml index 0607617bdc5..4ab1212186f 100644 --- a/compiler/frontend/ast_exp_handle_external.ml +++ b/compiler/frontend/ast_exp_handle_external.ml @@ -34,20 +34,13 @@ let handle_debugger loc (payload : Ast_payload.t) = | _ -> Location.raise_errorf ~loc "%%debugger extension doesn't accept arguments" -let handle_raw ~kind loc payload = +let handle_raw loc payload = let is_function = ref None in - match Ast_payload.raw_as_string_exp_exn ~kind ~is_function payload with - | None -> ( - match kind with - | Raw_re -> - Location.raise_errorf ~loc - "%%re extension can only be applied to a string" - | Raw_exp -> - Location.raise_errorf ~loc - "%%raw extension can only be applied to a string" - | Raw_program -> - Location.raise_errorf ~loc - "%%%%raw extension can only be applied to a string") + match + Ast_payload.raw_as_string_exp_exn ~kind:Raw_exp ~is_function payload + with + | None -> + Location.raise_errorf ~loc "%%raw extension can only be applied to a string" | Some exp -> { exp with diff --git a/compiler/frontend/ast_exp_handle_external.mli b/compiler/frontend/ast_exp_handle_external.mli index 74829c3547c..c8cb803bee4 100644 --- a/compiler/frontend/ast_exp_handle_external.mli +++ b/compiler/frontend/ast_exp_handle_external.mli @@ -26,11 +26,7 @@ val handle_debugger : Location.t -> Ast_payload.t -> Parsetree.expression_desc val handle_ffi : loc:Location.t -> payload:Ast_payload.t -> Parsetree.expression -val handle_raw : - kind:Js_raw_info.raw_kind -> - Location.t -> - Ast_payload.t -> - Parsetree.expression +val handle_raw : Location.t -> Ast_payload.t -> Parsetree.expression val handle_raw_structure : Location.t -> Ast_payload.t -> Parsetree.structure_item diff --git a/compiler/frontend/bs_builtin_ppx.ml b/compiler/frontend/bs_builtin_ppx.ml index 8886205d148..15cb0243743 100644 --- a/compiler/frontend/bs_builtin_ppx.ml +++ b/compiler/frontend/bs_builtin_ppx.ml @@ -112,6 +112,23 @@ let expr_mapper ~async_context ~in_function_def (self : mapper) in_function_def := false; match e.pexp_desc with (* Its output should not be rewritten anymore *) + | Pexp_regexp {pattern; flags} -> + let loc = e.pexp_loc in + let source = "/" ^ pattern ^ "/" ^ flags in + Ast_payload.validate_raw_source ~kind:Raw_re ~loc ~offset:0 source; + let raw = + Ast_external_mk.local_external_apply loc + ~pval_prim:(Prim_name "#raw_expr") + ~pval_type: + (Ast_helper.Typ.arrow + [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}] + (Ast_helper.Typ.any ())) + [Ast_helper.Exp.constant ~loc (Pconst_raw_source source)] + in + Ast_helper.Exp.constraint_ ~loc + ~attrs:(self.attributes self e.pexp_attributes) + {e with pexp_desc = raw; pexp_attributes = []} + (Ast_comb.to_regexp_type loc) | Pexp_extension extension -> Ast_exp_extension.handle_extension e self extension | Pexp_constant (Pconst_integer (s, Some 'l')) -> diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 7dbc41bb1ae..b08d7bca33f 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -205,6 +205,8 @@ module Exp = struct let pack ?loc ?attrs a = mk ?loc ?attrs (Pexp_pack a) let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c)) let extension ?loc ?attrs a = mk ?loc ?attrs (Pexp_extension a) + let regexp ?loc ?attrs pattern flags = + mk ?loc ?attrs (Pexp_regexp {pattern; flags}) let template ?loc ?attrs source_segments values = mk ?loc ?attrs (Pexp_template {source_segments; values}) let tagged_template ?loc ?attrs tag raw_sources values = diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 3ba39f44407..a4947466e4f 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -226,6 +226,8 @@ module Exp : sig val object_literal : ?loc:loc -> ?attrs:attrs -> (str * expression) list -> expression + val regexp : ?loc:loc -> ?attrs:attrs -> string -> string -> expression + val template : ?loc:loc -> ?attrs:attrs -> str list -> expression list -> expression val letmodule : diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index a4b6fa9cd62..cbb9fb72199 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -390,6 +390,7 @@ module E = struct iter_loc sub lid; sub.expr sub e | Pexp_extension x -> sub.extension sub x + | Pexp_regexp _ -> () | Pexp_template {values} -> List.iter (sub.expr sub) values | Pexp_tagged_template {tag; values} -> sub.expr sub tag; diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 207d2880cbc..029d95e3004 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -352,6 +352,7 @@ module E = struct | Pexp_for_await_of (p, e1, e2) -> Exp.mk ~loc ~attrs (Pexp_for_await_of (sub.pat sub p, sub.expr sub e1, sub.expr sub e2)) + | Pexp_regexp {pattern; flags} -> regexp ~loc ~attrs pattern flags | Pexp_template {source_segments; values} -> Exp.template ~loc ~attrs (List.map (map_loc sub) source_segments) diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 858d93c7aed..d229e75362b 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -123,7 +123,7 @@ let map_pattern_constant ~loc = function | constant -> map_constant ~loc constant let is_raw_source_extension = function - | "raw" | "ffi" | "re" -> true + | "raw" | "ffi" -> true | _ -> false let map_raw_source_payload sub = function @@ -1132,6 +1132,49 @@ module E = struct | Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me) | Pexp_open (ovf, lid, e) -> open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e) + | Pexp_extension ({txt = "re"}, payload) -> ( + let malformed ~loc = + Location.raise_errorf ~loc + "A PPX returned a malformed regexp payload. Expected a string \ + containing one regexp literal." + in + match payload with + | PStr + [ + { + pstr_desc = + Pstr_eval + ( { + pexp_desc = Pexp_constant (Pconst_string (source, _)); + pexp_loc = source_loc; + pexp_attributes = source_attrs; + }, + eval_attrs ); + }; + ] -> ( + let env = Parser_env.init_env None source in + let (_, expression), errors = + Parser_flow.do_parse env Parser_flow.Parse.expression false + in + match expression with + | Flow_ast.Expression.RegExpLiteral {pattern; raw} + when errors = [] && Parser_env.Peek.token env = Token.T_EOF -> + (* Flow filters unknown flags in its [flags] field. Keep the raw + spelling so the bridge never silently changes a PPX's regexp. *) + let flags_start = String.length pattern + 2 in + let flags = + String.sub raw flags_start (String.length raw - flags_start) + in + (* Payload wrappers disappear at this boundary. Keep the expression's + location and transfer both levels of payload attributes to it. *) + regexp ~loc + ~attrs: + (attrs + @ sub.attributes sub eval_attrs + @ sub.attributes sub source_attrs) + pattern flags + | _ -> malformed ~loc:(sub.location sub source_loc)) + | _ -> malformed ~loc) | Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x) | Pexp_unreachable -> assert false end @@ -1369,6 +1412,9 @@ let default_mapper = location = (fun _this l -> l); extension = (fun this (s, payload) -> + if s.txt = "re" then + Location.raise_errorf ~loc:(this.location this s.loc) + "A PPX returned a regexp extension outside an expression."; let payload = if is_raw_source_extension s.txt then match map_raw_source_payload this payload with diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index 2ba915dd928..446d7e3ec62 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -652,6 +652,16 @@ module E = struct ~attrs:(for_await_of_attr :: attrs) (sub.pat sub pat) start_expr end_expr Asttypes.Upto (sub.expr sub body_expr) + | Pexp_regexp {pattern; flags} -> + (* %re is only a frozen PPX wire encoding, not source syntax. *) + extension ~loc ~attrs + ( Location.mkloc "re" loc, + Pt.PStr + [ + Ast_helper0.Str.eval ~loc + (Ast_helper0.Exp.constant ~loc + (Pt.Pconst_string ("/" ^ pattern ^ "/" ^ flags, Some "js"))); + ] ) | Pexp_template {source_segments; values} -> let segments = List.map diff --git a/compiler/ml/ast_payload.ml b/compiler/ml/ast_payload.ml index 05a536ddb08..76f9dda8f99 100644 --- a/compiler/ml/ast_payload.ml +++ b/compiler/ml/ast_payload.ml @@ -176,6 +176,31 @@ let constructor_tag_of_payload payload = | Some (Lident "undefined") -> Some Pct_undefined | Some _ | None -> None))))) +let validate_raw_source ~(kind : Js_raw_info.raw_kind) ?is_function ~loc ~offset + str = + Bs_flow_ast_utils.check_flow_errors ~loc ~offset + (match kind with + | Raw_re | Raw_exp -> + let ((_loc, expression) as program), errors = + let open Parser_flow in + let env = Parser_env.init_env None str in + do_parse env Parse.expression false + in + (if kind = Raw_re then + match expression with + | RegExpLiteral _ -> () + | _ -> + Location.raise_errorf ~loc + "Syntax error: a valid JS regex literal expected"); + (match is_function with + | Some is_function -> ( + match Classify_function.classify_exp program with + | Js_function {arity; _} -> is_function := Some arity + | _ -> ()) + | None -> ()); + errors + | Raw_program -> snd (Parser_flow.parse_program false None str)) + let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) : Parsetree.expression option = let string_expression = @@ -214,28 +239,7 @@ let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) : in match string_expression with | Some (str, offset, ({pexp_loc = loc} as expression)) -> - Bs_flow_ast_utils.check_flow_errors ~loc ~offset - (match kind with - | Raw_re | Raw_exp -> - let ((_loc, expression) as program), errors = - let open Parser_flow in - let env = Parser_env.init_env None str in - do_parse env Parse.expression false - in - (if kind = Raw_re then - match expression with - | RegExpLiteral _ -> () - | _ -> - Location.raise_errorf ~loc - "Syntax error: a valid JS regex literal expected"); - (match is_function with - | Some is_function -> ( - match Classify_function.classify_exp program with - | Js_function {arity; _} -> is_function := Some arity - | _ -> ()) - | None -> ()); - errors - | Raw_program -> snd (Parser_flow.parse_program false None str)); + validate_raw_source ~kind ?is_function ~loc ~offset str; Some {expression with pexp_desc = Pexp_constant (Pconst_raw_source str)} | None -> None diff --git a/compiler/ml/ast_payload.mli b/compiler/ml/ast_payload.mli index 40a740ddcf3..de44dcd3541 100644 --- a/compiler/ml/ast_payload.mli +++ b/compiler/ml/ast_payload.mli @@ -53,6 +53,17 @@ val is_single_int : t -> int option val constructor_tag_of_payload : t -> Parsetree.constructor_tag option (** The literal denoted by a valid variant-constructor [@as] payload. *) +val validate_raw_source : + kind:Js_raw_info.raw_kind -> + ?is_function:int option ref -> + loc:Location.t -> + offset:int -> + string -> + unit + +(** Validate JavaScript source using the same rules as raw extensions. + [offset] accounts for delimiters preceding the source in its location. *) + val raw_as_string_exp_exn : kind:Js_raw_info.raw_kind -> ?is_function:int option ref -> diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index 80fc2dc8cbb..28181b57e8e 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -225,6 +225,7 @@ let rec add_expr bv exp = | Pexp_apply {funct = e; args = el} -> add_expr bv e; List.iter (fun (_, e) -> add_expr bv e) el + | Pexp_regexp _ -> () | Pexp_template {values} -> List.iter (add_expr bv) values | Pexp_tagged_template {tag; values} -> add_expr bv tag; diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 120e518a2cd..e736c90b76f 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -54,8 +54,8 @@ type constant = otherwise the frontend rejects it. The string is JavaScript source, not a decoded ReScript string value. *) | Pconst_raw_source of string - (* JavaScript source carried by a compiler extension such as [raw], [ffi], or - [re]. For example, [%raw("x + 1")] stores ["x + 1"]. The extension + (* JavaScript source carried by a compiler extension such as [raw] or [ffi]. + For example, [%raw("x + 1")] stores ["x + 1"]. The extension interprets the string as JavaScript source rather than as a ReScript runtime string value. *) | Pconst_float of string * char option @@ -389,6 +389,9 @@ and expression_desc = (* for pattern of array_expr do body_expr *) | Pexp_for_await_of of pattern * expression * expression (* for await pattern of iterable_expr do body_expr *) + | Pexp_regexp of {pattern: string; flags: string} + (* Literal source without delimiters; escapes and flag order are preserved. + Validation happens during frontend lowering, not AST construction. *) | Pexp_template of {source_segments: string loc list; values: expression list} (* An ordinary backquoted expression. [source_segments] contains the validated text between and around the interpolations, including escape spelling; diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index 201d62ffee6..12a8cafaf52 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -781,6 +781,7 @@ and expression ctxt f x = pp f "@[<2>`%s@;%a@]" l (simple_expr ctxt) payload | Pexp_extension e -> extension ctxt f e | Pexp_await e -> pp f "@[await@ %a@]" (simple_expr ctxt) e + | Pexp_regexp {pattern; flags} -> pp f "/%s/%s" pattern flags | Pexp_template {source_segments; values} -> let rec parts f (source_segments, values) = match (source_segments, values) with diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 7b16b1592d4..bee24026932 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -382,6 +382,8 @@ and expression i ppf x = | Pexp_extension (s, arg) -> line i ppf "Pexp_extension \"%s\"\n" s.txt; payload i ppf arg + | Pexp_regexp {pattern; flags} -> + line i ppf "Pexp_regexp %S %S\n" pattern flags | Pexp_template {source_segments; values} -> line i ppf "Pexp_template\n"; List.iter diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 1f4fac9ee6c..0f2d516fbe3 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -174,6 +174,7 @@ let iter_expression f e = | Pexp_apply {funct = e; args = lel} -> expr e; List.iter (fun (_, e) -> expr e) lel + | Pexp_regexp _ -> () | Pexp_template {values} -> List.iter expr values | Pexp_tagged_template {tag; values} -> expr tag; @@ -2568,6 +2569,7 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp | Pexp_fun {newtypes = []; params; body = sfun_body; async} -> type_function ~async loc sexp.pexp_attributes env ty_expected params sfun_body + | Pexp_regexp _ -> assert false (* Lowered by the built-in frontend mapper. *) | Pexp_template {source_segments; values} -> begin_def (); let segments : Asttypes.template_segment list = diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index c2b3f4c986d..7f191c78d41 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -763,6 +763,8 @@ module Sexp_ast = struct ] | Pexp_extension ext -> Sexp.list [Sexp.atom "Pexp_extension"; extension ext] + | Pexp_regexp {pattern; flags} -> + Sexp.list [Sexp.atom "Pexp_regexp"; string pattern; string flags] | Pexp_template {source_segments; values} -> Sexp.list [ diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index f9181092f6a..c60c3a76021 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -993,7 +993,7 @@ and walk_expression expr t comments = in match expr.Parsetree.pexp_desc with | _ when comments = [] -> () - | Pexp_constant _ -> + | Pexp_regexp _ | Pexp_constant _ -> let leading, trailing = partition_leading_trailing comments expr.pexp_loc in attach t.leading expr.pexp_loc leading; attach t.trailing expr.pexp_loc trailing diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 211642fd55a..c1a905c33c4 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -2048,15 +2048,7 @@ and parse_constrained_expr_region p = and parse_regex ~start_pos p pattern flags = Parser.next p; let loc = mk_loc start_pos (Parser.position p) in - let payload = - Parsetree.PStr - [ - Ast_helper.Str.eval ~loc - (Ast_helper.Exp.constant ~loc - (Pconst_raw_source ("/" ^ pattern ^ "/" ^ flags))); - ] - in - Ast_helper.Exp.extension (Location.mkloc "re" loc, payload) + Ast_helper.Exp.regexp ~loc pattern flags (* Atomic expressions represent unambiguous expressions. * This means that regardless of the context, these expressions @@ -2134,8 +2126,7 @@ and parse_atomic_expr p = Parser.next_regex_token p; match Parser.peek p with | Regex (pattern, flags) -> parse_regex ~start_pos p pattern flags - | _ -> Ast_helper.Exp.extension (Location.mknoloc "re", Parsetree.PStr []) - ) + | _ -> assert false (* next_regex_token always returns Regex. *)) | token -> ( let err_pos = Parser.position p in Parser.err ~start_pos:err_pos p @@ -7558,9 +7549,14 @@ and parse_extension ?(module_language = false) p = else Parser.expect Percent p; let attr_id = parse_attribute_id ~start_pos p in let payload = parse_payload p in + if attr_id.txt = "re" then + Parser.err ~start_pos:attr_id.loc.loc_start ~end_pos:attr_id.loc.loc_end p + (Diagnostics.message + "The %re extension has been removed. Use a regexp literal such as \ + /abc/i."); let payload = match (attr_id.txt, payload) with - | ( ("raw" | "ffi" | "re"), + | ( ("raw" | "ffi"), Parsetree.PStr [ ({ diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index 6d958dd52d7..b24c5fbede3 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -271,9 +271,9 @@ let field_expr expr = | { pexp_desc = ( Pexp_assert _ | Pexp_extension _ (* %extension.x vs (%extension).x *) - | Pexp_object_literal _ (* ({"a": 1})["a"] *) | Pexp_fun _ - | Pexp_constraint _ | Pexp_coerce _ | Pexp_setfield _ | Pexp_match _ - | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_regexp _ | Pexp_object_literal _ (* ({"a": 1})["a"] *) + | Pexp_fun _ | Pexp_constraint _ | Pexp_coerce _ | Pexp_setfield _ + | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized @@ -365,11 +365,11 @@ let jsx_child_expr expr = | _ when Parsetree_viewer.expr_is_await expr -> Parenthesized | { Parsetree.pexp_desc = - ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _ - | Pexp_variant _ | Pexp_array _ | Pexp_pack _ | Pexp_record _ - | Pexp_object_literal _ | Pexp_extension _ | Pexp_letmodule _ - | Pexp_letexception _ | Pexp_open _ | Pexp_sequence _ | Pexp_let _ - | Pexp_jsx_element _ ); + ( Pexp_ident _ | Pexp_constant _ | Pexp_regexp _ | Pexp_field _ + | Pexp_construct _ | Pexp_variant _ | Pexp_array _ | Pexp_pack _ + | Pexp_record _ | Pexp_object_literal _ | Pexp_extension _ + | Pexp_letmodule _ | Pexp_letexception _ | Pexp_open _ + | Pexp_sequence _ | Pexp_let _ | Pexp_jsx_element _ ); pexp_attributes = []; } -> Nothing diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index a0fec1d8db8..7854dbe7091 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3471,20 +3471,9 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = Doc.soft_line; Doc.rbrace; ]) - | Pexp_extension extension -> ( - match extension with - | ( {txt = "re"}, - PStr - [ - { - pstr_desc = - Pstr_eval - ({pexp_desc = Pexp_constant (Pconst_raw_source expr)}, []); - }; - ] ) -> - Doc.text expr - | extension -> - print_extension ~state ~at_module_lvl:false extension cmt_tbl) + | Pexp_extension extension -> + print_extension ~state ~at_module_lvl:false extension cmt_tbl + | Pexp_regexp {pattern; flags} -> Doc.text ("/" ^ pattern ^ "/" ^ flags) | Pexp_template {source_segments; values} -> print_template_literal ~state ~source_segments ~values cmt_tbl | Pexp_tagged_template {tag; raw_sources; values} -> diff --git a/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected b/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected new file mode 100644 index 00000000000..5da2a14b576 --- /dev/null +++ b/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected @@ -0,0 +1,9 @@ + + Syntax error! + /.../fixtures/syntaxErrorsRemovedReExtension.res:1:10-12 + + 1 β”‚ let re = %re("/abc/i") + 2 β”‚ // Use the literal syntax instead. + 3 β”‚ let literal = /abc/i + + The %re extension has been removed. Use a regexp literal such as /abc/i. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res b/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res new file mode 100644 index 00000000000..6b21713fab9 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res @@ -0,0 +1,3 @@ +let re = %re("/abc/i") +// Use the literal syntax instead. +let literal = /abc/i diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml index 79b7a6bf3d3..5bc5a592b43 100644 --- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml +++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml @@ -1055,7 +1055,157 @@ let test_raw_extension_payloads_roundtrip_through_ast0 _ = assert_raw_extension_payload ~name ~expected:encoded expression; assert_raw_extension_payload ~name ~expected:encoded (map_expr0 (map_expr_to0 expression))) - ["raw"; "ffi"; "re"] + ["raw"; "ffi"] + +let test_removed_regexp_extension _ = + List.iter + (fun source -> + let result = + Res_driver.parse_implementation_from_source + ~display_filename:"Regexp.res" ~source + in + OUnit.assert_bool "legacy regexp syntax is rejected" result.invalid; + OUnit.assert_equal + [ + "The %re extension has been removed. Use a regexp literal such as \ + /abc/i."; + ] + (List.map Res_diagnostics.explain result.diagnostics)) + ["let re = %re(\"/abc/i\")"; "let re = %re(`/abc/i`)"; "%%re(\"/abc/i\")"] + +let test_regexp_parser_locations _ = + let source = "let re = /a/g" in + let result = + Res_driver.parse_implementation_from_source ~display_filename:"Regexp.res" + ~source + in + OUnit.assert_bool "valid regexp" (not result.invalid); + match result.parsetree with + | [ + { + Parsetree.pstr_desc = + Pstr_value + ( _, + [ + { + pvb_expr = + { + pexp_desc = Pexp_regexp {pattern = "a"; flags = "g"}; + pexp_loc; + }; + }; + ] ); + }; + ] -> + OUnit.assert_equal 9 pexp_loc.loc_start.pos_cnum; + OUnit.assert_equal 13 pexp_loc.loc_end.pos_cnum + | _ -> assert_failure "Expected a located regexp expression" + +let test_regexp_roundtrip_through_ast0 _ = + let loc = source_loc 10 31 in + let attrs = [attr "test.regexp" (Parsetree.PStr [])] in + let expression = Ast_helper.Exp.regexp ~loc ~attrs {|a\/[/]\d|} "ig" in + let expression0 = map_expr_to0 expression in + (match expression0.pexp_desc with + | Pexp_extension + ( {txt = "re"; loc = name_loc}, + PStr + [ + { + pstr_desc = + Pstr_eval + ( { + pexp_desc = + Pexp_constant (Pconst_string (source, Some "js")); + pexp_loc; + pexp_attributes = []; + }, + [] ); + pstr_loc; + }; + ] ) -> + OUnit.assert_equal {|/a\/[/]\d/ig|} source; + OUnit.assert_equal loc name_loc; + OUnit.assert_equal loc pexp_loc; + OUnit.assert_equal loc pstr_loc + | _ -> assert_failure "Expected the legacy regexp extension wire shape"); + OUnit.assert_equal expression (map_expr0 expression0) + +let regexp_payload0 ?(delimiter = Some "js") source = + let outer_loc = source_loc 10 50 in + Ast_helper0.Exp.extension ~loc:outer_loc + ~attrs:[attr "test.outer" (Parsetree0.PStr [])] + ( Location.mkloc "re" (source_loc 11 13), + Parsetree0.PStr + [ + Ast_helper0.Str.eval ~loc:(source_loc 14 49) + ~attrs:[attr "test.eval" (Parsetree0.PStr [])] + (Ast_helper0.Exp.constant ~loc:(source_loc 15 48) + ~attrs:[attr "test.payload" (Parsetree0.PStr [])] + (Parsetree0.Pconst_string (source, delimiter))); + ] ) + +let test_ppx_regexp_payloads _ = + List.iter + (fun delimiter -> + let expression = + map_expr0 (regexp_payload0 ~delimiter {| /a\/[/]\d/ig |}) + in + let expected = + Ast_helper.Exp.regexp ~loc:(source_loc 10 50) + ~attrs: + (List.map + (fun name -> attr name (Parsetree.PStr [])) + ["test.outer"; "test.eval"; "test.payload"]) + {|a\/[/]\d|} "ig" + in + OUnit.assert_equal expected expression; + OUnit.assert_equal expected (map_expr0 (map_expr_to0 expression))) + [None; Some "js"; Some "*j"; Some "quoted"]; + (* The JS parser's filtered flags must not silently change PPX output. *) + match (map_expr0 (regexp_payload0 "/a/zig")).pexp_desc with + | Pexp_regexp {pattern = "a"; flags = "zig"} -> () + | _ -> assert_failure "Expected the original regexp flags" + +let test_malformed_ppx_regexp_payloads _ = + let reject expression0 expected_loc = + match map_expr0 expression0 with + | _ -> + assert_failure + "Expected malformed PPX regexp to be rejected at the bridge" + | exception Location.Error error -> + OUnit.assert_equal expected_loc error.loc; + OUnit.assert_equal + "A PPX returned a malformed regexp payload. Expected a string \ + containing one regexp literal." + error.msg + in + List.iter + (fun source -> reject (regexp_payload0 source) (source_loc 15 48)) + ["not a literal"; ""; "/a"; "/[/"; "/a/; other()"; "/a/ + /b/"]; + reject + (Ast_helper0.Exp.extension ~loc (Location.mknoloc "re", Parsetree0.PStr [])) + loc; + reject + (Ast_helper0.Exp.extension ~loc + ( Location.mknoloc "re", + Parsetree0.PStr + [ + Ast_helper0.Str.eval + (Ast_helper0.Exp.constant + (Parsetree0.Pconst_integer ("1", None))); + ] )) + loc; + match + Ast_mapper_from0.default_mapper.extension Ast_mapper_from0.default_mapper + (Location.mkloc "re" (source_loc 1 3), Parsetree0.PStr []) + with + | _ -> + assert_failure "Expected non-expression regexp extension to be rejected" + | exception Location.Error error -> + OUnit.assert_equal (source_loc 1 3) error.loc; + OUnit.assert_equal + "A PPX returned a regexp extension outside an expression." error.msg let test_tagged_templates_roundtrip_through_ast0 _ = let head_loc = source_loc 4 16 in @@ -1353,6 +1503,11 @@ let suites = >:: test_ppx_byte_strings_convert_to_valid_utf8; "string_literals_roundtrip_through_ast0" >:: test_string_literals_roundtrip_through_ast0; + "removed_regexp_extension" >:: test_removed_regexp_extension; + "regexp_parser_locations" >:: test_regexp_parser_locations; + "regexp_roundtrip_through_ast0" >:: test_regexp_roundtrip_through_ast0; + "ppx_regexp_payloads" >:: test_ppx_regexp_payloads; + "malformed_ppx_regexp_payloads" >:: test_malformed_ppx_regexp_payloads; "raw_extension_payloads_roundtrip_through_ast0" >:: test_raw_extension_payloads_roundtrip_through_ast0; "tagged_templates_roundtrip_through_ast0" diff --git a/tests/ounit_tests/ounit_lambda_constant_tests.ml b/tests/ounit_tests/ounit_lambda_constant_tests.ml index 5c41811a444..0b875eccd29 100644 --- a/tests/ounit_tests/ounit_lambda_constant_tests.ml +++ b/tests/ounit_tests/ounit_lambda_constant_tests.ml @@ -191,6 +191,95 @@ let suites = __FILE__ >::: switch_tests @ [ + ( "regexp literals lower to raw JavaScript" >:: fun _ -> + let expression = Ast_helper.Exp.regexp {|a\/b\d|} "ig" in + let wire = + Ast_mapper_to0.default_mapper.expr Ast_mapper_to0.default_mapper + expression + in + let payload_loc = {Location.none with loc_ghost = false} in + let payload_attr = + (Location.mknoloc "test.payload", Parsetree0.PStr []) + in + let legacy ?(delimiter = Some "js") ?(attrs = []) () = + Ast_helper0.Exp.extension + ( Location.mknoloc "re", + Parsetree0.PStr + [ + Ast_helper0.Str.eval + (Ast_helper0.Exp.constant ~loc:payload_loc ~attrs + (Parsetree0.Pconst_string ({|/a\/b\d/ig|}, delimiter))); + ] ) + in + List.iter + (fun wire -> + let expression = + Ast_mapper_from0.default_mapper.expr + Ast_mapper_from0.default_mapper wire + in + (match expression.Parsetree.pexp_desc with + | Pexp_regexp {pattern; flags} -> + OUnit.assert_equal {|a\/b\d|} pattern; + OUnit.assert_equal "ig" flags + | _ -> + OUnit.assert_failure + "AST0 regexp must leave the bridge as a literal"); + let lowered = + Bs_builtin_ppx.mapper.expr Bs_builtin_ppx.mapper expression + in + let raw = + match lowered.Parsetree.pexp_desc with + | Pexp_constraint + ( raw, + { + ptyp_desc = + Ptyp_constr + ( { + txt = + Longident.Ldot (Lident "Stdlib_RegExp", "t"); + }, + [] ); + } ) -> + raw + | _ -> + OUnit.assert_failure "expected the regexp type constraint" + in + let structure = + [ + Ast_helper.Str.value Asttypes.Nonrecursive + [ + Ast_helper.Vb.mk + (Ast_helper.Pat.var (Location.mknoloc "regexp")) + raw; + ]; + ] + in + let typed, _, _ = + Typemod.type_structure Env.initial_safe_string structure + Location.none + in + let implementation = + Translmod.transl_implementation "RegexpTest" + (typed, Typedtree.Tcoerce_none) + in + let sources = ref [] in + let rec collect lambda = + (match lambda with + | Lambda.Lprim {primitive = Praw_js_code {code}; args = []} + -> + sources := code :: !sources + | _ -> ()); + Lambda_traverse.iter collect lambda + in + collect implementation.lambda; + OUnit.assert_equal ~printer:Ext_obj.dump [{|/a\/b\d/ig|}] + !sources) + [ + wire; + legacy (); + legacy ~attrs:[payload_attr] (); + legacy ~delimiter:None (); + ] ); ( "typed string constants" >:: fun _ -> Lambda.const_string "value" =~ Lambda.Const_string "value" ); ( "compiler-generated strings normalize malformed bytes" >:: fun _ -> diff --git a/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt b/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt new file mode 100644 index 00000000000..5818664601b --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt @@ -0,0 +1,13 @@ +let plain = /abc/ +let escaped = /a\/b\\c\d/ig +let characterClass = /[/\]]+/u +let unicode = /δΈ–η•Œ+/g +let dot = /./s +let flags = /a/dgimsuy +@foo +let attributed = /a/g +let comments = [/* before */ /a/g /* after */, /b/] +let use = f(/a/i, /b/m) +let expressionAttributes = @foo /a/g +let choice = flag ? /a/i : /b/m +let indexed = /a/g["lastIndex"] diff --git a/tests/syntax_tests/data/ast-mapping/regexp.res b/tests/syntax_tests/data/ast-mapping/regexp.res new file mode 100644 index 00000000000..5818664601b --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/regexp.res @@ -0,0 +1,13 @@ +let plain = /abc/ +let escaped = /a\/b\\c\d/ig +let characterClass = /[/\]]+/u +let unicode = /δΈ–η•Œ+/g +let dot = /./s +let flags = /a/dgimsuy +@foo +let attributed = /a/g +let comments = [/* before */ /a/g /* after */, /b/] +let use = f(/a/i, /b/m) +let expressionAttributes = @foo /a/g +let choice = flag ? /a/i : /b/m +let indexed = /a/g["lastIndex"] diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt index 1fc664599d0..efc2b664ec1 100644 --- a/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt +++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt @@ -29,6 +29,6 @@ unterminated regex -let re = [%re {js|/[]/|js}] -;;[%re {js|//|js}] -let r = [%re {js|/./|js}] \ No newline at end of file +let re = /[]/ +;;// +let r = /./ \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt new file mode 100644 index 00000000000..d9f16f6fa0e --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt @@ -0,0 +1,23 @@ + + Syntax error! + syntax_tests/data/parsing/errors/expressions/removedReExtension.res:1:14-16 + + 1 β”‚ let legacy = %re("/abc/i") + 2 β”‚ let backquoted = %re(`/abc/i`) + 3 β”‚ let literal = /abc/i + + The %re extension has been removed. Use a regexp literal such as /abc/i. + + + Syntax error! + syntax_tests/data/parsing/errors/expressions/removedReExtension.res:2:18-20 + + 1 β”‚ let legacy = %re("/abc/i") + 2 β”‚ let backquoted = %re(`/abc/i`) + 3 β”‚ let literal = /abc/i + + The %re extension has been removed. Use a regexp literal such as /abc/i. + +let legacy = [%re {js|/abc/i|js}] +let backquoted = [%re `/abc/i`] +let literal = /abc/i \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res b/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res new file mode 100644 index 00000000000..27d52e684f3 --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res @@ -0,0 +1,3 @@ +let legacy = %re("/abc/i") +let backquoted = %re(`/abc/i`) +let literal = /abc/i \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/angle_operators.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/angle_operators.res.txt index 06524342a93..a30403f20d0 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/angle_operators.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/angle_operators.res.txt @@ -11,7 +11,7 @@ let call = f ((a >> b), (a >= b)) (fun [arity:1]x -> (Some [|x|] : int array option)) let comment = a >>> b let template = `prefix ${(a >> 2) >= b} suffix` -let regex = [%re {js|/[<>]+>>=/g|js}] +let regex = /[<>]+>>=/g let jsx =
= b)[@res.braces ]) data-value=((a >> 1) [@res.braces ])>((a << 1)[@res.braces ]) <>((`value ${a >>> 1}`)[@res.braces ])
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt index 05493287227..76723856271 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt @@ -1,374 +1,373 @@ -let re = [%re {js|/a*[^a|b]/|js}] -let re = [%re {js|/.*[^a|b]/|js}] -let re = [%re {js|/(?Pa)/|js}] -let re = [%re {js|/(?Pa)/|js}] -let re = [%re {js|/(?Pa)/|js}] -let re = [%re {js|/(?Pa)(?P=foo_123/|js}] -let re = [%re {js|/(?Pa)(?P=1)/|js}] -let re = [%re {js|/(?Pa)(?P=!)/|js}] -let re = [%re {js|/(?Pa)(?P=foo_124/|js}] -let re = [%re {js|/(?Pa)/|js}] -let re = [%re {js|/(?Pa)(?P=foo_123)/|js}] -let re = [%re {js|/\\1/|js}] -let re = [%re {js|/[\\1]/|js}] -let re = [%re {js|/\\09/|js}] -let re = [%re {js|/\\141/|js}] -let re = [%re {js|/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/|js}] -let re = [%re {js|/\0/|js}] -let re = [%re {js|/[\0a]/|js}] -let re = [%re {js|/[a\0]/|js}] -let re = [%re {js|/[^a\0]/|js}] -let re = [%re {js|/\a[\b]\f\n\r\t\v/|js}] -let re = [%re {js|/[\a][\b][\f][\n][\r][\t][\v]/|js}] -let re = [%re {js|/\u/|js}] -let re = [%re {js|/\xff/|js}] -let re = [%re {js|/\x00ffffffffffffff/|js}] -let re = [%re {js|/\x00f/|js}] -let re = [%re {js|/\x00fe/|js}] +let re = /a*[^a|b]/ +let re = /.*[^a|b]/ +let re = /(?Pa)/ +let re = /(?Pa)/ +let re = /(?Pa)/ +let re = /(?Pa)(?P=foo_123/ +let re = /(?Pa)(?P=1)/ +let re = /(?Pa)(?P=!)/ +let re = /(?Pa)(?P=foo_124/ +let re = /(?Pa)/ +let re = /(?Pa)(?P=foo_123)/ +let re = /\\1/ +let re = /[\\1]/ +let re = /\\09/ +let re = /\\141/ +let re = /(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/ +let re = /\0/ +let re = /[\0a]/ +let re = /[a\0]/ +let re = /[^a\0]/ +let re = /\a[\b]\f\n\r\t\v/ +let re = /[\a][\b][\f][\n][\r][\t][\v]/ +let re = /\u/ +let re = /\xff/ +let re = /\x00ffffffffffffff/ +let re = /\x00f/ +let re = /\x00fe/ let re = - [%re - {js|/^\w+=(\\[\000-\277]|[^\n\\])*", "SRC=eval.c g.c blah blah blah \\\\\n\tapes.c/|js}] -let re = [%re {js|/a.b/|js}] -let re = [%re {js|/a.b/|js}] -let re = [%re {js|/a.*b/|js}] -let re = [%re {js|/a.{4,5}b/|js}] -let re = [%re {js|/a.b/|js}] -let re = [%re {js|/(?s)a.b/|js}] -let re = [%re {js|/(?s)a.*b/|js}] -let re = [%re {js|/(?s)a.{4,5}b/|js}] -let re = [%re {js|/(?s)a.b/|js}] -let re = [%re {js|/)/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/ab*c/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab?c/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/^abc/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/abc$/|js}] -let re = [%re {js|/^/|js}] -let re = [%re {js|/$/|js}] -let re = [%re {js|/a.c/|js}] -let re = [%re {js|/a.c/|js}] -let re = [%re {js|/a.*c/|js}] -let re = [%re {js|/a.*c/|js}] -let re = [%re {js|/a[bc]d/|js}] -let re = [%re {js|/a[bc]d/|js}] -let re = [%re {js|/a[b-d]e/|js}] -let re = [%re {js|/a[b-d]e/|js}] -let re = [%re {js|/a[b-d]/|js}] -let re = [%re {js|/a[-b]/|js}] -let re = [%re {js|/a[\\-b]/|js}] -let re = [%re {js|/a[]b/|js}] -let re = [%re {js|/a\\/|js}] -let re = [%re {js|/a]/|js}] -let re = [%re {js|/a[]]b/|js}] -let re = [%re {js|/a[\\]]b/|js}] -let re = [%re {js|/a[^bc]d/|js}] -let re = [%re {js|/a[^bc]d/|js}] -let re = [%re {js|/a[^-b]c/|js}] -let re = [%re {js|/a[^-b]c/|js}] -let re = [%re {js|/a[^]b]c/|js}] -let re = [%re {js|/a[^]b]c/|js}] -let re = [%re {js|/\\ba\\b/|js}] -let re = [%re {js|/\\ba\\b/|js}] -let re = [%re {js|/\\ba\\b/|js}] -let re = [%re {js|/\\by\\b/|js}] -let re = [%re {js|/\\by\\b/|js}] -let re = [%re {js|/\\by\\b/|js}] -let re = [%re {js|/x\\b/|js}] -let re = [%re {js|/x\\B/|js}] -let re = [%re {js|/\\Bz/|js}] -let re = [%re {js|/z\\B/|js}] -let re = [%re {js|/\\Bx/|js}] -let re = [%re {js|/\\Ba\\B/|js}] -let re = [%re {js|/\\Ba\\B/|js}] -let re = [%re {js|/\\Ba\\B/|js}] -let re = [%re {js|/\\By\\B/|js}] -let re = [%re {js|/\\By\\B/|js}] -let re = [%re {js|/\\By\\b/|js}] -let re = [%re {js|/\\by\\B/|js}] -let re = [%re {js|/\\By\\B/|js}] -let re = [%re {js|/ab|cd/|js}] -let re = [%re {js|/ab|cd/|js}] -let re = [%re {js|/()ef/|js}] -let re = [%re {js|/$b/|js}] -let re = [%re {js|/a\\(b/|js}] -let re = [%re {js|/a\\(*b/|js}] -let re = [%re {js|/a\\(*b/|js}] -let re = [%re {js|/a\\\\b/|js}] -let re = [%re {js|/((a))/|js}] -let re = [%re {js|/(a)b(c)/|js}] -let re = [%re {js|/a+b+c/|js}] -let re = [%re {js|/(a+|b)*/|js}] -let re = [%re {js|/(a+|b)+/|js}] -let re = [%re {js|/(a+|b)?/|js}] -let re = [%re {js|/)(/|js}] -let re = [%re {js|/[^ab]*/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/a*/|js}] -let re = [%re {js|/a|b|c|d|e/|js}] -let re = [%re {js|/(a|b|c|d|e)f/|js}] -let re = [%re {js|/abcd*efg/|js}] -let re = [%re {js|/ab*/|js}] -let re = [%re {js|/ab*/|js}] -let re = [%re {js|/(ab|cd)e/|js}] -let re = [%re {js|/[abhgefdc]ij/|js}] -let re = [%re {js|/^(ab|cd)e/|js}] -let re = [%re {js|/(abc|)ef/|js}] -let re = [%re {js|/(a|b)c*d/|js}] -let re = [%re {js|/(ab|ab*)bc/|js}] -let re = [%re {js|/a([bc]*)c*/|js}] -let re = [%re {js|/a([bc]*)(c*d)/|js}] -let re = [%re {js|/a([bc]+)(c*d)/|js}] -let re = [%re {js|/a([bc]*)(c+d)/|js}] -let re = [%re {js|/a[bcd]*dcdcde/|js}] -let re = [%re {js|/a[bcd]+dcdcde/|js}] -let re = [%re {js|/(ab|a)b*c/|js}] -let re = [%re {js|/((a)(b)c)(d)/|js}] -let re = [%re {js|/[a-zA-Z_][a-zA-Z0-9_]*/|js}] -let re = [%re {js|/^a(bc+|b[eh])g|.h$/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(((((((((a)))))))))/|js}] -let re = [%re {js|/multiple words of text/|js}] -let re = [%re {js|/multiple words/|js}] -let re = [%re {js|/(.*)c(.*)/|js}] -let re = [%re {js|/\\((.*), (.*)\\)/|js}] -let re = [%re {js|/[k]/|js}] -let re = [%re {js|/a[-]?c/|js}] -let re = [%re {js|/(abc)\\1/|js}] -let re = [%re {js|/([a-c]*)\\1/|js}] -let re = [%re {js|/^(.+)?B/|js}] -let re = [%re {js|/(a+).\\1$/|js}] -let re = [%re {js|/^(a+).\\1$/|js}] -let re = [%re {js|/(abc)\\1/|js}] -let re = [%re {js|/([a-c]+)\\1/|js}] -let re = [%re {js|/(a)\\1/|js}] -let re = [%re {js|/(a+)\\1/|js}] -let re = [%re {js|/(a+)+\\1/|js}] -let re = [%re {js|/(a).+\\1/|js}] -let re = [%re {js|/(a)ba*\\1/|js}] -let re = [%re {js|/(aa|a)a\\1$/|js}] -let re = [%re {js|/(a|aa)a\\1$/|js}] -let re = [%re {js|/(a+)a\\1$/|js}] -let re = [%re {js|/([abc]*)\\1/|js}] -let re = [%re {js|/(a)(b)c|ab/|js}] -let re = [%re {js|/(a)+x/|js}] -let re = [%re {js|/([ac])+x/|js}] -let re = [%re {js|/([^.]*)\\.([^:]*):[T ]+(.*)/|js}] -let re = [%re {js|/([^N]*N)+/|js}] -let re = [%re {js|/([^N]*N)+/|js}] -let re = [%re {js|/([abc]*)x/|js}] -let re = [%re {js|/([abc]*)x/|js}] -let re = [%re {js|/([xyz]*)x/|js}] -let re = [%re {js|/(a)+b|aac/|js}] -let re = [%re {js|/(?Paaa)a/|js}] -let re = [%re {js|/(?Paaa)a/|js}] -let re = [%re {js|/(?Paa)(?P=id)/|js}] -let re = [%re {js|/(?Paa)(?P=xd)/|js}] -let re = [%re {js|/\\1/|js}] -let re = [%re {js|/\\09/|js}] -let re = [%re {js|/\\141/|js}] -let re = [%re {js|/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/ab*c/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab*bc/|js}] -let re = [%re {js|/ab{0,}bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab{1,}bc/|js}] -let re = [%re {js|/ab+bc/|js}] -let re = [%re {js|/ab{1,}bc/|js}] -let re = [%re {js|/ab{1,3}bc/|js}] -let re = [%re {js|/ab{3,4}bc/|js}] -let re = [%re {js|/ab{4,5}bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab{0,1}bc/|js}] -let re = [%re {js|/ab?bc/|js}] -let re = [%re {js|/ab?c/|js}] -let re = [%re {js|/ab{0,1}c/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/^abc/|js}] -let re = [%re {js|/^abc$/|js}] -let re = [%re {js|/abc$/|js}] -let re = [%re {js|/^/|js}] -let re = [%re {js|/$/|js}] -let re = [%re {js|/a.c/|js}] -let re = [%re {js|/a.c/|js}] -let re = [%re {js|/a.*c/|js}] -let re = [%re {js|/a.*c/|js}] -let re = [%re {js|/a[bc]d/|js}] -let re = [%re {js|/a[bc]d/|js}] -let re = [%re {js|/a[b-d]e/|js}] -let re = [%re {js|/a[b-d]e/|js}] -let re = [%re {js|/a[b-d]/|js}] -let re = [%re {js|/a[-b]/|js}] -let re = [%re {js|/a[b-]/|js}] -let re = [%re {js|/a[b-a]/|js}] -let re = [%re {js|/a[]b/|js}] -let re = [%re {js|/a]/|js}] -let re = [%re {js|/a[]]b/|js}] -let re = [%re {js|/a[^bc]d/|js}] -let re = [%re {js|/a[^bc]d/|js}] -let re = [%re {js|/a[^-b]c/|js}] -let re = [%re {js|/a[^-b]c/|js}] -let re = [%re {js|/a[^]b]c/|js}] -let re = [%re {js|/a[^]b]c/|js}] -let re = [%re {js|/ab|cd/|js}] -let re = [%re {js|/ab|cd/|js}] -let re = [%re {js|/()ef/|js}] -let re = [%re {js|/(*)b/|js}] -let re = [%re {js|/$b/|js}] -let re = [%re {js|/a\\/|js}] -let re = [%re {js|/a\\(b/|js}] -let re = [%re {js|/a\\(*b/|js}] -let re = [%re {js|/a\\(*b/|js}] -let re = [%re {js|/a\\\\b/|js}] -let re = [%re {js|/((a))/|js}] -let re = [%re {js|/(a)b(c)/|js}] -let re = [%re {js|/a+b+c/|js}] -let re = [%re {js|/a{1,}b{1,}c/|js}] -let re = [%re {js|/a**/|js}] -let re = [%re {js|/a.+?c/|js}] -let re = [%re {js|/(a+|b)*/|js}] -let re = [%re {js|/(a+|b){0,}/|js}] -let re = [%re {js|/(a+|b)+/|js}] -let re = [%re {js|/(a+|b){1,}/|js}] -let re = [%re {js|/(a+|b)?/|js}] -let re = [%re {js|/(a+|b){0,1}/|js}] -let re = [%re {js|/)(/|js}] -let re = [%re {js|/[^ab]*/|js}] -let re = [%re {js|/abc/|js}] -let re = [%re {js|/a*/|js}] -let re = [%re {js|/([abc])*d/|js}] -let re = [%re {js|/([abc])*bcd/|js}] -let re = [%re {js|/a|b|c|d|e/|js}] -let re = [%re {js|/(a|b|c|d|e)f/|js}] -let re = [%re {js|/abcd*efg/|js}] -let re = [%re {js|/ab*/|js}] -let re = [%re {js|/ab*/|js}] -let re = [%re {js|/(ab|cd)e/|js}] -let re = [%re {js|/[abhgefdc]ij/|js}] -let re = [%re {js|/^(ab|cd)e/|js}] -let re = [%re {js|/(abc|)ef/|js}] -let re = [%re {js|/(a|b)c*d/|js}] -let re = [%re {js|/(ab|ab*)bc/|js}] -let re = [%re {js|/a([bc]*)c*/|js}] -let re = [%re {js|/a([bc]*)(c*d)/|js}] -let re = [%re {js|/a([bc]+)(c*d)/|js}] -let re = [%re {js|/a([bc]*)(c+d)/|js}] -let re = [%re {js|/a[bcd]*dcdcde/|js}] -let re = [%re {js|/a[bcd]+dcdcde/|js}] -let re = [%re {js|/(ab|a)b*c/|js}] -let re = [%re {js|/((a)(b)c)(d)/|js}] -let re = [%re {js|/[a-zA-Z_][a-zA-Z0-9_]*/|js}] -let re = [%re {js|/^a(bc+|b[eh])g|.h$/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}] -let re = [%re {js|/((((((((((a))))))))))/|js}] -let re = [%re {js|/((((((((((a))))))))))\\10/|js}] -let re = [%re {js|/((((((((((a))))))))))\\41/|js}] -let re = [%re {js|/(?i)((((((((((a))))))))))\\41/|js}] -let re = [%re {js|/(((((((((a)))))))))/|js}] -let re = [%re {js|/multiple words of text/|js}] -let re = [%re {js|/multiple words/|js}] -let re = [%re {js|/(.*)c(.*)/|js}] -let re = [%re {js|/\\((.*), (.*)\\)/|js}] -let re = [%re {js|/[k]/|js}] -let re = [%re {js|/a[-]?c/|js}] -let re = [%re {js|/(abc)\\1/|js}] -let re = [%re {js|/([a-c]*)\\1/|js}] -let re = [%re {js|/a(?!b)./|js}] -let re = [%re {js|/a(?=d)./|js}] -let re = [%re {js|/a(?=c|d)./|js}] -let re = [%re {js|/a(?:b|c|d)(.)/|js}] -let re = [%re {js|/a(?:b|c|d)*(.)/|js}] -let re = [%re {js|/a(?:b|c|d)+?(.)/|js}] -let re = [%re {js|/a(?:b|(c|e){1,2}?|d)+?(.)/|js}] -let re = [%re {js|/^(.+)?B/|js}] -let re = [%re {js|/(?]*?b/|js}] -let re = [%re {js|/^a*?$/|js}] -let re = [%re {js|/^((a)c)?(ab)$/|js}] -let re = [%re {js|/^([ab]*?)(?=(b)?)c/|js}] -let re = [%re {js|/^([ab]*?)(?!(b))c/|js}] -let re = [%re {js|/^([ab]*?)(?aaa)a/ +let re = /(?Paaa)a/ +let re = /(?Paa)(?P=id)/ +let re = /(?Paa)(?P=xd)/ +let re = /\\1/ +let re = /\\09/ +let re = /\\141/ +let re = /(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/ +let re = /abc/ +let re = /abc/ +let re = /abc/ +let re = /abc/ +let re = /abc/ +let re = /abc/ +let re = /ab*c/ +let re = /ab*bc/ +let re = /ab*bc/ +let re = /ab*bc/ +let re = /ab{0,}bc/ +let re = /ab+bc/ +let re = /ab+bc/ +let re = /ab+bc/ +let re = /ab{1,}bc/ +let re = /ab+bc/ +let re = /ab{1,}bc/ +let re = /ab{1,3}bc/ +let re = /ab{3,4}bc/ +let re = /ab{4,5}bc/ +let re = /ab?bc/ +let re = /ab?bc/ +let re = /ab{0,1}bc/ +let re = /ab?bc/ +let re = /ab?c/ +let re = /ab{0,1}c/ +let re = /^abc$/ +let re = /^abc$/ +let re = /^abc/ +let re = /^abc$/ +let re = /abc$/ +let re = /^/ +let re = /$/ +let re = /a.c/ +let re = /a.c/ +let re = /a.*c/ +let re = /a.*c/ +let re = /a[bc]d/ +let re = /a[bc]d/ +let re = /a[b-d]e/ +let re = /a[b-d]e/ +let re = /a[b-d]/ +let re = /a[-b]/ +let re = /a[b-]/ +let re = /a[b-a]/ +let re = /a[]b/ +let re = /a]/ +let re = /a[]]b/ +let re = /a[^bc]d/ +let re = /a[^bc]d/ +let re = /a[^-b]c/ +let re = /a[^-b]c/ +let re = /a[^]b]c/ +let re = /a[^]b]c/ +let re = /ab|cd/ +let re = /ab|cd/ +let re = /()ef/ +let re = /(*)b/ +let re = /$b/ +let re = /a\\/ +let re = /a\\(b/ +let re = /a\\(*b/ +let re = /a\\(*b/ +let re = /a\\\\b/ +let re = /((a))/ +let re = /(a)b(c)/ +let re = /a+b+c/ +let re = /a{1,}b{1,}c/ +let re = /a**/ +let re = /a.+?c/ +let re = /(a+|b)*/ +let re = /(a+|b){0,}/ +let re = /(a+|b)+/ +let re = /(a+|b){1,}/ +let re = /(a+|b)?/ +let re = /(a+|b){0,1}/ +let re = /)(/ +let re = /[^ab]*/ +let re = /abc/ +let re = /a*/ +let re = /([abc])*d/ +let re = /([abc])*bcd/ +let re = /a|b|c|d|e/ +let re = /(a|b|c|d|e)f/ +let re = /abcd*efg/ +let re = /ab*/ +let re = /ab*/ +let re = /(ab|cd)e/ +let re = /[abhgefdc]ij/ +let re = /^(ab|cd)e/ +let re = /(abc|)ef/ +let re = /(a|b)c*d/ +let re = /(ab|ab*)bc/ +let re = /a([bc]*)c*/ +let re = /a([bc]*)(c*d)/ +let re = /a([bc]+)(c*d)/ +let re = /a([bc]*)(c+d)/ +let re = /a[bcd]*dcdcde/ +let re = /a[bcd]+dcdcde/ +let re = /(ab|a)b*c/ +let re = /((a)(b)c)(d)/ +let re = /[a-zA-Z_][a-zA-Z0-9_]*/ +let re = /^a(bc+|b[eh])g|.h$/ +let re = /(bc+d$|ef*g.|h?i(j|k))/ +let re = /(bc+d$|ef*g.|h?i(j|k))/ +let re = /(bc+d$|ef*g.|h?i(j|k))/ +let re = /(bc+d$|ef*g.|h?i(j|k))/ +let re = /(bc+d$|ef*g.|h?i(j|k))/ +let re = /((((((((((a))))))))))/ +let re = /((((((((((a))))))))))\\10/ +let re = /((((((((((a))))))))))\\41/ +let re = /(?i)((((((((((a))))))))))\\41/ +let re = /(((((((((a)))))))))/ +let re = /multiple words of text/ +let re = /multiple words/ +let re = /(.*)c(.*)/ +let re = /\\((.*), (.*)\\)/ +let re = /[k]/ +let re = /a[-]?c/ +let re = /(abc)\\1/ +let re = /([a-c]*)\\1/ +let re = /a(?!b)./ +let re = /a(?=d)./ +let re = /a(?=c|d)./ +let re = /a(?:b|c|d)(.)/ +let re = /a(?:b|c|d)*(.)/ +let re = /a(?:b|c|d)+?(.)/ +let re = /a(?:b|(c|e){1,2}?|d)+?(.)/ +let re = /^(.+)?B/ +let re = /(?]*?b/ +let re = /^a*?$/ +let re = /^((a)c)?(ab)$/ +let re = /^([ab]*?)(?=(b)?)c/ +let re = /^([ab]*?)(?!(b))c/ +let re = /^([ab]*?)(? +let jsxValue = /} /> +let jsxValueSpace = /} /> +let jsxValueTab = /} /> +let jsxValueUnicode = /u} /> +let jsxValueDollar = /} /> +let jsxField = /).source /> diff --git a/tests/syntax_tests/data/printer/regexp.res b/tests/syntax_tests/data/printer/regexp.res new file mode 100644 index 00000000000..1c562b979e4 --- /dev/null +++ b/tests/syntax_tests/data/printer/regexp.res @@ -0,0 +1,20 @@ +let plain = /abc/ +let escaped = /a\/b\\c\d/ig +let characterClass = /[/\]]+/u +let unicode = /δΈ–η•Œ+/g +let dot = /./s +let flags = /a/dgimsuy +@foo +let attributed = /a/g +let comments = [/* before */ /a/g /* after */, /b/] +let use = f(/a/i, /b/m) +let expressionAttributes = @foo /a/g +let choice = flag ? /a/i : /b/m +let indexed = /a/g["lastIndex"] +let jsxPlain = +let jsxValue = /) /> +let jsxValueSpace = /) /> +let jsxValueTab = /) /> +let jsxValueUnicode = /u) /> +let jsxValueDollar = /) /> +let jsxField = /).source /> diff --git a/tests/tests/src/regexp_literal_test.mjs b/tests/tests/src/regexp_literal_test.mjs new file mode 100644 index 00000000000..4c549137fcd --- /dev/null +++ b/tests/tests/src/regexp_literal_test.mjs @@ -0,0 +1,32 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function make() { + return /a/g; +} + +Mocha.describe("Regexp_literal_test", () => { + Mocha.test("fresh instances and independent lastIndex", () => { + let first = make(); + let second = make(); + Test_utils.eq("File \"regexp_literal_test.res\", line 10, characters 7-14", first === second, false); + Test_utils.eq("File \"regexp_literal_test.res\", line 11, characters 7-14", first.test("aa"), true); + Test_utils.eq("File \"regexp_literal_test.res\", line 12, characters 7-14", first.lastIndex, 1); + Test_utils.eq("File \"regexp_literal_test.res\", line 13, characters 7-14", second.lastIndex, 0); + Test_utils.eq("File \"regexp_literal_test.res\", line 14, characters 7-14", first.test("aa"), true); + Test_utils.eq("File \"regexp_literal_test.res\", line 15, characters 7-14", first.test("aa"), false); + Test_utils.eq("File \"regexp_literal_test.res\", line 16, characters 7-14", first.lastIndex, 0); + }); + Mocha.test("escapes and flags survive lowering", () => { + Test_utils.eq("File \"regexp_literal_test.res\", line 19, characters 7-14", /a\/b\d/i.test("A/b2"), true); + Test_utils.eq("File \"regexp_literal_test.res\", line 20, characters 7-14", /[/]/.test("/"), true); + Test_utils.eq("File \"regexp_literal_test.res\", line 21, characters 7-14", /δΈ–η•Œ/u.test("δΈ–η•Œ"), true); + }); +}); + +export { + make, +} +/* Not a pure module */ diff --git a/tests/tests/src/regexp_literal_test.res b/tests/tests/src/regexp_literal_test.res new file mode 100644 index 00000000000..b59d29b55c1 --- /dev/null +++ b/tests/tests/src/regexp_literal_test.res @@ -0,0 +1,23 @@ +open Mocha +open Test_utils + +let make = () => /a/g + +describe(__MODULE__, () => { + test("fresh instances and independent lastIndex", () => { + let first = make() + let second = make() + eq(__LOC__, first === second, false) + eq(__LOC__, first->RegExp.test("aa"), true) + eq(__LOC__, first->RegExp.lastIndex, 1) + eq(__LOC__, second->RegExp.lastIndex, 0) + eq(__LOC__, first->RegExp.test("aa"), true) + eq(__LOC__, first->RegExp.test("aa"), false) + eq(__LOC__, first->RegExp.lastIndex, 0) + }) + test("escapes and flags survive lowering", () => { + eq(__LOC__, /a\/b\d/i->RegExp.test("A/b2"), true) + eq(__LOC__, /[/]/->RegExp.test("/"), true) + eq(__LOC__, /δΈ–η•Œ/u->RegExp.test("δΈ–η•Œ"), true) + }) +})