text:
// https://msdn.microsoft.com/en-us/library/ze12yx1d(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions
open FsRegEx

let regex = new Regex("-")         // Split on hyphens.
let substrings = regex.Split("plum--pear")

for m in substrings do
    printfn "'%s'" m

// The example displays the following output:
//    'plum'
//    ''
//    'pear'

let pattern = @"\d+"
let rgx = new Regex(pattern)
let input = "123ABCDE456FGHIJKL789MNOPQ012"

string[] result = rgx.Split(input)
for (int ctr = 0 ctr < result.Length ctr++) {
    Console.Write("'{0}'", result[ctr])
    if (ctr < result.Length - 1)
    Console.Write(", ")
}
printfn )

// The example displays the following output:
//       '', 'ABCDE', 'FGHIJKL', 'MNOPQ', ''

Regex regex = new Regex("(-)")         // Split on hyphens.
string[] substrings = regex.Split("plum-pear")
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}
// The example displays the following output:
//    'plum'
//    '-'
//    'pear'

string input = @"07/14/2007"
string pattern = @"(-)|(/)"
Regex regex = new Regex(pattern)
foreach (string result in regex.Split(input))
{
    printfn "'{0}'", result)
}
// Under .NET 1.0 and 1.1, the method returns an array of
// 3 elements, as follows:
//    '07'
//    '14'
//    '2007'
//
// Under .NET 2.0 and later, the method returns an array of
// 5 elements, as follows:
//    '07'
//    '/'
//    '14'
//    '/'
//    '2007'

string input = "characters"
Regex regex = new Regex("")
string[] substrings = regex.Split(input)
Console.Write("{")
for(int ctr = 0 ctr < substrings.Length ctr++)
{
    Console.Write(substrings[ctr])
    if (ctr < substrings.Length - 1)
    Console.Write(", ")
}
printfn "}")

// The example displays the following output:
//    {, c, h, a, r, a, c, t, e, r, s, }

// https://msdn.microsoft.com/en-us/library/98awefz7(v=vs.110).aspx

string pattern = @"\d+"
Regex rgx = new Regex(pattern)
string input = "123ABCDE456FGHIJKL789MNOPQ012"

string[] result = rgx.Split(input, 3)
for (int ctr = 0 ctr < result.Length ctr++) {
    Console.Write("'{0}'", result[ctr])
    if (ctr < result.Length - 1)
    Console.Write(", ")
}
printfn )

// The example displays the following output:
//       '', 'ABCDE', 'FGHIJKL789MNOPQ012'

string pattern = "(-)"
string input = "apple-apricot-plum-pear-banana"
Regex regex = new Regex(pattern)         // Split on hyphens.
string[] substrings = regex.Split(input, 4)
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}
// The example displays the following output:
//       'apple'
//       '-'
//       'apricot'
//       '-'
//       'plum'
//       '-'
//       'pear-banana'

string input = @"07/14/2007"
string pattern = @"(-)|(/)"
Regex regex = new Regex(pattern)
foreach (string result in regex.Split(input, 2))
{
    printfn "'{0}'", result)
}
// Under .NET 1.0 and 1.1, the method returns an array of
// 2 elements, as follows:
//    '07'
//    '14/2007'
//
// Under .NET 2.0 and later, the method returns an array of
// 3 elements, as follows:
//    '07'
//    '/'
//    '14/2007'

// https://msdn.microsoft.com/en-us/library/8yttk7sy(v=vs.110).aspx

string input = "plum--pear"
string pattern = "-"            // Split on hyphens

string[] substrings = Regex.Split(input, pattern)
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}
// The method displays the following output:
//    'plum'
//    ''
//    'pear'

string pattern = @"\d+"
string input = "123ABCDE456FGHIJKL789MNOPQ012"
string[] result = Regex.Split(input, pattern)
for (int ctr = 0 ctr < result.Length ctr++) {
    Console.Write("'{0}'", result[ctr])
    if (ctr < result.Length - 1)
    Console.Write(", ")
}
printfn )

// The example displays the following output:
//       '', 'ABCDE', 'FGHIJKL', 'MNOPQ', ''

string input = "plum-pear"
string pattern = "(-)"

string[] substrings = Regex.Split(input, pattern)    // Split on hyphens
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}
// The example displays the following output:
//    'plum'
//    '-'
//    'pear'

string input = @"07/14/2007"
string pattern = @"(-)|(/)"

foreach (string result in Regex.Split(input, pattern))
{
    printfn "'{0}'", result)
}
// In .NET 1.0 and 1.1, the method returns an array of
// 3 elements, as follows:
//    '07'
//    '14'
//    '2007'
//
// In .NET 2.0 and later, the method returns an array of
// 5 elements, as follows:
//    '07'
//    '/'
//    '14'
//    '/'
//    '2007'

string input = "characters"
string[] substrings = Regex.Split(input, "")
Console.Write("{")
for(int ctr = 0 ctr < substrings.Length ctr++)
{
    Console.Write("'{0}'", substrings[ctr])
    if (ctr < substrings.Length - 1)
    Console.Write(", ")
}
printfn "}")

// The example produces the following output:
//    {'', 'c', 'h', 'a', 'r', 'a', 'c', 't', 'e', 'r', 's', ''}

// https://msdn.microsoft.com/en-us/library/byy2946e(v=vs.110).aspx

string pattern = "[a-z]+"
string input = "Abc1234Def5678Ghi9012Jklm"
string[] result = Regex.Split(input, pattern,
                            RegexOptions.IgnoreCase)
for (int ctr = 0 ctr < result.Length ctr++) {
    Console.Write("'{0}'", result[ctr])
    if (ctr < result.Length - 1)
    Console.Write(", ")
}
printfn )

// The example displays the following output:
//       '', '1234', '5678', '9012', ''

string input = "plum-pear"
string pattern = "(-)"

string[] substrings = Regex.Split(input, pattern)    // Split on hyphens
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}
// The example displays the following output:
//    'plum'
//    '-'
//    'pear'

string input = @"07/14/2007"
string pattern = @"(-)|(/)"

foreach (string result in Regex.Split(input, pattern))
{
    printfn "'{0}'", result)
}
// In .NET 1.0 and 1.1, the method returns an array of
// 3 elements, as follows:
//    '07'
//    '14'
//    '2007'
//
// In .NET 2.0 and later, the method returns an array of
// 5 elements, as follows:
//    '07'
//    '/'
//    '14'
//    '/'
//    '2007'

// https://msdn.microsoft.com/en-us/library/hh160264(v=vs.110).aspx

string pattern = "[a-z]+"
string input = "Abc1234Def5678Ghi9012Jklm"
string[] result = Regex.Split(input, pattern,
                            RegexOptions.IgnoreCase,
                            TimeSpan.FromMilliseconds(500))
for (int ctr = 0 ctr < result.Length ctr++) {
    Console.Write("'{0}'", result[ctr])
    if (ctr < result.Length - 1)
    Console.Write(", ")
}
printfn )

// The example displays the following output:
//       '', '1234', '5678', '9012', ''

string input = "plum-pear"
string pattern = "(-)"

string[] substrings = Regex.Split(input, pattern)    // Split on hyphens
foreach (string match in substrings)
{
    printfn "'{0}'", match)
}

// The example displays the following output:
//    'plum'
//    '-'
//    'pear'

string input = @"07/14/2007"
string pattern = @"(-)|(/)"

foreach (string result in Regex.Split(input, pattern))
{
    printfn "'{0}'", result)
}

// In .NET 1.0 and 1.1, the method returns an array of
// 3 elements, as follows:
//    '07'
//    '14'
//    '2007'
//
// In .NET 2.0 and later, the method returns an array of
// 5 elements, as follows:
//    '07'
//    '/'
//    '14'
//    '/'
//    '2007'


text:
#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions
open FsRegEx

let emailregex = new Regex("(?<user>[^@]+)@(?<host>.+)")

let printMatch (m : Match) s =

    if m.Success  then
        m.Groups.["user"].Value
        |> printfn "User: %s"
        m.Groups.["host"].Value
        |> printfn "Host: %s"
        m.Captures
        |> printfn "%A"
    else
        printfn "%s is not a valid email address" s

let s = "johndoe@tempuri.org"
let m = emailregex.Match(s)

let s' = "jackfoxy@gmail.com"
let m' = emailregex.Match(s')

printMatch m s

printMatch m' s'

let emailFsRegEx = new FsRegEx("(?<user>[^@]+)@(?<host>.+)")

emailFsRegEx

//@@@@@@@@@@@@@@@@@@@@@@@
let parseRegexTimeStamp (regex : Regex) source =
    try
        let m = regex.Matches source
        m.[0].Groups.[1].Value
        //|> Success
    with _ ->
        sprintf "cannot parse timestamp from source: %s" source
       // |> Failure

//@@@@@@@@@@@@@@@@@@@@@@@

let parseTimeStamp (fsRegEx : FsRegEx) source =
    try
        let m = fsRegEx.Matches source
        m.[0].Groups().[1].Value
        //|> Success
    with _ ->
        sprintf "cannot parse timestamp from source: %s" source
       // |> Failure

let timeStampFsRegEx = (new FsRegEx("\"timeStamp\"\s*:\s*\"(\d{4}-\d{2}-\d{2}-\d{2}-\d{2})\""))
let timeStampRegex = (new Regex("\"timeStamp\"\s*:\s*\"(\d{4}-\d{2}-\d{2}-\d{2}-\d{2})\""))

let source1 = """ "quick": false,
        "timeStamp": "2016-07-14-08-14"
    } """

let source2 = """ "quick": false,
        "timeStamp": "2015-06-10-11-12"
    } """

parseRegexTimeStamp timeStampRegex source1
parseRegexTimeStamp timeStampRegex source2

//bug:
parseTimeStamp timeStampFsRegEx source1
parseTimeStamp timeStampFsRegEx source2

//@@@@@@@@@@@@@@@@@@@@

let text = "One car red car blue car";
let pat = @"(\w+)\s+(car)";

let fsMatches = matches pat text

let groupNumberRegExp = @"COD(?<GroupNumber>[0-9]{3})END"
let groupNumberName = "GroupNumber"

capture groupNumberRegExp groupNumberName "COD123END"
|> printfn "%s"

let carRegExp = @"(\w+)\s+(car)"

sprintf "%s %s %s" "One car" "red car" "blue car"
|> matches carRegExp
|> Array.map (fun m -> m.Value)
|> Array.iter(fun x -> printfn "%s" x)

sprintf "%s" "COD123END"
|> capture groupNumberRegExp groupNumberName
|> printfn "%s"

repo_name: jackfoxy/FsRegEx

repo_language: F#

mime_type: text/plain


text:
// https://msdn.microsoft.com/en-us/library/3y21t6y4(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open FsRegEx
open System.Text.RegularExpressions

