aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spec/parcom_spec.cr50
-rw-r--r--src/parcom.cr85
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