From 4732d4312a3df624c4ea3d861279ab186788979f Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Sun, 19 Mar 2023 23:21:36 +1300 Subject: sep_by, and some small tweaks --- spec/parcom_spec.cr | 28 +++++++++++++++++++++++++++- src/parcom/parser.cr | 16 +++++++++++++--- 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 -- cgit v1.2.1