require "./parser.cr" module Parcom # `Assert` is a `Parser` that runs another `Parser` and then # performs a test on its result. The parser will then either fail if # the result does not pass the test, or forward it on if it does. # # Example: # ``` # letter = Assert.new(AnyToken(Char).new) { |x| x.letter? } # non_letter = Assert.new(AnyToken(Char).new) { |x| !x.letter? } # input = Tokens.from_string("hi") # letter.parse(input) # this succeeds # non_letter.parse(input) # this fails # ``` # # `Assert` instance can also be created by calling # `Parser#assert` on any parser: # ``` # # This parser is identical to the above example. # letter = AnyToken.new(Char).assert { |x| x.letter? } # ``` class Assert(T, V) < Parser(T, V) # Accepts the parser to run and a `Bool`-retuning block # containing the test to perform on the other parser's result. def initialize(@p : Parser(T, V), &block : V -> Bool) @f = block end # Runs the parser it was initialized with, and returns that parser's # result if it passes the provided test. Raises `ParserFail` otherwise. def parse(tokens : Tokens(T)) : Result(T, V) result = @p.parse(tokens) rescue ex : ParserFail raise ParserFail.new("Assert (pre-assertion): #{ex.message}") else unless @f.call(result.value) raise ParserFail.new("Assert: predicate failed for <#{result.value}>") end return result end end end