let partNumbers = [| "1298-673-4192"; "A08Z-931-468A";
                        "_A90-123-129X"; "12345-KKA-1230";
                        "0919-2893-1256" |]
let rgx = new Regex(@"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$")

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
                    partNumber
                    (if (rgx.IsMatch(partNumber)) then "is"
                     else "is not")

// The example displays the following output:
//       1298-673-4192 is a valid part number.
//       A08Z-931-468A is a valid part number.
//       _A90-123-129X is not a valid part number.
//       12345-KKA-1230 is not a valid part number.
//       0919-2893-1256 is not a valid part number.

let fsRegEx = new FsRegEx(@"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$")

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
                    partNumber
                    (if (fsRegEx.IsMatch(partNumber)) then "is"
                     else "is not")

// https://msdn.microsoft.com/en-us/library/z1hx7a5y(v=vs.110).aspx

let partNumbers = [| "Part Number: 1298-673-4192"; "Part No: A08Z-931-468A";
                        "_A90-123-129X"; "123K-000-1230";
                        "SKU: 0919-2893-1256" |]
let rgx = new Regex(@"[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$")

for partNumber in partNumbers do
    let start = partNumber.IndexOf(':')
    if (start >= 0) then
        printfn "%s %s a valid part number."
                        partNumber
                        (if (rgx.IsMatch(partNumber, start)) then "is" else "is not")
    else
        printfn "Cannot find starting position in %s." partNumber

// The example displays the following output:
//       Part Number: 1298-673-4192 is a valid part number.
//       Part No: A08Z-931-468A is a valid part number.
//       Cannot find starting position in _A90-123-129X.
//       Cannot find starting position in 123K-000-1230.
//       SKU: 0919-2893-1256 is not a valid part number.

let fsRegEx = new FsRegEx(@"[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$")

for partNumber in partNumbers do
    let start = partNumber.IndexOf(':')
    if (start >= 0) then
        printfn "%s %s a valid part number."
                        partNumber
                        (if (fsRegEx.IsMatch(partNumber, start)) then "is" else "is not")
    else
        printfn "Cannot find starting position in %s." partNumber

// https://msdn.microsoft.com/en-us/library/sdx2bds0(v=vs.110).aspx

let partNumbers = [| "1298-673-4192"; "A08Z-931-468A";
                        "_A90-123-129X"; "12345-KKA-1230";
                        "0919-2893-1256" |]
let pattern = @"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$"

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
                    partNumber
                    (if (Regex.IsMatch(partNumber, pattern)) then
                        "is"
                     else "is not")

// The example displays the following output:
//       1298-673-4192 is a valid part number.
//       A08Z-931-468A is a valid part number.
//       _A90-123-129X is not a valid part number.
//       12345-KKA-1230 is not a valid part number.
//       0919-2893-1256 is not a valid part number.

// https://msdn.microsoft.com/en-us/library/ktzf2d23(v=vs.110).aspx

let pattern = @"^[A-Z0-9]\d{2}[A-Z0-9](-\d{3}){2}[A-Z0-9]$"

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
                    partNumber
                    (if (Regex.IsMatch(partNumber, pattern, RegexOptions.IgnoreCase)) then
                        "is"
                     else "is not")

// The example displays the following output:
//       1298-673-4192 is a valid part number.
//       A08Z-931-468a is a valid part number.
//       _A90-123-129X is not a valid part number.
//       12345-KKA-1230 is not a valid part number.
//       0919-2893-1256 is not a valid part number

// https://msdn.microsoft.com/en-us/library/hh160210(v=vs.110).aspx

for partNumber in partNumbers do
    try
        printfn "%s %s a valid part number."
                        partNumber
                        (if (Regex.IsMatch(partNumber, pattern, RegexOptions.IgnoreCase, (System.TimeSpan.FromMilliseconds(500.)))) then
                            "is"
                         else "is not")
    with :? RegexMatchTimeoutException as e ->

        printfn "Timeout after %i seconds matching %s."
            e.MatchTimeout.Seconds e.Input

// The example displays the following output:
//       1298-673-4192 is a valid part number.
//       A08Z-931-468a is a valid part number.
//       _A90-123-129X is not a valid part number.
//       12345-KKA-1230 is not a valid part number.
//       0919-2893-1256 is not a valid part number.

for partNumber in partNumbers do
    try
        printfn "%s %s a valid part number."
                        partNumber
                        (if (Regex.IsMatch(partNumber, pattern, RegexOptions.IgnoreCase, (System.TimeSpan.FromMilliseconds(500.)))) then
                            "is"
                         else "is not")
    with :? RegexMatchTimeoutException as e ->

        printfn "Timeout after %i seconds matching %s."
            e.MatchTimeout.Seconds e.Input


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.matchtimeout(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System
open System.Text.RegularExpressions

let domain = AppDomain.CurrentDomain
// Set a timeout interval of 2 seconds.
domain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(2.))
let timeout = domain.GetData("REGEX_DEFAULT_MATCH_TIMEOUT")
printfn "Default regex match timeout: %O"
                    (if (isNull timeout) then ("<null>" :> obj) else timeout)

let rgx = new Regex("[aeiouy]")
printfn "Regular expression pattern: %s" (rgx.ToString())
printfn "Timeout interval for this regex: %f seconds" rgx.MatchTimeout.TotalSeconds


text:
// https://msdn.microsoft.com/en-us/library/twcw2f1c(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

let text = "One car red car blue car"
let pat = @"(\w+)\s+(car)"

// Instantiate the regular expression object.
let r = new Regex(pat, RegexOptions.IgnoreCase)

// Match the regular expression pattern against a text string.
let mutable m = r.Match(text)
let mutable matchCount = 0
while m.Success do
    matchCount <- matchCount + 1
    printfn "Match%i" matchCount
    for (int i = 1 i <= 2 i++)
        {
        Group g = m.Groups[i]
        printfn "Group"+i+"='" + g + "'")
        CaptureCollection cc = g.Captures
        for (int j = 0 j < cc.Count j++)
        {
            Capture c = cc[j]
            System.printfn "Capture"+j+"='" + c + "', Position="+c.Index)
        }
        }
    m = m.NextMatch()
// This example displays the following output:
//       Match1
//       Group1='One'
//       Capture0='One', Position=0
//       Group2='car'
//       Capture0='car', Position=4
//       Match2
//       Group1='red'
//       Capture0='red', Position=8
//       Group2='car'
//       Capture0='car', Position=12
//       Match3
//       Group1='blue'
//       Capture0='blue', Position=16
//       Group2='car'
//       Capture0='car', Position=21

// https://msdn.microsoft.com/en-us/library/h09aybcd(v=vs.110).aspx
//(no code sample)

// https://msdn.microsoft.com/en-us/library/0z2heewz(v=vs.110).aspx

let input = "ablaze beagle choral dozen elementary fanatic " +
                "glaze hunger inept jazz kitchen lemon minus " +
                "night optical pizza quiz restoration stamina " +
                "train unrest vertical whiz xray yellow zealous"
let pattern = @"\b\w*z+\w*\b"

let mutable m = Regex.Match(input, pattern)

while m.Success do
    printfn "'%s' found at position %i" m.Value m.Index
    m <- m.NextMatch()

// The example displays the following output:
//    'ablaze' found at position 0
//    'dozen' found at position 21
//    'glaze' found at position 46
//    'jazz' found at position 65
//    'pizza' found at position 104
//    'quiz' found at position 110
//    'whiz' found at position 157
//    'zealous' found at position 174

FsRegEx.matches pattern input
|> Array.iter (fun m -> printfn "'%s' found at position %i" m.Value m.Index)

// https://msdn.microsoft.com/en-us/library/bk1x0726(v=vs.110).aspx

let pattern = @"\ba\w*\b"
let input = "An extraordinary day dawns with each new day."
let m = Regex.Match(input, pattern, RegexOptions.IgnoreCase)

if m.Success then
    printfn "Found '%s' at position %i." m.Value m.Index

// The example displays the following output:
//        Found 'An' at position 0.

let m = FsRegEx.firstMatchOpt pattern RegexOptions.IgnoreCase input

if m.Success then
    printfn "Found '%s' at position %i." m.Value m.Index

// https://msdn.microsoft.com/en-us/library/hh160204(v=vs.110).aspx
// (no code sample)


text:
// Warning: generated file; your changes could be lost when a new file is generated.
#I __SOURCE_DIRECTORY__
#r "System.Core.dll"
#r "System.dll"
#r "System.Numerics.dll"


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.groupcollection(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

let pattern = @"\b(\w+?)([\u00AE\u2122])"
let input = "Microsoft® Office Professional Edition combines several office " +
                "productivity products, including Word, Excel®, Access®, Outlook®, " +
                "PowerPoint®, and several others. Some guidelines for creating " +
                "corporate documents using these productivity tools are available " +
                "from the documents created using Silverlight™ on the corporate " +
                "intranet site."

let matches = Regex.Matches(input, pattern)
for match' in matches do
    let groups = match'.Groups
    printfn "%s: %s" groups.[2].Value groups.[1].Value

printfn ""
printfn "Found %i trademarks or registered trademarks." matches.Count

// ®: Microsoft
// ®: Excel
// ®: Access
// ®: Outlook
// ®: PowerPoint
// ™: Silverlight


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.group(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

let pattern = @"(\b(\w+?)[,:;]?\s?)+[?.!]"
let input = "This is one sentence. This is a second sentence."

let match' = Regex.Match(input, pattern)
printfn "Match: %s" match'.Value
let mutable groupCtr = 0

for group in match'.Groups do
    groupCtr <- groupCtr + 1
    printfn "   Group %i: '%s'" groupCtr group.Value
    let mutable captureCtr = 0
    for capture in group.Captures do
        captureCtr <- captureCtr + 1
        printfn "      Capture %i: '%s'" captureCtr capture.Value

// The example displays the following output:
//       Match: This is one sentence.
//          Group 1: 'This is one sentence.'
//             Capture 1: 'This is one sentence.'
//          Group 2: 'sentence'
//             Capture 1: 'This '
//             Capture 2: 'is '
//             Capture 3: 'one '
//             Capture 4: 'sentence'
//          Group 3: 'sentence'
//             Capture 1: 'This'
//             Capture 2: 'is'
//             Capture 3: 'one'
//             Capture 4: 'sentence'

let m' = FsRegEx.firstMatch pattern input
printfn "Match: %s" m'.Value

printfn "Named Groups:"
m'.Groups()
|> Array.iteri (fun i g ->
    printfn "   Group %i: '%s'" i g.Value
    g.Captures()
    |> Array.iteri (fun i c ->
        printfn "      Capture %i: '%s'" i c.Value
        )
    )


text:
// https://msdn.microsoft.com/en-us/library/cft8645c(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions
open System

let capText (m : Match) =
    // Get the matched string.
    let x = m.Value
    // If the first char is lower case...
    if (Char.IsLower(x.[0])) then
        // Capitalize it.
        (Char.ToUpper(x.[0]).ToString()) + (x.Substring(1, x.Length - 1))
    else
        x

let text = "four score and seven years ago"

printfn "text=[%s]" text

let rx = new Regex(@"\w+")

let result = rx.Replace(text, new MatchEvaluator(capText))

printfn "result=[%s]" result

// The example displays the following output:
//       text=[four score and seven years ago]
//       result=[Four Score And Seven Years Ago]

printfn "result=[%s]"
    (text
     |> FsRegEx.replaceByMatch @"\w+" (new MatchEvaluator(capText)) )

// https://msdn.microsoft.com/en-us/library/e47f3dkc(v=vs.110).aspx

public static void Main()
{
    string input = "deceive relieve achieve belief fierce receive"
    string pattern = @"\w*(ie|ei)\w*"
    Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase)
    printfn "Original string: " + input)

    string result = rgx.Replace(input, new MatchEvaluator(Example.ReverseLetter),
                                input.Split(' ').Length / 2)
    printfn "Returned string: " + result)
}

