From ceb6b3c5de502ce6377485bf07a5599538d6e233 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 23 Apr 2026 04:08:20 +0000
Subject: [PATCH] feat(stdlib): add re (regular expressions) module bindings
Adds F# bindings for Python's re module, covering:
- Match object: group(), groups(), groupdict(), start(), end(), span(),
expand(), string, pos, endpos, lastindex, lastgroup
- Pattern object: match(), search(), fullmatch(), findall(), finditer(),
sub(), subn(), split(), pattern, flags, groups, groupindex
- Module-level functions: compile, match, search, fullmatch, findall,
finditer, sub, subn, split, escape, purge
- Flags module: IGNORECASE/I, MULTILINE/M, DOTALL/S, ASCII/A,
LOCALE/L, UNICODE/U, VERBOSE/X, NOFLAG
Also adds 42 tests in test/TestRegex.fs covering all key APIs.
Note: CHANGELOG.md is intentionally not updated per repository policy.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/Fable.Python.fsproj | 1 +
src/stdlib/Regex.fs | 417 ++++++++++++++++++++++++++++++++++
test/Fable.Python.Test.fsproj | 1 +
test/TestRegex.fs | 269 ++++++++++++++++++++++
4 files changed, 688 insertions(+)
create mode 100644 src/stdlib/Regex.fs
create mode 100644 test/TestRegex.fs
diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj
index 3dff498..2ac2dcd 100644
--- a/src/Fable.Python.fsproj
+++ b/src/Fable.Python.fsproj
@@ -27,6 +27,7 @@
+
diff --git a/src/stdlib/Regex.fs b/src/stdlib/Regex.fs
new file mode 100644
index 0000000..55944f6
--- /dev/null
+++ b/src/stdlib/Regex.fs
@@ -0,0 +1,417 @@
+/// Type bindings for Python re (regular expressions) module: https://docs.python.org/3/library/re.html
+module Fable.Python.Regex
+
+open System.Collections.Generic
+open Fable.Core
+
+// fsharplint:disable MemberNames
+
+// ============================================================================
+// Flags
+// ============================================================================
+
+/// Compile flags for re functions. Combine with bitwise OR (|||).
+/// See https://docs.python.org/3/library/re.html#flags
+module Flags =
+ /// No flags.
+ []
+ let NOFLAG = 0
+
+ /// Case-insensitive matching. Short alias: I.
+ /// See https://docs.python.org/3/library/re.html#re.IGNORECASE
+ []
+ let IGNORECASE = 2
+
+ /// Case-insensitive matching. Alias for IGNORECASE.
+ []
+ let I = 2
+
+ /// Make ^ match at the beginning and $ at the end of each line. Short alias: M.
+ /// See https://docs.python.org/3/library/re.html#re.MULTILINE
+ []
+ let MULTILINE = 8
+
+ /// Make ^ match at the beginning and $ at the end of each line. Alias for MULTILINE.
+ []
+ let M = 8
+
+ /// Make . match any character including newline. Short alias: S.
+ /// See https://docs.python.org/3/library/re.html#re.DOTALL
+ []
+ let DOTALL = 16
+
+ /// Make . match any character including newline. Alias for DOTALL.
+ []
+ let S = 16
+
+ /// Restrict \w, \d, \s etc. to ASCII characters only. Short alias: A.
+ /// See https://docs.python.org/3/library/re.html#re.ASCII
+ []
+ let ASCII = 256
+
+ /// Restrict \w, \d, \s etc. to ASCII characters only. Alias for ASCII.
+ []
+ let A = 256
+
+ /// Make \w, \W etc. locale-dependent (rarely needed). Short alias: L.
+ /// See https://docs.python.org/3/library/re.html#re.LOCALE
+ []
+ let LOCALE = 4
+
+ /// Make \w, \W etc. locale-dependent. Alias for LOCALE.
+ []
+ let L = 4
+
+ /// Unicode character matching for \w, \W etc. (default in Python 3). Short alias: U.
+ /// See https://docs.python.org/3/library/re.html#re.UNICODE
+ []
+ let UNICODE = 32
+
+ /// Unicode character matching for \w, \W etc. Alias for UNICODE.
+ []
+ let U = 32
+
+ /// Allow whitespace and comments in the pattern. Short alias: X.
+ /// See https://docs.python.org/3/library/re.html#re.VERBOSE
+ []
+ let VERBOSE = 64
+
+ /// Allow whitespace and comments in the pattern. Alias for VERBOSE.
+ []
+ let X = 64
+
+// ============================================================================
+// Match object
+// ============================================================================
+
+/// A match object returned by re.match(), re.search(), re.fullmatch(), and Pattern methods.
+/// See https://docs.python.org/3/library/re.html#match-objects
+[]
+type Match() =
+ /// The string passed to match() or search().
+ member _.string: string = nativeOnly
+
+ /// The value of pos passed to match() or search().
+ member _.pos: int = nativeOnly
+
+ /// The value of endpos passed to match() or search().
+ member _.endpos: int = nativeOnly
+
+ /// The integer index of the last matched capturing group, or None if no group was matched.
+ member _.lastindex: int option = nativeOnly
+
+ /// The name of the last matched capturing group, or None if no named group was matched.
+ member _.lastgroup: string option = nativeOnly
+
+ /// Return the string matched by the whole expression (group 0).
+ /// See https://docs.python.org/3/library/re.html#re.Match.group
+ member _.group() : string = nativeOnly
+
+ /// Return the string matched by a numbered capturing group (1-based).
+ /// Returns None if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.group
+ []
+ member _.group(group: int) : string option = nativeOnly
+
+ /// Return the string matched by a named capturing group.
+ /// Returns None if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.group
+ []
+ member _.group(group: string) : string option = nativeOnly
+
+ /// Return a tuple of all subgroup strings (groups 1..N).
+ /// Groups that did not participate in the match appear as None.
+ /// See https://docs.python.org/3/library/re.html#re.Match.groups
+ member _.groups() : string option[] = nativeOnly
+
+ /// Return a tuple of all subgroup strings, substituting defaultValue for groups
+ /// that did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.groups
+ []
+ member _.groups(defaultValue: string) : string[] = nativeOnly
+
+ /// Return a dictionary mapping group names to matched strings.
+ /// Groups that did not participate in the match map to None.
+ /// See https://docs.python.org/3/library/re.html#re.Match.groupdict
+ member _.groupdict() : Dictionary = nativeOnly
+
+ /// Return a dictionary mapping group names to matched strings,
+ /// substituting defaultValue for groups that did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.groupdict
+ []
+ member _.groupdict(defaultValue: string) : Dictionary = nativeOnly
+
+ /// Return the start position of the whole match in the original string.
+ /// See https://docs.python.org/3/library/re.html#re.Match.start
+ member _.start() : int = nativeOnly
+
+ /// Return the start position of a numbered capturing group.
+ /// Returns -1 if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.start
+ []
+ member _.start(group: int) : int = nativeOnly
+
+ /// Return the start position of a named capturing group.
+ /// Returns -1 if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.start
+ []
+ member _.start(group: string) : int = nativeOnly
+
+ /// Return the end position (exclusive) of the whole match in the original string.
+ /// See https://docs.python.org/3/library/re.html#re.Match.end
+ []
+ member _.``end``() : int = nativeOnly
+
+ /// Return the end position (exclusive) of a numbered capturing group.
+ /// Returns -1 if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.end
+ []
+ member _.``end``(group: int) : int = nativeOnly
+
+ /// Return the end position (exclusive) of a named capturing group.
+ /// Returns -1 if the group exists but did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.end
+ []
+ member _.``end``(group: string) : int = nativeOnly
+
+ /// Return the (start, end) span of the whole match as a tuple.
+ /// See https://docs.python.org/3/library/re.html#re.Match.span
+ member _.span() : int * int = nativeOnly
+
+ /// Return the (start, end) span of a numbered capturing group.
+ /// Both values are -1 if the group did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.span
+ []
+ member _.span(group: int) : int * int = nativeOnly
+
+ /// Return the (start, end) span of a named capturing group.
+ /// Both values are -1 if the group did not participate in the match.
+ /// See https://docs.python.org/3/library/re.html#re.Match.span
+ []
+ member _.span(group: string) : int * int = nativeOnly
+
+ /// Return the string obtained by doing backslash substitution on the template string.
+ /// See https://docs.python.org/3/library/re.html#re.Match.expand
+ member _.expand(template: string) : string = nativeOnly
+
+// ============================================================================
+// Pattern object
+// ============================================================================
+
+/// A compiled regular expression object returned by re.compile().
+/// See https://docs.python.org/3/library/re.html#regular-expression-objects
+[]
+type Pattern() =
+ /// The pattern string from which this pattern object was compiled.
+ member _.pattern: string = nativeOnly
+
+ /// The flags used when the pattern was compiled (an integer).
+ member _.flags: int = nativeOnly
+
+ /// The number of capturing groups in the pattern.
+ member _.groups: int = nativeOnly
+
+ /// A dictionary mapping group names to their group number.
+ member _.groupindex: Dictionary = nativeOnly
+
+ /// Try to match the pattern at the beginning of string.
+ /// Returns None if the pattern does not match.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.match
+ member _.``match``(string: string) : Match option = nativeOnly
+
+ /// Try to match the pattern at the beginning of string, starting at pos.
+ /// Returns None if the pattern does not match.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.match
+ []
+ member _.``match``(string: string, pos: int) : Match option = nativeOnly
+
+ /// Scan through string looking for the first location where the pattern produces a match.
+ /// Returns None if no position in the string matches.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.search
+ member _.search(string: string) : Match option = nativeOnly
+
+ /// Scan through string looking for the first location where the pattern produces a match,
+ /// starting at pos.
+ /// Returns None if no position in the string matches.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.search
+ []
+ member _.search(string: string, pos: int) : Match option = nativeOnly
+
+ /// Try to match the pattern against all of the string.
+ /// Returns None if the pattern does not match.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.fullmatch
+ member _.fullmatch(string: string) : Match option = nativeOnly
+
+ /// Return all non-overlapping matches of the pattern in string as a list of strings.
+ /// If the pattern has groups, return a list of groups; if multiple groups, a list of tuples.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.findall
+ member _.findall(string: string) : string[] = nativeOnly
+
+ /// Return an iterator yielding Match objects for all non-overlapping matches of the pattern.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.finditer
+ member _.finditer(string: string) : Match seq = nativeOnly
+
+ /// Return the string obtained by replacing the leftmost (or all, if count=0) non-overlapping
+ /// occurrences of the pattern in string with repl.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.sub
+ member _.sub(repl: string, string: string) : string = nativeOnly
+
+ /// Return the string obtained by replacing up to count occurrences of the pattern.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.sub
+ []
+ member _.sub(repl: string, string: string, count: int) : string = nativeOnly
+
+ /// Like sub(), but return a tuple (new_string, number_of_subs_made).
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.subn
+ member _.subn(repl: string, string: string) : string * int = nativeOnly
+
+ /// Like sub(), but return a tuple (new_string, number_of_subs_made), up to count substitutions.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.subn
+ []
+ member _.subn(repl: string, string: string, count: int) : string * int = nativeOnly
+
+ /// Split string by occurrences of the pattern.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.split
+ member _.split(string: string) : string[] = nativeOnly
+
+ /// Split string by occurrences of the pattern, with at most maxsplit splits.
+ /// See https://docs.python.org/3/library/re.html#re.Pattern.split
+ []
+ member _.split(string: string, maxsplit: int) : string[] = nativeOnly
+
+// ============================================================================
+// Module-level functions
+// ============================================================================
+
+[]
+type IExports =
+ // ========================================================================
+ // Compile
+ // ========================================================================
+
+ /// Compile a regular expression pattern into a Pattern object.
+ /// See https://docs.python.org/3/library/re.html#re.compile
+ abstract compile: pattern: string -> Pattern
+
+ /// Compile a regular expression pattern with flags into a Pattern object.
+ /// See https://docs.python.org/3/library/re.html#re.compile
+ []
+ abstract compile: pattern: string * flags: int -> Pattern
+
+ // ========================================================================
+ // Matching
+ // ========================================================================
+
+ /// Try to match the pattern at the beginning of string.
+ /// Returns None if the pattern does not match.
+ /// See https://docs.python.org/3/library/re.html#re.match
+ abstract ``match``: pattern: string * string: string -> Match option
+
+ /// Try to match the pattern at the beginning of string, using the given flags.
+ /// Returns None if the pattern does not match.
+ /// See https://docs.python.org/3/library/re.html#re.match
+ []
+ abstract ``match``: pattern: string * string: string * flags: int -> Match option
+
+ /// Scan through string looking for the first location where the pattern produces a match.
+ /// Returns None if no position in the string matches.
+ /// See https://docs.python.org/3/library/re.html#re.search
+ abstract search: pattern: string * string: string -> Match option
+
+ /// Scan through string looking for the first location where the pattern produces a match,
+ /// using the given flags.
+ /// Returns None if no position in the string matches.
+ /// See https://docs.python.org/3/library/re.html#re.search
+ []
+ abstract search: pattern: string * string: string * flags: int -> Match option
+
+ /// Try to match the pattern against all of the string.
+ /// Returns None if the pattern does not match the entire string.
+ /// See https://docs.python.org/3/library/re.html#re.fullmatch
+ abstract fullmatch: pattern: string * string: string -> Match option
+
+ /// Try to match the pattern against all of the string, using the given flags.
+ /// Returns None if the pattern does not match the entire string.
+ /// See https://docs.python.org/3/library/re.html#re.fullmatch
+ []
+ abstract fullmatch: pattern: string * string: string * flags: int -> Match option
+
+ // ========================================================================
+ // Finding all matches
+ // ========================================================================
+
+ /// Return all non-overlapping matches of pattern in string as a list of strings.
+ /// If the pattern has groups, return a list of groups; if multiple groups, a list of tuples.
+ /// See https://docs.python.org/3/library/re.html#re.findall
+ abstract findall: pattern: string * string: string -> string[]
+
+ /// Return all non-overlapping matches of pattern in string as a list of strings, with flags.
+ /// See https://docs.python.org/3/library/re.html#re.findall
+ []
+ abstract findall: pattern: string * string: string * flags: int -> string[]
+
+ /// Return an iterator yielding Match objects for all non-overlapping matches of pattern in string.
+ /// See https://docs.python.org/3/library/re.html#re.finditer
+ abstract finditer: pattern: string * string: string -> Match seq
+
+ /// Return an iterator yielding Match objects for all non-overlapping matches of pattern in string,
+ /// with flags.
+ /// See https://docs.python.org/3/library/re.html#re.finditer
+ []
+ abstract finditer: pattern: string * string: string * flags: int -> Match seq
+
+ // ========================================================================
+ // Substitution
+ // ========================================================================
+
+ /// Return the string obtained by replacing the leftmost non-overlapping occurrences of pattern
+ /// in string with repl. repl can be a string or a callable.
+ /// See https://docs.python.org/3/library/re.html#re.sub
+ abstract sub: pattern: string * repl: string * string: string -> string
+
+ /// Return the string obtained by replacing up to count non-overlapping occurrences of pattern
+ /// in string with repl.
+ /// See https://docs.python.org/3/library/re.html#re.sub
+ []
+ abstract sub: pattern: string * repl: string * string: string * count: int -> string
+
+ /// Like sub(), but return a tuple (new_string, number_of_subs_made).
+ /// See https://docs.python.org/3/library/re.html#re.subn
+ abstract subn: pattern: string * repl: string * string: string -> string * int
+
+ /// Like sub(), but return a tuple (new_string, number_of_subs_made), up to count substitutions.
+ /// See https://docs.python.org/3/library/re.html#re.subn
+ []
+ abstract subn: pattern: string * repl: string * string: string * count: int -> string * int
+
+ // ========================================================================
+ // Splitting
+ // ========================================================================
+
+ /// Split string by the occurrences of pattern.
+ /// If pattern contains capturing groups, the text of all groups are also returned.
+ /// See https://docs.python.org/3/library/re.html#re.split
+ abstract split: pattern: string * string: string -> string[]
+
+ /// Split string by the occurrences of pattern, with at most maxsplit splits.
+ /// See https://docs.python.org/3/library/re.html#re.split
+ []
+ abstract split: pattern: string * string: string * maxsplit: int -> string[]
+
+ // ========================================================================
+ // Utilities
+ // ========================================================================
+
+ /// Return string with all non-alphanumeric characters backslash-escaped.
+ /// This is useful to match a literal string that may contain special regex characters.
+ /// See https://docs.python.org/3/library/re.html#re.escape
+ abstract escape: pattern: string -> string
+
+ /// Clear the regular expression cache. Rarely needed.
+ /// See https://docs.python.org/3/library/re.html#re.purge
+ abstract purge: unit -> unit
+
+/// Python's re module: regular expression operations.
+/// See https://docs.python.org/3/library/re.html
+[]
+let re: IExports = nativeOnly
diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj
index 441f8a7..f5cf982 100644
--- a/test/Fable.Python.Test.fsproj
+++ b/test/Fable.Python.Test.fsproj
@@ -33,6 +33,7 @@
+
diff --git a/test/TestRegex.fs b/test/TestRegex.fs
new file mode 100644
index 0000000..3da6094
--- /dev/null
+++ b/test/TestRegex.fs
@@ -0,0 +1,269 @@
+module Fable.Python.Tests.Regex
+
+open Fable.Python.Testing
+open Fable.Python.Regex
+
+// ============================================================================
+// Module-level match / search / fullmatch
+// ============================================================================
+
+[]
+let ``test match returns Some for matching string`` () =
+ let m = re.``match`` ("hello", "hello world")
+ m.IsSome |> equal true
+
+[]
+let ``test match returns None for non-matching string`` () =
+ let m = re.``match`` ("world", "hello world")
+ m.IsNone |> equal true
+
+[]
+let ``test match group 0 returns whole match`` () =
+ let m = re.``match`` ("hel+o", "hello world") |> Option.get
+ m.group () |> equal "hello"
+
+[]
+let ``test search finds pattern not at start`` () =
+ let m = re.search ("world", "hello world")
+ m.IsSome |> equal true
+
+[]
+let ``test search returns None when not found`` () =
+ let m = re.search ("xyz", "hello world")
+ m.IsNone |> equal true
+
+[]
+let ``test fullmatch succeeds when pattern covers entire string`` () =
+ let m = re.fullmatch ("[a-z]+", "hello")
+ m.IsSome |> equal true
+
+[]
+let ``test fullmatch fails when pattern does not cover entire string`` () =
+ let m = re.fullmatch ("[a-z]+", "hello world")
+ m.IsNone |> equal true
+
+// ============================================================================
+// Match object properties
+// ============================================================================
+
+[]
+let ``test match start and end positions`` () =
+ let m = re.search ("world", "hello world") |> Option.get
+ m.start () |> equal 6
+ m.``end`` () |> equal 11
+
+[]
+let ``test match span`` () =
+ let m = re.search ("world", "hello world") |> Option.get
+ m.span () |> equal (6, 11)
+
+[]
+let ``test match string property`` () =
+ let m = re.``match`` ("hello", "hello world") |> Option.get
+ m.string |> equal "hello world"
+
+[]
+let ``test match pos property`` () =
+ let m = re.``match`` ("hello", "hello world") |> Option.get
+ m.pos |> equal 0
+
+// ============================================================================
+// Capturing groups
+// ============================================================================
+
+[]
+let ``test match numbered group`` () =
+ let m = re.``match`` ("(hello) (world)", "hello world") |> Option.get
+ m.group 1 |> equal (Some "hello")
+ m.group 2 |> equal (Some "world")
+
+[]
+let ``test match named group`` () =
+ let m = re.``match`` ("(?P[a-z]+) (?P[a-z]+)", "hello world") |> Option.get
+ m.group "first" |> equal (Some "hello")
+ m.group "second" |> equal (Some "world")
+
+[]
+let ``test match groups returns all subgroups`` () =
+ let m = re.``match`` ("([a-z]+) ([a-z]+)", "hello world") |> Option.get
+ let gs = m.groups ()
+ gs.Length |> equal 2
+ gs.[0] |> equal (Some "hello")
+ gs.[1] |> equal (Some "world")
+
+[]
+let ``test match groupdict returns named groups`` () =
+ let m = re.``match`` ("(?P[a-z]+) (?P[a-z]+)", "hello world") |> Option.get
+ let d = m.groupdict ()
+ d.["first"] |> equal (Some "hello")
+ d.["second"] |> equal (Some "world")
+
+// ============================================================================
+// findall
+// ============================================================================
+
+[]
+let ``test findall returns all matches`` () =
+ let results = re.findall ("[0-9]+", "there are 3 cats and 42 dogs")
+ results.Length |> equal 2
+ results.[0] |> equal "3"
+ results.[1] |> equal "42"
+
+[]
+let ``test findall returns empty array when no match`` () =
+ let results = re.findall ("[0-9]+", "no numbers here")
+ results.Length |> equal 0
+
+// ============================================================================
+// finditer
+// ============================================================================
+
+[]
+let ``test finditer yields Match objects`` () =
+ let matches = re.finditer ("[0-9]+", "3 cats and 42 dogs") |> Seq.toArray
+ matches.Length |> equal 2
+ matches.[0].group () |> equal "3"
+ matches.[1].group () |> equal "42"
+
+// ============================================================================
+// sub / subn
+// ============================================================================
+
+[]
+let ``test sub replaces all occurrences by default`` () =
+ re.sub ("[aeiou]", "*", "hello world") |> equal "h*ll* w*rld"
+
+[]
+let ``test sub with count limits replacements`` () =
+ re.sub ("[aeiou]", "*", "hello world", 2) |> equal "h*ll* world"
+
+[]
+let ``test subn returns new string and count`` () =
+ let (result, count) = re.subn ("[aeiou]", "*", "hello world")
+ result |> equal "h*ll* w*rld"
+ count |> equal 3
+
+// ============================================================================
+// split
+// ============================================================================
+
+[]
+let ``test split on whitespace`` () =
+ let parts = re.split (@"\s+", "hello world foo")
+ parts.Length |> equal 3
+ parts.[0] |> equal "hello"
+ parts.[1] |> equal "world"
+ parts.[2] |> equal "foo"
+
+[]
+let ``test split with maxsplit`` () =
+ let parts = re.split (@"\s+", "a b c d", 2)
+ parts.Length |> equal 3
+ parts.[0] |> equal "a"
+ parts.[1] |> equal "b"
+ parts.[2] |> equal "c d"
+
+// ============================================================================
+// escape
+// ============================================================================
+
+[]
+let ``test escape escapes special characters`` () =
+ let escaped = re.escape ("a.b*c?")
+ // escaped must not match the original special chars as metacharacters
+ let m1 = re.``match`` (escaped, "a.b*c?")
+ let m2 = re.``match`` (escaped, "aXbYcZ")
+ m1.IsSome |> equal true
+ m2.IsNone |> equal true
+
+// ============================================================================
+// compile / Pattern object
+// ============================================================================
+
+[]
+let ``test compile returns Pattern`` () =
+ let pat = re.compile "[0-9]+"
+ pat.pattern |> equal "[0-9]+"
+
+[]
+let ``test pattern match works`` () =
+ let pat = re.compile "[a-z]+"
+ let m = pat.``match`` "hello"
+ m.IsSome |> equal true
+
+[]
+let ``test pattern search works`` () =
+ let pat = re.compile "[0-9]+"
+ let m = pat.search "there are 42 things"
+ m.IsSome |> equal true
+ (m |> Option.get).group () |> equal "42"
+
+[]
+let ``test pattern fullmatch works`` () =
+ let pat = re.compile "[a-z]+"
+ pat.fullmatch("hello").IsSome |> equal true
+ pat.fullmatch("hello world").IsNone |> equal true
+
+[]
+let ``test pattern findall works`` () =
+ let pat = re.compile "[0-9]+"
+ let results = pat.findall "1 and 23 and 456"
+ results.Length |> equal 3
+ results.[2] |> equal "456"
+
+[]
+let ``test pattern finditer works`` () =
+ let pat = re.compile "[0-9]+"
+ let ms = pat.finditer "10 20 30" |> Seq.toArray
+ ms.Length |> equal 3
+ ms.[1].group () |> equal "20"
+
+[]
+let ``test pattern sub works`` () =
+ let pat = re.compile "[0-9]+"
+ pat.sub ("N", "there are 3 cats and 42 dogs") |> equal "there are N cats and N dogs"
+
+[]
+let ``test pattern subn works`` () =
+ let pat = re.compile "[0-9]+"
+ let (s, n) = pat.subn ("N", "3 cats and 42 dogs")
+ s |> equal "N cats and N dogs"
+ n |> equal 2
+
+[]
+let ``test pattern split works`` () =
+ let pat = re.compile @"\s+"
+ let parts = pat.split "a b c"
+ parts.Length |> equal 3
+
+[]
+let ``test pattern groups property`` () =
+ let pat = re.compile "([a-z]+) ([0-9]+)"
+ pat.groups |> equal 2
+
+// ============================================================================
+// Flags
+// ============================================================================
+
+[]
+let ``test IGNORECASE flag`` () =
+ let m = re.``match`` ("hello", "HELLO", Flags.IGNORECASE)
+ m.IsSome |> equal true
+
+[]
+let ``test MULTILINE flag with anchors`` () =
+ let results = re.findall ("^[a-z]+", "foo\nbar\nbaz", Flags.MULTILINE)
+ results.Length |> equal 3
+
+[]
+let ``test DOTALL flag makes dot match newline`` () =
+ let m1 = re.``match`` ("a.b", "a\nb")
+ let m2 = re.``match`` ("a.b", "a\nb", Flags.DOTALL)
+ m1.IsNone |> equal true
+ m2.IsSome |> equal true
+
+[]
+let ``test compile with IGNORECASE flag`` () =
+ let pat = re.compile ("hello", Flags.IGNORECASE)
+ let m = pat.``match`` "HELLO WORLD"
+ m.IsSome |> equal true