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.

Monadic parsers in C++

52 views

Published on

Presentation for my talk "Monadic Parsers in C++" (C++ Russia 2019, Moscow)

Published in: Software
  • Be the first to comment

  • Be the first to like this

Monadic parsers in C++

  1. 1. Monadic parsers Alexander Granin graninas@gmail.com C++ Russia 2019, Moscow 1
  2. 2. С++ Siberia 2019, Keynote The Present and the Future of Functional Programming in C++ С++ Russia 2018 Functional Approach to Software Transactional Memory in C++ С++ Russia 2016 Functional “Life”: Parallel Cellular Automata and Comonads in C++ С++ Siberia 2015 Functional Microscope: Lenses in C++ С++ User Group 2014 Functional and Declarative Design in C++ 3
  3. 3. struct Presentation { Introduction Parsing and Backus-Naur form boost::spirit (qi) cpp_parsec_free Monadic parsers State & context-sensitive grammars Conclusion }; 4
  4. 4. age integer first name string last name string salary float employee {35, “Jane”, “Street”, 54321} struct Employee { int age; string firstName; string lastName; double salary; }; Parsing 5
  5. 5. employee ::= "employee" spaces "{" spaces age "," spaces firstName "," spaces lastName "," spaces salary spaces "}" Backus-Naur Form quotedString ::= '"' string '"' age ::= integer firstName ::= quotedString lastName ::= quotedString salary ::= double 6
  6. 6. boost::spirit (Qi) 7
  7. 7. quoted_string %= lexeme['"' >> +(char_ - '"') >> '"']; start %= lit("employee") >> '{' >> int_ >> ',' >> quoted_string >> ',' >> quoted_string >> ',' >> double_ >> '}'; } 8
  8. 8. quoted_string %= lexeme['"' >> +(char_ - '"') >> '"']; start %= lit("employee") >> '{' >> int_ >> ',' >> quoted_string >> ',' >> quoted_string >> ',' >> double_ >> '}'; } qi::rule<Iterator, string(), ascii::space_type> quoted_string; qi::rule<Iterator, employee(), ascii::space_type> start; 9
  9. 9. template <typename Iterator> struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type>{ employee_parser() : employee_parser::base_type(start) { quoted_string %= lexeme['"' >> +(char_ - '"') >> '"']; start %= lit("employee") >> '{' >> int_ >> ',' >> quoted_string >> ',' >> quoted_string >> ',' >> double_ >> '}'; } qi::rule<Iterator, string(), ascii::space_type> quoted_string; qi::rule<Iterator, employee(), ascii::space_type> start; }; 10
  10. 10. template <typename Iterator> struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type>{ employee_parser() : employee_parser::base_type(start) { quoted_string %= lexeme['"' >> +(char_ - '"') >> '"']; start %= lit("employee") >> '{' >> int_ >> ',' >> quoted_string >> ',' >> quoted_string >> ',' >> double_ >> '}'; } qi::rule<Iterator, string(), ascii::space_type> quoted_string; qi::rule<Iterator, employee(), ascii::space_type> start; }; 11
  11. 11. 12
  12. 12. cpp_parsec_free 13
  13. 13. ParserT<Employee> employee = app<Employee, int, string, string, double>( mkEmployee, prefix >> intP, comma >> quotedString, comma >> quotedString, comma >> (doubleP << postfix)); 14
  14. 14. ParserT<Employee> employee = app<Employee, int, string, string, double>( mkEmployee, prefix >> intP, comma >> quotedString, comma >> quotedString, comma >> (doubleP << postfix)); auto prefix = lit("employee") >> between(spaces, symbol('{')); auto postfix = between(spaces, symbol('}')); auto comma = symbol(',') >> spaces; auto mkEmployee = [](int a, const string& sn, const string& fn, double s) { return Employee {a, sn, fn, s}; }; 15
  15. 15. auto s = "employee {35, “Jane”, “Street”, 54321}"; ParserResult<Employee> result = parse(employee, s); 16
  16. 16. auto s = "employee {35, “Jane”, “Street”, 54321}"; ParserResult<Employee> result = parse(employee, s); if (isLeft(result)) { std::cout << "Parse error: " << getError(result).message; } else { Employee employee = getParsed(result); } 17
  17. 17. Parser combinators 18
  18. 18. ParserT<int> intP; // integer number ParserT<double> doubleP; // double precision float ParserT<Char> digit; // 0..9 ParserT<Char> lower; // a..z ParserT<Char> upper; // A..Z ParserT<Char> letter; // a..z A..Z ParserT<Char> alphaNum; // 0..9 a..z A..Z ParserT<Char> symbol(Char ch); // symbol ParserT<string> lit(const string&); // string Basic parsers 19
  19. 19. template <typename A, typename B> ParserT<A> fst(const ParserT<A>& p1, const ParserT<B>& p2); template <typename A, typename B> ParserT<B> snd(const ParserT<A>& p1, const ParserT<B>& p2); Parser combinators 20
  20. 20. template <typename A, typename B> ParserT<A> fst(const ParserT<A>& p1, const ParserT<B>& p2); template <typename A, typename B> ParserT<B> snd(const ParserT<A>& p1, const ParserT<B>& p2); // fst template <typename A, typename B> ParserT<A> operator<< (const ParserT<A>& l, const ParserT<B>& r); // snd template <typename A, typename B> ParserT<B> operator>> (const ParserT<A>& l, const ParserT<B>& r); Parser combinators 21
  21. 21. ParserT<string> helloWorld = snd(lit("Hello"), snd(symbol(' '), lit("world"))); ParserT<string> helloWorld = lit("Hello") >> symbol(' ') >> lit("world"); Parsing “Hello world” 22
  22. 22. template <typename A> ParserT<Many<A>> many(const ParserT<A>& p); // 0 or many template <typename A> ParserT<Many<A>> many1(const ParserT<A>& p); // 1 or many template <typename A, typename B> ParserT<B> between(const ParserT<A>& bracketP, // between const ParserT<B>& p); More combinators 23
  23. 23. template <typename A> ParserT<A> alt(const ParserT<A>& l, const ParserT<A>& r); template <typename A> ParserT<A> operator| (const ParserT<A>& l, const ParserT<A>& r); Alternative 24
  24. 24. template <typename A> ParserT<A> alt(const ParserT<A>& l, const ParserT<A>& r); template <typename A> ParserT<A> operator| (const ParserT<A>& l, const ParserT<A>& r); ParserT<string> boolean = alt(lit("true"), lit("false")); ParserT<string> currency = lit("EUR") | lit("USD") | lit("GBP"); Alternative 25
  25. 25. Monadic parsers 26
  26. 26. Functor fmap :: (A -> B) -> ParserT<A> -> ParserT<B> Applicative app :: (A -> B -> C) -> ParserT<A> -> ParserT<B> -> ParserT<C> Monad bind :: ParserT<A> -> (A -> ParserT<B>) 27
  27. 27. fmap :: (A -> B) -> ParserT<A> -> ParserT<B> template <typename A, typename B> ParserT<B> fmap(const function<B(A)>& f, const ParserT<A>& p); Functor 28
  28. 28. fmap :: (A -> B) -> ParserT<A> -> ParserT<B> template <typename A, typename B> ParserT<B> fmap(const function<B(A)>& f, const ParserT<A>& p); ParserT<string> strBoolean = lit("true") | lit("false"); Functor 29
  29. 29. fmap :: (A -> B) -> ParserT<A> -> ParserT<B> template <typename A, typename B> ParserT<B> fmap(const function<B(A)>& f, const ParserT<A>& p); ParserT<string> strBoolean = lit("true") | lit("false"); ParserT<bool> boolean = fmap<string, bool>(f, strBoolean); function<bool(string)> f = [](const string& s) { return s == "true"; }; Functor 30
  30. 30. Functor fmap :: (A -> B) -> ParserT<A> -> ParserT<B> Applicative app :: (A -> B -> C) -> ParserT<A> -> ParserT<B> -> ParserT<C> Monad bind :: ParserT<A> -> (A -> ParserT<B>) 31
  31. 31. app :: (A -> B -> C) -> ParserT<A> -> ParserT<B> -> ParserT<C> template <typename A, typename B, typename C> ParserT<C> app(const function<C(A, B)>& f, const ParserT<A>& a, const ParserT<B>& b); Applicative 32
  32. 32. app :: (A -> B -> C) -> ParserT<A> -> ParserT<B> -> ParserT<C> template <typename A, typename B, typename C> ParserT<C> app(const function<C(A, B)>& f, const ParserT<A>& a, const ParserT<B>& b); ParserT<string> helloUsername = app<string, string>( mkHello, letters, spaces >> letters); string mkHello = [](const string& firstName, const string& secondName) { return string("Hello, ") + firstName + " " + secondName + "!"; }; Applicative 33
  33. 33. ParserT<Value> value = app(mkValue, intP | doubleP); Applicative is combinable 34
  34. 34. ParserT<Value> value = app(mkValue, intP | doubleP); ParserT<BinOp> binOp = app(mkBinOp, symbol('+') | symbol('-') | symbol('*') | symbol('/')); Applicative is combinable 35
  35. 35. ParserT<Value> value = app(mkValue, intP | doubleP); ParserT<BinOp> binOp = app(mkBinOp, symbol('+') | symbol('-') | symbol('*') | symbol('/')); ParserT<FuncArgs> funcArgs = app(mkFuncArgs, expr, opt(symbol(',') >> funcArgs)); Applicative is combinable 36
  36. 36. ParserT<Value> value = app(mkValue, intP | doubleP); ParserT<BinOp> binOp = app(mkBinOp, symbol('+') | symbol('-') | symbol('*') | symbol('/')); ParserT<FuncArgs> funcArgs = app(mkFuncArgs, expr, opt(symbol(',') >> funcArgs)); ParserT<FuncCall> funcCall = app(mkFuncCall, identifier, between('(', ')', funcArgs)); Applicative is combinable 37
  37. 37. ParserT<Value> value = app(mkValue, intP | doubleP); ParserT<BinOp> binOp = app(mkBinOp, symbol('+') | symbol('-') | symbol('*') | symbol('/')); ParserT<FuncArgs> funcArgs = app(mkFuncArgs, expr, opt(symbol(',') >> funcArgs)); ParserT<FuncCall> funcCall = app(mkFuncCall, identifier, between('(', ')', funcArgs)); ParserT<BinExpr> binExpr = app(mkBinExpr, expr, binOp, expr); Applicative is combinable 38
  38. 38. ParserT<Value> value = app(mkValue, intP | doubleP); ParserT<BinOp> binOp = app(mkBinOp, symbol('+') | symbol('-') | symbol('*') | symbol('/')); ParserT<FuncArgs> funcArgs = app(mkFuncArgs, expr, opt(symbol(',') >> funcArgs)); ParserT<FuncCall> funcCall = app(mkFuncCall, identifier, between('(', ')', funcArgs)); ParserT<BinExpr> binExpr = app(mkBinExpr, expr, binOp, expr); ParserT<Expr> expr = app(mkExpr, binExpr | funcCall | value); Applicative is combinable 39
  39. 39. Functor fmap :: (A -> B) -> ParserT<A> -> ParserT<B> Applicative app :: (A -> B -> C) -> ParserT<A> -> ParserT<B> -> ParserT<B> Monad bind :: ParserT<A> -> (A -> ParserT<B>) 40
  40. 40. template <typename A> ParserT<A> pure(const A& a); template <typename A, typename B> ParserT<B> bind( const ParserT<A>& ma, const function<ParserT<B>(A)>& f); ParserT monad 41
  41. 41. ParserT<string> helloUsername = bind<string, string>(letters, [=](auto firstName) { return bind<string, string>(spaces, [=](auto) { return bind<string, string>(letters, [=](auto lastName) { return pure<string>(mkHello(firstName, lastName)); }); }); }); ParserT<string> helloUsername = bind<string, string>(letters, [=](auto firstName) { return bind<string, string>(spaces, [=](auto) { return bind<string, string>(letters, [=](auto lastName) { return pure<string>(mkHello(firstName, lastName)); }); }); }); Monadic binding 42
  42. 42. ParserT<string> helloUsername = bind<string, string>(letters, [=](auto firstName) { return bind<string, string>(spaces, [=](auto) { return bind<string, string>(letters, [=](auto lastName) { return pure<string>(mkHello(firstName, lastName)); }); }); }); ParserT<string> helloUsername = bind<string, string>(letters, [=](auto firstName) { return bind<string, string>(spaces, [=](auto) { return bind<string, string>(letters, [=](auto lastName) { return pure<string>(mkHello(firstName, lastName)); }); }); }); Monadic binding, reformatted 43
  43. 43. ParserT<string> helloUsername = do { auto firstName <- letters; auto _ <- spaces; auto lastName <- letters; return pure<string>(mkHello(firstName, lastName)); }; Dear Santa, I was a good boy last year. Can I have?.. 44
  44. 44. State & context-sensitive grammars 45
  45. 45. age integer first name string last name string salary float employee {35, “Jane”, “Street”, 54321} address {35, “Jane”, “Street”, 54321} house integer title string post index integer type string 46
  46. 46. struct Employee { int age; string firstName; string lastName; double salary; }; 47 struct Address { int house; string title; string name; int postIndex; }; Algebraic Data Type
  47. 47. struct Employee { int age; string firstName; string lastName; double salary; }; 48 struct Address { int house; string title; string name; int postIndex; }; using LogEntry = std::variant<Employee, Address>; Algebraic Data Type
  48. 48. struct Employee { int age; string firstName; string lastName; double salary; }; 49 struct Address { int house; string title; string name; int postIndex; }; using LogEntry = std::variant<Employee, Address>; function<LogEntry(int a, const string& fn, const string& ln, double s)> mkEmployeeEntry; function<LogEntry(int h, const string& ttl, const string& n, int idx)> mkAddressEntry; Algebraic Data Type
  49. 49. struct Config { string currentEntry; }; ParserT<Config, string> logToken = lit("employee") | lit("address"); ParserT<Config, Unit> setEntry = bind<string, Unit>( logToken, [](const string& s) { return putSt(Config { s }); }); Putting state 50
  50. 50. struct Config { string currentEntry; }; ParserT<Config, string> logToken = lit("employee") | lit("address"); ParserT<Config, Unit> setEntry = bind<string, Unit>( logToken, [](const string& s) { return putSt(Config { s }); }); Putting state 51
  51. 51. ParserT<Config, LogEntry> logEntry = bind<Config, LogEntry>(getSt, [=](Config cfg) { if (cfg.currentEntry == "employee") return fmap(mkEmployeeEntry, employeeParser); return fmap(mkAddressEntry, addressParser); }); Getting state 52
  52. 52. ParserT<Config, LogEntry> logEntry = bind<Config, LogEntry>(getSt, [=](Config cfg) { if (cfg.currentEntry == "employee") return fmap(mkEmployeeEntry, employeeParser); return fmap(mkAddressEntry, addressParser); }); Getting state 53
  53. 53. Conclusion 54
  54. 54. Monadic parser combinators ● Monad: do-notation, bind, when, unless, sequence, mapM, guard ● Applicative: app, pure ● Alternative: alt, operator| ● Functor: fmap ● Time travelling: lookForward, lookBackward ● State handling: getSt, putSt ● Error handling: tryP, safeP, onFail, onSuccess, failWith 55
  55. 55. 56 GitHub List: Functional Programming in C++ https://github.com/graninas/cpp_functional_programming�� ● Books ● Articles ● Papers ● Talks ● QA ● Libraries ● Showcase projects
  56. 56. Thanks for watching! Alexander Granin graninas@gmail.com Twitter: @graninas C++ Russia 2019, Moscow 57

×