static string ReverseLetter(Match match)
{
    return Regex.Replace(match.Value, "([ie])([ie])", "$2$1",
                        RegexOptions.IgnoreCase)
}
// The example displays the following output:
//    Original string: deceive relieve achieve belief fierce receive
//    Returned string: decieve releive acheive belief fierce receive

// https://msdn.microsoft.com/en-us/library/ms149474(v=vs.110).aspx
// (no code sample)

// https://msdn.microsoft.com/en-us/library/xwewhkd1(v=vs.110).aspx

string input = "This is   text with   far  too   much   " +
                "whitespace."
string pattern = "\\s+"
string replacement = " "
Regex rgx = new Regex(pattern)
string result = rgx.Replace(input, replacement)

printfn "Original String: {0}", input)
printfn "Replacement String: {0}", result)

// The example displays the following output:
//       Original String: This is   text with   far  too   much   whitespace.
//       Replacement String: This is text with far too much whitespace.

// https://msdn.microsoft.com/en-us/library/haekbhys(v=vs.110).aspx

string str = "aabccdeefgghiijkklmm"
string pattern = "(\\w)\\1"
string replacement = "$1"
Regex rgx = new Regex(pattern)

string result = rgx.Replace(str, replacement, 5)
printfn "Original String:    '{0}'", str)
printfn "Replacement String: '{0}'", result)

// The example displays the following output:
//       Original String:    'aabccdeefgghiijkklmm'
//       Replacement String: 'abcdefghijkklmm'

// https://msdn.microsoft.com/en-us/library/3fky5t5f(v=vs.110).aspx

string input = "Instantiating a New Type\n" +
                "Generally, there are two ways that an\n" +
                "instance of a class or structure can\n" +
                "be instantiated. "
string pattern = "^.*$"
string replacement = "\n$&"
Regex rgx = new Regex(pattern, RegexOptions.Multiline)
string result = String.Empty

Match match = rgx.Match(input)
// Double space all but the first line.
if (match.Success)
    result = rgx.Replace(input, replacement, -1, match.Index + match.Length + 1)

printfn result)

// The example displays the following output:
//       Instantiating a New Type
//
//       Generally, there are two ways that an
//
//       instance of a class or structure can
//
//       be instntiated.

// https://msdn.microsoft.com/en-us/library/ht1sxswy(v=vs.110).aspx

public static void Main()
{
    string words = "letter alphabetical missing lack release " +
                    "penchant slack acryllic laundry cease"
    string pattern = @"\w+"
    MatchEvaluator evaluator = new MatchEvaluator(WordScrambler)
    printfn "Original words:")
    printfn words)
    printfn )
    printfn "Scrambled words:")
    printfn Regex.Replace(words, pattern, evaluator))
}

public static string WordScrambler(Match match)
{
    int arraySize = match.Value.Length
    // Define two arrays equal to the number of letters in the match.
    double[] keys = new double[arraySize]
    char[] letters = new char[arraySize]

    // Instantiate random number generator'
    Random rnd = new Random()

    for (int ctr = 0 ctr < match.Value.Length ctr++)
    {
        // Populate the array of keys with random numbers.
        keys[ctr] = rnd.NextDouble()
        // Assign letter to array of letters.
        letters[ctr] = match.Value[ctr]
    }
    Array.Sort(keys, letters, 0, arraySize, Comparer.Default)
    return new String(letters)
}

// The example displays output similar to the following:
//    Original words:
//    letter alphabetical missing lack release penchant slack acryllic laundry cease
//
//    Scrambled words:
//    elrtte iaeabatlpchl igmnssi lcka aerslee hnpatnce ksacl lialcryc dylruna ecase

// https://msdn.microsoft.com/en-us/library/ms149475(v=vs.110).aspx

public static void Main()
{
    string words = "letter alphabetical missing lack release " +
                    "penchant slack acryllic laundry cease"
    string pattern = @"\w+  # Matches all the characters in a word."
    MatchEvaluator evaluator = new MatchEvaluator(WordScrambler)
    printfn "Original words:")
    printfn words)
    printfn )
    printfn "Scrambled words:")
    printfn Regex.Replace(words, pattern, evaluator,
                                    RegexOptions.IgnorePatternWhitespace))
}

public static string WordScrambler(Match match)
{
    int arraySize = match.Value.Length
    // Define two arrays equal to the number of letters in the match.
    double[] keys = new double[arraySize]
    char[] letters = new char[arraySize]

    // Instantiate random number generator'
    Random rnd = new Random()

    for (int ctr = 0 ctr < match.Value.Length ctr++)
    {
        // Populate the array of keys with random numbers.
        keys[ctr] = rnd.NextDouble()
        // Assign letter to array of letters.
        letters[ctr] = match.Value[ctr]
    }
    Array.Sort(keys, letters, 0, arraySize, Comparer.Default)
    return new String(letters)
}
// The example displays output similar to the following:
//    Original words:
//    letter alphabetical missing lack release penchant slack acryllic laundry cease
//
//    Scrambled words:
//    etlert liahepalbcat imsgsni alkc ereelsa epcnnaht lscak cayirllc alnyurd ecsae

// https://msdn.microsoft.com/en-us/library/hh194609(v=vs.110).aspx

public static void Main()
{
    string words = "letter alphabetical missing lack release " +
                    "penchant slack acryllic laundry cease"
    string pattern = @"\w+  # Matches all the characters in a word."
    MatchEvaluator evaluator = new MatchEvaluator(WordScrambler)
    printfn "Original words:")
    printfn words)
    printfn )
    try {
        printfn "Scrambled words:")
        printfn Regex.Replace(words, pattern, evaluator,
                                        RegexOptions.IgnorePatternWhitespace,
                                        TimeSpan.FromSeconds(.25)))
    }
    catch (RegexMatchTimeoutException) {
        printfn "Word Scramble operation timed out.")
        printfn "Returned words:")
    }
}

public static string WordScrambler(Match match)
{
    int arraySize = match.Value.Length
    // Define two arrays equal to the number of letters in the match.
    double[] keys = new double[arraySize]
    char[] letters = new char[arraySize]

    // Instantiate random number generator'
    Random rnd = new Random()

    for (int ctr = 0 ctr < match.Value.Length ctr++)
    {
        // Populate the array of keys with random numbers.
        keys[ctr] = rnd.NextDouble()
        // Assign letter to array of letters.
        letters[ctr] = match.Value[ctr]
    }
    Array.Sort(keys, letters, 0, arraySize, Comparer.Default)
    return new String(letters)
}
// The example displays output similar to the following:
//    Original words:
//    letter alphabetical missing lack release penchant slack acryllic laundry cease
//
//    Scrambled words:
//    etlert liahepalbcat imsgsni alkc ereelsa epcnnaht lscak cayirllc alnyurd ecsae

// https://msdn.microsoft.com/en-us/library/e7f5w83z(v=vs.110).aspx

string input = "This is   text with   far  too   much   " +
                "whitespace."
string pattern = "\\s+"
string replacement = " "
string result = Regex.Replace(input, pattern, replacement)

printfn "Original String: {0}", input)
printfn "Replacement String: {0}", result)

// The example displays the following output:
//       Original String: This is   text with   far  too   much   whitespace.
//       Replacement String: This is text with far too much whitespace.

// Get drives available on local computer and form into a single character expression.
string[] drives = Environment.GetLogicalDrives()
string driveNames = String.Empty
foreach (string drive in drives)
    driveNames += drive.Substring(0,1)
// Create regular expression pattern dynamically based on local machine information.
string pattern = @"\\\\(?i:" + Environment.MachineName + @")(?:\.\w+)*\\((?i:[" + driveNames + @"]))\$"

string replacement = "$1:"
string[] uncPaths = { @"\\MyMachine.domain1.mycompany.com\C$\ThingsToDo.txt",
                    @"\\MyMachine\c$\ThingsToDo.txt",
                    @"\\MyMachine\d$\documents\mydocument.docx" }

foreach (string uncPath in uncPaths)
{
    printfn "Input string: " + uncPath)
    printfn "Returned string: " + Regex.Replace(uncPath, pattern, replacement))
    printfn )
}
// The example displays the following output if run on a machine whose name is
// MyMachine:
//    Input string: \\MyMachine.domain1.mycompany.com\C$\ThingsToTo.txt
//    Returned string: C:\ThingsToDo.txt
//
//    Input string: \\MyMachine\c$\ThingsToDo.txt
//    Returned string: c:\ThingsToDo.txt
//
//    Input string: \\MyMachine\d$\documents\mydocument.docx
//    Returned string: d:\documents\mydocument.docx

// https://msdn.microsoft.com/en-us/library/taz3ak2f(v=vs.110).aspx

// Get drives available on local computer and form into a single character expression.
string[] drives = Environment.GetLogicalDrives()
string driveNames = String.Empty
foreach (string drive in drives)
    driveNames += drive.Substring(0,1)
// Create regular expression pattern dynamically based on local machine information.
string pattern = @"\\\\" + Environment.MachineName + @"(?:\.\w+)*\\([" + driveNames + @"])\$"

string replacement = "$1:"
string[] uncPaths = { @"\\MyMachine.domain1.mycompany.com\C$\ThingsToDo.txt",
                    @"\\MyMachine\c$\ThingsToDo.txt",
                    @"\\MyMachine\d$\documents\mydocument.docx" }

foreach (string uncPath in uncPaths)
{
    printfn "Input string: " + uncPath)
    printfn "Returned string: " + Regex.Replace(uncPath, pattern, replacement, RegexOptions.IgnoreCase))
    printfn )
}
// The example displays the following output if run on a machine whose name is
// MyMachine:
//    Input string: \\MyMachine.domain1.mycompany.com\C$\ThingsToTo.txt
//    Returned string: C:\ThingsToDo.txt
//
//    Input string: \\MyMachine\c$\ThingsToDo.txt
//    Returned string: c:\ThingsToDo.txt
//
//    Input string: \\MyMachine\d$\documents\mydocument.docx
//    Returned string: d:\documents\mydocument.docx

