<commit_before>require 'tempfile'
require 'open3'

module FFI

  # ConstGenerator turns C constants into ruby values.
  #
  # @example a simple example for stdio
  #  require 'ffi/tools/const_generator'
  #  cg = FFI::ConstGenerator.new('stdio') do |gen|
  #    gen.const(:SEEK_SET)
  #    gen.const('SEEK_CUR')
  #    gen.const('seek_end')   # this constant does not exist
  #  end            # #calculate called automatically at the end of the block
  #
  #  cg['SEEK_SET'] # => 0
  #  cg['SEEK_CUR'] # => 1
  #  cg['seek_end'] # => nil
  #  cg.to_ruby     # => "SEEK_SET = 0\nSEEK_CUR = 1\n# seek_end not available"
  class ConstGenerator
    @options = {}
    attr_reader :constants

    # Creates a new constant generator that uses +prefix+ as a name, and an
    # options hash.
    #
    # The only option is +:required+, which if set to +true+ raises an error if a
    # constant you have requested was not found.
    #
    # @param [#to_s] prefix
    # @param [Hash] options
    # @return
    # @option options [Boolean] :required
    # @overload initialize(prefix, options)
    # @overload initialize(prefix, options) { |gen| ... }
    #  @yieldparam [ConstGenerator] gen new generator is passed to the block
    #  When passed a block, {#calculate} is automatically called at the end of
    #  the block, otherwise you must call it yourself.
    def initialize(prefix = nil, options = {})
      @includes = ['stdio.h', 'stddef.h']
      @constants = {}
      @prefix = prefix

      @required = options[:required]
      @options = options

      if block_given? then
        yield self
        calculate self.class.options.merge(options)
      end
    end
    # Set class options
    # These options are merged with {#initialize} options when it is called with a block.
    # @param [Hash] options
    # @return [Hash] class options
    def self.options=(options)
      @options = options
    end
    # Get class options.
    # @return [Hash] class options
    def self.options
      @options
    end
    # @param [String] name
    # @return constant value (converted if a +converter+ was defined).
    # Access a constant by name.
    def [](name)
      @constants[name].converted_value
    end

    # Request the value for C constant +name+.
    #
    # @param [#to_s] name C constant name
    # @param [String] format a printf format string to print the value out
    # @param [String] cast a C cast for the value
    # @param ruby_name alternate ruby name for {#to_ruby}
    #
    # @overload const(name, format=nil, cast='', ruby_name=nil, converter=nil)
    #  +converter+ is a Method or a Proc.
    #  @param [#call] converter convert the value from a string to the appropriate
    #   type for {#to_ruby}.
    # @overload const(name, format=nil, cast='', ruby_name=nil) { |value| ... }
    #  Use a converter block. This block convert the value from a string to the
    #  appropriate type for {#to_ruby}.
    #  @yieldparam value constant value
    def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
              &converter_proc)
      format ||= '%d'
      cast ||= ''

      if converter_proc and converter then
        raise ArgumentError, "Supply only converter or converter block"
      end

      converter = converter_proc if converter.nil?

      const = Constant.new name, format, cast, ruby_name, converter
      @constants[name.to_s] = const
      return const
    end

    # Calculate constants values.
    # @param [Hash] options
    # @option options [String] :cppflags flags for C compiler
    # @return [nil]
    # @raise if a constant is missing and +:required+ was set to +true+ (see {#initialize})
    def calculate(options = {})
      binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"

      Tempfile.open("#{@prefix}.const_generator") do |f|
        @includes.each do |inc|
          f.puts "#include <#{inc}>"
        end
        f.puts "\nint main(int argc, char **argv)\n{"

        @constants.each_value do |const|
          f.puts <<-EOF
  #ifdef #{const.name}
  printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
  #endif
          EOF
        end

        f.puts "\n\treturn 0;\n}"
        f.flush

        cc = ENV['CC'] || 'gcc'
        output = `#{cc} #{options[:cppflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`

        unless $?.success? then
          output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
          raise "Compilation error generating constants #{@prefix}:\n#{output}"
        end
      end

      output = `#{binary}`
      File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))<commit_msg>prevent usage same binary file simultaneously<commit_after>require 'tempfile'
require 'open3'

