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