aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spec/parcom_spec.cr28
-rw-r--r--src/parcom/parser.cr16
2 files changed, 40 insertions, 4 deletions
diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr
index af95953..ba17584 100644
--- a/spec/parcom_spec.cr
+++ b/spec/parcom_spec.cr
@@ -651,7 +651,33 @@ describe Parser do
end
end
- # TODO: sep_by
+ describe "#sep_by" do
+ a = Parser.token('a')
+ b = Parser.token('b')
+ p = a.sep_by(b)
+
+ it "fails if it cannot parse the first element" do
+ tokens = Tokens.from_string("cba")
+ expect_raises(ParserFail) { p.parse(tokens) }
+ end
+
+ it "will parse a single element without separators" do
+ tokens = Tokens.from_string("aca")
+ result = p.parse(tokens)
+
+ result.value.should eq(['a'])
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "will parse one element, and as many others separated by sep" do
+ tokens = Tokens.from_string("abababaa")
+ result = p.parse(tokens)
+
+ result.value.should eq(['a', 'a', 'a', 'a'])
+ result.tokens.should eq(tokens[7..])
+ end
+ end
+
# TODO: phrase
# TODO: peek
end
diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr
index d9919e1..d91ac84 100644
--- a/src/parcom/parser.cr
+++ b/src/parcom/parser.cr
@@ -255,7 +255,7 @@ module Parcom
end
# Same as `#+`, but discards the second parser's result.
- def <<(p2 : Parser(T, V)) : Parser(T, U) forall V
+ def <<(p2 : Parser(T, _)) : Parser(T, U)
p1 = self
(p1 + p2).map(&.first).named("#{p1.name} << #{p2.name}")
end
@@ -267,7 +267,7 @@ module Parcom
end
# Creates a new parser from `self` that parses with `self` as many times
- # as possible. If it never succeeds, returns an empty list.
+ # as possible. Returns an empty list if it never succeeds.
def many : Parser(T, Array(U))
p = self
Parser(T, Array(U)).new("Many: #{p.name}") do |tokens|
@@ -371,7 +371,6 @@ module Parcom
min = n < m ? n : m
extra = (n - m).abs
- p = self
(exactly(min) + at_most(extra)).map do |t|
t.first + t.last
end.named("Between #{min}-#{extra} <#{@name}>")
@@ -412,6 +411,17 @@ module Parcom
at_least(r.begin).named("Between #{r.begin}-infinity <#{@name}> (from inifnite range)")
end
+
+ # Creates a new parser from `self` and another parser that tries to
+ # parse with `self` repeatedly, alternating with `sep` and discarding
+ # the results of `sep`. This parser will fail if it cannot parse the first
+ # instance of `self`. It will succeed if it can parse an instance of `self`
+ # that is not followed by any `sep` instances.
+ def sep_by(sep : Parser(T, _)) : Parser(T, Array(U))
+ (self + (sep >> self).many).map do |tup|
+ tup.last.unshift(tup.first)
+ end.named("<#{@name}> sep by <#{sep.name}>")
+ end
end
end