FSharpVerbalExpressions


FsVerbalExpressions

The FsVerbalExpressions 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 FsVerbalExpressions library can be installed from NuGet:
PM> Install-Package FsVerbalExpressions

Introduction

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

  • FsRegEx - Module contains 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.

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

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

  • VerbEx - A composable F# wrapper type over System.Text.RegularExpressions.Regex for natural language regular expression composition.

  • Basic functional operations on VerbEx (exists, fold, foldBack, iter, and map), among other composable functions.

  • VerbEx 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. A future release may include matches returned as a lazy list.

Better F# Composition

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
open FsVerbalExpressions.FsRegEx

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)

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

Short-cut common procedures to a single function

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let groupNumberRegExp = @"COD(?<GroupNumber>[0-9]{3})END"
let groupNumberName = "GroupNumber"

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

// 123

Natural Language Regular Expressions

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
open FsVerbalExpressions
open System.Text.RegularExpressions
open VerbalExpression

let allXs = new VerbEx("x+")

let verbEx =
    CommonVerbEx.Email
    |> verbExOrVerbEx RegexOptions.None CommonVerbEx.Url
    |> verbExOrVerbEx RegexOptions.None allXs

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

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

let foundAllXs =
    verbEx
    |> isMatch "xxxxx"

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

// true
// true
// true

The library also provides the composable Verbal Expressions language for constructing simple regular expressions in a readable fashion.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let groupName =  "GroupNumber"
 
VerbEx()
|> 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 Verbex to retrieve the database parameter from a connection string:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
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 databaseFromVerbEx connectionString =

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

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

// true

This example shows the more verbose and desctriptive Verbal Expressions:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let databaseFromVerboseVerbEx connectionString =

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

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

// true

Documentation

FsVerbalExpressions comes with comprehensive API documentation and a tutorial.

  • API Reference contains documentation for all types, modules, and functions in the library.

  • F# Verbal Expressions Tutorial contains further explanation of FsVerbalExpressions natural language syntax and many more usage examples.

  • For enhanced debugging and API documentation experience, FsVerbalExpressions implements SourceLink

Versioning

FsVerbalExpressions adheres to 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

FsVerbalExpressions is hosted on GitHub where you can report 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 and Verbal Expressions

FsVerbalExpressions 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 in the GitHub repository.

namespace FsVerbalExpressions
module FsRegEx

from FsVerbalExpressions
val carRegExp : string

Full name: Index.carRegExp
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val matches : regularExpression:string -> input:string -> FsVerbalExpressions.FsMatch []

Full name: FsVerbalExpressions.FsRegEx.matches
module Array

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val m : FsVerbalExpressions.FsMatch
property FsVerbalExpressions.FsMatch.Value: string
val iter : action:('T -> unit) -> array:'T [] -> unit

Full name: Microsoft.FSharp.Collections.Array.iter
val x : string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val groupNumberRegExp : string

Full name: Index.groupNumberRegExp
val groupNumberName : string

Full name: Index.groupNumberName
val capture : regularExpression:string -> groupName:string -> input:string -> string

Full name: FsVerbalExpressions.FsRegEx.capture
namespace System
namespace System.Text
namespace System.Text.RegularExpressions
module VerbalExpression

from FsVerbalExpressions
val allXs : VerbEx

Full name: Index.allXs
Multiple items
type VerbEx =
  new : unit -> VerbEx
  new : regularExpression:string -> VerbEx
  new : regexOptions:RegexOptions -> VerbEx
  new : regularExpression:string * regexOptions:RegexOptions -> VerbEx
  new : regularExpression:string * regexOptions:RegexOptions * matchTimeout:TimeSpan -> VerbEx
  member Capture : input:string -> string -> string
  member GroupNameFromNumber : n:int -> string option
  member GroupNames : unit -> string array
  member GroupNumberFromName : groupName:string -> int option
  member GroupNumbers : unit -> int array
  ...

Full name: FsVerbalExpressions.VerbalExpression.VerbEx

--------------------
new : unit -> VerbEx
new : regexOptions:RegexOptions -> VerbEx
new : regularExpression:string -> VerbEx
new : regularExpression:string * regexOptions:RegexOptions -> VerbEx
new : regularExpression:string * regexOptions:RegexOptions * matchTimeout:System.TimeSpan -> VerbEx
val verbEx : VerbEx

Full name: Index.verbEx
module CommonVerbEx

from FsVerbalExpressions
val Email : VerbEx

Full name: FsVerbalExpressions.CommonVerbEx.Email
val verbExOrVerbEx : regexOptions:RegexOptions -> verbEx:VerbEx -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.verbExOrVerbEx
type RegexOptions =
  | None = 0
  | IgnoreCase = 1
  | Multiline = 2
  | ExplicitCapture = 4
  | Compiled = 8
  | Singleline = 16
  | IgnorePatternWhitespace = 32
  | RightToLeft = 64
  | ECMAScript = 256
  | CultureInvariant = 512

Full name: System.Text.RegularExpressions.RegexOptions
field RegexOptions.None = 0
val Url : VerbEx

Full name: FsVerbalExpressions.CommonVerbEx.Url
val foundEmail : bool

Full name: Index.foundEmail
val isMatch : value:string -> verbEx:VerbEx -> bool

Full name: FsVerbalExpressions.VerbalExpression.isMatch
val foundUrl : bool

Full name: Index.foundUrl
val foundAllXs : bool

Full name: Index.foundAllXs
val groupName : string

Full name: Index.groupName
val add : value:string -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.add
val beginCaptureNamed : groupName:string -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.beginCaptureNamed
val any : value:string -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.any
val repeatPrevious : n:int -> VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.repeatPrevious
val endCapture : verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.endCapture
val then' : value:string -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.then'
val capture : input:string -> groupName:string -> verbEx:VerbEx -> string

Full name: FsVerbalExpressions.VerbalExpression.capture
val TestConnString : string

Full name: Index.TestConnString
val databaseFromRegex : connectionString:string -> string

Full name: Index.databaseFromRegex
val connectionString : string
Multiple items
type Regex =
  new : pattern:string -> Regex + 1 overload
  member GetGroupNames : unit -> string[]
  member GetGroupNumbers : unit -> int[]
  member GroupNameFromNumber : i:int -> string
  member GroupNumberFromName : name:string -> int
  member IsMatch : input:string -> bool + 1 overload
  member Match : input:string -> Match + 2 overloads
  member Matches : input:string -> MatchCollection + 1 overload
  member Options : RegexOptions
  member Replace : input:string * replacement:string -> string + 5 overloads
  ...

Full name: System.Text.RegularExpressions.Regex

--------------------
Regex(pattern: string) : unit
Regex(pattern: string, options: RegexOptions) : unit
Regex.Match(input: string, pattern: string) : Match
Regex.Match(input: string, pattern: string, options: RegexOptions) : Match
val databaseFromVerbEx : connectionString:string -> string

Full name: Index.databaseFromVerbEx
val databaseFromVerboseVerbEx : connectionString:string -> string

Full name: Index.databaseFromVerboseVerbEx
val anyOf : value:string -> verbEx:VerbEx -> VerbEx

Full name: FsVerbalExpressions.VerbalExpression.anyOf
Fork me on GitHub