<commit_before>require 'time'
require 'rational'

module Qreport
  class TimeParser
    class Error < ::Exception; end

    attr_accessor :input, :result, :now, :debug

    def initialize
      @input = ''
      @unit_for_now = { :today => :day, :t => nil }
      @debug = false # true
      @token = nil
      @token_stack = [ ]
      @taken_tokens = [ ]
    end

    def parse str
      @input = str.dup
      @result = p_start
      @result = @result.value if @result.respond_to?(:value)
      @result
    end

    def _p_start
      # debugger
      p_range or
      p_time_expr or
      raise Error
    end

    def _p_range
      if p_between and a = p_time_expr and p_and and b = p_time_expr
        TimeRange.new(a, b)
      end
    end

    def _p_between
      token.type == :range &&
        token.value == :between &&
        take_token.value
    end

    def _p_and
      token.type == :logical and
        token.value == :and and
        take_token.value
    end

    def _p_time_expr
      case
      when v = p_numeric_relative
        v
      when v = p_time
        v
      when v = p_time_or_date_relative
        v
      when (r = p_relation) && (v = p_time_expr)
        v += r
      else
        return nil
      end

      if (op = p_operation) &&
          (interval = p_interval)
        case op
        when :+
          v += interval
        when :-
          v -= interval
        else
          raise Error, op
        end
      end

      v
    end

    # 10 sec before now
    # 10 sec ago
    def _p_numeric_relative
      if (interval = p_interval)
        case 
        when (direction = p_relation) && (time = p_time_expr)
          time + (interval * direction)
        when (direction = p_relative)
          TimeWithUnit.new(now, interval) + (interval * direction)
        end
      end
    end
    
    # 10 secs|day
    def _p_interval
      case token.value
      when Numeric 
        amount = take_token.value
        case
        when TimeInterval === token.value
          interval = take_token.value
          interval *= amount
        end
      when TimeInterval
        interval = take_token.value
      end
    end

    # before|after|since
    def _p_relation
      token.type == :relation && 
        take_token.value
    end

    # ago
    def _p_relative
      token.type == :relative && 
        take_token.value
    end

    # + interval
    def _p_operation
      token.type == :operation && 
        take_token.value
    end

    def _p_time
      TimeWithUnit === token.value && 
        take_token.value
    end

    def _p_time_or_date_relative
      case
      when tr = _p_time_relative
        dr = _p_date_relative
      when dr = _p_date_relative
        tr = _p_time_relative
      else
        return nil
      end

      tr ||= dr
      tr = tr.dup
      if tr && dr && tr != dr
         tr.merge!(dr)
      end

      unit = tr.unit
      t = p_time_expr || now
      # debugger
      tr.merge!(t)
      t = TimeWithUnit.new(tr.to_time, unit)
      # $stderr.puts "  tr = #{tr.inspect} dr = #{dr.inspect} => t = #{t.inspect}"
      t
    end

    # 12pm|12:30a|12:34:56pm
    def _p_time_relative
      if token.type == :time_relative && TimeRelative === token.value
        tr = take_token.value
      end
    end

    # 2001/01|2001-02-20
    def _p_date_relative
      if token.type == :date_relative && TimeRelative === token.value
        tr = take_token.value
      end
    end

    def restore_tokens_on_failure!(sel)
      restore = true
      (@taken_tokens_stack ||= [ ]) << @taken_tokens
      @taken_tokens = [ ]

      result = yield

      # $stderr.puts "  #{sel.inspect} taken_tokens = #{@taken_tokens.inspect}"
      restore = false if result

      result

    ensure
      if restore && ! @taken_tokens.empty?
        $stderr.puts "  #{sel.inspect} restoring tokens #{@taken_tokens.inspect}"
        @taken_tokens.reverse.each do | t |
          push_token! t
        end 
      end
      @taken_tokens = @taken_tokens_stack.pop
    end

    def method_missing sel, *args, &blk
      if ! block_given? && args.empty? && sel.to_s =~ /^p_/
        result = nil
        if @debug
          @p_depth ||= 0 
          $stderr.puts "  #{' ' * @p_depth} #{sel} ... | #{token.inspect} #{token.value.inspect}"
          @p_depth += 1
        end
        restore_tokens_on_failure!(sel) do
          result = send(:"_#{sel}", *args, &blk)
        end
        if @debug<commit_msg>Debugging output.<commit_after>require 'time'
require 'rational'

