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 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), @f : V -> Bool) end def parse(tokens : TokenStream(T)) : Result(T, V) result = @p.parse(tokens) unless @f.call(result.value) raise ParserException.new("Assert: predicate failed") end result end end class Satisfy(T) < Parser(T, T) def initialize(@f : T -> Bool) end def parse(tokens : TokenStream(T)) : Result(T, T) AnyToken(T).new.assert(@f).parse(tokens) end end class Token(T) < Parser(T, T) def initialize(@expected : T) end def parse(tokens : TokenStream(T)) : Result(T, T) Satisfy(T).new(->(x : T) { x == @expected }).parse(tokens) 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), @f : V -> U) 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) def initialize(@p : Parser(T, V)) end def parse(tokens : TokenStream(T)) : Result(T, V) r = @p.parse(tokens) unless r.tokens.empty? raise ParserException.new("Phrase: some of the input was not parsed") end r 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) def initialize(@p : Parser(T, V), @default : V) end def parse(tokens : TokenStream(T)) : Result(T, V) @p.parse?(tokens) || Result.new(tokens, @default) end end # TODO: find a way to make this use Recover on the back 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 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