// https://msdn.microsoft.com/en-us/library/hh194617(v=vs.110).aspx

// Get drives available on local computer and form into a single character expression.
string[] drives = Environment.GetLogicalDrives()
string driveNames = String.Empty
foreach (string drive in drives)
    driveNames += drive.Substring(0,1)
// Create regular expression pattern dynamically based on local machine information.
string pattern = @"\\\\" + Environment.MachineName + @"(?:\.\w+)*\\([" + driveNames + @"])\$"

string replacement = "$1:"
string[] uncPaths = { @"\\MyMachine.domain1.mycompany.com\C$\ThingsToDo.txt",
                    @"\\MyMachine\c$\ThingsToDo.txt",
                    @"\\MyMachine\d$\documents\mydocument.docx" }

foreach (string uncPath in uncPaths)
{
    printfn "Input string: " + uncPath)
    string localPath = null
    try {
    localPath = Regex.Replace(uncPath, pattern, replacement,
                                RegexOptions.IgnoreCase,
                                TimeSpan.FromSeconds(0.5))
    printfn "Returned string: " + localPath)
    }
    catch (RegexMatchTimeoutException) {
    printfn "The replace operation timed out.")
    printfn "Returned string: " + localPath)
    if (uncPath.Equals(localPath))
        printfn "Equal to original path.")
    else
        printfn "Original string: " + uncPath)
    }
    printfn )
}
// The example displays the following output if run on a machine whose name is
// MyMachine:
//    Input string: \\MyMachine.domain1.mycompany.com\C$\ThingsToTo.txt
//    Returned string: C:\ThingsToDo.txt
//
//    Input string: \\MyMachine\c$\ThingsToDo.txt
//    Returned string: c:\ThingsToDo.txt
//
//    Input string: \\MyMachine\d$\documents\mydocument.docx
//    Returned string: d:\documents\mydocument.docx


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.groupnamefromnumber(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

open System
open System.Collections.Generic

let pattern = @"(?<city>[A-Za-z\s]+), (?<state>[A-Za-z]{2}) (?<zip>\d{5}(-\d{4})?)"
let cityLines = [|"New York, NY 10003"; "Brooklyn, NY 11238"; "Detroit, MI 48204";
                    "San Francisco, CA 94109"; "Seattle, WA 98109" |]
let rgx = new Regex(pattern)
let names = new List<string>()
let mutable ctr = 1
let mutable exitFlag = false

while (not exitFlag) do
    let name = rgx.GroupNameFromNumber(ctr)
    if (not (String.IsNullOrEmpty(name))) then
        ctr <- ctr + 1
        names.Add(name)
    else
        exitFlag <- true

for cityLine in cityLines do
    let m = rgx.Match(cityLine)
    if (m.Success) then
        printfn "Zip code %s is in %s, %s."
                        m.Groups.[names.[3]].Value
                        m.Groups.[names.[1]].Value
                        m.Groups.[names.[2]].Value

// The example displays the following output:
//       Zip code 10003 is in New York, NY.
//       Zip code 11238 is in Brooklyn, NY.
//       Zip code 48204 is in Detroit, MI.
//       Zip code 94109 is in San Francisco, CA.
//       Zip code 98109 is in Seattle, WA.

let names' = FsRegEx.groupNames

cityLines
|> Array.iter ()


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.getgroupnumbers(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions
open System

let pattern = @"\b((?<word>\w+)\s*)+(?<end>[.?!])"
let input = "This is a sentence. This is a second sentence."

let rgx = new Regex(pattern)
let groupNumbers = rgx.GetGroupNumbers()

let m = rgx.Match(input)

// does GroupNameFromNumber & inverse need to be option type?
if m.Success then
    printfn "Match: %s" m.Value
    for groupNumber in groupNumbers do
        let name = rgx.GroupNameFromNumber(groupNumber)

        let isNumber, _ = Int32.TryParse(name)

        printfn "   Group %i%s: '%s'"
            groupNumber
            (if (String.IsNullOrEmpty(name) |> not)
                && (not isNumber) then
                " (" + name + ")"
                else String.Empty)
            m.Groups.[groupNumber].Value
else ()

// The example displays the following output:
//       Match: This is a sentence.
//          Group 0: 'This is a sentence.'
//          Group 1: 'sentence'
//          Group 2 (word): 'sentence'
//          Group 3 (end): '.'

let groupNumbers' = FsRegEx.groupNumbers pattern
let groupNames = FsRegEx.groupNames pattern

let m' = FsRegEx.firstMatch pattern input

Array.zip groupNumbers' groupNames
|> Array.iter (fun (n, name) ->
    let isNumber, _ = Int32.TryParse(name)
    printfn "   Group %i%s: '%s'"
        n
        (if (not isNumber) then
            " (" + name + ")"
         else String.Empty)
        (m'.Groups()).[n].Value
    )


text:
// https://msdn.microsoft.com/en-us/library/9ek5zak6(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

let compilationList = new List<RegexCompilationInfo>()

// Define regular expression to detect duplicate words
let expr = new RegexCompilationInfo(@"\b(?<word>\w+)\s+(\k<word>)\b",
            RegexOptions.IgnoreCase || RegexOptions.CultureInvariant,
            "DuplicatedString",
            "Utilities.RegularExpressions",
            true)
// Add info object to list of objects
compilationList.Add(expr)

// Define regular expression to validate format of email address
let expr = new RegexCompilationInfo(@"^(?("")(""[^""]+?""@)|(([0-9A-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9A-Z])@))" +
            @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9A-Z][-\w]*[0-9A-Z]\.)+[A-Z]{2,6}))$",
            RegexOptions.IgnoreCase | RegexOptions.CultureInvariant,
            "EmailAddress",
            "Utilities.RegularExpressions",
            true)
// Add info object to list of objects
compilationList.Add(expr)

// Generate assembly with compiled regular expressions
let compilationArray = new RegexCompilationInfo[compilationList.Count]
let assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
compilationList.CopyTo(compilationArray)
Regex.CompileToAssembly(compilationArray, assemName)

// https://msdn.microsoft.com/en-us/library/ew6t47sw(v=vs.110).aspx

let compilationList = new List<RegexCompilationInfo>()

// Define regular expression to detect duplicate words
let expr = new RegexCompilationInfo(@"\b(?<word>\w+)\s+(\k<word>)\b",
            RegexOptions.IgnoreCase | RegexOptions.CultureInvariant,
            "DuplicatedString",
            "Utilities.RegularExpressions",
            true)
// Add info object to list of objects
compilationList.Add(expr)

// Define regular expression to validate format of email address
let expr = new RegexCompilationInfo(@"^(?("")(""[^""]+?""@)|(([0-9A-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9A-Z])@))" +
            @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9A-Z][-\w]*[0-9A-Z]\.)+[zA-Z]{2,6}))$",
            RegexOptions.IgnoreCase | RegexOptions.CultureInvariant,
            "EmailAddress",
            "Utilities.RegularExpressions",
            true)
// Add info object to list of objects
compilationList.Add(expr)

// Apply AssemblyTitle attribute to the new assembly
//
// Define the parameter(s) of the AssemblyTitle attribute's constructor
Type[] parameters = { typeof(string) }
// Define the assembly's title
object[] paramValues = { "General-purpose library of compiled regular expressions" }
// Get the ConstructorInfo object representing the attribute's constructor
ConstructorInfo ctor = typeof(System.Reflection.AssemblyTitleAttribute).GetConstructor(parameters)
// Create the CustomAttributeBuilder object array
CustomAttributeBuilder[] attBuilder = { new CustomAttributeBuilder(ctor, paramValues) }

// Generate assembly with compiled regular expressions
let compilationArray = new RegexCompilationInfo[compilationList.Count]
AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
compilationList.CopyTo(compilationArray)
Regex.CompileToAssembly(compilationArray, assemName, attBuilder)

// https://msdn.microsoft.com/en-us/library/5a9btak8(v=vs.110).aspx

// (no sample code)


text:
// https://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.escape(v=vs.110).aspx

#I __SOURCE_DIRECTORY__
#load "load-project-debug.fsx"

open System.Text.RegularExpressions

let pattern = Regex.Escape("[") + "(.*?)]"
let input = "The animal [what kind?] was visible [by whom?] from the window."

let matches = Regex.Matches(input, pattern)
let mutable commentNumber = 0

printfn "%s produces the following matches:" pattern
for match' in matches do
    commentNumber <- commentNumber + 1
    printfn "   %i: %s" commentNumber match'.Value

// This example displays the following output:
//       \[(.*?)] produces the following matches:
//          1: [what kind?]
//          2: [by whom?]

printfn "%s produces the following matches:" pattern

FsRegEx.matches pattern input
|> Array.iteri (fun i x -> printfn "   %i: %s" (i + 1) x.Value)


text:
// Warning: generated file; your changes could be lost when a new file is generated.
#I __SOURCE_DIRECTORY__
#load "load-references-debug.fsx"
#load "../FsRegEx.fsi"
      "../FsRegEx.fs"
      "../VerbalExpressions.fsi"
      "../VerbalExpressions.fs"
      "../CommonFsRegEx.fsi"
      "../CommonFsRegEx.fs"


text:
// Warning: generated file; your changes could be lost when a new file is generated.
#I __SOURCE_DIRECTORY__
#r "System.Core.dll"
#r "System.dll"
#r "System.Numerics.dll"
#r "System.ComponentModel.Composition.dll"
#r "../../../packages/System.ValueTuple/lib/netstandard1.0/System.ValueTuple.dll"


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"

(**
FsRegEx
======================

The `FsRegEx` library provides composable F# functionality for nearly all the capabilites of the .NET `Regex` class, supporting uniform pipe forward `|>` composability and all `Regex` features except timeouts. Optionally you can compose F# verbal expressions in natural language. Lazy evaluation ensures natural language composition imposes no performance penalty.

      The FsRegEx library can be installed from NuGet:

PM> Install-Package FsRegEx

Introduction
------------

The `FsRegEx` library brings a composable regular expression experience to F#.

 * Composable functions representing all available `Regex` functionality (except timeouts) with the target input string uniformly the last parameter to better support pipe forward `|>` composition and partial application.

 * Collections returned as F# arrays rather than `Regex` special collections for better composability (but sacrificing lazy match evaluation and timeout support).

 * Short-cut functions like `capture` on group name make common multi-step processes a single function.

  * `FsRegEx` - A composable F# wrapper type over `System.Text.RegularExpressions.Regex` for natural language regular expression composition.

 * `FsMatch` - A composable F# wrapper type over `System.Text.RegularExpressions.Match`.

 * `FsGroup` - A composable F# wrapper type over `System.Text.RegularExpressions.Group`.

 * Basic functional operations on `FsRegEx` (`exists`, `fold`, `foldBack`, `iter`, and `map`), among other composable functions.

* `FsRegEx` speciall collections are returned as arrays. This does lose the advantange of lazy match evaluation the special collections provide.
The underlying object can always be returned from the wrapper.
*)

