<commit_before>require 'securerandom'
require 'strscan'
require 'pp'

module Dice
  class Parser
    class Error < Exception; end
    class NoModifier < Exception; end

    module Term

      class Number
        attr_accessor :number, :value, :math_symbol
        def initialize( number, sign = :+ )
          @number = number.to_i
          @math_symbol = sign.to_sym
        end

        def roll
          @value = @number
        end

        def sum(other)
          @value.send(@math_symbol, other )
        end

        def output(callbacks)
          "#{@math_symbol} #{callbacks[:number].(self,number)}"
        end
      end

      class Die
        class Modifier
          def self.new(match)
            if match[:reroll]
              Reroll.new(match)
            elsif match[:keep]
              Keep.new(match)
            elsif match[:drop]
              Drop.new(match)
            else
              raise Dice::Parser::NoModifier.new( "No such modifier: #{match[0]}" )
            end
          end

          class Reroll
            def initialize(match)
              if match[:conditional] != ""
                @condition_test = match[:conditional] == "=" ? :== : match[:conditional].to_sym
              else
                @condition_test = :==
              end
              @condition_num  = match[:condition_number].to_i
            end

            def reroll_with?(number)
              number.send( @condition_test, @condition_num )
            end

            def output(callbacks)
            end
          end

          class Keep
            attr_reader :dropped

            def initialize(match)
              @keep_number = match[:keep_num].nil? ? 1 : match[:keep_num].to_i
              @keep_method = match[:keep_lowest] ? :max : :min
              @dropped = []
            end

            def process(numbers)
              @dropped = []
              until numbers.count <= @keep_number
                remove = numbers.send(@keep_method)
                puts "Keep dropping #{remove}" if $DEBUG
                @dropped << remove
                numbers.delete_at( numbers.index( remove ) ) 
              end
              numbers
            end
            
            def output(callbacks)
              output = @dropped.map do |die|
                callbacks[:die].(self, die)
              end
              "(unkept: #{output.join(", ")})"
            end
          end

          class Drop
            attr_reader :dropped

            def initialize(match)
              @drop_number = match[:drop_num].nil? ? 1 : match[:drop_num].to_i
              @drop_method = match[:drop_lowest] ? :min : :max
              @dropped = []
            end

            def process(numbers)
              @dropped = []
              dropped = 0
              until dropped == @drop_number or numbers.count == 0
                remove = numbers.send(@drop_method)
                puts "Dropping #{remove}" if $DEBUG
                @dropped << remove
                dropped += 1
                numbers.delete_at( numbers.index( remove ) )
              end
              numbers
            end

            def output(callbacks)
              output = @dropped.map do |die|
                callbacks[:die].(self,die)
              end
              "(dropped #{output.join(", ")})"
            end
          end
        end

        attr_accessor :count, :size, :math_symbol, :value
        def initialize( options = {} )
          @math_symbol = options[:math_symbol].to_sym
          @modifiers = options[:modifiers]
          @size = options[:size]
          @exploding = options[:exploding]
          @compounding = options[:compounding]
          @penetrating = options[:penetrating]
          @count = options[:count]
          @string = options[:string]
          @value = nil
          @rolls = Hash.new(0)<commit_msg>Detect invalid reroll modifiers<commit_after>require 'securerandom'
require 'strscan'
require 'pp'

module Dice
  class Parser
    class Error < Exception; end
    class NoModifier < Exception; end

    module Term

      class Number
        attr_accessor :number, :value, :math_symbol
        def initialize( number, sign = :+ )
          @number = number.to_i
          @math_symbol = sign.to_sym
        end

        def roll
          @value = @number
        end

        def sum(other)
          @value.send(@math_symbol, other )
        end

        def output(callbacks)
          "#{@math_symbol} #{callbacks[:number].(self,number)}"
        end
      end

      class Die
        class Modifier
          def self.new(match)
            if match[:reroll]
              Reroll.new(match)
            elsif match[:keep]
              Keep.new(match)
            elsif match[:drop]
              Drop.new(match)
            else
              raise Dice::Parser::NoModifier.new( "No such modifier: #{match[0]}" )
            end
          end

          class Reroll
            def initialize(match)
              if match[:conditional] != ""
                @condition_test = match[:conditional] == "=" ? :== : match[:conditional].to_sym
              else
                @condition_test = :==
              end
              @condition_num  = match[:condition_number].to_i
            end

            def reroll_with?(number)
              number.send( @condition_test, @condition_num )
            end

            def output(callbacks)
            end
          end

          class Keep
            attr_reader :dropped

            def initialize(match)
              @keep_number = match[:keep_num].nil? ? 1 : match[:keep_num].to_i
              @keep_method = match[:keep_lowest] ? :max : :min
              @dropped = []
            end

            def process(numbers)
              @dropped = []
              until numbers.count <= @keep_number
                remove = numbers.send(@keep_method)
                puts "Keep dropping #{remove}" if $DEBUG
                @dropped << remove
                numbers.delete_at( numbers.index( remove ) ) 
              end
              numbers
            end
            
            def output(callbacks)
              output = @dropped.map do |die|
                callbacks[:die].(self, die)
              end
              "(unkept: #{output.join(", ")})"
            end
          end

          class Drop
            attr_reader :dropped

            def initialize(match)
              @drop_number = match[:drop_num].nil? ? 1 : match[:drop_num].to_i
              @drop_method = match[:drop_lowest] ? :min : :max
              @dropped = []
            end

            def process(numbers)
              @dropped = []
              dropped = 0
              until dropped == @drop_number or numbers.count == 0
                remove = numbers.send(@drop_method)
                puts "Dropping #{remove}" if $DEBUG
                @dropped << remove
                dropped += 1
                numbers.delete_at( numbers.index( remove ) )
              end
              numbers
            end

            def output(callbacks)
              output = @dropped.map do |die|
                callbacks[:die].(self,die)
              end
              "(dropped #{output.join(", ")})"
            end
          end
        end

        attr_accessor :count, :size, :math_symbol, :value
        def initialize( options = {} )
          @math_symbol = options[:math_symbol].to_sym
          @modifiers = options[:modifiers]
          @size = options[:size]
          @exploding = options[:exploding]
          @compounding = options[:compounding]
          @penetrating = options[:penetrating]
          @count = options[:count]
          @string = options[:string]
          @value = nil
          @rolls = Hash.new(0)
          @reroll_modifiers = @modifiers.select { |m| m.is_a? Modifier::Reroll }
          if (1..@size).all? { |r| @reroll_modifiers.any? { |m| m.reroll_with? r } }
            raise Dice::Parser::Error.new( "Invalid reroll rules: No die roll is possible" )
          end
{'fraction_non_alphanumeric': 0.10038514101130576, 'fraction_numerical': 0.0018635855385762206, 'mean_word_length': 1.3558677202224174, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 3, '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': '2685912', 'n_tokens_mistral': 2180, 'n_tokens_neox': 2103, 'n_words': 497}