aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-17 23:32:54 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-17 23:32:54 +1300
commit72236e9bcaf7bff4a2bfbeb50ddf5d0261f74927 (patch)
tree231d0b059e1a19c8b096395d88d14b069711b763
parent982d239252b54297ff3558894038871f8d3a4175 (diff)
So far so good
-rw-r--r--spec/parcom_spec.cr83
-rw-r--r--src/parcom/parser.cr44
2 files changed, 117 insertions, 10 deletions
diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr
index 05fd12d..14e232e 100644
--- a/spec/parcom_spec.cr
+++ b/spec/parcom_spec.cr
@@ -80,7 +80,7 @@ describe Result do
end
describe Parser do
- describe "Parser.pure" do
+ describe "self.pure" do
v = 'a'
p = Parser(Char, Char).pure(v)
tokens = Tokens.from_string("____")
@@ -95,14 +95,14 @@ describe Parser do
end
end
- describe "Parser.flunk" do
+ describe "self.flunk" do
p = Parser(Char, Char).flunk
it "always fails" do
expect_raises(ParserFail) { p.parse(Tokens.from_string("arbitrary")) }
end
end
- describe "Parser.any_token" do
+ describe "self.any_token" do
p = Parser(Char, Nil).any_token
it "parses the first token in the input stream" do
@@ -119,7 +119,7 @@ describe Parser do
end
end
- describe "Parser.eof" do
+ describe "self.eof" do
p = Parser(Char, Nil).eof
it "succeeds with nil if the input stream is empty" do
@@ -136,7 +136,7 @@ describe Parser do
end
end
- describe "Parser.satisfy" do
+ describe "self.satisfy" do
p = Parser(Char, Char).satisfy { |c| c == 'a' }
it "succeeds if the token passes the predicate" do
@@ -153,6 +153,23 @@ describe Parser do
end
end
+ describe "self.token" do
+ a = Parser(Char, Char).token('a')
+
+ it "succeeds with the given token if it is at the start" do
+ tokens = Tokens.from_string("abc")
+ result = a.parse(tokens)
+
+ result.value.should eq('a')
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "fails if the given token is not present" do
+ tokens = Tokens.from_string("bbc")
+ expect_raises(ParserFail) { a.parse(tokens) }
+ end
+ end
+
describe "#assert" do
p = Parser(Char, Char).any_token.assert { |c| c == 'a' }
@@ -169,8 +186,58 @@ describe Parser do
expect_raises(ParserFail) { p.parse(tokens) }
end
end
-end
-#pending Basic do
-##end
+ describe "#map" do
+ p = Parser(Char, Char).any_token
+ p_mapped = p.map { |c| c.ord }
+ tokens = Tokens.from_string("abcd")
+
+ normal_result = p.parse(tokens)
+ mapped_result = p_mapped.parse(tokens)
+
+ it "parses the same amount from the input" do
+ mapped_result.tokens.should eq(normal_result.tokens)
+ end
+
+ it "changes the value of the result" do
+ mapped_result.value.should eq('a'.ord)
+ end
+ end
+
+ describe "#|" do
+ a = Parser(Char, Char).token('a')
+ b = Parser(Char, Char).token('b')
+ p = a | b
+
+ it "succeeds with the first value if both parsers succeed" do
+ p2 = a | Parser(Char, Char).pure('z')
+ tokens = Tokens.from_string("abcd")
+ result = p2.parse(tokens)
+
+ result.value.should eq('a')
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "succeeds with the first value if only the first parser succeeds" do
+ tokens = Tokens.from_string("abcd")
+ result = p.parse(tokens)
+
+ result.value.should eq('a')
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "succeeds with the second value if only the second parser succeeds" do
+ tokens = Tokens.from_string("bbcd")
+ result = p.parse(tokens)
+
+ result.value.should eq('b')
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "fails if both parsers fail" do
+ tokens = Tokens.from_string("cbcd")
+ expect_raises(ParserFail) { p.parse(tokens) }
+ end
+ end
+end
diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr
index adf0bba..3098080 100644
--- a/src/parcom/parser.cr
+++ b/src/parcom/parser.cr
@@ -80,9 +80,19 @@ module Parcom
satisfy(block)
end
+ # Creates a parser that parses the fist token in the input stream
+ # if that token matches the provided token.
+ def self.token(token : T) : Parser(T, T)
+ Parser(T, T).satisfy { |x| x == token }.named("Token <#{token}>")
+ end
+
+ # Creates a new parser from a `Proc`.
+ # The `Proc` should have the properties outlined above.
def initialize(@name : String, @f : Tokens(T) -> Result(T, U))
end
+ # Creates a new parser from a block.
+ # The block should have the properties outline above.
def initialize(@name : String, &block : Tokens(T) -> Result(T, U))
@f = block
end
@@ -97,16 +107,24 @@ module Parcom
self
end
+ # Tries to parse some kind of data from the given input stream.
+ # This method calls the `Proc` or block this parser object was
+ # initialized with.
def parse(tokens : Tokens(T)) : Result(T, U)
@f.call(tokens)
end
+ # Same as `#parse(Tokens(T)) : Result(T, U)`, but returns `nil`
+ # instead of raising an exception if parsing fails.
def parse?(tokens : Tokens(T)) : Result(T, U)?
parse(tokens)
rescue
nil
end
+ # Creates a new parser that is the same as the parser object it is
+ # called from, but tests the result against a given predicate.
+ # If the value does not pass the test, the parser fails.
def assert(f : U -> Bool) : Parser(T, U)
p = self
Parser(T, U).new("#{p.name} (assertion)") do |tokens|
@@ -118,20 +136,42 @@ module Parcom
end
end
+ # :ditto:
def assert(&block : U -> Bool) : Parser(T, U)
assert(block)
end
- def map(f : U -> T) : Parser(T, V) forall V
+ # Creates a new parser that is the same as the parser object it is
+ # called from, but transforms the result into something else via a
+ # given function.
+ # The function in question should not introduce side effects.
+ def map(f : U -> V) : Parser(T, V) forall V
p = self
Parser(T, V).new("#{p.name} (mapped)") do |tokens|
p.parse(tokens).map(f)
end
end
- def map(&block : U -> T) : Parser(T, V) forall V
+ # :ditto:
+ def map(&block : U -> V) : Parser(T, V) forall V
map(block)
end
+
+ def |(p2 : Parser(T, U)) : Parser(T, U)
+ p1 = self
+ Parser(T, U).new("#{p1.name} or #{p2.name}") do |tokens|
+ p1.parse(tokens)
+ rescue ParserFail
+ p2.parse(tokens)
+ end
+ end
+
+ # 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
end
end