(*** hide ***)
#r "FsRegEx.dll"

(**
Better F# Composition
------------------------------------
*)
open FsRegEx

let carRegExp = @"(\w+)\s+(car)"

sprintf "%s %s %s" "One car" "red car" "blue car"
|> matches carRegExp
|> Array.iter(fun m -> printfn "%s" m.Value)

// One car
// red car
// blue car

(**
Short-cut common procedures to a single function
------------------------------------------------
*)
let groupNumberRegExp = @"COD(?<GroupNumber>[0-9]{3})END"
let groupNumberName = "GroupNumber"

sprintf "%s" "COD123END"
|> capture groupNumberRegExp groupNumberName
|> printfn "%s"

// 123

(**
Natural Language Regular Expressions
------------------------------------

The library also provides the composable Verbal Expressions language for constructing simple regular expressions in a readable fashion.
*)
open FsRegEx
open System.Text.RegularExpressions
open VerbalExpressions

let groupName =  "GroupNumber"

FsRegEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

(**
A comparison of using Regex natively, and Verbal Expressions to retrieve the database parameter from a connection string:
*)

let TestConnString =
    """<add name="MyConnString" connectionString="Server=MyServer;Database=MyDatabase;User ID=me;Password=secret;Encrypt=True;TrustServerCertificate=True;Enlist=False;" />"""

let databaseFromRegex connectionString =

    Regex.Match(connectionString, "[dD]atabase=(?<database>.*?);").Groups.["database"].Value

let databaseFromFsRegEx connectionString =

    FsRegEx()
    |> add "[dD]atabase=(?<database>.*?);"
    |> capture connectionString "database"

(databaseFromRegex TestConnString) = (databaseFromFsRegEx TestConnString)
|> printfn "%b"

// true

(**
This example shows the more verbose and descriptive Verbal Expressions:
*)

let databaseFromVerboseFsRegEx connectionString =

    FsRegEx()
    |> anyOf "dD"
    |> then' "atabase="
    |> beginCaptureNamed "database"
    |> add ".*?"
    |> endCapture
    |> then' ";"
    |> capture connectionString "database"

(databaseFromRegex TestConnString) = (databaseFromVerboseFsRegEx TestConnString)
|> printfn "%b"

// true

