require "./parcom/*" module Parcom VERSION = "0.2.0" # A ParserFail exception should be raised by `Parser#parse` when # a parse attempt is unsuccessful. class ParserFail < Exception end # Provides a more convenient syntax for combining parsers via `Parser#and_then`. # The first and second arguments are types used for the parser's type. # The thirs argument is a string literal used for the name of the parser. # This is followed by any number of 2-tuples containing a variable name and # an expression resolving to a `Parser(t.class, _)`, whose success value will # be stored in the aformentioned variable. The `make` named argument is an # expression that resolves to a `Parser(t.class, u.class)`. This parser is # the final step of the parser chain. The `pure` named argument is an # expression that resolves to a `u.class`. This expression is passed to a # call to `Parser(t.class, u.class).pure`, which will be the final step # of the parser chain. # # Example: # ``` # any_word = Parser(Char, Char).satisfy(&.letter?).some.map(&.join) # ws = Parser(Char, Array(Char)).satisfy(&.whitespace?).many # two_of_same_word = parser_chain Char, String, "two words", # {word, any_word}, # {_, ws}, # make: Parser.token_sequence(word.chars).map(&.join) # # tokens = Tokens.from_string("foo foo") # result = two_of_same_word.parse(tokens) # result.value # => "foo" # # # The above definition of `two_of_same word` # # is an alternative way of doing this: # two_of_same_word = any_word.and_then do |word| # ws.and_then do |_| # Parser.token_sequence(word.chars).map(&.join) # end # end.named("two words") # ``` # # This macro is based on Haskell's do-notation. macro parser_chain(t, u, name, *steps, make = nil, pure = nil ) {% if make.nil? && pure.nil? %} raise ArgumentError.new("Expected exactly one of 'make' and 'pure', but got neither") {% elsif !make.nil? && !pure.nil? %} raise ArgumentError.new("Expected exactly one of 'make' and 'pure', but got both") {% else %} Parser({{t}}, {{u}}).new({{name}}) do |tokens| {% for tup, index in steps %} {{tup.last}}.and_then do |{{tup.first}}| {% end %} {% if !make.nil? %} {{make}} {% else %} Parser({{t}}, {{u}}).pure({{pure}}) {% end %} {% for _, _ in steps %} end {% end %} .parse(tokens) end {% end %} end end