From 9dc06838e3c78e4a77731cab6bc773846eafce99 Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Thu, 9 Mar 2023 20:48:34 +1300 Subject: Implement AtLeast and AtMost --- spec/parcom_spec.cr | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/parcom.cr | 24 ++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr index 713445f..6301c2e 100644 --- a/spec/parcom_spec.cr +++ b/spec/parcom_spec.cr @@ -531,10 +531,57 @@ describe Exactly do end end -pending AtLeast do +describe AtLeast do + letter_a = Token.new('a') + tokens = TokenStream.from_string("aaaabcd") + + describe "#parse" do + it "fails if there are not enough matching tokens to parse" do + p = AtLeast.new(5, letter_a) + expect_raises(ParserException) { p.parse(tokens) } + end + + it "parses n or more times with the given parser" do + p0 = AtLeast.new(0, letter_a) + p2 = AtLeast.new(2, letter_a) + p4 = AtLeast.new(4, letter_a) + + result0 = p0.parse(tokens) + result2 = p2.parse(tokens) + result4 = p4.parse(tokens) + + result0.value.should eq("aaaa".chars) + result0.tokens.should eq(tokens[4..]) + + result2.should eq(result0) + result4.should eq(result0) + end + end end -pending AtMost do +describe AtMost do + letter_a = Token.new('a') + tokens = TokenStream.from_string("aaaabcd") + + describe "#parse" do + it "does not parse more than n times" do + p0 = AtMost.new(0, letter_a) + p2 = AtMost.new(2, letter_a) + p6 = AtMost.new(6, letter_a) + + r0 = p0.parse(tokens) + r0.value.empty?.should be_true + r0.tokens.should eq(tokens) + + r2 = p2.parse(tokens) + r2.value.should eq("aa".chars) + r2.tokens.should eq(tokens[2..]) + + r6 = p6.parse(tokens) + r6.value.should eq("aaaa".chars) + r6.tokens.should eq(tokens[4..]) + end + end end pending Between do diff --git a/src/parcom.cr b/src/parcom.cr index c7f4012..0a97e51 100644 --- a/src/parcom.cr +++ b/src/parcom.cr @@ -308,10 +308,30 @@ module Parcom end end - class AtLeast + class AtLeast(T, V) < Parser(T, Array(V)) + @p : Map(T, {Array(V), Array(V)}, Array(V)) + + def initialize(i : Int, p : Parser(T, V)) + @p = (Exactly.new(i, p) + Many.new(p)).map do |tup| + tup[0] + tup[1] + end + end + + def parse(tokens : TokenStream(T)) : Result(T, Array(V)) + @p.parse(tokens) + end end - class AtMost + class AtMost(T, V) < Parser(T, Array(V)) + @p : Map(T, Array(V?), Array(V)) + + def initialize(i : Int, p : Parser(T, V)) + @p = Exactly.new(i, Optional.new(p)).map(&.compact) + end + + def parse(tokens : TokenStream(T)) : Result(T, Array(V)) + @p.parse(tokens) + end end class Between -- cgit v1.2.1