diff options
Diffstat (limited to 'src/__OLD_parcom')
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 + |
