aboutsummaryrefslogtreecommitdiff
path: root/src/__OLD_parcom
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2023-03-16 20:36:03 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2023-03-16 20:36:03 +1300
commitb274828831fec26cd8b3089ffef14cb96ce2de2f (patch)
treeff5927b85a59b4d85c9e4aa269a475a7a37a54a0 /src/__OLD_parcom
parent77c370d27be174e0b036b33d1469e84e67a7153a (diff)
Second rewrite attempt, this one should work, monkaS
Diffstat (limited to 'src/__OLD_parcom')
-rw-r--r--src/__OLD_parcom/alt.cr28
-rw-r--r--src/__OLD_parcom/any_token.cr18
-rw-r--r--src/__OLD_parcom/assert.cr45
-rw-r--r--src/__OLD_parcom/at_least.cr29
-rw-r--r--src/__OLD_parcom/at_most.cr29
-rw-r--r--src/__OLD_parcom/between.cr37
-rw-r--r--src/__OLD_parcom/eof.cr23
-rw-r--r--src/__OLD_parcom/exactly.cr28
-rw-r--r--src/__OLD_parcom/first_of.cr40
-rw-r--r--src/__OLD_parcom/flunk.cr12
-rw-r--r--src/__OLD_parcom/left.cr26
-rw-r--r--src/__OLD_parcom/many.cr36
-rw-r--r--src/__OLD_parcom/map.cr30
-rw-r--r--src/__OLD_parcom/optional.cr40
-rw-r--r--src/__OLD_parcom/parser.cr87
-rw-r--r--src/__OLD_parcom/peek.cr23
-rw-r--r--src/__OLD_parcom/phrase.cr33
-rw-r--r--src/__OLD_parcom/plus.cr54
-rw-r--r--src/__OLD_parcom/recover.cr27
-rw-r--r--src/__OLD_parcom/right.cr26
-rw-r--r--src/__OLD_parcom/satisfy.cr33
-rw-r--r--src/__OLD_parcom/sep_by.cr38
-rw-r--r--src/__OLD_parcom/sequence.cr33
-rw-r--r--src/__OLD_parcom/some.cr33
-rw-r--r--src/__OLD_parcom/token.cr20
-rw-r--r--src/__OLD_parcom/token_seq.cr38
26 files changed, 866 insertions, 0 deletions
diff --git a/src/__OLD_parcom/alt.cr b/src/__OLD_parcom/alt.cr
new file mode 100644
index 0000000..dedd41d
--- /dev/null
+++ b/src/__OLD_parcom/alt.cr
@@ -0,0 +1,28 @@
+require "./parser.cr"
+
+module Parcom
+ # `Alt` is a `Parser` that accepts two other parsers and tries
+ # to parse with one of them.
+ # If the first (left) parser succeeds, its result is returned.
+ # If the first parser fails, it will try the second (right) parser.
+ class Alt(T, V) < Parser(T, V)
+ # Accepts the two parsers to try to parse with.
+ def initialize(@p1 : Parser(T, V), @p2 : Parser(T, V))
+ end
+
+ # Tries to parse using both parsers.
+ #
+ # It will initially try to parse with the first parser.
+ # If it fails, it will try to parse with the second parser.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ @p1.parse(tokens)
+ rescue ex1 : ParserFail
+ begin
+ @p2.parse(tokens)
+ rescue ex2 : ParserFail
+ raise ParserFail.new("Alt (#{ex1.message}), (#{ex2.message})")
+ end
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/any_token.cr b/src/__OLD_parcom/any_token.cr
new file mode 100644
index 0000000..1f65bfc
--- /dev/null
+++ b/src/__OLD_parcom/any_token.cr
@@ -0,0 +1,18 @@
+require "./parser.cr"
+
+module Parcom
+ # `AnyToken` is a `Parser` that consumes exactly one token from
+ # the input stream and returns it.
+ # It fails if the input stream is empty.
+ class AnyToken(T) < Parser(T, T)
+ # Parses the first token in the input, fails if the input is empty.
+ def parse(tokens : Tokens(T)) : Result(T, T)
+ if tokens.empty?
+ raise ParserFail.new("AnyToken: input was empty")
+ else
+ Result.new(tokens[1..], tokens[0])
+ end
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/assert.cr b/src/__OLD_parcom/assert.cr
new file mode 100644
index 0000000..5a3e621
--- /dev/null
+++ b/src/__OLD_parcom/assert.cr
@@ -0,0 +1,45 @@
+require "./parser.cr"
+
+module Parcom
+ # `Assert` is a `Parser` that runs another `Parser` and then
+ # performs a test on its result. The parser will then either fail if
+ # the result does not pass the test, or forward it on if it does.
+ #
+ # Example:
+ # ```
+ # letter = Assert.new(AnyToken(Char).new) { |x| x.letter? }
+ # non_letter = Assert.new(AnyToken(Char).new) { |x| !x.letter? }
+ # input = Tokens.from_string("hi")
+ # letter.parse(input) # this succeeds
+ # non_letter.parse(input) # this fails
+ # ```
+ #
+ # `Assert` instance can also be created by calling
+ # `Parser#assert` on any parser:
+ # ```
+ # # This parser is identical to the above example.
+ # letter = AnyToken.new(Char).assert { |x| x.letter? }
+ # ```
+ class Assert(T, V) < Parser(T, V)
+ # Accepts the parser to run and a `Bool`-retuning block
+ # containing the test to perform on the other parser's result.
+ def initialize(@p : Parser(T, V), &block : V -> Bool)
+ @f = block
+ end
+
+ # Runs the parser it was initialized with, and returns that parser's
+ # result if it passes the provided test. Raises `ParserFail` otherwise.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ result = @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Assert (pre-assertion): #{ex.message}")
+ else
+ unless @f.call(result.value)
+ raise ParserFail.new("Assert: predicate failed for <#{result.value}>")
+ end
+
+ return result
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/at_least.cr b/src/__OLD_parcom/at_least.cr
new file mode 100644
index 0000000..2fb8dcf
--- /dev/null
+++ b/src/__OLD_parcom/at_least.cr
@@ -0,0 +1,29 @@
+require "./parser.cr"
+require "./map.cr"
+require "./exactly.cr"
+
+module Parcom
+ # `AtLeast` is a `Parser` that tries to parser with another parser
+ # a specific number of times. The results of each successful parse
+ # are returned in an array. If the number of successes is less than
+ # the specified number, the parser fails.
+ class AtLeast(T, V) < Parser(T, Array(V))
+ @p : Map(T, {Array(V), Array(V)}, Array(V))
+
+ # Accepts the number of parsing attempts, and the parser to use.
+ # If a negative int is given, it is treated as if it were 0.
+ def initialize(i : Int, p : Parser(T, V))
+ @p = (Exactly.new(i, p) + Many.new(p)).map do |tup|
+ tup[0] + tup[1]
+ end
+ end
+
+ # Tries to parse the given number of times, or more.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("AtLeast: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/at_most.cr b/src/__OLD_parcom/at_most.cr
new file mode 100644
index 0000000..b70164f
--- /dev/null
+++ b/src/__OLD_parcom/at_most.cr
@@ -0,0 +1,29 @@
+require "./parser.cr"
+require "./map.cr"
+require "./exactly.cr"
+require "./optional.cr"
+
+module Parcom
+ # `AtMost` is a `Parser` that tries to parse with another parser a specific
+ # number of times. The results of each successful parse are returned in an
+ # array. If the specific number of parses succeed, the parsing stops, even
+ # if it is possible to keep parsing. If the parser is never successful, it
+ # succeeds with an empty array.
+ class AtMost(T, V) < Parser(T, Array(V))
+ @p : Map(T, Array(V?), Array(V))
+
+ # Accepts the number of parsing attempts, and the parser to use.
+ def initialize(i : Int, p : Parser(T, V))
+ @p = Exactly.new(i, Optional.new(p)).map(&.compact)
+ end
+
+ # Tries to parse up to the given number of times.
+ # If the parser never succeeds, returns an empty array.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("AtMost: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/between.cr b/src/__OLD_parcom/between.cr
new file mode 100644
index 0000000..05519e4
--- /dev/null
+++ b/src/__OLD_parcom/between.cr
@@ -0,0 +1,37 @@
+require "./parser.cr"
+require "./map.cr"
+require "./exactly.cr"
+require "./at_most.cr"
+
+module Parcom
+ # `Between` is a `Parser` that tries to parse with another parser a number
+ # of times within a specified range. The results of each successful parse
+ # are returned in an array. If the number of successful parses is out of
+ # the lower bound of the range, the parser fails. If the number of successful
+ # parses reaches thhe upper bound of the range, the parsing stops, even if it
+ # is possible to keep parsing.
+ class Between(T, V) < Parser(T, Array(V))
+ @p : Map(T, {Array(V), Array(V)}, Array(V))
+
+ # Accepts the lower and upper bounds for the number of parsing attempts,
+ # as well as the parser to use. This method works the same way whether or
+ # not the larger value is passed first or second. If a negative int value
+ # is given for either parameter, it is treated as `0`.
+ # TODO: Overload to allow for Range objects.
+ def initialize(i : Int, j : Int, p : Parser(T, V))
+ lower = i < j ? i : j
+ upper = (i - j).abs
+ @p = (Exactly.new(lower, p) + AtMost.new(upper, p)).map do |tup|
+ tup[0] + tup[1]
+ end
+ end
+
+ # Tries to parse a numebr of times within the given range.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Between: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/eof.cr b/src/__OLD_parcom/eof.cr
new file mode 100644
index 0000000..650da56
--- /dev/null
+++ b/src/__OLD_parcom/eof.cr
@@ -0,0 +1,23 @@
+require "./parser.cr"
+
+module Parcom
+ # `EOF` is a `Parser` succeeds if the input stream is empty.
+ #
+ # This parser retuns `nil` when successful because there is no
+ # other meaningful value to return.
+ #
+ # This parser is also one of the few cases where it is ideal to not
+ # modify or take from the input stream, as it should be empty anyway.
+ class EOF(T) < Parser(T, Nil)
+ # Succeeds is the input stream is empty and returns `nil`.
+ # Raises a `ParserFail` exception when the input is not empty.
+ def parse(tokens : Tokens(T)) : Result(T, Nil)
+ if tokens.empty?
+ Result.new(tokens, nil)
+ else
+ raise ParserFail.new("Eof: input was not empty")
+ end
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/exactly.cr b/src/__OLD_parcom/exactly.cr
new file mode 100644
index 0000000..a34fd0d
--- /dev/null
+++ b/src/__OLD_parcom/exactly.cr
@@ -0,0 +1,28 @@
+require "./parser.cr"
+
+module Parcom
+ # `Exactly` is a `Parser` that tries to parse with another parser
+ # a specific number of times. The results of each successful parse
+ # are returned in an array. If the number of successes is less than
+ # OR greater than the specified number, the parser fails.
+ class Exactly(T, V) < Parser(T, Array(V))
+ @p : Sequence(T, V)
+
+ # Accepts the number of parsing attempts, and the parser to use.
+ # If a negative int is given, it is treated as if it were `0`.
+ def initialize(i : Int, p : Parser(T, V))
+ i = i.negative? ? 0 : i
+ # put `i` copies of `p` into an array
+ ps = ([p] of Parser(T, V)) * i
+ @p = Sequence.new(ps)
+ end
+
+ # Tries to parse the given number of times.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Exactly: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/first_of.cr b/src/__OLD_parcom/first_of.cr
new file mode 100644
index 0000000..c4077eb
--- /dev/null
+++ b/src/__OLD_parcom/first_of.cr
@@ -0,0 +1,40 @@
+require "./parser.cr"
+
+module Parcom
+ # `FirstOf` is a `Parser` that accepts multiple parsers, and tries to parse
+ # with all of them, in order. As soon as one of the parsers succeeds,
+ # that parser's result is returned. If none of the parsers are successful,
+ # the parsing fails.
+ class FirstOf(T, V) < Parser(T, V)
+ @p : Parser(T, V)
+
+ # Accepts the parsers to use. Raises an `ArgumentError` if
+ # no parsers are provided.
+ def initialize(ps : Iterable(Parser(T, V)))
+ ps_iter = ps.each
+ p = ps_iter.next
+
+ if p.is_a?(Iterator::Stop)
+ raise ArgumentError.new("FirstOf requires atleast one parser, got none")
+ end
+
+ # Combine all the parsers into one by wrapping them with `Alt`.
+ @p = p
+
+ loop do
+ p = ps_iter.next
+ break if p.is_a?(Iterator::Stop)
+ @p = @p | p
+ end
+ end
+
+ # Tries to parse with each of the given parsers. Either returns the first
+ # successful result or fails if no parsers succeed.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("FirstOf: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/flunk.cr b/src/__OLD_parcom/flunk.cr
new file mode 100644
index 0000000..00a0bd6
--- /dev/null
+++ b/src/__OLD_parcom/flunk.cr
@@ -0,0 +1,12 @@
+require "./parser.cr"
+
+module Parcom
+ # `Flunk` is a `Parser` that always fails.
+ class Flunk(T, V) < Parser(T, V)
+ # Always raises `ParserFail`
+ def parse(tokens) : Result(T, V)
+ raise ParserFail.new("Flunk: parsing failed")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/left.cr b/src/__OLD_parcom/left.cr
new file mode 100644
index 0000000..201f497
--- /dev/null
+++ b/src/__OLD_parcom/left.cr
@@ -0,0 +1,26 @@
+require "./parser.cr"
+require "./map.cr"
+
+module Parcom
+ # `Left` is a `Parser` that tries to parse with two different
+ # parsers in succession and fails if either of the two parsers fails.
+ # This parser behaves the same as `Plus`, but only returns the result
+ # of the first parser.
+ class Left(T, V, U) < Parser(T, V)
+ @p : Map(T, {V, U}, V)
+
+ # Accepts the two parsers to use, in order.
+ def initialize(p1 : Parser(T, V), p2 : Parser(T, U))
+ @p = (p1 + p2).map(&.first)
+ end
+
+ # Tries to parse with the two given parsers, and returns the
+ # result of the first parser if they both succeed.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Left: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/many.cr b/src/__OLD_parcom/many.cr
new file mode 100644
index 0000000..a734c63
--- /dev/null
+++ b/src/__OLD_parcom/many.cr
@@ -0,0 +1,36 @@
+require "./parser.cr"
+
+module Parcom
+ # `Many` is a `Parser` that repeatedly tries to parse with another parser.
+ # The `Many` object will collect all success values in an array, and return
+ # them with the final state of the input stream. If the wrapped parser ever
+ # fails or succeeds without parsing any input, the `Many` object will stop
+ # attempting to parse will will return the array of prrevious successes.
+ #
+ # `Many` will return an empty array if the parser never succeeds or consumes
+ # input. For cases where at least one parserr success is needed, use `Some`.
+ class Many(T, V) < Parser(T, Array(V))
+ # Accepts the parser to use.
+ def initialize(@p : Parser(T, V))
+ end
+
+ # Continuously parses with the wrapped parser, returns all successes.
+ # Parsing stops when the parser fails, or succeeds without consuming input.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ parsed = [] of V
+
+ loop do
+ result = @p.parse?(tokens)
+ break unless !result.nil? && result.tokens != tokens
+
+ parsed << result.value
+ tokens = result.tokens
+ end
+
+ Result.new(tokens, parsed)
+ rescue ex : ParserFail
+ raise ParserFail.new("Many: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/map.cr b/src/__OLD_parcom/map.cr
new file mode 100644
index 0000000..34961b5
--- /dev/null
+++ b/src/__OLD_parcom/map.cr
@@ -0,0 +1,30 @@
+require "./parser.cr"
+
+module Parcom
+ # `Map` is a `Parser` that tries to parse using another parser,
+ # and then changing the result of that parser to a different value.
+ #
+ # Example:
+ # ```
+ # # Where `Digits` is some parser that returns an array of digit characters
+ # parse_i32 = Digits.new.map { |x| x.join.to_i32 }
+ # result = parse_i32.parse(Tokens.from_string("1234"))
+ # result.value # => 1234 (Int32)
+ # ```
+ class Map(T, V, U) < Parser(T, U)
+ # Accepts the parser to use and the function to apply to the result.
+ def initialize(@p : Parser(T, V), &block : V -> U)
+ @f = block
+ end
+
+ # Tries to parse with the given parser and applies the
+ # function to the result if successful.
+ def parse(tokens : Tokens(T)) : Result(T, U)
+ result = @p.parse(tokens)
+ Result.new(result.tokens, @f.call(result.value))
+ rescue ex : ParserFail
+ raise ParserFail.new("Map: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/optional.cr b/src/__OLD_parcom/optional.cr
new file mode 100644
index 0000000..b368d4e
--- /dev/null
+++ b/src/__OLD_parcom/optional.cr
@@ -0,0 +1,40 @@
+require "./parser.cr"
+
+module Parcom
+ # `Optional` is a `Parser` that tries to parse with another parser,
+ # but does not fail the parser chain if the wrapped parser fails.
+ # If the wrapped parser fails, an `Optional` will not modify the input
+ # stream, and return `nil` instead.
+ #
+ # Example:
+ # ```
+ # digit = Satisfy(Char).new { |c| c.digit? }
+ # digits = Many.new(digit)
+ # plus_sign = Optional.new(Token.new('+'))
+ # positive_int = plus_sign >> digits
+ #
+ # input1 = Tokens.from_string("+12345")
+ # input2 = Tokens.from_string("12345")
+ #
+ # # Both of these has the same result
+ # positive_int.parse(input1)
+ # positive_int.parse(input2)
+ # ```
+ class Optional(T, V) < Parser(T, V?)
+ # Accepts the parser to use.
+ def initialize(@p : Parser(T, V))
+ end
+
+ # Tries to parse with the given parser, succeeds with `nil`
+ # instead of failing. This parser does not consume input if
+ # the wrapped parser fails.
+ def parse(tokens : Tokens(T)) : Result(T, V?)
+ 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
+
diff --git a/src/__OLD_parcom/parser.cr b/src/__OLD_parcom/parser.cr
new file mode 100644
index 0000000..ca350df
--- /dev/null
+++ b/src/__OLD_parcom/parser.cr
@@ -0,0 +1,87 @@
+require "../parcom.cr"
+
+module Parcom
+ # `Parser` is the base class of all parser objects and contains
+ # a handful of helper methods that can allow for cleaner syntax.
+ #
+ # The generic type `T` represents the type of tokens a parser will
+ # operate on (usually `Char`).
+ # The generic type `V` represents the type of value that a parser
+ # will return when parsing is successful.
+ abstract class Parser(T, V)
+ # Accepts a `Tokens(T)` and either retuns a `Result(T, V)`, or raises a
+ # `ParserFail` exception with a message providing context of where
+ # in the chain the parsing failed.
+ #
+ # The `V` in the returned `Result` is arbitrary, but the `Tokens(T)` in
+ # the returned result MUST contain the parser's input, with some tokens
+ # removed from the front proprtional to what was parsed.
+ #
+ # Example:
+ # ```
+ # hello = ParsesHello.new # inherits from Parser(Char, String)
+ # input = Tokens.from_string("Hello world!")
+ #
+ # result = hello.parse(input)
+ # result.value # => "Hello"
+ # result.tokens # => [' ', 'w', 'o', 'r', 'l', 'd', '!']
+ #
+ # bad_input = Tokens.from_string("Good evening world!")
+ # hello.parse(bad_input) # raises ParserFail
+ # ```
+ abstract def parse(tokens : Tokens(T)) : Result(T, V)
+
+ # Same as `#parse`, but returns `nil` instead of raising `ParserFail`.
+ def parse?(tokens : Tokens(T)) : Result(T, V)?
+ self.parse(tokens)
+ rescue ParserFail
+ return nil
+ end
+
+ # Constructs an `Alt` parser with `self` and `other`.
+ def |(other : Parser(T, V)) : Alt(T, V)
+ Alt.new(self, other)
+ end
+
+ # Constructs a `Plus` parser with `self` and `other`.
+ def +(other : Parser(T, U)) : Plus(T, V, U) forall U
+ Plus.new(self, other)
+ end
+
+ # Constructs a `Left` parser with `self` and `other`.
+ def <<(other : Parser(T, U)) : Left(T, V, U) forall U
+ Left.new(self, other)
+ end
+
+ # Constructs a `Right` parser with `self` and `other`.
+ def >>(other : Parser(T, U)) : Right(T, V, U) forall U
+ Right.new(self, other)
+ end
+
+ # Constructs an `Assert` parser with `self` and the given block.
+ def assert(&block : V -> Bool) : Assert(T, V)
+ Assert.new(self, &block)
+ end
+
+ # Constructs an `Assert` parser with `self` and the given proc.
+ def assert(f : V -> Bool) : Assert(T, V)
+ Assert.new(self, &f)
+ end
+
+ # Constructs a `Map` parser with `self` and the given block.
+ def map(&block : V -> U) : Map(T, V, U) forall U
+ Map.new(self, &block)
+ end
+
+ # Constructs a `Map` parser with `self` and the given proc.
+ def map(f : V -> U) : Map(T, V, U) forall U
+ Map.new(self, &f)
+ end
+
+ # Constructs a `Recover` parser with `self` and the given `V`.
+ def recover(default : V) : Recover(T, V)
+ Recover.new(self, default)
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/peek.cr b/src/__OLD_parcom/peek.cr
new file mode 100644
index 0000000..2b6f657
--- /dev/null
+++ b/src/__OLD_parcom/peek.cr
@@ -0,0 +1,23 @@
+require "./parser.cr"
+
+module Parcom
+ # `Peek` is a `Parser` that runs another `Parser` while
+ # leaving the input stream unmodified.
+ class Peek(T, V) < Parser(T, V)
+ # Accepts the parser to run.
+ def initialize(@p : Parser(T, V))
+ end
+
+ # Runs the parser it was initialized with, and returns
+ # that parser's result along with the original input.
+ #
+ # Re-raises a `ParserFail` exception if the other parser fails.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ result = @p.parse(tokens)
+ Result.new(tokens, result.value)
+ rescue ex : ParserFail
+ raise ParserFail.new("Peek: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/phrase.cr b/src/__OLD_parcom/phrase.cr
new file mode 100644
index 0000000..1996fd4
--- /dev/null
+++ b/src/__OLD_parcom/phrase.cr
@@ -0,0 +1,33 @@
+require "./parser.cr"
+require "./eof.cr"
+require "./map.cr"
+
+module Parcom
+ # `Phrase` is a `Parser` that tries to parse with another parser,
+ # but will fail if any of the input has not been consumed.
+ #
+ # Example:
+ # ```
+ # letter_a = Token.new('a')
+ # tokens = Tokens.from_string("aaa")
+ # one_a = Phrase(Char, Char).new(letter_a)
+ # result = one_a.parse(tokens) # This fails
+ # ```
+ class Phrase(T, V) < Parser(T, V)
+ @p : Map(T, {V, Nil}, V)
+
+ # Accepts the parser to parse with.
+ def initialize(p : Parser(T, V))
+ @p = (p + EOF(T).new).map &.first
+ end
+
+ # Tries to parse with the given parser, fails if there
+ # is any un-parsed input remaining.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Phrase: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/plus.cr b/src/__OLD_parcom/plus.cr
new file mode 100644
index 0000000..53c9b4f
--- /dev/null
+++ b/src/__OLD_parcom/plus.cr
@@ -0,0 +1,54 @@
+require "./parser.cr"
+
+module Parcom
+ # `Plus` is a parser that tries to parse with two different
+ # parsers in succession and fails if either of the two parsers fails.
+ # The return type of this parser is `{V, U}`, where `V` is the return
+ # type of the first parser and `U` is the return type of the second.
+ #
+ # Example:
+ # ```
+ # parse1 = Token.new('1')
+ # parse2 = Token.new('2')
+ # tokens = Tokens.from_string("12")
+ # result = (parse1 + parse2).parse(tokens) # succeeds
+ # result = (parse2 + parse1).parse(tokens) # fails, wrong order
+ # ```
+ #
+ # `Plus` parsers can be chained together, but the resulting return type
+ # can become unweildly with too many combined parsers:
+ # ```
+ # letter_a = Token.new('a')
+ # a5 = letter_a + letter_a + letter_a + letter_a + letter_a
+ # tokens = Tokens.from_string("aaaaa")
+ # a5.parse(tokens) # succeeds
+ # # `a5` has a return type of { { { {Char, Char}, Char}, Char}, Char}
+ # ```
+ #
+ # If you need to parse more than two things in this manner,
+ # consider using `Many`, `Some`, `Sequence`, or `TokenSeq` instead.
+ class Plus(T, V, U) < Parser(T, {V, U})
+ # Accepts the two parsers to use, in order.
+ def initialize(@p1 : Parser(T, V), @p2 : Parser(T, U))
+ end
+
+ # Tries to parse with the two given parsers, and returns
+ # their results in a tuple if the both succeed.
+ def parse(tokens : Tokens(T)) : Result(T, {V, U})
+ begin
+ r1 = @p1.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Plus (left): #{ex.message}")
+ end
+
+ begin
+ r2 = @p2.parse(r1.tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Plus (right): #{ex.message}")
+ end
+
+ Result.new(r2.tokens, {r1.value, r2.value})
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/recover.cr b/src/__OLD_parcom/recover.cr
new file mode 100644
index 0000000..378f8d6
--- /dev/null
+++ b/src/__OLD_parcom/recover.cr
@@ -0,0 +1,27 @@
+require "./parser.cr"
+require "./optional.cr"
+require "./map.cr"
+
+module Parcom
+ # `Recover` is a `Parser` that tries to parse with another parser,
+ # but does not fail the parser chain if the wrapped parser fails.
+ # If the wrapped parser fails, a `Recover` will not modify the input
+ # stream, and return an arbitrary value of type `V` instead.
+ class Recover(T, V) < Parser(T, V)
+ @p : Map(T, V?, V)
+
+ # Accepts the parser to use, and the default value
+ # to return if the parser fails.
+ def initialize(p : Parser(T, V), default : V)
+ @p = Optional.new(p).map { |x| x.nil? ? default : x }
+ end
+
+ # Tries to parse with the given parser, succeeds with the
+ # default value instead of failing. This parser does not
+ # consume input if the wrapped parser fails.
+ def parse(tokens : Tokens(T)) : Result(T, V)
+ @p.parse(tokens)
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/right.cr b/src/__OLD_parcom/right.cr
new file mode 100644
index 0000000..a0489b1
--- /dev/null
+++ b/src/__OLD_parcom/right.cr
@@ -0,0 +1,26 @@
+require "./parser.cr"
+require "./map.cr"
+
+module Parcom
+ # `Right` is a `Parser` that tries to parse with two different
+ # parsers in succession and fails if either of the two parsers fails.
+ # This parser behaves the same as `Plus`, but only returns the result
+ # of the second parser.
+ class Right(T, V, U) < Parser(T, U)
+ @p : Map(T, {V, U}, U)
+
+ # Accepts the two parsers to use, in order.
+ def initialize(p1 : Parser(T, V), p2 : Parser(T, U))
+ @p = (p1 + p2).map(&.last)
+ end
+
+ # Tries to parse with the two given parsers, and returns the
+ # result of the second parser if they both succeed.
+ def parse(tokens : Tokens(T)) : Result(T, U)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Left: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/satisfy.cr b/src/__OLD_parcom/satisfy.cr
new file mode 100644
index 0000000..9734635
--- /dev/null
+++ b/src/__OLD_parcom/satisfy.cr
@@ -0,0 +1,33 @@
+require "./parser.cr"
+require "./any_token.cr"
+require "./assert.cr"
+
+module Parcom
+ # `Satisfy` is a `Parser` that parses a single token
+ # if that token passes a predefined test, similar
+ # to `Assert`. This class is effectively a shorthand
+ # for the following:
+ # ```
+ # # These parsers are equivalent.
+ # letter_assert = Assert.new(AnyToken(Char).new) { |x| x.letter? }
+ # letter_satisfy = Satisfy(Char).new { |x| x.letter? }
+ # ```
+ class Satisfy(T) < Parser(T, T)
+ @p : Assert(T, T)
+
+ # Accepts the `Bool`-returning block containing the test
+ # to run on the parsed token.
+ def initialize(&block : T -> Bool)
+ @p = AnyToken(T).new.assert(&block)
+ end
+
+ # Returns the first token of the input if that
+ # token passes the test.
+ def parse(tokens : Tokens(T)) : Result(T, T)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Satisfy: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/sep_by.cr b/src/__OLD_parcom/sep_by.cr
new file mode 100644
index 0000000..fa19027
--- /dev/null
+++ b/src/__OLD_parcom/sep_by.cr
@@ -0,0 +1,38 @@
+require "./parser.cr"
+require "./map.cr"
+require "./many.cr"
+
+module Parcom
+ # `SepBy` is a `Parser` that tries to parse one or more times with one
+ # parser, alternating with a second parser.
+ #
+ # Example:
+ # ```
+ # letter_a = Token.new('a')
+ # letter_b = Token.new('b')
+ # p = SepBy(Char, Car, Char).new(letter_a, letter_b)
+ # tokens = Tokens.from_string("ababababa")
+ #
+ # result = p.parse(tokens)
+ # puts result.value # => ['a', 'a', 'a', 'a', 'a']
+ # ```
+ class SepBy(T, V, U) < Parser(T, Array(V))
+ @p : Map(T, {V, Array(V)}, Array(V))
+
+ # Accepts the parser that parses the result values, and the
+ # parser that parses the sepatators.
+ def initialize(elem : Parser(T, V), sep : Parser(T, U))
+ @p = (elem + Many(T, U).new(sep >> elem)).map do |tup|
+ [tup[0]] + tup[1]
+ end
+ end
+
+ # Tries to parse, alternating the first and second parsers.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("SepBy: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/sequence.cr b/src/__OLD_parcom/sequence.cr
new file mode 100644
index 0000000..6a05cde
--- /dev/null
+++ b/src/__OLD_parcom/sequence.cr
@@ -0,0 +1,33 @@
+require "./parser.cr"
+
+module Parcom
+ # `Sequence` is a `Parser` that combines multiple parsers and
+ # tries to parse all of them in succession. If all of the parsers
+ # succeed, the values parsed are returned in an array, in the order
+ # they were parsed in. If any of the parsers fail,
+ # the `Sequence` also fails.
+ class Sequence(T, V) < Parser(T, Array(V))
+ # Accepts the parsers to use.
+ def initialize(@ps : Iterable(Parser(T, V)))
+ end
+
+ # Tries each parser in order, and returns their results.
+ # Fail if any of the wrapped parsers fail.
+ # TODO: this can probably be optimised more for Arrays
+ # TODO: might be better to use #zip
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ parsed = [] of V
+
+ @ps.each do |p|
+ r = p.parse(tokens)
+ parsed << r.value
+ tokens = r.tokens
+ end
+
+ Result.new(tokens, parsed)
+ rescue ex : ParserFail
+ raise ParserFail.new("Sequence: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/some.cr b/src/__OLD_parcom/some.cr
new file mode 100644
index 0000000..a2e3563
--- /dev/null
+++ b/src/__OLD_parcom/some.cr
@@ -0,0 +1,33 @@
+require "./parser.cr"
+require "./assert.cr"
+require "./many.cr"
+
+module Parcom
+ # `Some` is a `Parser` that repeatedly tries to parse with another parser.
+ # The `Some` object will collect all success values in an array, and
+ # return them with the final state of the input stream. If the wrapped
+ # parser ever fails or succeeds without parsing any input, the `Some`
+ # object will stop attempting to parse and will return the array of
+ # previous successes.
+ #
+ # `Some` will raise a `ParserFail` exception if the parser never succeeds
+ # or consumes input. For cases where parsing should allow for 0 successes,
+ # use `Many`.
+ class Some(T, V) < Parser(T, Array(V))
+ @p : Assert(T, Array(V))
+
+ # Accepts the parser to use.
+ def initialize(p : Parser(T, V))
+ @p = Many.new(p).assert { |arr| !arr.empty? }
+ end
+
+ # Continuously parses with the wrapped parser, returns all successes.
+ # Fails if there are no successes.
+ def parse(tokens : Tokens(T)) : Result(T, Array(V))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Some: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/token.cr b/src/__OLD_parcom/token.cr
new file mode 100644
index 0000000..b4e1fef
--- /dev/null
+++ b/src/__OLD_parcom/token.cr
@@ -0,0 +1,20 @@
+require "./satisfy"
+
+module Parcom
+ # `Token` is a `Parser` that tries to parse a specific token
+ # from the input stream.
+ class Token(T) < Parser(T, T)
+ # Accepts the token that this parser will try to parse.
+ def initialize(@expected : T)
+ @p = Satisfy(T).new { |x| x == @expected }
+ end
+
+ # Tries to parse the specified token from the input stream.
+ def parse(tokens : Tokens(T)) : Result(T, T)
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("Token <#{@expected}>: #{ex.message}")
+ end
+ end
+end
+
diff --git a/src/__OLD_parcom/token_seq.cr b/src/__OLD_parcom/token_seq.cr
new file mode 100644
index 0000000..45900f9
--- /dev/null
+++ b/src/__OLD_parcom/token_seq.cr
@@ -0,0 +1,38 @@
+require "./parser.cr"
+require "./sequence.cr"
+
+module Parcom
+ # `TokenSeq` is a `Parser` that attempts to parse a specific
+ # string of tokens. If the expected tokens are not at the start
+ # of the input stream, the parser fails.
+ #
+ # Example:
+ # ```
+ # parse_test = TokenSeq(Char).new("test".chars)
+ # tokens = Tokens.from_string("testing")
+ #
+ # result = parse_test.parse(tokens)
+ #
+ # puts result.value # => ['t', 'e', 's', 't']
+ # puts result.tokens # => ['i', 'n', 'g']
+ # ```
+ class TokenSeq(T) < Parser(T, Array(T))
+ @p : Sequence(T, T)
+
+ # Accepts the tokens to try and parse, in order.
+ def initialize(expected : Iterable(T))
+ ps = [] of Parser(T, T)
+ expected.each { |t| ps << Token.new(t) }
+
+ @p = Sequence.new(ps)
+ end
+
+ # Tries to parse the list of tokens.
+ def parse(tokens : Tokens(T)) : Result(T, Array(T))
+ @p.parse(tokens)
+ rescue ex : ParserFail
+ raise ParserFail.new("TokenSeq: #{ex.message}")
+ end
+ end
+end
+