module Qreport
  class TimeParser
    class Error < ::Exception; end

    attr_accessor :input, :result, :now, :debug

    def initialize
      @input = ''
      @unit_for_now = { :today => :day, :t => nil }
      @debug = false # true
      @token = nil
      @token_stack = [ ]
      @taken_tokens = [ ]
    end

    def parse str
      @input = str.dup
      @result = p_start
      @result = @result.value if @result.respond_to?(:value)
      @result
    end

    def _p_start
      # debugger
      p_range or
      p_time_expr or
      raise Error
    end

    def _p_range
      if p_between and a = p_time_expr and p_and and b = p_time_expr
        TimeRange.new(a, b)
      end
    end

    def _p_between
      token.type == :range &&
        token.value == :between &&
        take_token.value
    end

    def _p_and
      token.type == :logical and
        token.value == :and and
        take_token.value
    end

    def _p_time_expr
      case
      when v = p_numeric_relative
        v
      when v = p_time
        v
      when v = p_time_or_date_relative
        v
      when (r = p_relation) && (v = p_time_expr)
        v += r
      else
        return nil
      end

      if (op = p_operation) &&
          (interval = p_interval)
        case op
        when :+
          v += interval
        when :-
          v -= interval
        else
          raise Error, op
        end
      end

      v
    end

    # 10 sec before now
    # 10 sec ago
    def _p_numeric_relative
      if (interval = p_interval)
        case 
        when (direction = p_relation) && (time = p_time_expr)
          time + (interval * direction)
        when (direction = p_relative)
          TimeWithUnit.new(now, interval) + (interval * direction)
        end
      end
    end
    
    # 10 secs|day
    def _p_interval
      case token.value
      when Numeric 
        amount = take_token.value
        case
        when TimeInterval === token.value
          interval = take_token.value
          interval *= amount
        end
      when TimeInterval
        interval = take_token.value
      end
    end

    # before|after|since
    def _p_relation
      token.type == :relation && 
        take_token.value
    end

    # ago
    def _p_relative
      token.type == :relative && 
        take_token.value
    end

    # + interval
    def _p_operation
      token.type == :operation && 
        take_token.value
    end

    def _p_time
      TimeWithUnit === token.value && 
        take_token.value
    end

    def _p_time_or_date_relative
      case
      when tr = _p_time_relative
        dr = _p_date_relative
      when dr = _p_date_relative
        tr = _p_time_relative
      else
        return nil
      end

      tr ||= dr
      tr = tr.dup
      if tr && dr && tr != dr
         tr.merge!(dr)
      end

      unit = tr.unit
      t = p_time_expr || now
      # debugger
      tr.merge!(t)
      t = TimeWithUnit.new(tr.to_time, unit)
      # $stderr.puts "  tr = #{tr.inspect} dr = #{dr.inspect} => t = #{t.inspect}"
      t
    end

    # 12pm|12:30a|12:34:56pm
    def _p_time_relative
      if token.type == :time_relative && TimeRelative === token.value
        tr = take_token.value
      end
    end

    # 2001/01|2001-02-20
    def _p_date_relative
      if token.type == :date_relative && TimeRelative === token.value
        tr = take_token.value
      end
    end

    def restore_tokens_on_failure!(sel)
      restore = true
      (@taken_tokens_stack ||= [ ]) << @taken_tokens
      @taken_tokens = [ ]

      result = yield

      # $stderr.puts "  #{sel.inspect} taken_tokens = #{@taken_tokens.inspect}"
      restore = false if result

      result

    ensure
      if restore && ! @taken_tokens.empty?
        $stderr.puts "  #{sel.inspect} restoring tokens #{@taken_tokens.inspect}" if @debug
        @taken_tokens.reverse.each do | t |
          push_token! t
        end 
      end
      @taken_tokens = @taken_tokens_stack.pop
    end

    def method_missing sel, *args, &blk
      if ! block_given? && args.empty? && sel.to_s =~ /^p_/
        result = nil
        if @debug
          @p_depth ||= 0 
          $stderr.puts "  #{' ' * @p_depth} #{sel} ... | #{token.inspect} #{token.value.inspect}"
          @p_depth += 1
        end
        restore_tokens_on_failure!(sel) do
          result = send(:"_#{sel}", *args, &blk)
        end
        if @debug
{'fraction_non_alphanumeric': 0.09583519928347514, 'fraction_numerical': 0.00761307657859382, 'mean_word_length': 1.6546805349182763, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4792798', 'n_tokens_mistral': 2968, 'n_tokens_neox': 2825, 'n_words': 724}