blob: 5a3e62162d04e2dd312b67b5227d10644d7b7dfc (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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
|