aboutsummaryrefslogtreecommitdiff
path: root/src/parcom.cr
diff options
context:
space:
mode:
Diffstat (limited to 'src/parcom.cr')
-rw-r--r--src/parcom.cr56
1 files changed, 43 insertions, 13 deletions
diff --git a/src/parcom.cr b/src/parcom.cr
index ddb2e50..d12b011 100644
--- a/src/parcom.cr
+++ b/src/parcom.cr
@@ -5,17 +5,6 @@ module Parcom
# A ParserFail exception should be raised by `Parser#parse` when
# a parse attempt is unsuccessful.
- # Raising this exception in the `#parse` method of a Parser "Foo"
- # usually follows this pattern to allow for error tracing:
- #
- # ```
- # class Foo(T, V) < Parser(T, V)
- # def parse(tokens : Tokens(T)) : Result(T, V)
- # helper.parse(tokens)
- # rescue ex : ParserFail
- # raise ParserFail.new("Foo: #{ex.message}")
- # end
- # ```
class ParserFail < Exception
end
@@ -74,10 +63,51 @@ module Parcom
# This is used instead of a `Tuple` or `NamedTuple` because:
# 1. This is more idiomatic than a `Tuple`.
# 2. Crystal does not support generic named tuples.
- struct Result(T, V)
+ struct Result(T, U)
getter tokens, value
- def initialize(@tokens : Tokens(T), @value : V)
+ def initialize(@tokens : Tokens(T), @value : U)
+ end
+ end
+
+ class Parser(T, U)
+ getter name
+
+ def initialize(@name : String, @f : Tokens(T) -> Result(T, U))
+ end
+
+ def initialize(@name : String, &block : Tokens(T) -> Result(T, U))
+ @f = block
+ end
+
+ def named(name : String) : self
+ @name = name
+ self
+ end
+
+ def parse(tokens : Tokens(T)) : Result(T, U)
+ @f.call(tokens)
+ end
+
+ def parse(tokens : Tokens(T)) : Result(T, U)?
+ parse(tokens)
+ rescue
+ nil
+ end
+
+ def assert(f : U -> Bool) : Parser(T, U)
+ p = self
+ Parser.new("#{p.name} assertion") do |tokens|
+ result = p.parse(tokens)
+ unless f.call(r.value)
+ raise ParserFail.new("Assertion failed for value #{r.value}")
+ end
+ result
+ end
+ end
+
+ def assert(&block : U -> Bool) : Parser(T, U)
+ assert(block)
end
end
end