diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | src/gleam/regex.gleam | 75 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 29 | ||||
-rw-r--r-- | test/gleam/regex_test.gleam | 46 |
4 files changed, 151 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6d071..0815744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - The `option` module gains the the `map`, `flatten`, `then` and `or` functions. - The `result` module gains the the `or` function. +- Created the `regex` module with the `from_string`, `from_string_with`, and + `match` functions. ## 0.9.0 - 2020-05-26 diff --git a/src/gleam/regex.gleam b/src/gleam/regex.gleam new file mode 100644 index 0000000..3740984 --- /dev/null +++ b/src/gleam/regex.gleam @@ -0,0 +1,75 @@ +//// This module contains regular expression matching functions for strings. +//// The matching algorithms of the library are based on the PCRE library, but not +//// all of the PCRE library is interfaced and some parts of the library go beyond +//// what PCRE offers. Currently PCRE version 8.40 (release date 2017-01-11) is used. + +pub external type Regex + +/// When a regular expression fails to compile: +/// +/// - error — a descriptive error message +/// - index — the index of the cause in the regex string +/// +pub type FromStringError { + FromStringError(error: String, index: Int) +} + +/// Create a new Regex. +/// +/// ## Examples +/// +/// > let Ok(re) = from_string("[0-9]") +/// > match(re, "abc123") +/// True +/// +/// > match(re, "abcxyz") +/// False +/// +/// > from_string("[0-9") +/// Error( +/// FromStringError( +/// error: "missing terminating ] for character class", +/// index: 4 +/// ) +/// ) +/// +pub external fn from_string(String) -> Result(Regex, FromStringError) = + "gleam_stdlib" "regex_from_string" + +pub type Options { + Options(case_insensitive: Bool, multi_line: Bool) +} + +/// Create a Regex with some additional options. +/// +/// ## Examples +/// +/// > let options = Options(case_insensitive: False, multi_line: True) +/// > let Ok(re) = from_string_with(options, "^[0-9]") +/// > match(re, "abc\n123") +/// True +/// +/// > let options = Options(case_insensitive: True, multi_line: False) +/// > let Ok(re) = from_string_with(options, "[A-Z]") +/// > match(re, "abc123") +/// True +/// +pub external fn from_string_with( + Options, + String, +) -> Result(Regex, FromStringError) = + "gleam_stdlib" "regex_from_string_with" + +/// Returns a boolean indicating whether there was a match or not. +/// +/// ## Examples +/// +/// > let Ok(re) = from_string("^f.o.?") +/// > match(re, "foo") +/// True +/// +/// > match(re, "boo") +/// False +/// +pub external fn match(Regex, String) -> Bool = + "gleam_stdlib" "regex_match" diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 4b9c6a1..f0adbd0 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -10,7 +10,8 @@ string_pop_grapheme/1, string_starts_with/2, string_ends_with/2, string_pad/4, decode_tuple2/1, decode_map/1, bit_string_int_to_u32/1, bit_string_int_from_u32/1, bit_string_append/2, bit_string_part_/3, - decode_bit_string/1]). + decode_bit_string/1, regex_from_string/1, regex_from_string_with/2, + regex_match/2]). should_equal(Actual, Expected) -> ?assertEqual(Expected, Actual). should_not_equal(Actual, Expected) -> ?assertNotEqual(Expected, Actual). @@ -162,3 +163,29 @@ bit_string_int_from_u32(<<I:32>>) -> {ok, I}; bit_string_int_from_u32(_) -> {error, nil}. + +regex_from_string_with_opts(Options, String) -> + case re:compile(String, Options) of + {ok, MP} -> {ok, MP}; + {error, {Str, Pos}} -> + {error, {from_string_error, unicode:characters_to_binary(Str), Pos}} + end. + +regex_from_string(String) -> + regex_from_string_with_opts([unicode], String). + +regex_from_string_with(Options, String) -> + OptList = case Options of + {options, true, _} -> [unicode, caseless]; + _ -> [unicode] + end, + case Options of + {options, _, true} -> regex_from_string_with_opts([multiline | OptList], String); + _ -> regex_from_string_with_opts(OptList, String) + end. + +regex_match(Regex, String) -> + case re:run(String, Regex) of + {match, _} -> true; + _ -> false + end. diff --git a/test/gleam/regex_test.gleam b/test/gleam/regex_test.gleam new file mode 100644 index 0000000..d7ffc2d --- /dev/null +++ b/test/gleam/regex_test.gleam @@ -0,0 +1,46 @@ +import gleam/regex.{FromStringError, Options} +import gleam/should + +pub fn from_string_test() { + assert Ok(re) = regex.from_string("[0-9]") + + regex.match(re, "abc123") + |> should.equal(True) + + regex.match(re, "abcxyz") + |> should.equal(False) + + assert Error(from_string_err) = regex.from_string("[0-9") + + from_string_err + |> should.equal( + FromStringError( + error: "missing terminating ] for character class", + index: 4, + ), + ) +} + +pub fn from_string_with_test() { + let options = Options(case_insensitive: True, multi_line: False) + assert Ok(re) = regex.from_string_with(options, "[A-B]") + + regex.match(re, "abc123") + |> should.equal(True) + + let options = Options(case_insensitive: False, multi_line: True) + assert Ok(re) = regex.from_string_with(options, "^[0-9]") + + regex.match(re, "abc\n123") + |> should.equal(True) +} + +pub fn match_test() { + assert Ok(re) = regex.from_string("^f.o.?") + + regex.match(re, "foo") + |> should.equal(True) + + regex.match(re, "boo") + |> should.equal(False) +} |