From 467660e024bad8e2e084aa703686d0856a7e88b9 Mon Sep 17 00:00:00 2001 From: Matthew Hall Date: Sat, 18 Mar 2023 21:44:26 +1300 Subject: Sequence, +, and variants --- src/parcom/parser.cr | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr index b78ab77..fcfe292 100644 --- a/src/parcom/parser.cr +++ b/src/parcom/parser.cr @@ -86,6 +86,32 @@ module Parcom Parser(T, T).satisfy { |x| x == token }.named("Token <#{token}>") end + # Creates a parser from an array of parsers that parses with + # each of them in sequence. The results of all parsers are returned + # in an array. If any of the parsers fail, the whole parser fails. + # TODO: allow support for Iterable(Parser(T, U)) + def self.sequence(ps : Array(Parser(T, U))) : Parser(T, Array(U)) + Parser(T, Array(U)).new("Sequence: #{ps.map(&.name)}") do |tokens| + values = [] of U + ps.each do |p| + result = p.parse(tokens) + values << result.value + tokens = result.tokens + end + Result.new(tokens, values) + end + end + + # Creates a parser from an array of `T` that tries to parser + # each member of the array in sequence. An identical array is + # returned on success. If any of the tokens are absent, the + # whole parser fails. + # TODO: allow support for Iterable(T) + def self.token_sequence(ts : Array(T)) : Parser(T, Array(T)) + ps = ts.map{ |t| Parser(T, T).token(t) } + Parser(T, T).sequence(ps).named("Token Sequence: #{ts}") + 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)) @@ -157,9 +183,13 @@ module Parcom map(block) end + # Creates a new parser from `self` and another parser that will + # try to parse with either of them. If the first parser succeeds, + # it will return the result of the first parser. Otherwise, it will + # return the result of the second parser. def |(p2 : Parser(T, U)) : Parser(T, U) p1 = self - Parser(T, U).new("#{p1.name} or #{p2.name}") do |tokens| + Parser(T, U).new("#{p1.name} | #{p2.name}") do |tokens| p1.parse(tokens) rescue ParserFail p2.parse(tokens) @@ -187,6 +217,30 @@ module Parcom Result.new(new_tokens, new_value) end end + + # Creates a new parser from `self` and another parser that will + # try to parse with both parsers and return both results. If either + # sub-parser fails, the whole parser fails. + def +(p2 : Parser(T, V)) : Parser(T, {U, V}) forall V + p1 = self + Parser(T, {U, V}).new("#{p1.name} + #{p2.name}") do |tokens| + r1 = p1.parse(tokens) + r2 = p2.parse(r1.tokens) + Result.new(r2.tokens, {r1.value, r2.value}) + end + end + + # Same as `#+`, but discards the second parser's result. + def <<(p2 : Parser(T, V)) : Parser(T, U) forall V + p1 = self + (p1 + p2).map(&.first).named("#{p1.name} << #{p2.name}") + end + + # Same as `#+`, but discards the first parser's result. + def >>(p2 : Parser(T, V)) : Parser(T, V) forall V + p1 = self + (p1 + p2).map(&.last).named("#{p1.name} >> #{p2.name}") + end end end -- cgit v1.2.1