From 658876fa4db1bb8363a03135fd22ad61b25050b7 Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Fri, 17 Mar 2023 23:54:46 +1300 Subject: recover and optional --- spec/parcom_spec.cr | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/parcom/parser.cr | 21 ++++++++++++++++++--- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr index 14e232e..6acd1b2 100644 --- a/spec/parcom_spec.cr +++ b/spec/parcom_spec.cr @@ -239,5 +239,52 @@ describe Parser do expect_raises(ParserFail) { p.parse(tokens) } end end + + describe "#recover" do + p = Parser(Char, Char).token('a').recover('z') + + it "behaves as normal on success" do + tokens = Tokens.from_string("abcd") + result = p.parse(tokens) + + result.value.should eq('a') + result.tokens.should eq(tokens[1..]) + end + + tokens2 = Tokens.from_string("bbcd") + result2 = p.parse(tokens2) + + it "returns the default value instead of failing" do + result2.value.should eq('z') + end + + it "does not modify the input when recovering" do + result2.tokens.should eq(tokens2) + end + end + + describe "#optional" do + p = Parser(Char, Char).token('a').optional + + it "behaves as normal on success" do + tokens = Tokens.from_string("abcd") + result = p.parse(tokens) + + result.value.should eq('a') + result.tokens.should eq(tokens[1..]) + end + + tokens2 = Tokens.from_string("bbcd") + result2 = p.parse(tokens2) + + it "returns nil instead of failing" do + result2.value.should be_nil + end + + it "does not modify the input when recovering" do + result2.tokens.should eq(tokens2) + end + + end end diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr index 3098080..b78ab77 100644 --- a/src/parcom/parser.cr +++ b/src/parcom/parser.cr @@ -169,9 +169,24 @@ module Parcom # Creates a new parser that is the same as the parser object it is # called from, but it will return a default value without consuming # any input instead of failing. - #def recover(default : U) : Parser(T, U) - # nil - #end + def recover(default : U) : Parser(T, U) + r = Parser(T, U).pure(default) + (self | r).named("#{@name} (recover <#{default}>)") + end + + # Creates a new parser that is the same as the parser object it is + # called from, but it will return `nil` without consuming any input + # instead of returning. + def optional : Parser(T, U?) + p = self + Parser(T, U?).new("#{p.name} (optional)") do |tokens| + # We have to do it this way due to how type unions work. + r = p.parse?(tokens) + new_tokens = r.nil? ? tokens : r.tokens + new_value = r.nil? ? nil : r.value + Result.new(new_tokens, new_value) + end + end end end -- cgit v1.2.1