Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Functional Parsing with FParsec

236 views

Published on

Parsers can be a great tool for collecting metadata about a project or automating some of the processes in your workflow. Regardless of whether the parser is a script for gathering a subset of data or a full blown transpiler, relying on regular expressions, string methods, and indices to parse the information you need can get complicated.

FParsec is a parser combinator library written in F# and is an alternative means for parsing text into consumable data structures. Using functional programming techniques like function composition and pattern matching, FParsec provides a way for you to write parsers quickly, easily, and with a syntax that clearly communicates your intent.

This session offers an introduction to the FParsec library as well as the functional programming concepts needed to start using parser combinators. By working through an example parser from first principles towards a more complex use case, you will learn the tools and techniques provided by F# and FParsec for writing accurate and maintainable parsing applications.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Functional Parsing with FParsec

  1. 1. Functional Parsing with FParsec
  2. 2. A parser is an application or script that takes in data, usually in the form of text or a stream, and produces a data structure to represent syntactical relationship.
  3. 3. string input “(419)-423-2781” data structure output { AreaCode = 419 Prefix = 423 LineNumber = 2781 }
  4. 4. A parser combinator is a higher order function that takes in two or more parsers and combines them to make a new parser
  5. 5. string input “423-2781”
  6. 6. let pprefix = pint32
  7. 7. let pprefix = pint32 let plinenumber = pint32
  8. 8. let pdash = pchar ‘-’
  9. 9. let pprefix = pint32 let plinenumber = pint32 let pdash = pchar ‘-’
  10. 10. let pphonenumber = pprefix .>> pdash .>>. plinenumber
  11. 11. let pphonenumber = pprefix .>> pdash .>>. plinenumber
  12. 12. let pphonenumber = pprefix .>> pdash .>>. plinenumber
  13. 13. let pphonenumber = pprefix_and_pdash .>>. plinenumber
  14. 14. let pphonenumber = pprefix .>> pdash .>>. plinenumber
  15. 15. string input “423-2781” data structure output (423, 2781)
  16. 16. string input “(419)-423-2781” data structure output { AreaCode = 419 Prefix = 423 LineNumber = 2781 }
  17. 17. type PhoneNumber = { AreaCode : int Prefix : int LineNumber : int }
  18. 18. let createPhoneNumber a p l = { AreaCode = a Prefix = p LineNumber = l }
  19. 19. let popenparens = pchar ‘(‘ let pcloseparens = pchar ‘)’ let pareacode = popenparens >>. pint32 .>> pcloseparens
  20. 20. let pareacode = between popenparens pcloseparens pint32
  21. 21. let pdash = pchar ‘-’ let pareacode = between popenparens pcloseparens pint32 .>> pdash let pprefix = pint32 .>> pdash let plinenumber = pint32
  22. 22. let pphonenumber = pipe3 pareacode pprefix plinenumber createPhoneNumber
  23. 23. string input “(419)-423-2781” OR “419-423-2781” data structure output { AreaCode = 419 Prefix = 423 LineNumber = 2781 }
  24. 24. let A_and_B = pchar ‘A‘ .>>. pchar ‘B’
  25. 25. let A_and_B = pchar ‘A‘ .>>. pchar ‘B’ let A_or_B = pchar ‘A‘ <|> pchar ‘B’
  26. 26. let pareacode = between popenparens pcloseparens pint32 <|> pint32 .>> pdash
  27. 27. How do you actually run a parser against input?
  28. 28. type Parser<’TResult, ‘TUserState> = CharStream<‘TUserState> -> Reply<’TResult>
  29. 29. type Parser<’TResult, ‘TUserState> = CharStream<‘TUserState> -> Reply<’TResult> val run: Parser<’a, unit> -> string -> ParserResult<’a, unit>
  30. 30. type Parser<’TResult, ‘TUserState> = CharStream<‘TUserState> -> Reply<’TResult> val run: Parser<’a, unit> -> string -> ParserResult<’a, unit> type ParserResult<’Result, ‘UserState> = | Success of ’Result * ‘UserState * Position | Failure of string * ParserError * ‘UserState
  31. 31. let test p str = match run p str with | Success(result, state, pos) -> ... | Failure(errMsg, error, state) -> ...
  32. 32. let test p str = match run p str with | Success(result, _, _) -> printfn “Success: %A” result | Failure(errMsg, _, _) -> printfn “Failure: %s” errMsg
  33. 33. runParserOnString parser UserState.Default “Name for the stream” “Input string”
  34. 34. runParserOnStream parser UserState.Default “Name for the stream” stream Encoding.UTF8
  35. 35. How do you parse one of several possible types?
  36. 36. type State = OH | KY | IN | TN type StreetAddress = { Street : int * string City : string State : State Zipecode : int }
  37. 37. let createStreetAddress str c st zip = { Street = str City = c State = st Zipcode = zip }
  38. 38. let isStreetName c = isLetter c || isAnyOf “ .” c let pcomma = pchar ‘,‘ .>> spaces let pstreet = pint32 .>> spaces .>>. (manySatisfy isStreetName) .>> pcomma |> attempt
  39. 39. let pcity = manySatisfy isLetter .>> pcomma let pstate = choice [ stringReturn “OH” OH stringReturn “KY” KY stringReturn “IN” IN stringReturn “TN” TN ] .>> spaces let pzipcode = pint32
  40. 40. let paddress = pipe4 pstreet pcity pstate pzipcode createStreetAddress
  41. 41. string input “(419)-423-2781 129 Pineview Dr., Indianapolis, IN 40118 512 Kirk Dr., Columbus, OH 54778 7009 Elm St., Lexington, KY 89776 657-322-6578 (513)-277-9856”
  42. 42. type ContactInfo = | Phone of PhoneNumber | Address of StreetAddress
  43. 43. let pphonenumber = pipe3 pareacode pprefix plinenumber createPhoneNumber |>> Phone
  44. 44. let paddress = pipe4 pstreet pcity pstate pzipcode createStreetAddress |>> Address
  45. 45. let pcontactinfo = [ paddress .>> spaces pphonenumber .>> spaces ] |> choice |> many
  46. 46. Using UserState
  47. 47. runParserOnString parser UserState.Default “Name for the stream” “Input string”
  48. 48. type UserState = { PhoneCount: int AddressCount: int } with static member Default = { PhoneCount = 0 AddressCount = 0 }
  49. 49. let incrementPhoneNumber = let incrementPhone us = { us with PhoneCount = us.PhoneCount + 1 } updateUserState incrementPhone
  50. 50. let incrementPhoneNumber = let incrementPhone us = { us with PhoneCount = us.PhoneCount + 1 } updateUserState incrementPhone let incrementStreetAddress = let incrementAddress us = { us with AddressCount = us.AddressCount + 1 } updateUserState incrementPhone
  51. 51. let pcontactinfo = [ paddress .>> incrementStreetAddress .>> spaces phonenumber .>> incrementPhoneNumber .>> spaces ] |> choice |> many
  52. 52. Getting started with FParsec
  53. 53. ● Download the .NetCore SDK ● Create a directory for your parser scripts ● While in the directory, download Paket * *(Paket is an alternative solution to package management in dotnet)
  54. 54. ● Run the dotnet paket init command ● Add nuget FParsec to the paket.dependencies file created by paket ● Run dotnet paket install ● Run dotnet paket generate-load-scripts
  55. 55. Alternatively, you can add generate_load_scripts: true at the top of your paket.dependencies file before running dotnet paket install which will automatically generate the load scripts.
  56. 56. ● Create an F# script file in the parent directory of your project (ex. parsers.fsx) ● At the top of the file add the following code to load the dll references for FParsec and open the FParsec namespace #load @”.paketloadnetcoreapp3.0main.group.fsx” open FParsec

×