Source

par / lib / par.rb


module Par
  class Result
    attr_reader :obj

    def initialize obj
      @obj = obj
    end

    def >> other
      if other.respond_to? :obj
        return new(
          concat(obj, other.obj))
      end

      other == bot ? bot : self
    end
    def | other
      self
    end
    def maybe
      self
    end

    def new obj
      self.class.new obj
    end
    def bot
      self.class.bot
    end
    def top
      self.class.top
    end
    
    def concat obj1, obj2
      obj1 + obj2
    end

    def to_s
      obj.to_s
    end
    def <=> other
      return obj <=> other.to_str if other.respond_to? :to_str
    end
    include Comparable

    def self.bot
      @bot ||= Bot.new
    end
    def self.top
      @top ||= Top.new
    end

    class Bot
      def >> other
        self
      end
      def | other
        other
      end
      def maybe
        Par.top
      end
    end

    class Top
      def >> other
        other
      end
      def | other
        self
      end
      def maybe
        self
      end
    end
  end
  class Source
    attr_reader :str 

    def initialize str
      @str = str
      @pos = 0
    end

    def string str
      if head(str.size) == str
        return self.n(str.size)
      end

      Par.bot
    end
    def pattern pat
      idx = head.index(pat)
      return bot unless idx == 0
      md =head.match(pat)
      n(md[0].size)
    end

    def n n
      Par.result(head(n)).tap { @pos += n }
    end

    def conditional_rewind
      old_pos = @pos
      r = yield
      old_pos = @pos if r != Par.bot

      return r
    ensure
      @pos = old_pos
    end
    alias :cr :conditional_rewind

    def head n=nil
      n && str[@pos, n] || str[@pos..-1]
    end
  end

module_function
  def result str
    Result.new str
  end
  def source str
    Source.new str
  end
  def bot 
    Result.bot
  end
  def top
    Result.top
  end
end