(**
Documentation
-------------

FsRegEx comes with comprehensive API documentation and a tutorial.

 * [API Reference](reference/index.html) contains documentation for all types, modules, and functions in the library.

 * [F# Verbal Expressions Tutorial](tutorial.html) contains further explanation of the natural language DSL syntax and many more usage examples.

Versioning
-----------------------

 FsRegEx adheres to [Semantic Versioning](http://semver.org/ "Semantic Versioning"). So long as the project is pre-1.0.0 minor versions may be breaking. Once the project reaches 1.0.0 minor enhancements will be backwards-compatible.

Contributing and copyright
--------------------------

FsRegEx is hosted on [GitHub][gh] where you can [report issues][issues], fork
the project, and submit pull requests, so long as pull requests:

 * include excellent unit test coverage
 * include correct intellisense documentation
 * adhere to the concepts of composability

FsRegEx is available under Public Domain license, which allows modification and
redistribution for both commercial and non-commercial purposes. For more information see the
[License file][license] in the GitHub repository.

  [content]: http://github.com/jackfoxy/FsRegEx/tree/master/docs/content
  [gh]: http://github.com/jackfoxy/FsRegEx
  [issues]: http://github.com/jackfoxy/FsRegEx/issues
  [readme]: http://github.com/jackfoxy/FsRegEx/blob/master/README.md
  [license]: http://github.com/jackfoxy/FsRegEx/blob/master/LICENSE.txt
*)


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"

(**
Replacing and Splitting
=======================

Replace
-------

The `System.Text.RegularExpressions.Regex` Class provides several overloads of `replace`. The same overloads are available in the `FsRegEx` class and as individually
named functions, including overloads and functions using `RegexOptions`. `Timeout` is not supported at this time.
*)

(*** hide ***)
#r "FsRegEx.dll"

(**
### simple replacement of all occurence ###
*)
open System
open System.Text.RegularExpressions

"This is   text with   far  too   much   " +
                "whitespace."
|> FsRegEx.replace "\\s+" " "
|>printfn "Replacement String: %s"

// Replacement String: This is text with far too much whitespace.
(**
### replace max time starting at ###

Replaces a specified maximum number of strings starting at location that match a regular expression pattern with a specified replacement string.
*)
"Instantiating a New Type\n" +
    "Generally, there are two ways that an\n" +
    "instance of a class or structure can\n" +
    "be instantiated. "
|> FsRegEx.replaceMaxTimesStartAtOpt "^.*$" RegexOptions.Multiline "\n$&"  -1 1
|> printfn "%s"

// Instantiating a New Type
//
// Generally, there are two ways that an
//
// instance of a class or structure can
//
// be instntiated.
(**
### replace with MatchEvaluator ###
*)
let capText (m : Match) =
    // Get the matched string.
    let x = m.Value
    // If the first char is lower case...
    if (Char.IsLower(x.[0])) then
        // Capitalize it.
        (Char.ToUpper(x.[0]).ToString()) + (x.Substring(1, x.Length - 1))
    else
        x

"four score and seven years ago"
|> FsRegEx.replaceByMatch @"\w+" (new MatchEvaluator(capText))
|> printfn "result=[%s]"

// result=[Four Score And Seven Years Ago]
(**
Splitting
---------

The `System.Text.RegularExpressions.Regex` Class provides several overloads of `split` to split strings into arrays. The same overloads are available in the `FsRegEx` class and as individually
named functions, including overloads and functions using `RegexOptions`. `Timeout` is not supported at this time.

### split with Regex option ###
*)
"Abc1234Def5678Ghi9012Jklm"
|> FsRegEx.splitOpt "[a-z]+" RegexOptions.IgnoreCase
|> Array.iter (fun x -> printfn "'%s'" x)

// ''
// '1234'
// '5678'
// '9012'
// ''


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"
open FsRegEx

(**
Tutorial: F# Verbal Expressions
=================================

The `VerbalExpressions` module includes the `FsRegEx` type which wraps the familiar .NET `RegEx` in a type with useful functional members.
Multiple constructors start with a regular expression in the constructor.
*)

let fsRegEx = new FsRegEx(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}")

(**
This the module is an experimental DSL that allows you to compose regular expressions in natural language
using the immutable `FsRegEx` type. The remainder of this tutorial is concerned with the experimental DSL which is, frankly, not that practical.

For practical examples of using the core `FsRegEx` module for composability, see the following examples:

* [Matching](matching.html)

* [Matching Groups](group.html)

* [Replace and Split](replacesplit.html)

* [Capture](capture.html)

Verbal Expressions DSL
----------------------

You can compose values of the FsRegEx type with the |> operator, including creating a new regular expression by logical or on 2 existing FsRegExs.

*)
#r "FsRegEx.dll"
open FsRegEx
open System.Text.RegularExpressions
open VerbalExpressions

let v =
    CommonFsRegEx.Email
    |> fsRegExOrFsRegEx RegexOptions.None CommonFsRegEx.Url

let foundEmail =
    v
    |> isMatch "test@github.com"

let foundUrl =
    v
    |> isMatch "http://www.google.com"

printfn "%b" foundEmail
printfn "%b" foundUrl

// true
// true

(**
Natural language composition consists of building up a new FsRegEx from an old by functions which append special characters, groups, modifiers, and other attributes of the regular expression language.

function : 'T -> FsRegEx -> FsRegEx

See the API documentation for all the regular expression functions available.
*)

open VerbalExpressions

let foundFromGithub =
    FsRegEx()
    |> startOfLine
    |> something
    |> then' "github.com"
    |> endOfLine
    |> isMatch "test@github.com"

printfn "%b" foundFromGithub

// true

(**
You do not have to worry about escaping special characters in your regular expression.
*)

let foundSomethingSpecial =
    FsRegEx()
    |> startOfLine
    |> something
    |> then' "*+?"
    |> anything
    |> isMatch "blah blah blah*+?yackety yack"

printfn "%b" foundSomethingSpecial

// true

(**
Sometimes you may need more power than the natural language provides, or you just need to include a snippet of native regular expression. The add function lets you do that.
*)

let foundSpecialInMultiline =
    FsRegEx()
    |> add @"phrase1\*\+\?"
    |> anything
    |> isMatch @"phrase1*+?RestOfLine\n"

printfn "%b" foundSpecialInMultiline

// true

(**
FsRegExs posses all the power of the .Net RegEx class in a composable form.
*)

let n =
    FsRegEx()
    |> word
    |> matches "three words here"

printfn "%i" n.Length

// 3

let betterFormat =
    FsRegEx()
    |> add "\s+"
    |> or' "whitespace"
    |> replace "This     is   text with   far  too   much   whitespace" " "

printfn "%s" betterFormat

// This is text with far too much

let groupName =  "GroupNumber"

FsRegEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

(**
FsRegEx comes with first class support for unicode, including unicode general categories and .Net extension blocks.
*)

FsRegEx()
|> beginCaptureNamed "upper"
|> unicodeCategory Unicode.UnicodeGeneralCategory.LetterUppercase
|> add "+"
|> endCapture
|> capture "some mixed case WORDS" "upper"
|> printfn "%s"

// WORDS


text:
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"

(**
Matching
========

IsMatch
-------

The `System.Text.RegularExpressions.Regex` Class provides several overloads of `isMatch`. The same overloads are available in the `FsRegEx` class and as individually
named functions, including overloads and functions using `RegexOptions`. `Timeout` is not supported at this time.
*)

(*** hide ***)
#r "FsRegEx.dll"

(**
### is match on first occurence ###
*)
open FsRegEx
module F = FsRegEx

let partNumbers = [| "1298-673-4192"; "A08Z-931-468A";
                    "_A90-123-129X"; "12345-KKA-1230";
                    "0919-2893-1256" |]

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
        partNumber
        (if (F.isMatch @"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$" partNumber ) then "is"
            else "is not")

let fsRegEx = new FsRegEx(@"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$")

for partNumber in partNumbers do
    printfn "%s %s a valid part number."
        partNumber
        (if (fsRegEx.IsMatch(partNumber)) then "is"
            else "is not")

// 1298-673-4192 is a valid part number.
// A08Z-931-468A is a valid part number.
// _A90-123-129X is not a valid part number.
// 12345-KKA-1230 is not a valid part number.
// 0919-2893-1256 is not a valid part number.
(**
### is match starting at position ###
*)
let labeledPartNumbers = [| "Part Number: 1298-673-4192"; "Part No: A08Z-931-468A";
                        "_A90-123-129X"; "123K-000-1230";
                        "SKU: 0919-2893-1256" |]

for partNumber in labeledPartNumbers do
    let start = partNumber.IndexOf(':')
    if (start >= 0) then
        printfn "%s %s a valid part number."
            partNumber
            (if (F.isMatchAt @"^[a-zA-Z0-9]\d{2}[a-zA-Z0-9](-\d{3}){2}[A-Za-z0-9]$" start partNumber) then "is" else "is not")
    else
        printfn "Cannot find starting position in %s." partNumber

for partNumber in labeledPartNumbers do
    let start = partNumber.IndexOf(':')
    if (start >= 0) then
        printfn "%s %s a valid part number."
            partNumber
            (if (fsRegEx.IsMatch(partNumber, start)) then "is" else "is not")
    else
        printfn "Cannot find starting position in %s." partNumber

// Part Number: 1298-673-4192 is a valid part number.
// Part No: A08Z-931-468A is a valid part number.
// Cannot find starting position in _A90-123-129X.
// Cannot find starting position in 123K-000-1230.
// SKU: 0919-2893-1256 is not a valid part number.
(**
FsMatch
-------

`FsMatch` provides the functionality in the `System.Text.RegularExpressions.Match` Class, but returning arrays of objects instead of special collections.

### iterate over matches ###
*)
"Who writes these notes?"
|> FsRegEx.matches @"\b\w+es\b"
|> Array.iter (fun m -> printfn "Found '%s' at position %i" m.Value m.Index)

// Found 'writes' at position 4
// Found 'notes' at position 17
(**
### first match and iteration ###
*)
let regExpr = @"\b\w+es\b"
let sentence = "Who writes these notes and uses our paper?"

let m = FsRegEx.firstMatch regExpr sentence

if m.Success then
    printfn "Found first 'es' in '%s' at position %i" m.Value m.Index
    sentence
    |> FsRegEx.matchesAt regExpr (m.Index + m.Length)
    |> Array.iter (fun m -> printfn "Also found '%s' at position %i" m.Value m.Index)

// Found first 'es' in 'writes' at position 4
// Also found 'notes' at position 17
// Also found 'uses' at position 27


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"

(**
Capture Groups
==============

FsGroup
-------

`FsGroup` provides the functionality in the `System.Text.RegularExpressions.Group` Class, but returning arrays of objects instead of special collections and makes the group name a property.
*)

(*** hide ***)
#r "FsRegEx.dll"

(**
### anonymous groups ###
*)

let pattern = @"(\b(\w+?)[,:;]?\s?)+[?.!]"
let input = "This is one sentence. This is a second sentence."

let m = FsRegEx.firstMatch pattern input
printfn "Match: %s" m.Value

m.Groups()
|> Array.iteri (fun i g ->
    printfn "   Group %i: '%s'" i g.Value
    g.Captures()
    |> Array.iteri (fun i c ->
        printfn "      Capture %i: '%s'" i c.Value
        )
    )

// Match: This is one sentence.
//    Group 0: 'This is one sentence.'
//        Capture 0: 'This is one sentence.'
//    Group 1: 'sentence'
//        Capture 0: 'This '
//        Capture 1: 'is '
//        Capture 2: 'one '
//        Capture 3: 'sentence'
//    Group 2: 'sentence'
//        Capture 0: 'This'
//        Capture 1: 'is'
//        Capture 2: 'one'
//        Capture 3: 'sentence'
(**
### named groups ###
*)
let pattern' = @"\b(?<FirstWord>\w+)\s?((\w+)\s)*(?<LastWord>\w+)?(?<Punctuation>\p{Po})"
let input' = "The cow jumped over the moon."

let m' = FsRegEx.firstMatch pattern' input'

printfn "Named Groups:"

m'.Groups()
|> Array.iter (fun g -> printfn "   %s: '%s'" g.Name g.Value)

// Named Groups:
//    0: 'The cow jumped over the moon.'
//    1: 'the '
//    2: 'the'
//    FirstWord: 'The'
//    LastWord: 'moon'
//    Punctuation: '.'
(**
### capturing groups ###
*)
let regExpr = @"\b(\w+?)([\u00AE\u2122])"

let matches =
    "Microsoft® Office Professional Edition combines several office " +
        "productivity products, including Word, Excel®, Access®, Outlook®, " +
        "PowerPoint®, and several others. Some guidelines for creating " +
        "corporate documents using these productivity tools are available " +
        "from the documents created using Silverlight™ on the corporate " +
        "intranet site."
    |> FsRegEx.matches regExpr

matches
|> Array.iter (fun m ->
    let groups = m.Groups()
    printfn "%s: %s" groups.[2].Value groups.[1].Value)

printfn ""
printfn "Found %i trademarks or registered trademarks." matches.Length

// ®: Microsoft
// ®: Excel
// ®: Access
// ®: Outlook
// ®: PowerPoint
// ™: Silverlight
//
// Found 6 trademarks or registered trademarks.
(**
### group numbers and names ###
*)
open System

let pattern'' = @"\b((?<word>\w+)\s*)+(?<end>[.?!])"
let input'' = "This is a sentence. This is a second sentence."

let groupNumbers' = FsRegEx.groupNumbers pattern''
let groupNames = FsRegEx.groupNames pattern''

let m'' = FsRegEx.firstMatch pattern'' input''

printfn "Match: %s" m''.Value

Array.zip groupNumbers' groupNames
|> Array.iter (fun (n, name) ->
    let isNumber, _ = Int32.TryParse(name)
    printfn "   Group %i%s: '%s'"
        n
        (if (not isNumber) then
            " (" + name + ")"
         else String.Empty)
        (m'.Groups()).[n].Value
    )

// Match: This is a sentence.
//    Group 0: 'This is a sentence.'
//    Group 1: 'sentence'
//    Group 2 (word): 'sentence'
//    Group 3 (end): '.'


text:
(*** hide ***)
// This block of code is omitted in the generated HTML documentation. Use
// it to define helpers that you do not want to show in the documentation.
#I "../../bin/FsRegEx/net47"

(**
Capturing
=========

Capturing is a shortcut through matching to a named group or the group's captured value.
*)

(*** hide ***)
#r "FsRegEx.dll"

(**
### capture ###
*)
open FsRegEx

let groupNumberRegExp = @"COD(?<GroupNumber>[0-9]{3})END"
let groupNumberName = "GroupNumber"

capture groupNumberRegExp groupNumberName "COD123END"
|> printfn "%s"

// 123
(**
### capture group ###
*)
let group = captureGroup groupNumberRegExp groupNumberName "COD123END"

printfn "%s" group.Value

// 123


text:
(**
On the Futility of Email Regex Validation
=========================================

Using regular expressions to validate email addresses is notoriously error prone.
The complexity of the address format specification makes constructing a machine readable
grammar correctly discerning the difference between valid and invalid addresses hard.

Evolution of the Internet Message Format
----------------------------------------

[RFC 561](http://www.ietf.org/rfc/rfc0561.txt) Standardizing Network Mail Headers, September 5, 1973

[RFC 680](http://www.ietf.org/rfc/rfc0680.txt) Message Transmission Protocol, April 30, 1975

[RFC 724](http://www.ietf.org/rfc/rfc0724.txt) Proposed Official Standard for the Format of ARPA Network Messages, May 12, 1977

[RFC 733](http://www.ietf.org/rfc/rfc0733.txt) STANDARD FOR THE FORMAT OF ARPA NETWORK TEXT MESSAGES, November 21, 1977

Obsoletes:  RFC 561, RFC 680, RFC 724

[RFC 822](http://www.ietf.org/rfc/rfc0822.txt) STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES, August 13, 1982

Obsoletes:  RFC 733

[RFC 2822](http://www.ietf.org/rfc/rfc2822.txt) Internet Message Format, April 2001

Obsoletes: RFC 822

[RFC 5322](http://www.ietf.org/rfc/rfc5322.txt) Internet Message Format, October 2008

Obsoletes: RFC 2822

Testing Regular Expressions that Parse Email Addresses
------------------------------------------------------

The tests are derived from a suite of tests available at [RFC 822 Email Address Parser in PHP](http://code.iamcal.com/php/rfc822/),
which we have converted to run under FsRegEx. The PHP parser author claims a much better success rate than we were able to achieve with
any regular expression, although still not perfect. The site also provides more background on the "muddy" internet specification.

The suite consists of 280 validataion tests, which we converted to [Expecto](https://github.com/haf/expecto) tests
[here](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/RFC822.fs). Run with the
[Email.Tests](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/Email.Tests.fsproj) console app.

We chose some email parsing regular expressions available on the internet for testing:

[Simple](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/SimpleEmail.fs), from [emailregex.com](http://emailregex.com/).

[Moderate](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/ModerateEmail.fs), from [emailregex.com](http://emailregex.com/).

[Complex](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/ComplexEmail.fs), from [emailregex.com](http://emailregex.com/).

[Ultimate](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/Ultimate.fs), from
[Mail::RFC822::Address: regexp-based address validation](http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html). This regular expression was machine generated.

Note that [emailregex.com](http://emailregex.com/) headlines "Email Address Regular Expression That 99.99% Works". The site offers many different
email address regular expressions. Apparently we never found the one that works 99.99% on our test suite.

We also tested a much simpler (non-regular expression) checker, that simply tests string for a single at sign that is not wrapped in quotes and is not either
the first or last character in the string [here](http://github.com/jackfoxy/FsRegEx/blob/master/tests/Email.Tests/OneAtSign.fs).

Results
-------

**Simple**:

132 passed, 148 failed, 98 false negatives

**Moderate**:

190 passed, 90 failed, 72 false negatives

**Complex**:

172 passed, 108 failed, 60 false negatives

**Ultimate**:

170 passed, 110 failed, 11 false negatives

**One At Sign**:

162 passed, 118 failed, 1 false negative (test166)

Conclusion
----------

The evolution of the Internet Email Address specification and way it is documented make it impossible to settle on a completely deterministic grammar covering all cases.
With that in mind it is already impossible to make any complete parser, let alone one based on a regular expression. That being said, the test results do not reflect
the likelihood of meeting any of the 280 test cases in the wild.

Simply determining if there is a correctly placed at sign is perhaps the best filter for most practical purposes, given the importance of this criterion.
(Note that even our attempt at rejecting multiple at signs resulted in a false negative.)
*)


text:
<!DOCTYPE html>

    <meta charset="utf-8"/>
    <title>@Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta name="description" content="@Description"/>
    <meta name="author" content="@Properties["project-author"]"/>

    <link href="https://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet"/>

    <link type="text/css" rel="stylesheet" href="@Root/content/style.css" />

fsharp.org

github page

@Properties["project-name"]

          @RenderBody()

@Properties["project-name"]

Home page

Get Library via NuGet

Source Code on GitHub

License

Release Notes

Examples

Matching

Match Groups

Replace and Split

Capture

Documentation

API Reference

More...

On the Futility of Email Regex Validation

F# Verbal Expressions Tutorial


text:
namespace FsRegEx.Tests

open Expecto

module RunTests =

    [<EntryPoint>]
    let main args =

        Tests.runTestsWithArgs defaultConfig args FsRegEx.testMatches |> ignore
        Tests.runTestsWithArgs defaultConfig args FsRegEx.testReplace |> ignore
        Tests.runTestsWithArgs defaultConfig args FsRegEx.testSplit |> ignore
        Tests.runTestsWithArgs defaultConfig args FsRegEx.testCapture |> ignore
        Tests.runTestsWithArgs defaultConfig args FsRegEx.testGroup |> ignore
        Tests.runTestsWithArgs defaultConfig args FsRegEx.testIsMatch |> ignore

        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testAdd |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testCapture |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testEndOfLine |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testMatch |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testMaybe |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testMisc |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testMultiple |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testOr' |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testRange |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testRegexOptions |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testRepeatPrevious |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testSomethingBut |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testSomething |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testStartOfLine |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testThen' |> ignore
        Tests.runTestsWithArgs defaultConfig args VerbalExpressions.testTutorial |> ignore

        0


text:
FSharp.Core

Expecto
Expecto.BenchmarkDotNet
Expecto.FsCheck
Expecto.VisualStudio.TestAdapter


text:
namespace FsRegEx.Tests

open Expecto
open FsRegEx
open VerbalExpressions
open System
open System.Text.RegularExpressions

module VerbalExpressions =

    [<Tests>]
    let testAdd =
        testList "VerbalExpressions.Add " [
            testCase "special characters unescaped" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Multiline)
                    |> add "Line1\*+?"
                    |> isMatch "Line1\*+?Line2"
                Expect.isFalse value "Expected False"

            testCase "special characters escaped" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Multiline)
                    |> add "Line1\*\+\?"
                    |> isMatch "Line1*+?Line2"
                Expect.isTrue value "Expected True"

            testCase "Does Not Match GoogleCom With Escaped Dot" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Multiline)
                    |> add "\.com"
                    |> isMatch "http://www.googlecom/"
                Expect.isFalse value "Expected False"

            testCase "Does Not Match GoogleCom With Dot" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Multiline)
                    |> add ".com"
                    |> isMatch "http://www.googlecom/"
                Expect.isTrue value "Expected True"
        ]

    [<Tests>]
    let testCapture =
        let duplicateIdentifier =
            FsRegEx()
            |> beginCapture
            |> word
            |> endCapture
            |> whiteSpace
            |> beginCapture
            |> backReference 1
            |> endCapture

        let groupName =  "GroupNumber"

        let v =
            FsRegEx()
            |> add "COD"
            |> beginCaptureNamed groupName
            |> any "0-9"
            |> repeatPrevious 3
            |> endCapture
            |> add "END"

        testList "VerbalExpressions.Capture" [
            testCase "begin and end" <| fun () ->
                let value =
                    FsRegEx()
                    |> beginCapture
                    |> add "com"
                    |> or' "org"
                    |> endCapture
                    |> isMatch "((com)|(org))"
                Expect.isTrue value "Expected True"

            testCase "Duplicates Identifier string value" <| fun () ->
                let value =
                    duplicateIdentifier
                    |> toString
                Expect.isTrue (value = @"(\w+)\s(\1)") "Expected True"

            testCase "Duplicates Identifier Does Match" <| fun () ->
                Expect.isTrue (duplicateIdentifier.IsMatch "He said that that was the the correct answer." ) "Expected True"

            testCase "Duplicates Identifier Does Not Match" <| fun () ->
                Expect.isFalse (duplicateIdentifier.IsMatch "He said that was the correct answer." ) "Expected False"

            testCase "Group Names" <| fun () ->
                let value =
                    duplicateIdentifier
                    |> groupNames
                Expect.isTrue (value = [|"0"; "1"; "2"|]) "Expected True"

            testCase "Group Numbers" <| fun () ->
                let value =
                    duplicateIdentifier
                    |> groupNumbers
                Expect.isTrue (value = [|0; 1; 2|]) "Expected True"

            testCase "Group Name ToString" <| fun () ->
                let value =
                    v
                    |> toString
                Expect.isTrue (value = @"COD(?<GroupNumber>[0-9]{3})END") "Expected True"

            testCase "Group Name capture" <| fun () ->
                let value =
                    v
                    |> capture "COD123END" groupName
                Expect.isTrue (value = "123") "Expected True"

            testCase "Group Names2" <| fun () ->
                let value =
                    v
                    |> groupNames
                Expect.isTrue (value = [|"0"; "GroupNumber"|]) "Expected True"

            testCase "Nmaed Backreference" <| fun () ->
                let groupName = "char"
                let value =
                    FsRegEx()
                    |> add @"(?<char>\w)"
                    |> namedBackReference "char"
                    |> capture "trellis llama webbing dresser swagger" groupName
                Expect.isTrue (value = "l") "Expected True"

            testCase "Match has 1 capture" <| fun () ->
                let value =
                    FsRegEx()
                    |> word
                    |> matches "three words here"
                Expect.isTrue (value.[0].Captures().Length = 1) "Expected True"

            testCase "Match has 1 group" <| fun () ->
                let value =
                    FsRegEx()
                    |> word
                    |> matches "three words here"
                Expect.isTrue (value.[0].Groups().Length = 1) "Expected True"

            testCase "Group captures" <| fun () ->
                let testString = "This is a sentence. This is another sentence."
                let m =
                    FsRegEx()
                    |> add @"\b"
                    |> beginCapture
                    |> word
                    |> whiteSpace
                    |> add "*"
                    |> endCapture
                    |> add @"+\."
                    |> matches testString
                let g =
                    m.[0].Groups().[1]

                let testStringBegins =
                    ("", g.Captures())
                    ||> Array.fold (fun s t ->
                        if s.Length = 0 then t.Value
                        else (s + t.Value))

                Expect.isTrue (testString.StartsWith testStringBegins) "Expected True"
        ]

    [<Tests>]
    let testEndOfLine =
        testList "VerbalExpressions.EndOfLine" [
            testCase "Match Dot Com In End" <| fun () ->
                let value =
                    FsRegEx()
                    |> add ".com"
                    |> endOfLine
                    |> isMatch "www.google.com"
                Expect.isTrue value "Expected True"

            testCase "Does Not Match Forward Slash In End" <| fun () ->
                let value =
                    FsRegEx()
                    |> add ".com"
                    |> endOfLine
                    |> isMatch "www.google.com/"
                Expect.isFalse value "Expected False"
        ]

    [<Tests>]
    let testMatch =
        testList "VerbalExpressions.Match" [
            testCase "Returns Expected Number Of Matches" <| fun () ->
                let value =
                    FsRegEx()
                    |> word
                    |> matches "three words here"
                Expect.isTrue (value.Length = 3) "Expected True"

            testCase "No Matches returned" <| fun () ->
                let value =
                    FsRegEx()
                    |> word
                    |> matches "$#% )(*! {}"
                Expect.isTrue (value.Length = 0) "Expected True"
        ]

    [<Tests>]
    let testMaybe =
        let maybeSSL =
            FsRegEx()
            |> find "http"
            |> maybe "s"
            |> anythingBut " "
            |> then' "://"

        testList "VerbalExpressions.Maybe" [
            testCase "https present" <| fun () ->
                let value =
                    maybeSSL
                    |> isMatch "https://www.google.com"
                Expect.isTrue value "Expected True"

            testCase "http present" <| fun () ->
                let value =
                    maybeSSL
                    |> isMatch "http://www.google.com"
                Expect.isTrue value "Expected True"
        ]

    [<Tests>]
    let testMisc =
        testList "VerbalExpressions.Misc" [
            testCase "valid url" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> then' "http"
                    |> maybe "s"
                    |> then' "://"
                    |> maybe "www."
                    |> anythingBut " "
                    |> endOfLine
                    |> isMatch "https://www.google.com"
                Expect.isTrue value "Expected True"

            testCase "does match anything" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> anything
                    |> isMatch "'!@#$%¨&*()__+{}'"
                Expect.isTrue value "Expected True"

            testCase "lineBreak" <| fun () ->
                let value =
                    FsRegEx()
                    |> lineBreak
                    |> isMatch (sprintf "testing with %s line break" Environment.NewLine)
                Expect.isTrue value "Expected True"

            testCase "br" <| fun () ->
                let value =
                    FsRegEx()
                    |> br
                    |> isMatch (sprintf "testing with %s line break" Environment.NewLine)
                Expect.isTrue value "Expected True"

            testCase "tab" <| fun () ->
                let value =
                    FsRegEx()
                    |> tab
                    |> isMatch (sprintf "testing with %s tab" "\t")
                Expect.isTrue value "Expected True"
        ]

    [<Tests>]
    let testMultiple =
        let v =
            FsRegEx()
            |> add "y"
            |> multiple "aho"
            |> add "u"

        testList "VerbalExpressions.Multiple" [
            testCase "Match One Or Multiple Values Given ToString" <| fun () ->
                let value =
                    v
                    |> toString
                Expect.isTrue (value = "y(aho)+u") "Expected True"

            testCase "Match One Or Multiple Values Given" <| fun () ->
                let value =
                    v
                    |> isMatch "testesting 123 yahoahoahou another test"
                Expect.isTrue value "Expected True"
        ]

    [<Tests>]
    let testOr' =
        let v =
            FsRegEx()
            |> add "com"
            |> or' "org"

        let vAll =
            FsRegEx()
            |> or' "com"
            |> or' "org"

        testList "VerbalExpressions.Or'" [
            testCase "Matches Com And Org ToString" <| fun () ->
                let value =
                    v
                    |> toString
                Expect.isTrue (value = "com|org") "Expected True"

            testCase "Matches Com And Org" <| fun () ->
                let value1 =
                    v
                    |> isMatch "org"

                let value2 =
                    v
                    |> isMatch "com"
                Expect.isTrue (value1 && value2) "Expected True"

            testCase "Matches Com And Org all or ToString" <| fun () ->
                let value =
                    vAll
                    |> toString
                Expect.isTrue (value = "com|org") "Expected True"

            testCase "Matches Com And Org all or" <| fun () ->
                let value1 =
                    vAll
                    |> isMatch "org"
                let value2 =
                    vAll
                    |> isMatch "com"
                Expect.isTrue (value1 && value2) "Expected True"

            testCase "fsRegEx  email or url" <| fun () ->
                let value =
                    CommonFsRegEx.Email
                    |> fsRegExOrFsRegEx RegexOptions.None CommonFsRegEx.Url
                Expect.isTrue ((value.IsMatch "test@github.com") && (value.IsMatch "http://www.google.com")) "Expected True"

            testCase "fsRegEx  or regular expression" <| fun () ->
                let value =
                    CommonFsRegEx.Email
                    |> or' "org"
                Expect.isTrue ((value.IsMatch "test@github.com") && (value.IsMatch "org")) "Expected True"
        ]

    [<Tests>]
    let testRange =
        let vOdd =
            FsRegEx()
            |> range [1; 6; 7]

        let vEven =
            FsRegEx()
            |> range [|"1"; "6"; "a"; "c"|]

        testList "VerbalExpressions.Range" [
            testCase "Odd Number Of Items Append LastElement With Or Clause toString" <| fun () ->
                let value =
                    vOdd
                    |> toString
                Expect.isTrue (value = "[1-6]|7") "Expected True"

            testCase "Odd Number Of Items Append LastElement With Or Clause" <| fun () ->
                let value =
                    vOdd
                    |> isMatch "abcd7sdadqascdaswde"
                Expect.isTrue value "Expected True"

            testCase "Even Number Of Items toString" <| fun () ->
                let value =
                    vEven
                    |> toString
                Expect.isTrue (value = "[1-6][a-c]") "Expected True"

            testCase "Even Number Of Items isMatch" <| fun () ->
                let value =
                    vEven
                    |> isMatch "2b"
                Expect.isTrue value "Expected True"

            testCase "Has Only One Value  Throw Exception" <| fun () ->
                let value =
                    try
                        FsRegEx() |> range [1] |> ignore
                        ""
                    with e ->
                        e.Message
                Expect.isTrue (value = "one-element range\r\nParameter name: range") "Expected True"
        ]

    [<Tests>]
    let testRegexOptions =
        let testSingleLineUpperCase v =
            v
            |> add "First string"
            |> anything
            |> add "Second string"
            |> isMatch ("FIRST STRING" + Environment.NewLine + "SECOND STRING")

        let vSingleLine = FsRegEx(RegexOptions.Singleline)
        let vSingleLineIgnoreCase = new FsRegEx(RegexOptions.Singleline ||| RegexOptions.IgnoreCase)

        testList "VerbalExpressions.RegexOptions" [
            testCase "Multiline" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Multiline)
                    |> add "Line1"
                    |> endOfLine
                    |> isMatch "Line1\nLine2"
                Expect.isTrue value "Expected True"

            testCase "Multiline None" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "Line1"
                    |> endOfLine
                    |> isMatch @"Line1\nLine2"
                Expect.isFalse value "Expected False"

            testCase "IgnoreCase" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.IgnoreCase)
                    |> add "teststring"
                    |> endOfLine
                    |> isMatch "TESTSTRING"
                Expect.isTrue value "Expected True"

            testCase "IgnoreCase None" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "teststring"
                    |> endOfLine
                    |> isMatch "TESTSTRING"
                Expect.isFalse value "Expected False"

            testCase "Singleline" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Singleline)
                    |> add "First string"
                    |> anything
                    |> add "Second string"
                    |> isMatch ("First string" + Environment.NewLine + "Second string")
                Expect.isTrue value "Expected True"

            testCase "Singleline None" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "First string"
                    |> anything
                    |> add "Second string"
                    |> isMatch ("First string" + Environment.NewLine + "Second string")
                Expect.isFalse value "Expected False"

            testCase "Singleline IgnoreCase" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Singleline ||| RegexOptions.IgnoreCase)
                    |> testSingleLineUpperCase
                Expect.isTrue value "Expected True"

            testCase "Singleline IgnoreCase None" <| fun () ->
                let value =
                    FsRegEx(RegexOptions.Singleline)
                    |> testSingleLineUpperCase
                Expect.isFalse value "Expected False"

            testCase "resetRegexOptions single line" <| fun () ->
                let value =
                    vSingleLine
                    |> testSingleLineUpperCase
                Expect.isFalse value "Expected False"

            testCase "resetRegexOptions single line ignore case" <| fun () ->
                let value =
                    (Some (RegexOptions.Singleline ||| RegexOptions.IgnoreCase), vSingleLine)
                    ||> resetRegexOptions
                    |> testSingleLineUpperCase
                Expect.isTrue value "Expected True"

            testCase "identify options ignore case" <| fun () ->
                let value =
                    vSingleLineIgnoreCase.RegexOptions.HasFlag(RegexOptions.IgnoreCase)
                Expect.isTrue value "Expected True"

            testCase "identify options explicit capture" <| fun () ->
                let value =
                    vSingleLineIgnoreCase.RegexOptions.HasFlag(RegexOptions.ExplicitCapture)
                Expect.isFalse value "Expected False"
        ]

    [<Tests>]
    let testRepeatPrevious =
        testList "VerbalExpressions.RepeatPrevious" [
            testCase "toString" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "A"
                    |> repeatPrevious 3
                    |> toString
                Expect.isTrue (value = "A{3}") "Expected True"

            testCase "between 2 and 4" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "A"
                    |> repeatBetweenPrevious 3 4
                    |> toString
                Expect.isTrue (value = "A{3,4}") "Expected True"
        ]

    [<Tests>]
    let testSomethingBut =
        testList "VerbalExpressions.SomethingBut" [
            testCase "all characters in test" <| fun () ->
                let value =
                    FsRegEx()
                    |> somethingBut "Test"
                    |> isMatch "Test"
                Expect.isFalse value "Expected False"

            testCase "a characters out of test" <| fun () ->
                let value =
                    FsRegEx()
                    |> somethingBut "Test"
                    |> isMatch "TestA"
                Expect.isTrue value "Expected True"
        ]

    [<Tests>]
    let testSomething =
        testList "VerbalExpressions.Something" [
            testCase "something there" <| fun () ->
                let value =
                    FsRegEx()
                    |> something
                    |> isMatch "Test string should not be empty"
                Expect.isTrue value "Expected True"

            testCase "nothing there" <| fun () ->
                let value =
                    FsRegEx()
                    |> something
                    |> isMatch ""
                Expect.isFalse value "Expected False"
        ]

    [<Tests>]
    let testStartOfLine =
        testList "VerbalExpressions.StartOfLine" [
            testCase "line start" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> toString
                Expect.isTrue (value = "^") "Expected True"

            testCase "Prefix At Beginning Of The Expression" <| fun () ->
                let value =
                    FsRegEx()
                    |> add "test"
                    |> add "ing"
                    |> startOfLine
                    |> isMatch "testing1234"
                Expect.isTrue value "Expected True"

            testCase "match correct at start" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> then' "http"
                    |> maybe "www"
                    |> startOfLine
                    |> isMatch "http://xxx.test.xxx"
                Expect.isTrue value "Expected True"

            testCase "no match not at start" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> then' "http"
                    |> maybe "www"
                    |> startOfLine
                    |> isMatch "www.test.com"
                Expect.isFalse value "Expected False"
        ]

    [<Tests>]
    let testThen' =
        testList "VerbalExpressions.Then'" [
            testCase "Does Match" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> something
                    |> then' "github.com"
                    |> isMatch "test@github.com"
                Expect.isTrue value "Expected True"

            testCase "Does Not Match" <| fun () ->
                let value =
                    FsRegEx()
                    |> startOfLine
                    |> then' "github.com"
                    |> isMatch "test@"
                Expect.isFalse value "Expected False"

            testCase "special characters" <| fun () ->
                let value =
                    FsRegEx()
                    |> then' "*+?"
                    |> isMatch "Line1*+?Line2"
                Expect.isTrue value "Expected True"

            testCase "Does Not Match GoogleCom Without Dot" <| fun () ->
                let value =
                    FsRegEx()
                    |> then' ".com"
                    |> isMatch "http://www.googlecom/"
                Expect.isFalse value "Expected False"
        ]

    [<Tests>]
    let testTutorial =
        testList "VerbalExpressions.Tutorial" [
            testCase "Tutorial 1" <| fun () ->
                let v =
                    CommonFsRegEx.Email
                    |> fsRegExOrFsRegEx RegexOptions.None CommonFsRegEx.Url

                let foundEmail =
                    v
                    |> VerbalExpressions.isMatch "test@github.com"

                let foundUrl =
                    v
                    |> VerbalExpressions.isMatch "http://www.google.com"
                Expect.isTrue (foundEmail && foundUrl) "Expected True"

            testCase "Tutorial 2" <| fun () ->
                let foundFromGithub =
                    FsRegEx()
                    |> startOfLine
                    |> something
                    |> then' "github.com"
                    |> endOfLine
                    |> isMatch "test@github.com"
                Expect.isTrue foundFromGithub "Expected True"

            testCase "Tutorial 3" <| fun () ->
                let foundSomethingSpecial =
                    FsRegEx()
                    |> startOfLine
                    |> something
                    |> then' "*+?"
                    |> anything
                    |> isMatch "blah blah blah*+?yackety yack"
                Expect.isTrue foundSomethingSpecial "Expected True"

            testCase "Tutorial 4" <| fun () ->
                let foundSpecialInMultiline =
                    FsRegEx()
                    |> add @"phrase1\*\+\?"
                    |> anything
                    |> isMatch @"phrase1*+?RestOfLine\n"
                Expect.isTrue foundSpecialInMultiline "Expected True"

            testCase "Tutorial 5" <| fun () ->
                let n =
                    FsRegEx()
                    |> word
                    |> matches "three words here"
                Expect.isTrue (n.Length = 3) "Expected True"

            testCase "Tutorial 6" <| fun () ->
                let betterFormat =
                    FsRegEx()
                    |> add "\s+"
                    |> or' "whitespace"
                    |> replace "This     is   text with   far  too   much   whitespace" " "
                Expect.isTrue (betterFormat = "This is text with far too much  ") "Expected True"

            testCase "Tutorial 7" <| fun () ->
                let groupName =  "GroupNumber"

                let value =
                    FsRegEx()
                    |> add "COD"
                    |> beginCaptureNamed groupName
                    |> any "0-9"
                    |> repeatPrevious 3
                    |> endCapture
                    |> then' "END"
                    |> capture "COD123END" groupName
                Expect.isTrue (value = "123") "Expected True"

            testCase "Tutorial 8" <| fun () ->
                let value =
                    FsRegEx()
                    |> beginCaptureNamed "upper"
                    |> unicodeCategory  Unicode.UnicodeGeneralCategory.LetterUppercase
                    |> add "+"
                    |> endCapture
                    |> capture "some mixed case WORDS" "upper"
                Expect.isTrue (value = "WORDS") "Expected True"
        ]


text:
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net47</TargetFramework>
    <RootNamespace>FsRegEx.Tests</RootNamespace>
    <AssemblyName>FsRegEx.Tests</AssemblyName>
    <Name>FsRegEx.Tests</Name>
    <OutputType>Exe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="FsRegEx.fs" />
    <Compile Include="VerbalExpressions.fs" />
    <Compile Include="RunTests.fs" />
    <None Include="paket.references" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\src\FsRegEx\FsRegEx.fsproj"></ProjectReference>
  </ItemGroup>
  <Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>


text:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Expecto.VisualStudio.TestAdapter" version="5.0.1" />
</packages>