-
-
Save mausch/1552406 to your computer and use it in GitHub Desktop.
F# regex active patterns proposal #2 for fsharpx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Extensions | |
open System.Text.RegularExpressions | |
///Regex extensions | |
module Regex = | |
let replaceWithAcc folder state input (rx: Regex) = | |
let acc = ref state | |
let evaluator (m: Match) = | |
let newState, result = folder !acc m | |
acc := newState | |
result | |
let replacement: string = rx.Replace(input, evaluator) | |
!acc, replacement | |
let replaceWith replacements input (rx: Regex) = | |
let folder replacements (matx: Match) = | |
match replacements with | |
| [] -> [], matx.Value | |
| x::xs -> xs, x | |
replaceWithAcc folder replacements input rx |> snd | |
type ActiveMatch = | |
{ | |
Match: Match | |
MatchValue: string | |
Groups: Group list | |
OptionalGroups: (Group option) list | |
GroupValues: string list | |
OptionalGroupValues: (string option) list | |
} | |
let tryMatchWithOptions flags pattern input = | |
match input with | |
| null -> None //Regex.Match will throw with null input, we return None instead | |
| _ -> | |
//using the static Regex.Match takes advantage of Regex caching | |
match Regex.Match(input, pattern, flags) with | |
| m when m.Success -> | |
//n.b. the head value of m.Groups is the match itself, which we discard | |
//n.b. if a group is optional and doesn't match, it's Value is "" | |
let groups = [for x in m.Groups -> x].Tail | |
let optionalGroups = groups |> List.map (fun x -> if x.Success then Some(x) else None) | |
let groupValues = groups |> List.map (fun x -> x.Value) | |
let optionalGroupValues = optionalGroups |> List.map (function None -> None | Some(x) -> Some(x.Value)) | |
Some({ Match=m | |
MatchValue=m.Value | |
Groups=groups | |
OptionalGroups=optionalGroups | |
GroupValues=groupValues | |
OptionalGroupValues=optionalGroupValues }) | |
| _ -> None | |
let inline tryMatch x = tryMatchWithOptions RegexOptions.None x | |
let inline (|Match|_|) x = tryMatchWithOptions x | |
module Compiled = | |
//note: if we need to support Silverlight and other reduced runtimes that don't support RegexOptions.Compiled, | |
//then it would be nice for us to detect that and fall back on RegexOptions.None here (compiling is just an | |
//optimization detail, doesn't change behavior of regex otherwise, so doing this fall back allows library | |
//users to share code between their full vs. silverlight applications more easily). | |
let (|Match|_|) = (|Match|_|) RegexOptions.Compiled | |
module Interpreted = | |
let (|Match|_|) = (|Match|_|) RegexOptions.None | |
module RegexTests = | |
let ``tryMatch success``() = | |
let m = "John Smith" |> Regex.tryMatch "(?i)^john .*" |> Option.map (fun m -> { m with MatchValue = m.MatchValue.ToLowerInvariant() }) | |
match m with | |
| Some { MatchValue = "john doe" } -> false | |
| Some { MatchValue = "john smith" } -> true | |
| _ -> false | |
let ``replace with accumulator``() = | |
let count, r = | |
Regex "%." | |
|> Regex.replaceWithAcc (fun s _ -> let s = s+1 in s, sprintf "@p%d" s) 0 "values (%d, %s)" | |
count = 2 && r = "values (@p1, @p2)" | |
let ``replace with fixed number of replacements``() = | |
let r = | |
Regex "%." | |
|> Regex.replaceWith ["@p1"; "@p2"] "values (%d, %s, %i)" | |
r = "values (@p1, @p2, %i)" | |
let ``MatchWithGroupValues null input OK`` = | |
match null with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { GroupValues=["john"; ""; "smith"] } -> | |
false | |
| _ -> true | |
//unlike in other version | |
let ``MatchWithGroupValues no groups IS OK`` = | |
try | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^john smith$" { MatchValue="john smith"; GroupValues=_ } -> true | |
| _ -> true | |
with | |
| :? System.ArgumentException -> false | |
let ``MatchWithGroupValues success`` = | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { MatchValue="john smith"; GroupValues=["john"; ""; "smith"] } -> | |
true | |
| _ -> false | |
let ``MatchWithGroups success`` = | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { MatchValue="john smith"; OptionalGroupValues=[Some "john"; None; Some "smith"] } -> | |
true | |
| _ -> false | |
let ``GroupValues success`` = | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { GroupValues=["john"; ""; "smith"] }-> | |
true | |
| _ -> false | |
let ``Groups success`` = | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { OptionalGroupValues=[Some "john"; None; Some "smith"] }-> | |
true | |
| _ -> false | |
let ``Match success`` = | |
match "john smith" with | |
| Regex.Match RegexOptions.None "^(\w*) (\w* )?(\w*)$" { MatchValue="john smith" } -> | |
true | |
| _ -> false | |
let ``Compiled.GroupValues success`` = | |
match "john smith" with | |
| Regex.Compiled.Match "^(\w*) (\w* )?(\w*)$" { GroupValues=["john"; ""; "smith"] } -> | |
true | |
| _ -> false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment