From 8835f1de0dbc6a059a42fad78d4acbb0984b94e2 Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Wed, 8 Mar 2023 20:03:26 +1300 Subject: Implement Plus --- spec/parcom_spec.cr | 50 +++++++++++++++++++++++++++++-- src/parcom.cr | 85 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr index b2a3a80..f35465c 100644 --- a/spec/parcom_spec.cr +++ b/spec/parcom_spec.cr @@ -261,10 +261,56 @@ describe Map do end end -pending Phrase do +describe Plus do + describe "#parse" do + tokens = TokenStream.from_string("testing") + p_t = Token(Char).new('t') + p_e = Token(Char).new('e') + p_at = Token(Char).new('@') + + it "fails if the first parser fails" do + p = p_at + p_e + expect_raises(ParserException) { p.parse(tokens) } + end + + it "fails if the second parser fails" do + p = p_t + p_at + expect_raises(ParserException) { p.parse(tokens) } + end + + it "fails if both parsers fail" do + p = p_at + p_at + expect_raises(ParserException) { p.parse(tokens) } + end + + it "succeeds if both parsers succeed" do + p = p_t + p_e + result = p.parse(tokens) + + result.tokens.should eq(tokens[2..]) + result.value[0].should eq('t') + result.value[1].should eq('e') + end + + it "evaluates parsers from left to right (left associative)" do + p_succeeds = p_t + p_e + p_fails = p_e + p_t + + p_succeeds.parse(tokens) # should not raise an exception + expect_raises(ParserException) { p_fails.parse(tokens) } + + p_s = Token(Char).new('s') + + r = (p_t + p_e + p_s).parse(tokens) # should not raise an exception + r.value.should be_a({ {Char, Char}, Char}) + + r = (p_t + (p_e + p_s)).parse(tokens) # should not raise an exception + r.value.should be_a({Char, {Char, Char} }) + end + end end -pending Plus do +pending Phrase do end pending Recover do diff --git a/src/parcom.cr b/src/parcom.cr index ef1aa2a..8d1d47b 100644 --- a/src/parcom.cr +++ b/src/parcom.cr @@ -59,15 +59,18 @@ module Parcom return nil end - def assert(f : V -> Bool) - Assert.new(self, f) + def |(other : Parser(T, V)) : Alt(T, V) + Alt.new(self, other) end - def |(other : Parser(T, V)) : Parser(T, V) - Alt.new(self, other) + def +(other : Parser(T, U)) : Plus(T, V, U) forall U + Plus.new(self, other) + end + + def assert(f : V -> Bool) + Assert.new(self, f) end - # TODO: Find a way to annotate this method's type def map(f : V -> U) : Map(T, V, U) forall U Map.new(self, f) end @@ -162,53 +165,61 @@ module Parcom Result.new(result.tokens, @f.call(result.value)) end end -end -class Phrase -end + class Phrase + end -class Plus -end + class Plus(T, V, U) < Parser(T, {V, U}) + def initialize(@p1 : Parser(T, V), @p2 : Parser(T, U)) + end -class Recover -end + def parse(tokens : TokenStream(T)) : Result(T, {V, U}) + r1 = @p1.parse(tokens) + r2 = @p2.parse(r1.tokens) + Result.new(r2.tokens, {r1.value, r2.value}) + end + end -class Optional -end + class Recover + end -class Tokens -end + class Optional + end -class Many -end + class Tokens + end -class Some -end + class Many + end -class Exactly -end + class Some + end -class AtLeast -end + class Exactly + end -class AtMost -end + class AtLeast + end -class Between -end + class AtMost + end -class StopAt -end + class Between + end -class StopAfter -end + class StopAt + end -class StopIf -end + class StopAfter + end -class FirstOf -end + class StopIf + end -class SepBy + class FirstOf + end + + class SepBy + end end -- cgit v1.2.1