Skip to content

Commit

Permalink
Make some interface docs
Browse files Browse the repository at this point in the history
  • Loading branch information
davesnx committed Mar 4, 2024
1 parent 09127d5 commit be5f882
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 162 deletions.
55 changes: 26 additions & 29 deletions lib/RegExp.ml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type regex = {
type t = {
bc : Unsigned.uint8 Ctypes_static.ptr;
source : string;
flags : int;
Expand All @@ -7,41 +7,40 @@ type regex = {

type result = { captures : string array; input : string }

(* #define LRE_FLAG_GLOBAL (1 << 0) *)
let lre_flag_global = 0b01

(* #define LRE_FLAG_IGNORECASE (1 << 1) *)
let lre_flag_ignorecase = 0b10

(* #define LRE_FLAG_MULTILINE (1 << 2) *)
let lre_flag_multiline = 0b100

(* #define LRE_FLAG_DOTALL (1 << 3) *)
let lre_flag_dotall = 0b1000

(* #define LRE_FLAG_UNICODE (1 << 4) *)
let lre_flag_unicode = 0b10000

(* #define LRE_FLAG_STICKY (1 << 5) *)
let lre_flag_sticky = 0b100000
let has_flag flags flag = flags land flag != 0
let global flags = has_flag flags lre_flag_global
let ignorecase flags = has_flag flags lre_flag_ignorecase
let multiline flags = has_flag flags lre_flag_multiline
let dotall flags = has_flag flags lre_flag_dotall
let sticky flags = has_flag flags lre_flag_sticky
let global regexp = has_flag regexp.flags lre_flag_global
let ignorecase regexp = has_flag regexp.flags lre_flag_ignorecase
let multiline regexp = has_flag regexp.flags lre_flag_multiline
let dotall regexp = has_flag regexp.flags lre_flag_dotall
let sticky regexp = has_flag regexp.flags lre_flag_sticky

let parse_flags flags =
let rec parse_flags' flags acc =
match flags with
| [] -> acc
| 'g' :: rest ->
(* #define LRE_FLAG_GLOBAL (1 << 0) *)
parse_flags' rest (acc lor lre_flag_global)
| 'i' :: rest ->
(* #define LRE_FLAG_IGNORECASE (1 << 1) *)
parse_flags' rest (acc lor lre_flag_ignorecase)
| 'm' :: rest ->
(* #define LRE_FLAG_MULTILINE (1 << 2) *)
parse_flags' rest (acc lor lre_flag_multiline)
| 's' :: rest ->
(* #define LRE_FLAG_DOTALL (1 << 3) *)
parse_flags' rest (acc lor lre_flag_dotall)
| 'u' :: rest ->
(* #define LRE_FLAG_UNICODE (1 << 4) *)
parse_flags' rest (acc lor lre_flag_unicode)
| 'y' :: rest ->
(* #define LRE_FLAG_STICKY (1 << 5) *)
parse_flags' rest (acc lor lre_flag_sticky)
| 'g' :: rest -> parse_flags' rest (acc lor lre_flag_global)
| 'i' :: rest -> parse_flags' rest (acc lor lre_flag_ignorecase)
| 'm' :: rest -> parse_flags' rest (acc lor lre_flag_multiline)
| 's' :: rest -> parse_flags' rest (acc lor lre_flag_dotall)
| 'u' :: rest -> parse_flags' rest (acc lor lre_flag_unicode)
| 'y' :: rest -> parse_flags' rest (acc lor lre_flag_sticky)
| _ :: rest -> parse_flags' rest acc
in
parse_flags' (String.to_seq flags |> List.of_seq) 0
Expand Down Expand Up @@ -84,7 +83,7 @@ let compile re flags =
print_endline error;
raise (Invalid_argument "Compilation failed")

let index regexp = regexp.lastIndex
let lastIndex regexp = regexp.lastIndex
let source regexp = regexp.source
let input result = result.input
let setLastIndex regexp lastIndex = regexp.lastIndex <- lastIndex
Expand Down Expand Up @@ -116,7 +115,7 @@ let exec regexp input =
(* if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
last_index = 0;
} *)
match global regexp.flags || sticky regexp.flags with
match global regexp || sticky regexp with
| true -> regexp.lastIndex
| false -> 0
in
Expand Down Expand Up @@ -163,9 +162,7 @@ let exec regexp input =
{ captures = substrings; input }
| 0 ->
(* When there's no matches left, sticky goes to lastIndex 0 *)
(match sticky regexp.flags with
| true -> regexp.lastIndex <- 0
| false -> ());
(match sticky regexp with true -> regexp.lastIndex <- 0 | false -> ());
{ captures = [||]; input }
| _ (* -1 *) -> raise (Invalid_argument "Error")

Expand Down
56 changes: 42 additions & 14 deletions lib/RegExp.mli
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
type regex
type t
(* The RegExp object *)

type result
(* The result of a executing a RegExp on a string *)

val compile : string -> string -> t
(* Constructs a RegExp.t from a string describing a regex and their flags *)

val lastIndex : t -> int
(* returns the index where the next match will start its search *)

val setLastIndex : t -> int -> unit
(* sets the index at which the next match (RegExp.exec or RegExp.test) will start its search from *)

val flags : t -> string
(* returns the enabled flags as a string *)

val global : t -> bool
(* returns a bool indicating whether the global flag (g) is set *)

val ignorecase : t -> bool
(* returns a bool indicating whether the ignorecase (i) flag is set *)

val multiline : t -> bool
(* returns a bool indicating whether the multiline (m) flag is set *)

val dotall : t -> bool
(* returns a bool indicating whether the dot all (s) flag is set *)

val sticky : t -> bool
(* returns a bool indicating whether the sticky (y) flag is set *)

val source : t -> string
(* returns the regexp pattern as a string *)

val test : t -> string -> bool
(* checks whether the given RegExp.t will match (or not match) a given string *)

val exec : t -> string -> result
(* executes a search on a given string using the given RegExp.t *)

val global : int -> bool
val ignorecase : int -> bool
val multiline : int -> bool
val dotall : int -> bool
val sticky : int -> bool
val parse_flags : string -> int
val compile : string -> string -> regex
val index : regex -> int
val setLastIndex : regex -> int -> unit
val exec : regex -> string -> result
val test : regex -> string -> bool
val captures : result -> string array
val flags : regex -> string
(* *)

val input : result -> string
val source : regex -> string
131 changes: 12 additions & 119 deletions test/suite.ml
Original file line number Diff line number Diff line change
@@ -1,123 +1,16 @@
open Quickjs

(* let suites = Mt.[
"captures", (fun _ ->
let re = [%re "/(\\d+)-(?:(\\d+))?/g"] in
let str = "3-" in
match re |. Js.Re.exec_ str with
| Some result ->
let defined = (Js.Re.captures result).(1) in
let undefined = (Js.Re.captures result).(2) in
Eq((Js.Nullable.return "3", Js.Nullable.null), (defined, undefined))
| None -> Fail()
);
"fromString", (fun _ ->
(* From the example in js_re.mli *)
let contentOf tag xmlString =
Js.Re.fromString ("<" ^ tag ^ ">(.*?)<\\/" ^ tag ^">")
|. Js.Re.exec_ xmlString
|. function
| Some result -> Js.Nullable.toOption (Js.Re.captures result).(1)
| None -> None in
Eq (contentOf "div" "<div>Hi</div>", Some "Hi")
);
"exec_literal", (fun _ ->
match [%re "/[^.]+/"] |. Js.Re.exec_ "http://xxx.domain.com" with
| Some res ->
Eq(Js.Nullable.return "http://xxx", (Js.Re.captures res).(0))
| None ->
FailWith "regex should match"
);
"exec_no_match", (fun _ ->
match [%re "/https:\\/\\/(.*)/"] |. Js.Re.exec_ "http://xxx.domain.com" with
| Some _ -> FailWith "regex should not match"
| None -> Ok true
);
"test_str", (fun _ ->
let res = "foo"
|. Js.Re.fromString
|. Js.Re.test_ "#foo#" in
Eq(true, res)
);
"fromStringWithFlags", (fun _ ->
let res = Js.Re.fromStringWithFlags "foo" ~flags:"g" in
Eq(true, res |. Js.Re.global)
);
"result_index", (fun _ ->
match "zbar" |. Js.Re.fromString |. Js.Re.exec_ "foobarbazbar" with
| Some res ->
Eq(8, res |> Js.Re.index)
| None ->
Fail ()
);
"result_input", (fun _ ->
let input = "foobar" in
match [%re "/foo/g"] |. Js.Re.exec_ input with
| Some res ->
Eq(input, res |> Js.Re.input)
| None ->
Fail ()
);
(* es2015 *)
"t_flags", (fun _ ->
Eq("gi", [%re "/./ig"] |. Js.Re.flags)
);
"t_global", (fun _ ->
Eq(true, [%re "/./ig"] |. Js.Re.global)
);
"t_ignoreCase", (fun _ ->
Eq(true, [%re "/./ig"] |. Js.Re.ignoreCase)
);
"t_lastIndex", (fun _ ->
let re = [%re "/na/g"] in
let _ = re |. Js.Re.exec_ "banana" in (* Caml_option.null_to_opt post operation is not dropped in 4.06 which seems to be reduandant *)
Eq(4, re |. Js.Re.lastIndex)
);
"t_setLastIndex", (fun _ ->
let re = [%re "/na/g"] in
let before = Js.Re.lastIndex re in
let () = Js.Re.setLastIndex re 42 in
let after = Js.Re.lastIndex re in
Eq((0, 42), (before, after))
);
"t_multiline", (fun _ ->
Eq(false, [%re "/./ig"] |. Js.Re.multiline)
);
"t_source", (fun _ ->
Eq("f.+o", [%re "/f.+o/ig"] |. Js.Re.source)
);
(* es2015 *)
"t_sticky", (fun _ ->
Eq(true, [%re "/./yg"] |. Js.Re.sticky)
);
"t_unicode", (fun _ ->
Eq(false, [%re "/./yg"] |. Js.Re.unicode)
);
]
;; Mt.from_pair_suites __MODULE__ suites *)
let test title fn = Alcotest.test_case title `Quick fn

let assert_result left right =
Alcotest.(check (array string)) "match" right left
Alcotest.(check (array string)) "should be equal" right left

let assert_int left right = Alcotest.(check int) "should be equal" right left

let assert_string left right =
Alcotest.(check string) "should be equal" right left

let assert_int left right = Alcotest.(check int) "match" right left
let assert_string left right = Alcotest.(check string) "match" right left
let assert_bool left right = Alcotest.(check bool) "match" right left
let assert_bool left right = Alcotest.(check bool) "should be equal" right left

let () =
Alcotest.run "RegExp"
Expand Down Expand Up @@ -181,19 +74,19 @@ let () =
assert_result (RegExp.captures result) [| "0" |]);
test "with y (sticky)" (fun () ->
let regex = RegExp.compile "foo" "y" in
assert_int (RegExp.index regex) 0;
assert_int (RegExp.lastIndex regex) 0;
let input = "foofoofoo" in
let result = RegExp.exec regex input in
assert_int (RegExp.index regex) 3;
assert_int (RegExp.lastIndex regex) 3;
assert_result (RegExp.captures result) [| "foo" |];
let result = RegExp.exec regex input in
assert_int (RegExp.index regex) 6;
assert_int (RegExp.lastIndex regex) 6;
assert_result (RegExp.captures result) [| "foo" |];
let result = RegExp.exec regex input in
assert_int (RegExp.index regex) 9;
assert_int (RegExp.lastIndex regex) 9;
assert_result (RegExp.captures result) [| "foo" |];
let result = RegExp.exec regex input in
assert_int (RegExp.index regex) 0;
assert_int (RegExp.lastIndex regex) 0;
assert_result (RegExp.captures result) [||]);
test "groups" (fun () ->
let regex = RegExp.compile "(xyz)" "" in
Expand Down

0 comments on commit be5f882

Please sign in to comment.