require "./parser.cr" module Parcom # `Plus` is a parser that tries to parse with two different # parsers in succession and fails if either of the two parsers fails. # The return type of this parser is `{V, U}`, where `V` is the return # type of the first parser and `U` is the return type of the second. # # Example: # ``` # parse1 = Token.new('1') # parse2 = Token.new('2') # tokens = Tokens.from_string("12") # result = (parse1 + parse2).parse(tokens) # succeeds # result = (parse2 + parse1).parse(tokens) # fails, wrong order # ``` # # `Plus` parsers can be chained together, but the resulting return type # can become unweildly with too many combined parsers: # ``` # letter_a = Token.new('a') # a5 = letter_a + letter_a + letter_a + letter_a + letter_a # tokens = Tokens.from_string("aaaaa") # a5.parse(tokens) # succeeds # # `a5` has a return type of { { { {Char, Char}, Char}, Char}, Char} # ``` # # If you need to parse more than two things in this manner, # consider using `Many`, `Some`, `Sequence`, or `TokenSeq` instead. class Plus(T, V, U) < Parser(T, {V, U}) # Accepts the two parsers to use, in order. def initialize(@p1 : Parser(T, V), @p2 : Parser(T, U)) end # Tries to parse with the two given parsers, and returns # their results in a tuple if the both succeed. def parse(tokens : Tokens(T)) : Result(T, {V, U}) begin r1 = @p1.parse(tokens) rescue ex : ParserFail raise ParserFail.new("Plus (left): #{ex.message}") end begin r2 = @p2.parse(r1.tokens) rescue ex : ParserFail raise ParserFail.new("Plus (right): #{ex.message}") end Result.new(r2.tokens, {r1.value, r2.value}) end end end