module Parcom VERSION = "0.1.0" class ParserException < Exception end struct TokenStream(T) getter tokens def self.from_string(s : String) : TokenStream(Char) TokenStream.new(s.chars) end def initialize(ts : Iterable(T)) if ts.responds_to?(:to_a) @tokens = ts.to_a else @tokens = [] of T ts.each { |t| @tokens << t } end end def [](index : Int) : T @tokens[index] end def [](start : Int, count : Int) : TokenStream(T) TokenStream.new(@tokens[start, count]) end def [](range : Range) : TokenStream(T) TokenStream.new(@tokens[range]) end def []?(*args) self.[](*args) rescue IndexError nil end def empty? : Bool @tokens.empty? end end struct Result(T, V) getter tokens, value def initialize(@tokens : TokenStream(T), @value : V) end end abstract class Parser(T, V) abstract def parse(tokens : TokenStream(T)) : Result(T, V) def parse?(tokens : TokenStream(T)) : Result(T, V)? self.parse(tokens) rescue return nil end def |(other : Parser(T, V)) : Alt(T, V) Alt.new(self, other) end def +(other : Parser(T, U)) : Plus(T, V, U) forall U Plus.new(self, other) end def assert(f : V -> Bool) Assert.new(self, &f) end def assert(&block : V -> Bool) Assert.new(self, &block) end def map(&block : V -> U) : Map(T, V, U) forall U Map.new(self, &block) end def map(f : V -> U) : Map(T, V, U) forall U Map.new(self, &f) end def recover(default : V) : Recover(T, V) Recover.new(self, default) end end class Flunk(T, V) < Parser(T, V) def parse(tokens) : Result(T, V) raise ParserException.new("Flunk: parsing failed") end end class AnyToken(T) < Parser(T, T) def parse(tokens : TokenStream(T)) : Result(T, T) if tokens.empty? raise ParserException.new("AnyToken: input was empty") else Result.new(tokens[1..], tokens[0]) end end end class Eof(T) < Parser(T, Nil) def parse(tokens : TokenStream(T)) : Result(T, Nil) if tokens.empty? Result.new(tokens, nil) else raise ParserException.new("Eof: input was not empty") end end end class Peek(T, V) < Parser(T, V) def initialize(@p : Parser(T, V)) end def parse(tokens : TokenStream(T)) : Result(T, V) result = @p.parse(tokens) Result.new(tokens, result.value) end end class Assert(T, V) < Parser(T, V) def initialize(@p : Parser(T, V), &block : V -> Bool) @f = block end def parse(tokens : TokenStream(T)) : Result(T, V) result = @p.parse(tokens) unless @f.call(result.value) raise ParserException.new("Assert: predicate failed for <#{result.value}>") end result end end class Satisfy(T) < Assert(T, T) def initialize(&block : T -> Bool) super(AnyToken(T).new, &block) end end class Token(T) < Parser(T, T) def initialize(@expected : T) @p = Satisfy(T).new { |x| x == @expected } end def parse(tokens : TokenStream(T)) : Result(T, T) @p.parse(tokens) rescue ex : ParserException raise ParserException.new("Token <#{@expected}>: #{ex.message}") end end class Alt(T, V) < Parser(T, V) def initialize(@p1 : Parser(T, V), @p2 : Parser(T, V)) end def parse(tokens : TokenStream(T)) : Result(T, V) @p1.parse(tokens) rescue ParserException @p2.parse(tokens) end end class Map(T, V, U) < Parser(T, U) def initialize(@p : Parser(T, V), &block : V -> U) @f = block end def parse(tokens : TokenStream(T)) : Result(T, U) result = @p.parse(tokens) Result.new(result.tokens, @f.call(result.value)) end end class Phrase(T, V) @p : Map(T, {V, Nil}, V) def initialize(p : Parser(T, V)) @p = (p + Eof(T).new).map &.first end def parse(tokens : TokenStream(T)) : Result(T, V) @p.parse(tokens) end end class Plus(T, V, U) < Parser(T, {V, U}) def initialize(@p1 : Parser(T, V), @p2 : Parser(T, U)) end def parse(tokens : TokenStream(T)) : Result(T, {V, U}) r1 = @p1.parse(tokens) r2 = @p2.parse(r1.tokens) Result.new(r2.tokens, {r1.value, r2.value}) end end class Recover(T, V) < Parser(T, V) @p : Map(T, V?, V) def initialize(p : Parser(T, V), default : V) @p = Optional.new(p).map { |x| x.nil? ? default : x } end def parse(tokens : TokenStream(T)) : Result(T, V) @p.parse(tokens) end end class Optional(T, V) < Parser(T, V?) def initialize(@p : Parser(T, V)) end def parse(tokens : TokenStream(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 class Tokens(T) < Parser(T, Array(T)) def initialize(@expected : Iterable(T)) end # TODO: this can probably be optimised more for Arrays # TODO: might be better to use #zip? def parse(tokens : TokenStream(T)) : Result(T, Array(T)) parsed_tokens = [] of T @expected.each_with_index do |t, i| r = Token.new(t).parse(tokens[i..]) parsed_tokens << r.value end Result.new(tokens[parsed_tokens.size..], parsed_tokens) end end class Many end class Some end class Exactly end class AtLeast end class AtMost end class Between end class StopAt end class StopAfter end class StopIf end class FirstOf end class SepBy end end