├── .gitignore ├── CHANGELOG.md ├── gleam.toml ├── .github └── workflows │ └── test.yml ├── manifest.toml ├── README.md ├── src ├── gleam_regexp_ffi.erl ├── gleam_regexp_ffi.mjs └── gleam │ └── regexp.gleam └── test └── gleam_regexp_test.gleam /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | /build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.1.1 - 2025-04-06 4 | 5 | - Fixed a bug where JavaScript regexp's internal mutable state would not get 6 | reset, resulting in incorrect results for certain sequences of operations. 7 | 8 | ## v1.1.0 - 2025-02-05 9 | 10 | - Added the `match_map` function. 11 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "gleam_regexp" 2 | version = "1.1.1" 3 | gleam = ">= 1.0.0" 4 | licences = ["Apache-2.0"] 5 | description = "Regular expressions in Gleam!" 6 | 7 | repository = { type = "github", user = "gleam-lang", repo = "regexp" } 8 | links = [ 9 | { title = "Sponsor", href = "https://github.com/sponsors/lpil" }, 10 | ] 11 | 12 | [dependencies] 13 | gleam_stdlib = ">= 0.34.0 and < 2.0.0" 14 | 15 | [dev-dependencies] 16 | gleeunit = ">= 1.0.0 and < 2.0.0" 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: erlef/setup-beam@v1 16 | with: 17 | otp-version: "26.0.2" 18 | gleam-version: "1.6.0-rc1" 19 | rebar3-version: "3" 20 | # elixir-version: "1.15.4" 21 | - run: gleam deps download 22 | - run: gleam test 23 | - run: gleam format --check src test 24 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_stdlib", version = "0.41.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1B2F80CB1B66B027E3198A2FF71EF3F2F31DF89ED97AD606F25FD387A4C3C1EF" }, 6 | { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 7 | ] 8 | 9 | [requirements] 10 | gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 11 | gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Regexp 2 | 3 | Regular expressions in Gleam! 4 | 5 | [![Package Version](https://img.shields.io/hexpm/v/gleam_regexp)](https://hex.pm/packages/gleam_regexp) 6 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_regexp/) 7 | 8 | ```sh 9 | gleam add gleam_regexp@1 10 | ``` 11 | ```gleam 12 | import gleam/regexp 13 | 14 | pub fn main() { 15 | let assert Ok(re) = regexp.from_string("[0-9]") 16 | 17 | regexp.check(re, "abc123") 18 | // -> True 19 | 20 | regexp.check(re, "abcxyz") 21 | // -> False 22 | } 23 | ``` 24 | 25 | This package uses the regular expression engine of the underlying platform. 26 | Regular expressions in Erlang and JavaScript largely share the same syntax, but 27 | there are some differences and have different performance characteristics. Be 28 | sure to thoroughly test your code on all platforms that you support when using 29 | this library. 30 | 31 | Further documentation can be found at . 32 | -------------------------------------------------------------------------------- /src/gleam_regexp_ffi.erl: -------------------------------------------------------------------------------- 1 | -module(gleam_regexp_ffi). 2 | 3 | -export([compile/2, check/2, split/2, replace/3, scan/2, match_map/3]). 4 | 5 | compile(String, Options) -> 6 | {options, Caseless, Multiline} = Options, 7 | OptionsList = [ 8 | unicode, 9 | ucp, 10 | Caseless andalso caseless, 11 | Multiline andalso multiline 12 | ], 13 | FilteredOptions = [Option || Option <- OptionsList, Option /= false], 14 | case re:compile(String, FilteredOptions) of 15 | {ok, MP} -> {ok, MP}; 16 | {error, {Str, Pos}} -> 17 | {error, {compile_error, unicode:characters_to_binary(Str), Pos}} 18 | end. 19 | 20 | check(Regexp, String) -> 21 | re:run(String, Regexp) /= nomatch. 22 | 23 | split(Regexp, String) -> 24 | re:split(String, Regexp). 25 | 26 | submatches(_, {-1, 0}) -> none; 27 | submatches(String, {Start, Length}) -> 28 | BinarySlice = binary:part(String, {Start, Length}), 29 | case string:is_empty(binary_to_list(BinarySlice)) of 30 | true -> none; 31 | false -> {some, BinarySlice} 32 | end. 33 | 34 | matches(String, [{Start, Length} | Submatches]) -> 35 | Submatches1 = lists:map(fun(X) -> submatches(String, X) end, Submatches), 36 | {match, binary:part(String, Start, Length), Submatches1}. 37 | 38 | scan(Regexp, String) -> 39 | case re:run(String, Regexp, [global]) of 40 | {match, Captured} -> lists:map(fun(X) -> matches(String, X) end, Captured); 41 | nomatch -> [] 42 | end. 43 | 44 | replace(Regexp, Subject, Replacement) -> 45 | re:replace(Subject, Regexp, Replacement, [global, {return, binary}]). 46 | 47 | match_map(Regexp, Subject, Replacement) -> 48 | Replacement1 = fun(Content, Submatches) -> 49 | Submatches1 = lists:map(fun gleam@string:to_option/1, Submatches), 50 | Replacement({match, Content, Submatches1}) 51 | end, 52 | re:replace(Subject, Regexp, Replacement1, [global, {return, binary}]). 53 | -------------------------------------------------------------------------------- /src/gleam_regexp_ffi.mjs: -------------------------------------------------------------------------------- 1 | import { Error, List, Ok } from "./gleam.mjs"; 2 | import { 3 | CompileError as RegexCompileError, 4 | Match as RegexMatch, 5 | } from "./gleam/regexp.mjs"; 6 | import { Some, None } from "../gleam_stdlib/gleam/option.mjs"; 7 | 8 | export function check(regex, string) { 9 | regex.lastIndex = 0; 10 | return regex.test(string); 11 | } 12 | 13 | export function compile(pattern, options) { 14 | try { 15 | let flags = "gu"; 16 | if (options.case_insensitive) flags += "i"; 17 | if (options.multi_line) flags += "m"; 18 | return new Ok(new RegExp(pattern, flags)); 19 | } catch (error) { 20 | const number = (error.columnNumber || 0) | 0; 21 | return new Error(new RegexCompileError(error.message, number)); 22 | } 23 | } 24 | 25 | export function split(regex, string) { 26 | return List.fromArray( 27 | string.split(regex).map((item) => (item === undefined ? "" : item)), 28 | ); 29 | } 30 | 31 | export function scan(regex, string) { 32 | regex.lastIndex = 0; 33 | const matches = Array.from(string.matchAll(regex)).map((match) => { 34 | const content = match[0]; 35 | return new RegexMatch(content, submatches(match.slice(1))); 36 | }); 37 | return List.fromArray(matches); 38 | } 39 | 40 | export function replace(regex, original_string, replacement) { 41 | regex.lastIndex = 0; 42 | return original_string.replaceAll(regex, replacement); 43 | } 44 | 45 | export function match_map(regex, original_string, replacement) { 46 | regex.lastIndex = 0; 47 | let replace = (match, ...args) => { 48 | const hasNamedGroups = typeof args.at(-1) === "object"; 49 | const groups = args.slice(0, hasNamedGroups ? -3 : -2); 50 | let regexMatch = new RegexMatch(match, submatches(groups)); 51 | return replacement(regexMatch); 52 | }; 53 | return original_string.replaceAll(regex, replace); 54 | } 55 | 56 | function submatches(groups) { 57 | const submatches = []; 58 | for (let n = groups.length - 1; n >= 0; n--) { 59 | if (groups[n]) { 60 | submatches[n] = new Some(groups[n]); 61 | continue; 62 | } 63 | if (submatches.length > 0) { 64 | submatches[n] = new None(); 65 | } 66 | } 67 | return List.fromArray(submatches); 68 | } 69 | -------------------------------------------------------------------------------- /test/gleam_regexp_test.gleam: -------------------------------------------------------------------------------- 1 | import gleam/option.{None, Some} 2 | import gleam/regexp.{type Match, Match, Options} 3 | import gleeunit 4 | import gleeunit/should 5 | 6 | pub fn main() { 7 | gleeunit.main() 8 | } 9 | 10 | pub fn from_string_test() { 11 | let assert Ok(re) = regexp.from_string("[0-9]") 12 | 13 | regexp.check(re, "abc123") 14 | |> should.be_true 15 | 16 | regexp.check(re, "abcxyz") 17 | |> should.be_false 18 | 19 | let assert Error(_) = regexp.from_string("[0-9") 20 | } 21 | 22 | pub fn compile_test() { 23 | let options = Options(case_insensitive: True, multi_line: False) 24 | let assert Ok(re) = regexp.compile("[A-B]", options) 25 | 26 | regexp.check(re, "abc123") 27 | |> should.be_true 28 | 29 | let options = Options(case_insensitive: False, multi_line: True) 30 | let assert Ok(re) = regexp.compile("^[0-9]", options) 31 | 32 | regexp.check(re, "abc\n123") 33 | |> should.be_true 34 | 35 | // On target Erlang this test will only pass if unicode and ucp flags are set 36 | let assert Ok(re) = regexp.compile("\\s", options) 37 | // Em space == U+2003 == " " == used below 38 | regexp.check(re, " ") 39 | |> should.be_true 40 | } 41 | 42 | pub fn check_test() { 43 | let assert Ok(re) = regexp.from_string("^f.o.?") 44 | 45 | regexp.check(re, "foo") 46 | |> should.be_true 47 | 48 | regexp.check(re, "boo") 49 | |> should.be_false 50 | 51 | re 52 | |> regexp.check(content: "foo") 53 | |> should.be_true 54 | 55 | "boo" 56 | |> regexp.check(with: re) 57 | |> should.be_false 58 | 59 | // On target JavaScript internal `regexpp` objects are stateful when they 60 | // have the global or sticky flags set (e.g., /foo/g or /foo/y). 61 | // These following tests make sure that our implementation circumvents this. 62 | let assert Ok(re) = regexp.from_string("^-*[0-9]+") 63 | 64 | regexp.check(re, "1") 65 | |> should.be_true 66 | 67 | regexp.check(re, "12") 68 | |> should.be_true 69 | 70 | regexp.check(re, "123") 71 | |> should.be_true 72 | } 73 | 74 | pub fn split_test() { 75 | let assert Ok(re) = regexp.from_string(" *, *") 76 | 77 | regexp.split(re, "foo,32, 4, 9 ,0") 78 | |> should.equal(["foo", "32", "4", "9", "0"]) 79 | } 80 | 81 | pub fn matching_split_test() { 82 | let assert Ok(re) = regexp.from_string("([+-])( *)(d)*") 83 | 84 | regexp.split(re, "abc+ def+ghi+ abc") 85 | |> should.equal([ 86 | "abc", "+", " ", "d", "ef", "+", "", "", "ghi", "+", " ", "", "abc", 87 | ]) 88 | } 89 | 90 | pub fn scan_test() { 91 | let assert Ok(re) = regexp.from_string("Gl\\w+") 92 | 93 | regexp.scan(re, "!Gleam") 94 | |> should.equal([Match(content: "Gleam", submatches: [])]) 95 | 96 | regexp.scan(re, "हGleam") 97 | |> should.equal([Match(content: "Gleam", submatches: [])]) 98 | 99 | regexp.scan(re, "𐍈Gleam") 100 | |> should.equal([Match(content: "Gleam", submatches: [])]) 101 | 102 | let assert Ok(re) = regexp.from_string("[oi]n a(.?) (\\w+)") 103 | 104 | regexp.scan(re, "I am on a boat in a lake.") 105 | |> should.equal([ 106 | Match(content: "on a boat", submatches: [None, Some("boat")]), 107 | Match(content: "in a lake", submatches: [None, Some("lake")]), 108 | ]) 109 | 110 | let assert Ok(re) = regexp.from_string("answer (\\d+)") 111 | regexp.scan(re, "Is the answer 42?") 112 | |> should.equal([Match(content: "answer 42", submatches: [Some("42")])]) 113 | 114 | let assert Ok(re) = regexp.from_string("(\\d+)") 115 | regexp.scan(re, "hello 42") 116 | |> should.equal([Match(content: "42", submatches: [Some("42")])]) 117 | 118 | regexp.scan(re, "你好 42") 119 | |> should.equal([Match(content: "42", submatches: [Some("42")])]) 120 | 121 | regexp.scan(re, "你好 42 世界") 122 | |> should.equal([Match(content: "42", submatches: [Some("42")])]) 123 | 124 | let assert Ok(re) = regexp.from_string("([+|\\-])?(\\d+)(\\w+)?") 125 | regexp.scan(re, "+36kg") 126 | |> should.equal([ 127 | Match(content: "+36kg", submatches: [Some("+"), Some("36"), Some("kg")]), 128 | ]) 129 | 130 | regexp.scan(re, "36kg") 131 | |> should.equal([ 132 | Match(content: "36kg", submatches: [None, Some("36"), Some("kg")]), 133 | ]) 134 | 135 | regexp.scan(re, "36") 136 | |> should.equal([Match(content: "36", submatches: [None, Some("36")])]) 137 | 138 | regexp.scan(re, "-36") 139 | |> should.equal([Match(content: "-36", submatches: [Some("-"), Some("36")])]) 140 | 141 | regexp.scan(re, "-kg") 142 | |> should.equal([]) 143 | 144 | let assert Ok(re) = 145 | regexp.from_string("var\\s*(\\w+)\\s*(int|string)?\\s*=\\s*(.*)") 146 | regexp.scan(re, "var age int = 32") 147 | |> should.equal([ 148 | Match(content: "var age int = 32", submatches: [ 149 | Some("age"), 150 | Some("int"), 151 | Some("32"), 152 | ]), 153 | ]) 154 | 155 | regexp.scan(re, "var age = 32") 156 | |> should.equal([ 157 | Match(content: "var age = 32", submatches: [Some("age"), None, Some("32")]), 158 | ]) 159 | 160 | let assert Ok(re) = regexp.from_string("let (\\w+) = (\\w+)") 161 | regexp.scan(re, "let age = 32") 162 | |> should.equal([ 163 | Match(content: "let age = 32", submatches: [Some("age"), Some("32")]), 164 | ]) 165 | 166 | regexp.scan(re, "const age = 32") 167 | |> should.equal([]) 168 | } 169 | 170 | pub fn replace_0_test() { 171 | let assert Ok(re) = regexp.from_string(",") 172 | regexp.replace(in: "a,b,c,d", each: re, with: " ") 173 | |> should.equal("a b c d") 174 | } 175 | 176 | pub fn replace_1_test() { 177 | let assert Ok(re) = regexp.from_string("\\d") 178 | regexp.replace(in: "Hell1o, World!1", each: re, with: "") 179 | |> should.equal("Hello, World!") 180 | } 181 | 182 | pub fn replace_2_test() { 183 | let assert Ok(re) = regexp.from_string("🐈") 184 | regexp.replace(in: "🐈🐈 are great!", each: re, with: "🐕") 185 | |> should.equal("🐕🐕 are great!") 186 | } 187 | 188 | pub fn replace_3_test() { 189 | let assert Ok(re) = regexp.from_string("🐈") 190 | regexp.replace(re, "🐈🐈 are great!", "🐕") 191 | |> should.equal("🐕🐕 are great!") 192 | } 193 | 194 | pub fn match_map_0_test() { 195 | let replace = fn(match: Match) { 196 | case match.content { 197 | "1" -> "one" 198 | "2" -> "two" 199 | "3" -> "three" 200 | n -> n 201 | } 202 | } 203 | let assert Ok(re) = regexp.from_string("1|2|3") 204 | regexp.match_map(re, "1, 2, 3, 4", replace) 205 | |> should.equal("one, two, three, 4") 206 | } 207 | 208 | pub fn match_map_1_test() { 209 | let replace = fn(match: Match) { 210 | case match.submatches { 211 | [Some("1")] -> "one" 212 | [Some("2")] -> "two" 213 | [Some("3")] -> "three" 214 | _ -> match.content 215 | } 216 | } 217 | let assert Ok(re) = regexp.from_string("'(1|2|3)'") 218 | regexp.match_map(re, "'1', '2', '3', '4'", replace) 219 | |> should.equal("one, two, three, '4'") 220 | } 221 | 222 | // https://github.com/gleam-lang/regexp/issues/4 223 | pub fn last_index_bug_test() { 224 | let assert Ok(re) = regexp.from_string("(b)") 225 | let assert [Match("b", [Some("b")])] = regexp.scan(re, "b") 226 | let assert True = regexp.check(re, "b") 227 | let assert [Match("b", [Some("b")])] = regexp.scan(re, "b") 228 | } 229 | -------------------------------------------------------------------------------- /src/gleam/regexp.gleam: -------------------------------------------------------------------------------- 1 | //// This package uses the regular expression engine of the underlying platform. 2 | //// Regular expressions in Erlang and JavaScript largely share the same syntax, but 3 | //// there are some differences and have different performance characteristics. Be 4 | //// sure to thoroughly test your code on all platforms that you support when using 5 | //// this library. 6 | 7 | import gleam/option.{type Option} 8 | 9 | pub type Regexp 10 | 11 | /// The details about a particular match: 12 | /// 13 | pub type Match { 14 | Match( 15 | /// The full string of the match. 16 | content: String, 17 | /// A `Regexp` can have subpatterns, sup-parts that are in parentheses. 18 | submatches: List(Option(String)), 19 | ) 20 | } 21 | 22 | /// When a regular expression fails to compile: 23 | /// 24 | pub type CompileError { 25 | CompileError( 26 | /// The problem encountered that caused the compilation to fail 27 | error: String, 28 | /// The byte index into the string to where the problem was found 29 | /// This value may not be correct in JavaScript environments. 30 | byte_index: Int, 31 | ) 32 | } 33 | 34 | pub type Options { 35 | Options(case_insensitive: Bool, multi_line: Bool) 36 | } 37 | 38 | /// Creates a `Regexp` with some additional options. 39 | /// 40 | /// ## Examples 41 | /// 42 | /// ```gleam 43 | /// let options = Options(case_insensitive: False, multi_line: True) 44 | /// let assert Ok(re) = compile("^[0-9]", with: options) 45 | /// check(re, "abc\n123") 46 | /// // -> True 47 | /// ``` 48 | /// 49 | /// ```gleam 50 | /// let options = Options(case_insensitive: True, multi_line: False) 51 | /// let assert Ok(re) = compile("[A-Z]", with: options) 52 | /// check(re, "abc123") 53 | /// // -> True 54 | /// ``` 55 | /// 56 | pub fn compile( 57 | pattern: String, 58 | with options: Options, 59 | ) -> Result(Regexp, CompileError) { 60 | do_compile(pattern, options) 61 | } 62 | 63 | @external(erlang, "gleam_regexp_ffi", "compile") 64 | @external(javascript, "../gleam_regexp_ffi.mjs", "compile") 65 | fn do_compile( 66 | pattern: String, 67 | with with: Options, 68 | ) -> Result(Regexp, CompileError) 69 | 70 | /// Creates a new `Regexp`. 71 | /// 72 | /// ## Examples 73 | /// 74 | /// ```gleam 75 | /// let assert Ok(re) = from_string("[0-9]") 76 | /// check(re, "abc123") 77 | /// // -> True 78 | /// ``` 79 | /// 80 | /// ```gleam 81 | /// check(re, "abcxyz") 82 | /// // -> False 83 | /// ``` 84 | /// 85 | /// ```gleam 86 | /// from_string("[0-9") 87 | /// // -> Error(CompileError( 88 | /// // error: "missing terminating ] for character class", 89 | /// // byte_index: 4 90 | /// // )) 91 | /// ``` 92 | /// 93 | pub fn from_string(pattern: String) -> Result(Regexp, CompileError) { 94 | compile(pattern, Options(case_insensitive: False, multi_line: False)) 95 | } 96 | 97 | /// Returns a boolean indicating whether there was a match or not. 98 | /// 99 | /// ## Examples 100 | /// 101 | /// ```gleam 102 | /// let assert Ok(re) = from_string("^f.o.?") 103 | /// check(with: re, content: "foo") 104 | /// // -> True 105 | /// ``` 106 | /// 107 | /// ```gleam 108 | /// check(with: re, content: "boo") 109 | /// // -> False 110 | /// ``` 111 | /// 112 | pub fn check(with regexp: Regexp, content string: String) -> Bool { 113 | do_check(regexp, string) 114 | } 115 | 116 | @external(erlang, "gleam_regexp_ffi", "check") 117 | @external(javascript, "../gleam_regexp_ffi.mjs", "check") 118 | fn do_check(regexp: Regexp, string: String) -> Bool 119 | 120 | /// Splits a string. 121 | /// 122 | /// ## Examples 123 | /// 124 | /// ```gleam 125 | /// let assert Ok(re) = from_string(" *, *") 126 | /// split(with: re, content: "foo,32, 4, 9 ,0") 127 | /// // -> ["foo", "32", "4", "9", "0"] 128 | /// ``` 129 | /// 130 | pub fn split(with regexp: Regexp, content string: String) -> List(String) { 131 | do_split(regexp, string) 132 | } 133 | 134 | @external(erlang, "gleam_regexp_ffi", "split") 135 | @external(javascript, "../gleam_regexp_ffi.mjs", "split") 136 | fn do_split(regexp: Regexp, string: String) -> List(String) 137 | 138 | /// Collects all matches of the regular expression. 139 | /// 140 | /// ## Examples 141 | /// 142 | /// ```gleam 143 | /// let assert Ok(re) = from_string("[oi]n a (\\w+)") 144 | /// scan(with: re, content: "I am on a boat in a lake.") 145 | /// // -> [ 146 | /// // Match(content: "on a boat", submatches: [Some("boat")]), 147 | /// // Match(content: "in a lake", submatches: [Some("lake")]), 148 | /// // ] 149 | /// ``` 150 | /// 151 | /// ```gleam 152 | /// let assert Ok(re) = regexp.from_string("([+|\\-])?(\\d+)(\\w+)?") 153 | /// scan(with: re, content: "-36") 154 | /// // -> [ 155 | /// // Match(content: "-36", submatches: [Some("-"), Some("36")]) 156 | /// // ] 157 | /// 158 | /// scan(with: re, content: "36") 159 | /// // -> [ 160 | /// // Match(content: "36", submatches: [None, Some("36")]) 161 | /// // ] 162 | /// ``` 163 | /// 164 | /// ```gleam 165 | /// let assert Ok(re) = 166 | /// regexp.from_string("var\\s*(\\w+)\\s*(int|string)?\\s*=\\s*(.*)") 167 | /// scan(with: re, content: "var age = 32") 168 | /// // -> [ 169 | /// // Match( 170 | /// // content: "var age = 32", 171 | /// // submatches: [Some("age"), None, Some("32")], 172 | /// // ), 173 | /// // ] 174 | /// ``` 175 | /// 176 | /// ```gleam 177 | /// let assert Ok(re) = regexp.from_string("let (\\w+) = (\\w+)") 178 | /// scan(with: re, content: "let age = 32") 179 | /// // -> [ 180 | /// // Match( 181 | /// // content: "let age = 32", 182 | /// // submatches: [Some("age"), Some("32")], 183 | /// // ), 184 | /// // ] 185 | /// 186 | /// scan(with: re, content: "const age = 32") 187 | /// // -> [] 188 | /// ``` 189 | /// 190 | pub fn scan(with regexp: Regexp, content string: String) -> List(Match) { 191 | do_scan(regexp, string) 192 | } 193 | 194 | @external(erlang, "gleam_regexp_ffi", "scan") 195 | @external(javascript, "../gleam_regexp_ffi.mjs", "scan") 196 | fn do_scan(regexp: Regexp, string: String) -> List(Match) 197 | 198 | /// Creates a new `String` by replacing all substrings that match the regular 199 | /// expression. 200 | /// 201 | /// ## Examples 202 | /// 203 | /// ```gleam 204 | /// let assert Ok(re) = regexp.from_string("^https://") 205 | /// replace(each: re, in: "https://example.com", with: "www.") 206 | /// // -> "www.example.com" 207 | /// ``` 208 | /// 209 | /// ```gleam 210 | /// let assert Ok(re) = regexp.from_string("[, +-]") 211 | /// replace(each: re, in: "a,b-c d+e", with: "/") 212 | /// // -> "a/b/c/d/e" 213 | /// ``` 214 | @external(erlang, "gleam_regexp_ffi", "replace") 215 | @external(javascript, "../gleam_regexp_ffi.mjs", "replace") 216 | pub fn replace( 217 | each pattern: Regexp, 218 | in string: String, 219 | with substitute: String, 220 | ) -> String 221 | 222 | /// Creates a new `String` by replacing all substrings that match the regular 223 | /// expression with the result of applying the function to each match. 224 | /// 225 | /// ## Examples 226 | /// 227 | /// ```gleam 228 | /// let assert Ok(re) = regexp.from_string("\\w+") 229 | /// regexp.match_map(re, "hello, joe!", fn(m) { string.capitalise(m.content) }) 230 | /// // -> "Hello, Joe!" 231 | /// ``` 232 | @external(erlang, "gleam_regexp_ffi", "match_map") 233 | @external(javascript, "../gleam_regexp_ffi.mjs", "match_map") 234 | pub fn match_map( 235 | each pattern: Regexp, 236 | in string: String, 237 | with substitute: fn(Match) -> String, 238 | ) -> String 239 | --------------------------------------------------------------------------------