module FFI

  # ConstGenerator turns C constants into ruby values.
  #
  # @example a simple example for stdio
  #  require 'ffi/tools/const_generator'
  #  cg = FFI::ConstGenerator.new('stdio') do |gen|
  #    gen.const(:SEEK_SET)
  #    gen.const('SEEK_CUR')
  #    gen.const('seek_end')   # this constant does not exist
  #  end            # #calculate called automatically at the end of the block
  #
  #  cg['SEEK_SET'] # => 0
  #  cg['SEEK_CUR'] # => 1
  #  cg['seek_end'] # => nil
  #  cg.to_ruby     # => "SEEK_SET = 0\nSEEK_CUR = 1\n# seek_end not available"
  class ConstGenerator
    @options = {}
    attr_reader :constants

    # Creates a new constant generator that uses +prefix+ as a name, and an
    # options hash.
    #
    # The only option is +:required+, which if set to +true+ raises an error if a
    # constant you have requested was not found.
    #
    # @param [#to_s] prefix
    # @param [Hash] options
    # @return
    # @option options [Boolean] :required
    # @overload initialize(prefix, options)
    # @overload initialize(prefix, options) { |gen| ... }
    #  @yieldparam [ConstGenerator] gen new generator is passed to the block
    #  When passed a block, {#calculate} is automatically called at the end of
    #  the block, otherwise you must call it yourself.
    def initialize(prefix = nil, options = {})
      @includes = ['stdio.h', 'stddef.h']
      @constants = {}
      @prefix = prefix

      @required = options[:required]
      @options = options

      if block_given? then
        yield self
        calculate self.class.options.merge(options)
      end
    end
    # Set class options
    # These options are merged with {#initialize} options when it is called with a block.
    # @param [Hash] options
    # @return [Hash] class options
    def self.options=(options)
      @options = options
    end
    # Get class options.
    # @return [Hash] class options
    def self.options
      @options
    end
    # @param [String] name
    # @return constant value (converted if a +converter+ was defined).
    # Access a constant by name.
    def [](name)
      @constants[name].converted_value
    end

    # Request the value for C constant +name+.
    #
    # @param [#to_s] name C constant name
    # @param [String] format a printf format string to print the value out
    # @param [String] cast a C cast for the value
    # @param ruby_name alternate ruby name for {#to_ruby}
    #
    # @overload const(name, format=nil, cast='', ruby_name=nil, converter=nil)
    #  +converter+ is a Method or a Proc.
    #  @param [#call] converter convert the value from a string to the appropriate
    #   type for {#to_ruby}.
    # @overload const(name, format=nil, cast='', ruby_name=nil) { |value| ... }
    #  Use a converter block. This block convert the value from a string to the
    #  appropriate type for {#to_ruby}.
    #  @yieldparam value constant value
    def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
              &converter_proc)
      format ||= '%d'
      cast ||= ''

      if converter_proc and converter then
        raise ArgumentError, "Supply only converter or converter block"
      end

      converter = converter_proc if converter.nil?

      const = Constant.new name, format, cast, ruby_name, converter
      @constants[name.to_s] = const
      return const
    end

    # Calculate constants values.
    # @param [Hash] options
    # @option options [String] :cppflags flags for C compiler
    # @return [nil]
    # @raise if a constant is missing and +:required+ was set to +true+ (see {#initialize})
    def calculate(options = {})
      binary_path = nil

      Tempfile.open("#{@prefix}.const_generator") do |f|
        binary_path = f.path + ".bin"
        @includes.each do |inc|
          f.puts "#include <#{inc}>"
        end
        f.puts "\nint main(int argc, char **argv)\n{"

        @constants.each_value do |const|
          f.puts <<-EOF
  #ifdef #{const.name}
  printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
  #endif
          EOF
        end

        f.puts "\n\treturn 0;\n}"
        f.flush

        cc = ENV['CC'] || 'gcc'
        output = `#{cc} #{options[:cppflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary_path} 2>&1`

        unless $?.success? then
          output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
          raise "Compilation error generating constants #{@prefix}:\n#{output}"
        end
      end

      output = `#{binary_path}`
      File.unlink(binary_path + (FFI::Platform.windows? ? ".exe" : ""))
{'fraction_non_alphanumeric': 0.12309805579036348, 'fraction_numerical': 0.00253592561284869, 'mean_word_length': 2.587945413191812, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, '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': '4502630', 'n_tokens_mistral': 3028, 'n_tokens_neox': 2914, 'n_words': 962}