aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spec/parcom_spec.cr65
-rw-r--r--src/parcom.cr83
-rw-r--r--src/parcom/basic.cr65
-rw-r--r--src/parcom/parser.cr137
4 files changed, 169 insertions, 181 deletions
diff --git a/spec/parcom_spec.cr b/spec/parcom_spec.cr
index ceb7de8..05fd12d 100644
--- a/spec/parcom_spec.cr
+++ b/spec/parcom_spec.cr
@@ -79,29 +79,10 @@ describe Result do
end
end
-pending Parser do
- describe "#assert" do
- p = Basic(Char, Char).any_token.assert { |c| c == 'a' }
-
- it "succeeds if the parser succeeds and if the predicate passes" do
- tokens = Tokens.from_string("abcd")
- result = p.parse(tokens)
-
- result.value.should eq(tokens[0])
- result.tokens.should eq(tokens[1..])
- end
-
- it "fails if the predicate fails" do
- tokens = Tokens.from_string("bbcd")
- expect_raises(ParserFail) { p.parse(tokens) }
- end
- end
-end
-
-describe Basic do
- describe "Basic.pure" do
+describe Parser do
+ describe "Parser.pure" do
v = 'a'
- p = Basic(Char, Char).pure(v)
+ p = Parser(Char, Char).pure(v)
tokens = Tokens.from_string("____")
result = p.parse(tokens)
@@ -114,15 +95,15 @@ describe Basic do
end
end
- describe "Basic.flunk" do
- p = Basic(Char, Char).flunk
+ describe "Parser.flunk" do
+ p = Parser(Char, Char).flunk
it "always fails" do
expect_raises(ParserFail) { p.parse(Tokens.from_string("arbitrary")) }
end
end
- describe "Basic.any_token" do
- p = Basic(Char, Nil).any_token
+ describe "Parser.any_token" do
+ p = Parser(Char, Nil).any_token
it "parses the first token in the input stream" do
tokens = Tokens.from_string("abcd")
@@ -138,8 +119,8 @@ describe Basic do
end
end
- describe "Basic.eof" do
- p = Basic(Char, Nil).eof
+ describe "Parser.eof" do
+ p = Parser(Char, Nil).eof
it "succeeds with nil if the input stream is empty" do
tokens = Tokens.from_string("")
@@ -155,14 +136,12 @@ describe Basic do
end
end
- # TODO: the type checker hates me
- describe "Basic.satisfy" do
- #p = Basic.satisfy(Char) { |c| c == 'a' }
- p = Basic(Char, Char).satisfy(->(c : Char) { c == 'a' })
+ describe "Parser.satisfy" do
+ p = Parser(Char, Char).satisfy { |c| c == 'a' }
it "succeeds if the token passes the predicate" do
tokens = Tokens.from_string("abcd")
- result = p.parse(tokens).as_a(Result(Char, Char))
+ result = p.parse(tokens)
result.value.should eq(tokens[0])
result.tokens.should eq(tokens[1..])
@@ -173,5 +152,25 @@ describe Basic do
expect_raises(ParserFail) { p.parse(tokens) }
end
end
+
+ describe "#assert" do
+ p = Parser(Char, Char).any_token.assert { |c| c == 'a' }
+
+ it "succeeds if the parser succeeds and if the predicate passes" do
+ tokens = Tokens.from_string("abcd")
+ result = p.parse(tokens)
+
+ result.value.should eq(tokens[0])
+ result.tokens.should eq(tokens[1..])
+ end
+
+ it "fails if the predicate fails" do
+ tokens = Tokens.from_string("bbcd")
+ expect_raises(ParserFail) { p.parse(tokens) }
+ end
+ end
end
+#pending Basic do
+##end
+
diff --git a/src/parcom.cr b/src/parcom.cr
index ffa5654..8387752 100644
--- a/src/parcom.cr
+++ b/src/parcom.cr
@@ -77,88 +77,5 @@ module Parcom
map(block)
end
end
-
- # A parser defines a process of extracting some sort of data
- # from a stream of tokens.
- # `Parser` objects wrap a function/block that accepts a `Tokens` object,
- # and either returns a `Result`, or raises a `ParserFail`.
- # This struct also defines a handful of methods for modifying a parser's
- # behavior.
- #
- # The function that is wrapped by a parser should:
- # 1. Have the type `Tokens(T) -> Result(T, U)`
- # 1. Indicate failure by raising a `ParserFail`
- # 1. Not introduce side-effects
- #
- # Instead of inheriting from `Parser`, custom parsers should be
- # defined using a method that generates the parser directly:
- # ```
- # struct Foo(T, U) < Parser(T, Array(U))
- # def parse(tokens)
- # # This will cause headaches
- # end
- # end
- #
- # # Do this instead:
- # def foo(t : T.class, u : U.class, p : Parser(T, U)) : Parser(T, U) forall T, U
- # Parser(T, U).new("Foo") { |tokens| "your code here" }
- # end
- # ```
- struct Parser(T, U)
- getter name
-
- def initialize(@name : String, @f : Tokens(T) -> Result(T, U))
- end
-
- def initialize(@name : String, &block : Tokens(T) -> Result(T, U))
- @f = block
- end
-
- # Changes the `name` property and returns `self`.
- # This should be used to specify a custom name:
- # ```
- # a = Basic.token(Char, 'a').named("letter a")
- # ```
- def named(name : String) : self
- @name = name
- self
- end
-
- def parse(tokens : Tokens(T)) : Result(T, U)
- @f.call(tokens)
- end
-
- def parse?(tokens : Tokens(T)) : Result(T, U)?
- parse(tokens)
- rescue
- nil
- end
-
- def assert(f : U -> Bool) : Parser(T, U)
- p = self
- Parser.new("#{p.name} (assertion)") do |tokens|
- result = p.parse(tokens)
- unless f.call(r.value)
- raise ParserFail.new("Assertion failed for value #{r.value}")
- end
- result
- end
- end
-
- def assert(&block : U -> Bool) : Parser(T, U)
- assert(block)
- end
-
- def map(f : U -> T) : 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
- map(block)
- end
- end
end
diff --git a/src/parcom/basic.cr b/src/parcom/basic.cr
deleted file mode 100644
index 4f01021..0000000
--- a/src/parcom/basic.cr
+++ /dev/null
@@ -1,65 +0,0 @@
-require "../parcom.cr"
-
-module Parcom
- module Basic(T, U)
- # A collection of building-block parsers that can be combined into more
- # powerful parsers. If you can't find a parser here that does what you're
- # looking for, it may be defined as a method of the `Parser` struct itself.
- extend self
-
- # Creates a parser that always succeeds with the given value.
- # This parser consumes no input.
- def pure(value : U) : Parser(T, U) forall T, U
- Parser(T, U).new("Pure #{value}") { |tokens| Result.new(tokens, value) }
- end
-
- # Creates a parser that always fails.
- def flunk : Parser(T, U) forall T, U
- Parser(T, U).new("Flunk") { |_| raise ParserFail.new("Flunked parser") }
- end
-
- # Creates a parser that returns the first token in the input stream.
- # Fails if the input stream is empty.
- # Analagous to a `.` in a regular expression.
- def any_token : Parser(T, T) forall T
- Parser(T, T).new("Any Token") do |tokens|
- if tokens.empty?
- raise ParserFail.new("Expected a token, got EOF")
- else
- Result.new(tokens[1..], tokens[0])
- end
- end
- end
-
- # Creates a parser that succeeds with `nil` if the input stream is empty.
- # Fails if the input stream is not empty.
- # Analagous to a `$` in a regular expression.
- def eof : Parser(T, Nil) forall T
- Parser(T, Nil).new("EOF") do |tokens|
- if tokens.empty?
- Result.new(tokens, nil)
- else
- raise ParserFail.new("Expected EOF, got a token")
- end
- end
- end
-
- # Creates a parser that parses the first token in the input stream
- # if that token satisfies a given predicate.
- # Fails if the input stream is empty or if the predicate fails.
- # To test a predicate against any parser result, see `Parser#assert`.
- # TODO: the type-checker hates me
- def satisfy(f : T -> Bool) : Parser(T, T) forall T
- #p = any_token(T).assert(f).named("Satisfy")
- Parser(T, T).new("Satisfy") do |tokens|
- Basic.any_token.assert(f).parse(tokens)
- end
- end
-
- # :ditto:
- def satisfy(&block : T -> Bool) : Parser(T, T) forall T
- satisfy(T, block)
- end
- end
-end
-
diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr
new file mode 100644
index 0000000..adf0bba
--- /dev/null
+++ b/src/parcom/parser.cr
@@ -0,0 +1,137 @@
+require "../parcom.cr"
+
+module Parcom
+ # A parser defines a process of extracting some sort of data
+ # from a stream of tokens.
+ # `Parser` objects wrap a function/block that accepts a `Tokens` object,
+ # and either returns a `Result`, or raises a `ParserFail`.
+ # This struct also defines a handful of methods for modifying a parser's
+ # behavior.
+ #
+ # The function that is wrapped by a parser should:
+ # 1. Have the type `Tokens(T) -> Result(T, U)`
+ # 1. Indicate failure by raising a `ParserFail`
+ # 1. Not introduce side-effects
+ #
+ # Instead of inheriting from `Parser`, custom parsers should be
+ # defined using a method that generates the parser directly:
+ # ```
+ # struct Foo(T, U) < Parser(T, Array(U))
+ # def parse(tokens)
+ # # This will cause headaches
+ # end
+ # end
+ #
+ # # Do this instead:
+ # def foo(t : T.class, u : U.class, p : Parser(T, U)) : Parser(T, U) forall T, U
+ # Parser(T, U).new("Foo") { |tokens| "your code here" }
+ # end
+ # ```
+ struct Parser(T, U)
+ getter name
+
+ # Creates a parser that always succeeds with the given value.
+ # This parser consumes no input.
+ def self.pure(value : U) : Parser(T, U)
+ Parser(T, U).new("Pure #{value}") { |tokens| Result.new(tokens, value) }
+ end
+
+ # Creates a parser that always fails.
+ def self.flunk : Parser(T, U)
+ Parser(T, U).new("Flunk") { |_| raise ParserFail.new("Flunked parser") }
+ end
+
+ # Creates a parser that returns the first token in the input stream.
+ # Fails if the input stream is empty.
+ # Analagous to a `.` in a regular expression.
+ def self.any_token : Parser(T, T) forall T
+ Parser(T, T).new("Any Token") do |tokens|
+ if tokens.empty?
+ raise ParserFail.new("Expected a token, got EOF")
+ else
+ Result.new(tokens[1..], tokens[0])
+ end
+ end
+ end
+
+ # Creates a parser that succeeds with `nil` if the input stream is empty.
+ # Fails if the input stream is not empty.
+ # Analagous to a `$` in a regular expression.
+ def self.eof : Parser(T, Nil)
+ Parser(T, Nil).new("EOF") do |tokens|
+ if tokens.empty?
+ Result.new(tokens, nil)
+ else
+ raise ParserFail.new("Expected EOF, got a token")
+ end
+ end
+ end
+
+ # Creates a parser that parses the first token in the input stream
+ # if that token satisfies a given predicate.
+ # Fails if the input stream is empty or if the predicate fails.
+ # To test a predicate against any parser result, see `Parser#assert`.
+ def self.satisfy(f : T -> Bool) : Parser(T, T)
+ Parser(T, T).any_token.assert(f).named("Satisfy")
+ end
+
+ # :ditto:
+ def self.satisfy(&block : T -> Bool) : Parser(T, T)
+ satisfy(block)
+ end
+
+ def initialize(@name : String, @f : Tokens(T) -> Result(T, U))
+ end
+
+ def initialize(@name : String, &block : Tokens(T) -> Result(T, U))
+ @f = block
+ end
+
+ # Changes the `name` property and returns `self`.
+ # This should be used to specify a custom name:
+ # ```
+ # a = Basic.token(Char, 'a').named("letter a")
+ # ```
+ def named(name : String) : self
+ @name = name
+ self
+ end
+
+ def parse(tokens : Tokens(T)) : Result(T, U)
+ @f.call(tokens)
+ end
+
+ def parse?(tokens : Tokens(T)) : Result(T, U)?
+ parse(tokens)
+ rescue
+ nil
+ end
+
+ def assert(f : U -> Bool) : Parser(T, U)
+ p = self
+ Parser(T, U).new("#{p.name} (assertion)") do |tokens|
+ result = p.parse(tokens)
+ unless f.call(result.value)
+ raise ParserFail.new("Assertion failed for value #{result.value}")
+ end
+ result
+ end
+ end
+
+ def assert(&block : U -> Bool) : Parser(T, U)
+ assert(block)
+ end
+
+ def map(f : U -> T) : 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
+ map(block)
+ end
+ end
+end
+