Warning: file_get_contents(https://raw.githubusercontent.com/Den1xxx/Filemanager/master/languages/ru.json): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 88

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 215

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 216

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 217

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 218

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 219

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 220
PK!:ܶrd.rbnu[# frozen_string_literal: true ## # Parse a RD format file. The parsed RDoc::Markup::Document is attached as a # file comment. class RDoc::Parser::RD < RDoc::Parser include RDoc::Parser::Text parse_files_matching(/\.rd(?:\.[^.]+)?$/) ## # Creates an rd-format TopLevel for the given file. def scan comment = RDoc::Comment.new @content, @top_level comment.format = 'rd' @top_level.comment = comment end end PK!T͋͋c.rbnu[# frozen_string_literal: true require 'tsort' ## # RDoc::Parser::C attempts to parse C extension files. It looks for # the standard patterns that you find in extensions: +rb_define_class+, # +rb_define_method+ and so on. It tries to find the corresponding # C source for the methods and extract comments, but if we fail # we don't worry too much. # # The comments associated with a Ruby method are extracted from the C # comment block associated with the routine that _implements_ that # method, that is to say the method whose name is given in the # +rb_define_method+ call. For example, you might write: # # /* # * Returns a new array that is a one-dimensional flattening of this # * array (recursively). That is, for every element that is an array, # * extract its elements into the new array. # * # * s = [ 1, 2, 3 ] #=> [1, 2, 3] # * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] # * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] # * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # */ # static VALUE # rb_ary_flatten(VALUE ary) # { # ary = rb_obj_dup(ary); # rb_ary_flatten_bang(ary); # return ary; # } # # ... # # void # Init_Array(void) # { # ... # rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); # # Here RDoc will determine from the +rb_define_method+ line that there's a # method called "flatten" in class Array, and will look for the implementation # in the method +rb_ary_flatten+. It will then use the comment from that # method in the HTML output. This method must be in the same source file # as the +rb_define_method+. # # The comment blocks may include special directives: # # [Document-class: +name+] # Documentation for the named class. # # [Document-module: +name+] # Documentation for the named module. # # [Document-const: +name+] # Documentation for the named +rb_define_const+. # # Constant values can be supplied on the first line of the comment like so: # # /* 300: The highest possible score in bowling */ # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); # # The value can contain internal colons so long as they are escaped with a \ # # [Document-global: +name+] # Documentation for the named +rb_define_global_const+ # # [Document-variable: +name+] # Documentation for the named +rb_define_variable+ # # [Document-method\: +method_name+] # Documentation for the named method. Use this when the method name is # unambiguous. # # [Document-method\: ClassName::method_name] # Documentation for a singleton method in the given class. Use this when # the method name alone is ambiguous. # # [Document-method\: ClassName#method_name] # Documentation for a instance method in the given class. Use this when the # method name alone is ambiguous. # # [Document-attr: +name+] # Documentation for the named attribute. # # [call-seq: text up to an empty line] # Because C source doesn't give descriptive names to Ruby-level parameters, # you need to document the calling sequence explicitly # # In addition, RDoc assumes by default that the C method implementing a # Ruby function is in the same source file as the rb_define_method call. # If this isn't the case, add the comment: # # rb_define_method(....); // in filename # # As an example, we might have an extension that defines multiple classes # in its Init_xxx method. We could document them using # # /* # * Document-class: MyClass # * # * Encapsulate the writing and reading of the configuration # * file. ... # */ # # /* # * Document-method: read_value # * # * call-seq: # * cfg.read_value(key) -> value # * cfg.read_value(key} { |key| } -> value # * # * Return the value corresponding to +key+ from the configuration. # * In the second form, if the key isn't found, invoke the # * block and return its value. # */ class RDoc::Parser::C < RDoc::Parser parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) include RDoc::Text # :stopdoc: BOOL_ARG_PATTERN = /\s*+\b([01]|Q?(?:true|false)|TRUE|FALSE)\b\s*/ TRUE_VALUES = ['1', 'TRUE', 'true', 'Qtrue'].freeze # :startdoc: ## # Maps C variable names to names of Ruby classes or modules attr_reader :classes ## # C file the parser is parsing attr_accessor :content ## # Dependencies from a missing enclosing class to the classes in # missing_dependencies that depend upon it. attr_reader :enclosure_dependencies ## # Maps C variable names to names of Ruby classes (and singleton classes) attr_reader :known_classes ## # Classes found while parsing the C file that were not yet registered due to # a missing enclosing class. These are processed by do_missing attr_reader :missing_dependencies ## # Maps C variable names to names of Ruby singleton classes attr_reader :singleton_classes ## # The TopLevel items in the parsed file belong to attr_reader :top_level ## # Prepares for parsing a C file. See RDoc::Parser#initialize for details on # the arguments. def initialize top_level, file_name, content, options, stats super @known_classes = RDoc::KNOWN_CLASSES.dup @content = handle_tab_width handle_ifdefs_in @content @file_dir = File.dirname @file_name @classes = load_variable_map :c_class_variables @singleton_classes = load_variable_map :c_singleton_class_variables @markup = @options.markup # class_variable => { function => [method, ...] } @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } # missing variable => [handle_class_module arguments] @missing_dependencies = {} # missing enclosure variable => [dependent handle_class_module arguments] @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } @enclosure_dependencies.instance_variable_set :@missing_dependencies, @missing_dependencies @enclosure_dependencies.extend TSort def @enclosure_dependencies.tsort_each_node &block each_key(&block) rescue TSort::Cyclic => e cycle_vars = e.message.scan(/"(.*?)"/).flatten cycle = cycle_vars.sort.map do |var_name| delete var_name var_name, type, mod_name, = @missing_dependencies[var_name] "#{type} #{mod_name} (#{var_name})" end.join ', ' warn "Unable to create #{cycle} due to a cyclic class or module creation" retry end def @enclosure_dependencies.tsort_each_child node, &block fetch(node, []).each(&block) end end ## # Scans #content for rb_define_alias def do_aliases @content.scan(/rb_define_alias\s*\( \s*(\w+), \s*"(.+?)", \s*"(.+?)" \s*\)/xm) do |var_name, new_name, old_name| class_name = @known_classes[var_name] unless class_name then @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ var_name, new_name, old_name] next end class_obj = find_class var_name, class_name comment = find_alias_comment var_name, new_name, old_name comment.normalize if comment.to_s.empty? and existing_method = class_obj.method_list.find { |m| m.name == old_name} comment = existing_method.comment end add_alias(var_name, class_obj, old_name, new_name, comment) end end ## # Add alias, either from a direct alias definition, or from two # method that reference the same function. def add_alias(var_name, class_obj, old_name, new_name, comment) al = RDoc::Alias.new '', old_name, new_name, '' al.singleton = @singleton_classes.key? var_name al.comment = comment al.record_location @top_level class_obj.add_alias al @stats.add_alias al al end ## # Scans #content for rb_attr and rb_define_attr def do_attrs @content.scan(/rb_attr\s*\( \s*(\w+), \s*([\w"()]+), #{BOOL_ARG_PATTERN}, #{BOOL_ARG_PATTERN}, \s*\w+\);/xmo) do |var_name, attr_name, read, write| handle_attr var_name, attr_name, read, write end @content.scan(%r%rb_define_attr\( \s*([\w\.]+), \s*"([^"]+)", #{BOOL_ARG_PATTERN}, #{BOOL_ARG_PATTERN}\); %xmo) do |var_name, attr_name, read, write| handle_attr var_name, attr_name, read, write end end ## # Scans #content for boot_defclass def do_boot_defclass @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do |var_name, class_name, parent| parent = nil if parent == "0" handle_class_module(var_name, :class, class_name, parent, nil) end end ## # Scans #content for rb_define_class, boot_defclass, rb_define_class_under # and rb_singleton_class def do_classes_and_modules do_boot_defclass if @file_name == "class.c" @content.scan( %r( (?\s*\(\s*) {0} (?\s*\)\s*) {0} (?\s*"(?\w+)") {0} (?\s*(?: (?[\w\*\s\(\)\.\->]+) | rb_path2class\s*\(\s*"(?[\w:]+)"\s*\) )) {0} (?\w+) {0} (?[\w\.]+)\s* = \s*rb_(?: define_(?: class(?: # rb_define_class(name, parent_name) \(\s* \g, \g \s*\) | _under\g # rb_define_class_under(under, name, parent_name...) \g, \g, \g \g ) | (?) module(?: # rb_define_module(name) \g \g \g | _under\g # rb_define_module_under(under, name) \g, \g \g ) ) | (?(?:\s*"\w+",)*\s*NULL\s*) {0} struct_define(?: \g # rb_struct_define(name, ...) \g, | _under\g # rb_struct_define_under(under, name, ...) \g, \g, | _without_accessor(?: \g # rb_struct_define_without_accessor(name, parent_name, ...) | _under\g # rb_struct_define_without_accessor_under(under, name, parent_name, ...) \g, ) \g, \g, \s*\w+, # Allocation function ) \g \g | singleton_class\g # rb_singleton_class(target_class_name) (?\w+) \g ) )mx ) do if target_class_name = $~[:target_class_name] # rb_singleton_class(target_class_name) handle_singleton $~[:var_name], target_class_name next end var_name = $~[:var_name] type = $~[:module] ? :module : :class class_name = $~[:class_name] parent_name = $~[:parent_name] || $~[:path] under = $~[:under] attributes = $~[:attributes] handle_class_module(var_name, type, class_name, parent_name, under) if attributes and !parent_name # rb_struct_define *not* without_accessor true_flag = 'Qtrue' attributes.scan(/"\K\w+(?=")/) do |attr_name| handle_attr var_name, attr_name, true_flag, true_flag end end end end ## # Scans #content for rb_define_variable, rb_define_readonly_variable, # rb_define_const and rb_define_global_const def do_constants @content.scan(%r%\Wrb_define_ ( variable | readonly_variable | const | global_const ) \s*\( (?:\s*(\w+),)? \s*"(\w+)", \s*(.*?)\s*\)\s*; %xm) do |type, var_name, const_name, definition| var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" handle_constants type, var_name, const_name, definition end @content.scan(%r% \Wrb_curses_define_const \s*\( \s* (\w+) \s* \) \s*;%xm) do |consts| const = consts.first handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" end @content.scan(%r% \Wrb_file_const \s*\( \s* "([^"]+)", \s* (.*?) \s* \) \s*;%xm) do |name, value| handle_constants 'const', 'rb_mFConst', name, value end end ## # Scans #content for rb_include_module def do_includes @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| next unless cls = @classes[c] m = @known_classes[m] || m comment = new_comment '', @top_level, :c incl = cls.add_include RDoc::Include.new(m, comment) incl.record_location @top_level end end ## # Scans #content for rb_define_method, rb_define_singleton_method, # rb_define_module_function, rb_define_private_method, # rb_define_global_function and define_filetest_function def do_methods @content.scan(%r%rb_define_ ( singleton_method | method | module_function | private_method ) \s*\(\s*([\w\.]+), \s*"([^"]+)", \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? %xm) do |type, var_name, meth_name, function, param_count, source_file| # Ignore top-object and weird struct.c dynamic stuff next if var_name == "ruby_top_self" next if var_name == "nstr" var_name = "rb_cObject" if var_name == "rb_mKernel" handle_method(type, var_name, meth_name, function, param_count, source_file) end @content.scan(%r%rb_define_global_function\s*\( \s*"([^"]+)", \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? %xm) do |meth_name, function, param_count, source_file| handle_method("method", "rb_mKernel", meth_name, function, param_count, source_file) end @content.scan(/define_filetest_function\s*\( \s*"([^"]+)", \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| handle_method("method", "rb_mFileTest", meth_name, function, param_count) handle_method("singleton_method", "rb_cFile", meth_name, function, param_count) end end ## # Creates classes and module that were missing were defined due to the file # order being different than the declaration order. def do_missing return if @missing_dependencies.empty? @enclosure_dependencies.tsort.each do |in_module| arguments = @missing_dependencies.delete in_module next unless arguments # dependency on existing class handle_class_module(*arguments) end end ## # Finds the comment for an alias on +class_name+ from +new_name+ to # +old_name+ def find_alias_comment class_name, new_name, old_name content =~ %r%((?>/\*.*?\*/\s+)) rb_define_alias\(\s*#{Regexp.escape class_name}\s*, \s*"#{Regexp.escape new_name}"\s*, \s*"#{Regexp.escape old_name}"\s*\);%xm new_comment($1 || '', @top_level, :c) end ## # Finds a comment for rb_define_attr, rb_attr or Document-attr. # # +var_name+ is the C class variable the attribute is defined on. # +attr_name+ is the attribute's name. # # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or # neither must be provided. def find_attr_comment var_name, attr_name, read = nil, write = nil attr_name = Regexp.escape attr_name rw = if read and write then /\s*#{read}\s*,\s*#{write}\s*/xm else /.*?/m end comment = if @content =~ %r%((?>/\*.*?\*/\s+)) rb_define_attr\((?:\s*#{var_name},)?\s* "#{attr_name}"\s*, #{rw}\)\s*;%xm then $1 elsif @content =~ %r%((?>/\*.*?\*/\s+)) rb_attr\(\s*#{var_name}\s*, \s*#{attr_name}\s*, #{rw},.*?\)\s*;%xm then $1 elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) Document-attr:\s#{attr_name}\s*?\n ((?>(.|\n)*?\*/))%x then "#{$1}\n#{$2}" else '' end new_comment comment, @top_level, :c end ## # Generate a Ruby-method table def gen_body_table file_content table = {} file_content.scan(%r{ ((?>/\*.*?\*/\s*)?) ((?:(?:\w+)\s+)? (?:intern\s+)?VALUE\s+(\w+) \s*(?:\([^)]*\))(?:[^\);]|$)) | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) | ^\s*\#\s*define\s+(\w+)\s+(\w+) }xm) do case when $1 table[$3] = [:func_def, $1, $2, $~.offset(2)] if !table[$3] || table[$3][0] != :func_def when $4 table[$6] = [:macro_def, $4, $5, $~.offset(5), $7] if !table[$6] || table[$6][0] == :macro_alias when $8 table[$8] ||= [:macro_alias, $9] end end table end ## # Find the C code corresponding to a Ruby method def find_body class_name, meth_name, meth_obj, file_content, quiet = false if file_content @body_table ||= {} @body_table[file_content] ||= gen_body_table file_content type, *args = @body_table[file_content][meth_name] end case type when :func_def comment = new_comment args[0], @top_level, :c body = args[1] offset, = args[2] comment.remove_private if comment # try to find the whole body body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content # The comment block may have been overridden with a 'Document-method' # block. This happens in the interpreter when multiple methods are # vectored through to the same C method but those methods are logically # distinct (for example Kernel.hash and Kernel.object_id share the same # implementation override_comment = find_override_comment class_name, meth_obj comment = override_comment if override_comment comment.normalize find_modifiers comment, meth_obj if comment #meth_obj.params = params meth_obj.start_collecting_tokens tk = { :line_no => 1, :char_no => 1, :text => body } meth_obj.add_token tk meth_obj.comment = comment meth_obj.line = file_content[0, offset].count("\n") + 1 body when :macro_def comment = new_comment args[0], @top_level, :c body = args[1] offset, = args[2] find_body class_name, args[3], meth_obj, file_content, true comment.normalize find_modifiers comment, meth_obj meth_obj.start_collecting_tokens tk = { :line_no => 1, :char_no => 1, :text => body } meth_obj.add_token tk meth_obj.comment = comment meth_obj.line = file_content[0, offset].count("\n") + 1 body when :macro_alias # with no comment we hope the aliased definition has it and use it's # definition body = find_body(class_name, args[0], meth_obj, file_content, true) return body if body @options.warn "No definition for #{meth_name}" false else # No body, but might still have an override comment comment = find_override_comment class_name, meth_obj if comment then comment.normalize find_modifiers comment, meth_obj meth_obj.comment = comment '' else @options.warn "No definition for #{meth_name}" false end end end ## # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ def find_class(raw_name, name, base_name = nil) unless @classes[raw_name] if raw_name =~ /^rb_m/ container = @top_level.add_module RDoc::NormalModule, name else container = @top_level.add_class RDoc::NormalClass, name end container.name = base_name if base_name container.record_location @top_level @classes[raw_name] = container end @classes[raw_name] end ## # Look for class or module documentation above Init_+class_name+(void), # in a Document-class +class_name+ (or module) comment or above an # rb_define_class (or module). If a comment is supplied above a matching # Init_ and a rb_define_class the Init_ comment is used. # # /* # * This is a comment for Foo # */ # Init_Foo(void) { # VALUE cFoo = rb_define_class("Foo", rb_cObject); # } # # /* # * Document-class: Foo # * This is a comment for Foo # */ # Init_foo(void) { # VALUE cFoo = rb_define_class("Foo", rb_cObject); # } # # /* # * This is a comment for Foo # */ # VALUE cFoo = rb_define_class("Foo", rb_cObject); def find_class_comment class_name, class_mod comment = nil if @content =~ %r% ((?>/\*.*?\*/\s+)) (static\s+)? void\s+ Init(?:VM)?_(?i:#{class_name})\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xm then comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then comment = "/*\n#{$1}" elsif @content =~ %r%((?>/\*.*?\*/\s+)) ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm then comment = $1 elsif @content =~ %r%((?>/\*.*?\*/\s+)) ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm then comment = $1 else comment = '' end comment = new_comment comment, @top_level, :c comment.normalize look_for_directives_in class_mod, comment class_mod.add_comment comment, @top_level end ## # Generate a const table def gen_const_table file_content table = {} @content.scan(%r{ ((?>^\s*/\*.*?\*/\s+)) rb_define_(\w+)\((?:\s*(?:\w+),)?\s* "(\w+)"\s*, .*?\)\s*; | Document-(?:const|global|variable):\s ((?:\w+::)*\w+) \s*?\n((?>.*?\*/)) }mxi) do case when $1 then table[[$2, $3]] = $1 when $4 then table[$4] = "/*\n" + $5 end end table end ## # Finds a comment matching +type+ and +const_name+ either above the # comment or in the matching Document- section. def find_const_comment(type, const_name, class_name = nil) @const_table ||= {} @const_table[@content] ||= gen_const_table @content table = @const_table[@content] comment = table[[type, const_name]] || (class_name && table[class_name + "::" + const_name]) || table[const_name] || '' new_comment comment, @top_level, :c end ## # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. def find_modifiers comment, meth_obj comment.normalize comment.extract_call_seq meth_obj look_for_directives_in meth_obj, comment end ## # Finds a Document-method override for +meth_obj+ on +class_name+ def find_override_comment class_name, meth_obj name = Regexp.escape meth_obj.name prefix = Regexp.escape meth_obj.name_prefix comment = if @content =~ %r%Document-method: \s+#{class_name}#{prefix}#{name} \s*?\n((?>.*?\*/))%xm then "/*#{$1}" elsif @content =~ %r%Document-method: \s#{name}\s*?\n((?>.*?\*/))%xm then "/*#{$1}" end return unless comment new_comment comment, @top_level, :c end ## # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either # +read+, +write+ or both def handle_attr(var_name, attr_name, read, write) rw = '' rw += 'R' if TRUE_VALUES.include?(read) rw += 'W' if TRUE_VALUES.include?(write) class_name = @known_classes[var_name] return unless class_name class_obj = find_class var_name, class_name return unless class_obj comment = find_attr_comment var_name, attr_name comment.normalize name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') attr = RDoc::Attr.new '', name, rw, comment attr.record_location @top_level class_obj.add_attribute attr @stats.add_attribute attr end ## # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ # named +class_name+ in +parent+ which was assigned to the C +var_name+. def handle_class_module(var_name, type, class_name, parent, in_module) parent_name = @known_classes[parent] || parent if in_module then enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) if enclosure.nil? and enclosure = @known_classes[in_module] then enc_type = /^rb_m/ =~ in_module ? :module : :class handle_class_module in_module, enc_type, enclosure, nil, nil enclosure = @classes[in_module] end unless enclosure then @enclosure_dependencies[in_module] << var_name @missing_dependencies[var_name] = [var_name, type, class_name, parent, in_module] return end else enclosure = @top_level end if type == :class then full_name = if RDoc::ClassModule === enclosure then enclosure.full_name + "::#{class_name}" else class_name end if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then parent_name = $1 end cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name else cm = enclosure.add_module RDoc::NormalModule, class_name end cm.record_location enclosure.top_level find_class_comment cm.full_name, cm case cm when RDoc::NormalClass @stats.add_class cm when RDoc::NormalModule @stats.add_module cm end @classes[var_name] = cm @known_classes[var_name] = cm.full_name @store.add_c_enclosure var_name, cm end ## # Adds constants. By providing some_value: at the start of the comment you # can override the C value of the comment to give a friendly definition. # # /* 300: The perfect score in bowling */ # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); # # Will override INT2FIX(300) with the value +300+ in the output # RDoc. Values may include quotes and escaped colons (\:). def handle_constants(type, var_name, const_name, definition) class_name = @known_classes[var_name] return unless class_name class_obj = find_class var_name, class_name, class_name[/::\K[^:]+\z/] unless class_obj then @options.warn 'Enclosing class or module %p is not known' % [const_name] return end comment = find_const_comment type, const_name, class_name comment.normalize # In the case of rb_define_const, the definition and comment are in # "/* definition: comment */" form. The literal ':' and '\' characters # can be escaped with a backslash. if type.downcase == 'const' then no_match, new_definition, new_comment = comment.text.split(/(\A.*):/) if no_match and no_match.empty? then if new_definition.empty? then # Default to literal C definition new_definition = definition else new_definition = new_definition.gsub("\:", ":") new_definition = new_definition.gsub("\\", '\\') end new_definition.sub!(/\A(\s+)/, '') new_comment = "#{$1}#{new_comment.lstrip}" new_comment = self.new_comment(new_comment, @top_level, :c) con = RDoc::Constant.new const_name, new_definition, new_comment else con = RDoc::Constant.new const_name, definition, comment end else con = RDoc::Constant.new const_name, definition, comment end con.record_location @top_level @stats.add_constant con class_obj.add_constant con end ## # Removes #ifdefs that would otherwise confuse us def handle_ifdefs_in(body) body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') end ## # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned # to +var_name+. +type+ is the type of method definition function used. # +singleton_method+ and +module_function+ create a singleton method. def handle_method(type, var_name, meth_name, function, param_count, source_file = nil) class_name = @known_classes[var_name] singleton = @singleton_classes.key? var_name @methods[var_name][function] << meth_name return unless class_name class_obj = find_class var_name, class_name if existing_method = class_obj.method_list.find { |m| m.c_function == function } add_alias(var_name, class_obj, existing_method.name, meth_name, existing_method.comment) end if class_obj then if meth_name == 'initialize' then meth_name = 'new' singleton = true type = 'method' # force public end meth_obj = RDoc::AnyMethod.new '', meth_name meth_obj.c_function = function meth_obj.singleton = singleton || %w[singleton_method module_function].include?(type) p_count = Integer(param_count) rescue -1 if source_file then file_name = File.join @file_dir, source_file if File.exist? file_name then file_content = File.read file_name else @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" end else file_content = @content end body = find_body class_name, function, meth_obj, file_content if body and meth_obj.document_self then meth_obj.params = if p_count < -1 then # -2 is Array '(*args)' elsif p_count == -1 then # argc, argv rb_scan_args body else args = (1..p_count).map { |i| "p#{i}" } "(#{args.join ', '})" end meth_obj.record_location @top_level if meth_obj.section_title class_obj.temporary_section = class_obj.add_section(meth_obj.section_title) end class_obj.add_method meth_obj @stats.add_method meth_obj meth_obj.visibility = :private if 'private_method' == type end end end ## # Registers a singleton class +sclass_var+ as a singleton of +class_var+ def handle_singleton sclass_var, class_var class_name = @known_classes[class_var] @known_classes[sclass_var] = class_name @singleton_classes[sclass_var] = class_name end ## # Loads the variable map with the given +name+ from the RDoc::Store, if # present. def load_variable_map map_name return {} unless files = @store.cache[map_name] return {} unless name_map = files[@file_name] class_map = {} name_map.each do |variable, name| next unless mod = @store.find_class_or_module(name) class_map[variable] = if map_name == :c_class_variables then mod else name end @known_classes[variable] = name end class_map end ## # Look for directives in a normal comment block: # # /* # * :title: My Awesome Project # */ # # This method modifies the +comment+ def look_for_directives_in context, comment @preprocess.handle comment, context do |directive, param| case directive when 'main' then @options.main_page = param '' when 'title' then @options.default_title = param if @options.respond_to? :default_title= '' end end comment end ## # Extracts parameters from the +method_body+ and returns a method # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT def rb_scan_args method_body method_body =~ /rb_scan_args\((.*?)\)/m return '(*args)' unless $1 $1.split(/,/)[2] =~ /"(.*?)"/ # format argument format = $1.split(//) lead = opt = trail = 0 if format.first =~ /\d/ then lead = $&.to_i format.shift if format.first =~ /\d/ then opt = $&.to_i format.shift if format.first =~ /\d/ then trail = $&.to_i format.shift block_arg = true end end end if format.first == '*' and not block_arg then var = true format.shift if format.first =~ /\d/ then trail = $&.to_i format.shift end end if format.first == ':' then hash = true format.shift end if format.first == '&' then block = true format.shift end # if the format string is not empty there's a bug in the C code, ignore it args = [] position = 1 (1...(position + lead)).each do |index| args << "p#{index}" end position += lead (position...(position + opt)).each do |index| args << "p#{index} = v#{index}" end position += opt if var then args << '*args' position += 1 end (position...(position + trail)).each do |index| args << "p#{index}" end position += trail if hash then args << "p#{position} = {}" end args << '&block' if block "(#{args.join ', '})" end ## # Removes lines that are commented out that might otherwise get picked up # when scanning for classes and methods def remove_commented_out_lines @content = @content.gsub(%r%//.*rb_define_%, '//') end ## # Extracts the classes, modules, methods, attributes, constants and aliases # from a C file and returns an RDoc::TopLevel for this file def scan remove_commented_out_lines do_classes_and_modules do_missing do_constants do_methods do_includes do_aliases do_attrs @store.add_c_variables self @top_level end def new_comment text = nil, location = nil, language = nil RDoc::Comment.new(text, location, language).tap do |comment| comment.format = @markup end end end PK!733text.rbnu[# frozen_string_literal: true ## # Indicates this parser is text and doesn't contain code constructs. # # Include this module in a RDoc::Parser subclass to make it show up as a file, # not as part of a class or module. #-- # This is not named File to avoid overriding ::File module RDoc::Parser::Text end PK! />>ripper_state_lex.rbnu[# frozen_string_literal: true require 'ripper' class RDoc::Parser::RipperStateLex # TODO: Remove this constants after Ruby 2.4 EOL RIPPER_HAS_LEX_STATE = Ripper::Filter.method_defined?(:state) Token = Struct.new(:line_no, :char_no, :kind, :text, :state) EXPR_NONE = 0 EXPR_BEG = 1 EXPR_END = 2 EXPR_ENDARG = 4 EXPR_ENDFN = 8 EXPR_ARG = 16 EXPR_CMDARG = 32 EXPR_MID = 64 EXPR_FNAME = 128 EXPR_DOT = 256 EXPR_CLASS = 512 EXPR_LABEL = 1024 EXPR_LABELED = 2048 EXPR_FITEM = 4096 EXPR_VALUE = EXPR_BEG EXPR_BEG_ANY = (EXPR_BEG | EXPR_MID | EXPR_CLASS) EXPR_ARG_ANY = (EXPR_ARG | EXPR_CMDARG) EXPR_END_ANY = (EXPR_END | EXPR_ENDARG | EXPR_ENDFN) class InnerStateLex < Ripper::Filter attr_accessor :lex_state def initialize(code) @lex_state = EXPR_BEG @in_fname = false @continue = false reset super(code) end def reset @command_start = false @cmd_state = @command_start end def on_nl(tok, data) case @lex_state when EXPR_FNAME, EXPR_DOT @continue = true else @continue = false @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 end data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_ignored_nl(tok, data) case @lex_state when EXPR_FNAME, EXPR_DOT @continue = true else @continue = false @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 end data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_op(tok, data) case tok when '&', '|', '!', '!=', '!~' case @lex_state when EXPR_FNAME, EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_BEG end when '<<' # TODO next token? case @lex_state when EXPR_FNAME, EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_BEG end when '?' @lex_state = EXPR_BEG when '&&', '||', '+=', '-=', '*=', '**=', '&=', '|=', '^=', '<<=', '>>=', '||=', '&&=' @lex_state = EXPR_BEG when '::' case @lex_state when EXPR_ARG, EXPR_CMDARG @lex_state = EXPR_DOT when EXPR_FNAME, EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_BEG end else case @lex_state when EXPR_FNAME, EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_BEG end end data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_kw(tok, data) case tok when 'class' @lex_state = EXPR_CLASS @in_fname = true when 'def' @lex_state = EXPR_FNAME @continue = true @in_fname = true when 'if', 'unless', 'while', 'until' if ((EXPR_MID | EXPR_END | EXPR_ENDARG | EXPR_ENDFN | EXPR_ARG | EXPR_CMDARG) & @lex_state) != 0 # postfix if @lex_state = EXPR_BEG | EXPR_LABEL else @lex_state = EXPR_BEG end when 'begin', 'case', 'when' @lex_state = EXPR_BEG when 'return', 'break' @lex_state = EXPR_MID else if @lex_state == EXPR_FNAME @lex_state = EXPR_END else @lex_state = EXPR_END end end data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_tstring_beg(tok, data) @lex_state = EXPR_BEG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_tstring_end(tok, data) @lex_state = EXPR_END | EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_CHAR(tok, data) @lex_state = EXPR_END data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_period(tok, data) @lex_state = EXPR_DOT data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_int(tok, data) @lex_state = EXPR_END | EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_float(tok, data) @lex_state = EXPR_END | EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_rational(tok, data) @lex_state = EXPR_END | EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_imaginary(tok, data) @lex_state = EXPR_END | EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_symbeg(tok, data) @lex_state = EXPR_FNAME @continue = true @in_fname = true data << Token.new(lineno, column, __method__, tok, @lex_state) end private def on_variables(event, tok, data) if @in_fname @lex_state = EXPR_ENDFN @in_fname = false @continue = false elsif @continue case @lex_state when EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_ENDFN @continue = false end else @lex_state = EXPR_CMDARG end data << Token.new(lineno, column, event, tok, @lex_state) end def on_ident(tok, data) on_variables(__method__, tok, data) end def on_ivar(tok, data) @lex_state = EXPR_END on_variables(__method__, tok, data) end def on_cvar(tok, data) @lex_state = EXPR_END on_variables(__method__, tok, data) end def on_gvar(tok, data) @lex_state = EXPR_END on_variables(__method__, tok, data) end def on_backref(tok, data) @lex_state = EXPR_END on_variables(__method__, tok, data) end def on_lparen(tok, data) @lex_state = EXPR_LABEL | EXPR_BEG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_rparen(tok, data) @lex_state = EXPR_ENDFN data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_lbrace(tok, data) @lex_state = EXPR_LABEL | EXPR_BEG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_rbrace(tok, data) @lex_state = EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_lbracket(tok, data) @lex_state = EXPR_LABEL | EXPR_BEG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_rbracket(tok, data) @lex_state = EXPR_ENDARG data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_const(tok, data) case @lex_state when EXPR_FNAME @lex_state = EXPR_ENDFN when EXPR_CLASS, EXPR_CMDARG, EXPR_MID @lex_state = EXPR_ARG else @lex_state = EXPR_CMDARG end data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_sp(tok, data) data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_comma(tok, data) @lex_state = EXPR_BEG | EXPR_LABEL if (EXPR_ARG_ANY & @lex_state) != 0 data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_comment(tok, data) @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_ignored_sp(tok, data) @lex_state = EXPR_BEG unless (EXPR_LABEL & @lex_state) != 0 data << Token.new(lineno, column, __method__, tok, @lex_state) end def on_heredoc_beg(tok, data) data << Token.new(lineno, column, __method__, tok, @lex_state) @lex_state = EXPR_END data end def on_heredoc_end(tok, data) data << Token.new(lineno, column, __method__, tok, @lex_state) @lex_state = EXPR_BEG data end def on_default(event, tok, data) reset data << Token.new(lineno, column, event, tok, @lex_state) end end unless RIPPER_HAS_LEX_STATE class InnerStateLex < Ripper::Filter def initialize(code) super(code) end def on_default(event, tok, data) data << Token.new(lineno, column, event, tok, state) end end if RIPPER_HAS_LEX_STATE def get_squashed_tk if @buf.empty? tk = @tokens.shift else tk = @buf.shift end return nil if tk.nil? case tk[:kind] when :on_symbeg then tk = get_symbol_tk(tk) when :on_tstring_beg then tk = get_string_tk(tk) when :on_backtick then if (tk[:state] & (EXPR_FNAME | EXPR_ENDFN)) != 0 @inner_lex.lex_state = EXPR_ARG unless RIPPER_HAS_LEX_STATE tk[:kind] = :on_ident tk[:state] = Ripper::Lexer.const_defined?(:State) ? Ripper::Lexer::State.new(EXPR_ARG) : EXPR_ARG else tk = get_string_tk(tk) end when :on_regexp_beg then tk = get_regexp_tk(tk) when :on_embdoc_beg then tk = get_embdoc_tk(tk) when :on_heredoc_beg then @heredoc_queue << retrieve_heredoc_info(tk) @inner_lex.lex_state = EXPR_END unless RIPPER_HAS_LEX_STATE when :on_nl, :on_ignored_nl, :on_comment, :on_heredoc_end then if !@heredoc_queue.empty? get_heredoc_tk(*@heredoc_queue.shift) elsif tk[:text].nil? # :on_ignored_nl sometimes gives nil tk[:text] = '' end when :on_words_beg then tk = get_words_tk(tk) when :on_qwords_beg then tk = get_words_tk(tk) when :on_symbols_beg then tk = get_words_tk(tk) when :on_qsymbols_beg then tk = get_words_tk(tk) when :on_op then if '&.' == tk[:text] tk[:kind] = :on_period else tk = get_op_tk(tk) end end tk end private def get_symbol_tk(tk) is_symbol = true symbol_tk = Token.new(tk.line_no, tk.char_no, :on_symbol) if ":'" == tk[:text] or ':"' == tk[:text] tk1 = get_string_tk(tk) symbol_tk[:text] = tk1[:text] symbol_tk[:state] = tk1[:state] else case (tk1 = get_squashed_tk)[:kind] when :on_ident symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_tstring_content symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = get_squashed_tk[:state] # skip :on_tstring_end when :on_tstring_end symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_op symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_ivar symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_cvar symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_gvar symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_const symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] when :on_kw symbol_tk[:text] = ":#{tk1[:text]}" symbol_tk[:state] = tk1[:state] else is_symbol = false tk = tk1 end end if is_symbol tk = symbol_tk end tk end private def get_string_tk(tk) string = tk[:text] state = nil kind = :on_tstring loop do inner_str_tk = get_squashed_tk if inner_str_tk.nil? break elsif :on_tstring_end == inner_str_tk[:kind] string = string + inner_str_tk[:text] state = inner_str_tk[:state] break elsif :on_label_end == inner_str_tk[:kind] string = string + inner_str_tk[:text] state = inner_str_tk[:state] kind = :on_symbol break else string = string + inner_str_tk[:text] if :on_embexpr_beg == inner_str_tk[:kind] then kind = :on_dstring if :on_tstring == kind end end end Token.new(tk.line_no, tk.char_no, kind, string, state) end private def get_regexp_tk(tk) string = tk[:text] state = nil loop do inner_str_tk = get_squashed_tk if inner_str_tk.nil? break elsif :on_regexp_end == inner_str_tk[:kind] string = string + inner_str_tk[:text] state = inner_str_tk[:state] break else string = string + inner_str_tk[:text] end end Token.new(tk.line_no, tk.char_no, :on_regexp, string, state) end private def get_embdoc_tk(tk) string = tk[:text] until :on_embdoc_end == (embdoc_tk = get_squashed_tk)[:kind] do string = string + embdoc_tk[:text] end string = string + embdoc_tk[:text] Token.new(tk.line_no, tk.char_no, :on_embdoc, string, embdoc_tk.state) end private def get_heredoc_tk(heredoc_name, indent) string = '' start_tk = nil prev_tk = nil until heredoc_end?(heredoc_name, indent, tk = @tokens.shift) do start_tk = tk unless start_tk if (prev_tk.nil? or "\n" == prev_tk[:text][-1]) and 0 != tk[:char_no] string = string + (' ' * tk[:char_no]) end string = string + tk[:text] prev_tk = tk end start_tk = tk unless start_tk prev_tk = tk unless prev_tk @buf.unshift tk # closing heredoc heredoc_tk = Token.new(start_tk.line_no, start_tk.char_no, :on_heredoc, string, prev_tk.state) @buf.unshift heredoc_tk end private def retrieve_heredoc_info(tk) name = tk[:text].gsub(/\A<<[-~]?(['"`]?)(.+)\1\z/, '\2') indent = tk[:text] =~ /\A<<[-~]/ [name, indent] end private def heredoc_end?(name, indent, tk) result = false if :on_heredoc_end == tk[:kind] then tk_name = tk[:text].chomp tk_name.lstrip! if indent if name == tk_name result = true end end result end private def get_words_tk(tk) string = '' start_token = tk[:text] start_quote = tk[:text].rstrip[-1] line_no = tk[:line_no] char_no = tk[:char_no] state = tk[:state] end_quote = case start_quote when ?( then ?) when ?[ then ?] when ?{ then ?} when ?< then ?> else start_quote end end_token = nil loop do tk = get_squashed_tk if tk.nil? end_token = end_quote break elsif :on_tstring_content == tk[:kind] then string += tk[:text] elsif :on_words_sep == tk[:kind] or :on_tstring_end == tk[:kind] then if end_quote == tk[:text].strip then end_token = tk[:text] break else string += tk[:text] end else string += tk[:text] end end text = "#{start_token}#{string}#{end_token}" Token.new(line_no, char_no, :on_dstring, text, state) end private def get_op_tk(tk) redefinable_operators = %w[! != !~ % & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] []= ^ ` | ~] if redefinable_operators.include?(tk[:text]) and tk[:state] == EXPR_ARG then @inner_lex.lex_state = EXPR_ARG unless RIPPER_HAS_LEX_STATE tk[:state] = Ripper::Lexer.const_defined?(:State) ? Ripper::Lexer::State.new(EXPR_ARG) : EXPR_ARG tk[:kind] = :on_ident elsif tk[:text] =~ /^[-+]$/ then tk_ahead = get_squashed_tk case tk_ahead[:kind] when :on_int, :on_float, :on_rational, :on_imaginary then tk[:text] += tk_ahead[:text] tk[:kind] = tk_ahead[:kind] tk[:state] = tk_ahead[:state] when :on_heredoc_beg, :on_tstring, :on_dstring # frozen/non-frozen string literal tk[:text] += tk_ahead[:text] tk[:kind] = tk_ahead[:kind] tk[:state] = tk_ahead[:state] else @buf.unshift tk_ahead end end tk end def initialize(code) @buf = [] @heredoc_queue = [] @inner_lex = InnerStateLex.new(code) @tokens = @inner_lex.parse([]) end def self.parse(code) lex = self.new(code) tokens = [] begin while tk = lex.get_squashed_tk tokens.push tk end rescue StopIteration end tokens end def self.end?(token) (token[:state] & EXPR_END) end end PK!U simple.rbnu[# frozen_string_literal: true ## # Parse a non-source file. We basically take the whole thing as one big # comment. class RDoc::Parser::Simple < RDoc::Parser include RDoc::Parser::Text parse_files_matching(//) attr_reader :content # :nodoc: ## # Prepare to parse a plain file def initialize(top_level, file_name, content, options, stats) super preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include @content = preprocess.handle @content, @top_level end ## # Extract the file contents and attach them to the TopLevel as a comment def scan comment = remove_coding_comment @content comment = remove_private_comment comment comment = RDoc::Comment.new comment, @top_level @top_level.comment = comment @top_level end ## # Removes the encoding magic comment from +text+ def remove_coding_comment text text.sub(/\A# .*coding[=:].*$/, '') end ## # Removes private comments. # # Unlike RDoc::Comment#remove_private this implementation only looks for two # dashes at the beginning of the line. Three or more dashes are considered # to be a rule and ignored. def remove_private_comment comment # Workaround for gsub encoding for Ruby 1.9.2 and earlier empty = '' empty = RDoc::Encoding.change_encoding empty, comment.encoding comment = comment.gsub(%r%^--\n.*?^\+\+\n?%m, empty) comment.sub(%r%^--\n.*%m, empty) end end PK!4QՆ ruby_tools.rbnu[# frozen_string_literal: true ## # Collection of methods for writing parsers module RDoc::Parser::RubyTools ## # Adds a token listener +obj+, but you should probably use token_listener def add_token_listener(obj) @token_listeners ||= [] @token_listeners << obj end ## # Fetches the next token from the scanner def get_tk tk = nil if @tokens.empty? then if @scanner_point >= @scanner.size return nil else tk = @scanner[@scanner_point] @scanner_point += 1 @read.push tk[:text] end else @read.push @unget_read.shift tk = @tokens.shift end if tk == nil || :on___end__ == tk[:kind] tk = nil end return nil unless tk # inform any listeners of our shiny new token @token_listeners.each do |obj| obj.add_token(tk) end if @token_listeners tk end ## # Reads and returns all tokens up to one of +tokens+. Leaves the matched # token in the token list. def get_tk_until(*tokens) read = [] loop do tk = get_tk case tk when *tokens then unget_tk tk break end read << tk end read end ## # Retrieves a String representation of the read tokens def get_tkread read = @read.join("") @read = [] read end ## # Peek equivalent for get_tkread def peek_read @read.join('') end ## # Peek at the next token, but don't remove it from the stream def peek_tk unget_tk(tk = get_tk) tk end ## # Removes the token listener +obj+ def remove_token_listener(obj) @token_listeners.delete(obj) end ## # Resets the tools def reset @read = [] @tokens = [] @unget_read = [] @nest = 0 @scanner_point = 0 end ## # Skips whitespace tokens including newlines def skip_tkspace tokens = [] while (tk = get_tk) and (:on_sp == tk[:kind] or :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]) do tokens.push(tk) end unget_tk(tk) tokens end ## # Skips whitespace tokens excluding newlines def skip_tkspace_without_nl tokens = [] while (tk = get_tk) and :on_sp == tk[:kind] do tokens.push(tk) end unget_tk(tk) tokens end ## # Has +obj+ listen to tokens def token_listener(obj) add_token_listener obj yield ensure remove_token_listener obj end ## # Returns +tk+ to the scanner def unget_tk(tk) @tokens.unshift tk @unget_read.unshift @read.pop # Remove this token from any listeners @token_listeners.each do |obj| obj.pop_token end if @token_listeners nil end end PK!_A markdown.rbnu[# frozen_string_literal: true ## # Parse a Markdown format file. The parsed RDoc::Markup::Document is attached # as a file comment. class RDoc::Parser::Markdown < RDoc::Parser include RDoc::Parser::Text parse_files_matching(/\.(md|markdown)(?:\.[^.]+)?$/) ## # Creates an Markdown-format TopLevel for the given file. def scan comment = RDoc::Comment.new @content, @top_level comment.format = 'markdown' @top_level.comment = comment end end PK!5nnruby.rbnu[# frozen_string_literal: true ## # This file contains stuff stolen outright from: # # rtags.rb - # ruby-lex.rb - ruby lexcal analyzer # ruby-token.rb - ruby tokens # by Keiju ISHITSUKA (Nippon Rational Inc.) # ## # Extracts code elements from a source file returning a TopLevel object # containing the constituent file elements. # # This file is based on rtags # # RubyParser understands how to document: # * classes # * modules # * methods # * constants # * aliases # * private, public, protected # * private_class_function, public_class_function # * private_constant, public_constant # * module_function # * attr, attr_reader, attr_writer, attr_accessor # * extra accessors given on the command line # * metaprogrammed methods # * require # * include # # == Method Arguments # #-- # NOTE: I don't think this works, needs tests, remove the paragraph following # this block when known to work # # The parser extracts the arguments from the method definition. You can # override this with a custom argument definition using the :args: directive: # # ## # # This method tries over and over until it is tired # # def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try # puts thing_to_try # go_go_go thing_to_try, tries - 1 # end # # If you have a more-complex set of overrides you can use the :call-seq: # directive: #++ # # The parser extracts the arguments from the method definition. You can # override this with a custom argument definition using the :call-seq: # directive: # # ## # # This method can be called with a range or an offset and length # # # # :call-seq: # # my_method(Range) # # my_method(offset, length) # # def my_method(*args) # end # # The parser extracts +yield+ expressions from method bodies to gather the # yielded argument names. If your method manually calls a block instead of # yielding or you want to override the discovered argument names use # the :yields: directive: # # ## # # My method is awesome # # def my_method(&block) # :yields: happy, times # block.call 1, 2 # end # # == Metaprogrammed Methods # # To pick up a metaprogrammed method, the parser looks for a comment starting # with '##' before an identifier: # # ## # # This is a meta-programmed method! # # add_my_method :meta_method, :arg1, :arg2 # # The parser looks at the token after the identifier to determine the name, in # this example, :meta_method. If a name cannot be found, a warning is printed # and 'unknown is used. # # You can force the name of a method using the :method: directive: # # ## # # :method: some_method! # # By default, meta-methods are instance methods. To indicate that a method is # a singleton method instead use the :singleton-method: directive: # # ## # # :singleton-method: # # You can also use the :singleton-method: directive with a name: # # ## # # :singleton-method: some_method! # # You can define arguments for metaprogrammed methods via either the # :call-seq:, :arg: or :args: directives. # # Additionally you can mark a method as an attribute by # using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like # for :method:, the name is optional. # # ## # # :attr_reader: my_attr_name # # == Hidden methods and attributes # # You can provide documentation for methods that don't appear using # the :method:, :singleton-method: and :attr: directives: # # ## # # :attr_writer: ghost_writer # # There is an attribute here, but you can't see it! # # ## # # :method: ghost_method # # There is a method here, but you can't see it! # # ## # # this is a comment for a regular method # # def regular_method() end # # Note that by default, the :method: directive will be ignored if there is a # standard rdocable item following it. require 'ripper' require_relative 'ripper_state_lex' class RDoc::Parser::Ruby < RDoc::Parser parse_files_matching(/\.rbw?$/) include RDoc::TokenStream include RDoc::Parser::RubyTools ## # RDoc::NormalClass type NORMAL = "::" ## # RDoc::SingleClass type SINGLE = "<<" ## # Creates a new Ruby parser. def initialize(top_level, file_name, content, options, stats) super content = handle_tab_width(content) @size = 0 @token_listeners = nil content = RDoc::Encoding.remove_magic_comment content @scanner = RDoc::Parser::RipperStateLex.parse(content) @content = content @scanner_point = 0 @prev_seek = nil @markup = @options.markup @track_visibility = :nodoc != @options.visibility @encoding = @options.encoding reset end def tk_nl?(tk) :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] end ## # Retrieves the read token stream and replaces +pattern+ with +replacement+ # using gsub. If the result is only a ";" returns an empty string. def get_tkread_clean pattern, replacement # :nodoc: read = get_tkread.gsub(pattern, replacement).strip return '' if read == ';' read end ## # Extracts the visibility information for the visibility token +tk+ # and +single+ class type identifier. # # Returns the visibility type (a string), the visibility (a symbol) and # +singleton+ if the methods following should be converted to singleton # methods. def get_visibility_information tk, single # :nodoc: vis_type = tk[:text] singleton = single == SINGLE vis = case vis_type when 'private' then :private when 'protected' then :protected when 'public' then :public when 'private_class_method' then singleton = true :private when 'public_class_method' then singleton = true :public when 'module_function' then singleton = true :public else raise RDoc::Error, "Invalid visibility: #{tk.name}" end return vis_type, vis, singleton end ## # Look for the first comment in a file that isn't a shebang line. def collect_first_comment skip_tkspace comment = ''.dup comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding first_line = true first_comment_tk_kind = nil line_no = nil tk = get_tk while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) comment_body = retrieve_comment_body(tk) if first_line and comment_body =~ /\A#!/ then skip_tkspace tk = get_tk elsif first_line and comment_body =~ /\A#\s*-\*-/ then first_line = false skip_tkspace tk = get_tk else break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind] first_comment_tk_kind = tk[:kind] line_no = tk[:line_no] if first_line first_line = false comment << comment_body tk = get_tk if :on_nl === tk then skip_tkspace_without_nl tk = get_tk end end end unget_tk tk new_comment comment, line_no end ## # Consumes trailing whitespace from the token stream def consume_trailing_spaces # :nodoc: skip_tkspace_without_nl end ## # Creates a new attribute in +container+ with +name+. def create_attr container, single, name, rw, comment # :nodoc: att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE record_location att container.add_attribute att @stats.add_attribute att att end ## # Creates a module alias in +container+ at +rhs_name+ (or at the top-level # for "::") with the name from +constant+. def create_module_alias container, constant, rhs_name # :nodoc: mod = if rhs_name =~ /^::/ then @store.find_class_or_module rhs_name else container.find_module_named rhs_name end container.add_module_alias mod, rhs_name, constant, @top_level end ## # Aborts with +msg+ def error(msg) msg = make_message msg abort msg end ## # Looks for a true or false token. def get_bool skip_tkspace tk = get_tk if :on_kw == tk[:kind] && 'true' == tk[:text] true elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text]) false else unget_tk tk true end end ## # Look for the name of a class of module (optionally with a leading :: or # with :: separated named) and return the ultimate name, the associated # container, and the given name (with the ::). def get_class_or_module container, ignore_constants = false skip_tkspace name_t = get_tk given_name = ''.dup # class ::A -> A is in the top level if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug name_t = get_tk container = @top_level given_name << '::' end skip_tkspace_without_nl given_name << name_t[:text] is_self = name_t[:kind] == :on_op && name_t[:text] == '<<' new_modules = [] while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do prev_container = container container = container.find_module_named name_t[:text] container ||= if ignore_constants then c = RDoc::NormalModule.new name_t[:text] c.store = @store new_modules << [prev_container, c] c else c = prev_container.add_module RDoc::NormalModule, name_t[:text] c.ignore unless prev_container.document_children @top_level.add_to_classes_or_modules c c end record_location container get_tk skip_tkspace if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::() parse_method_or_yield_parameters break end name_t = get_tk unless :on_const == name_t[:kind] || :on_ident == name_t[:kind] raise RDoc::Error, "Invalid class or module definition: #{given_name}" end if prev_container == container and !ignore_constants given_name = name_t[:text] else given_name << '::' + name_t[:text] end end skip_tkspace_without_nl return [container, name_t, given_name, new_modules] end ## # Skip opening parentheses and yield the block. # Skip closing parentheses too when exists. def skip_parentheses(&block) left_tk = peek_tk if :on_lparen == left_tk[:kind] get_tk ret = skip_parentheses(&block) right_tk = peek_tk if :on_rparen == right_tk[:kind] get_tk end ret else yield end end ## # Return a superclass, which can be either a constant of an expression def get_class_specification tk = peek_tk if tk.nil? return '' elsif :on_kw == tk[:kind] && 'self' == tk[:text] return 'self' elsif :on_gvar == tk[:kind] return '' end res = get_constant skip_tkspace_without_nl get_tkread # empty out read buffer tk = get_tk return res unless tk case tk[:kind] when :on_nl, :on_comment, :on_embdoc, :on_semicolon then unget_tk(tk) return res end res += parse_call_parameters(tk) res end ## # Parse a constant, which might be qualified by one or more class or module # names def get_constant res = "" skip_tkspace_without_nl tk = get_tk while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do res += tk[:text] tk = get_tk end unget_tk(tk) res end ## # Get an included module that may be surrounded by parens def get_included_module_with_optional_parens skip_tkspace_without_nl get_tkread tk = get_tk end_token = get_end_token tk return '' unless end_token nest = 0 continue = false only_constant = true while tk != nil do is_element_of_constant = false case tk[:kind] when :on_semicolon then break if nest == 0 when :on_lbracket then nest += 1 when :on_rbracket then nest -= 1 when :on_lbrace then nest += 1 when :on_rbrace then nest -= 1 if nest <= 0 # we might have a.each { |i| yield i } unget_tk(tk) if nest < 0 break end when :on_lparen then nest += 1 when end_token[:kind] then if end_token[:kind] == :on_rparen nest -= 1 break if nest <= 0 else break if nest <= 0 end when :on_rparen then nest -= 1 when :on_comment, :on_embdoc then @read.pop if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and (!continue or (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) != 0) then break if !continue and nest <= 0 end when :on_comma then continue = true when :on_ident then continue = false if continue when :on_kw then case tk[:text] when 'def', 'do', 'case', 'for', 'begin', 'class', 'module' nest += 1 when 'if', 'unless', 'while', 'until', 'rescue' # postfix if/unless/while/until/rescue must be EXPR_LABEL nest += 1 unless (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) != 0 when 'end' nest -= 1 break if nest == 0 end when :on_const then is_element_of_constant = true when :on_op then is_element_of_constant = true if '::' == tk[:text] end only_constant = false unless is_element_of_constant tk = get_tk end if only_constant get_tkread_clean(/\s+/, ' ') else '' end end ## # Little hack going on here. In the statement: # # f = 2*(1+yield) # # We see the RPAREN as the next token, so we need to exit early. This still # won't catch all cases (such as "a = yield + 1" def get_end_token tk # :nodoc: case tk[:kind] when :on_lparen token = RDoc::Parser::RipperStateLex::Token.new token[:kind] = :on_rparen token[:text] = ')' token when :on_rparen nil else token = RDoc::Parser::RipperStateLex::Token.new token[:kind] = :on_nl token[:text] = "\n" token end end ## # Retrieves the method container for a singleton method. def get_method_container container, name_t # :nodoc: prev_container = container container = container.find_module_named(name_t[:text]) unless container then constant = prev_container.constants.find do |const| const.name == name_t[:text] end if constant then parse_method_dummy prev_container return end end unless container then # TODO seems broken, should starting at Object in @store obj = name_t[:text].split("::").inject(Object) do |state, item| state.const_get(item) end rescue nil type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule unless [Class, Module].include?(obj.class) then warn("Couldn't find #{name_t[:text]}. Assuming it's a module") end if type == RDoc::NormalClass then sclass = obj.superclass ? obj.superclass.name : nil container = prev_container.add_class type, name_t[:text], sclass else container = prev_container.add_module type, name_t[:text] end record_location container end container end ## # Extracts a name or symbol from the token stream. def get_symbol_or_name tk = get_tk case tk[:kind] when :on_symbol then text = tk[:text].sub(/^:/, '') next_tk = peek_tk if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then get_tk text << '=' end text when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then tk[:text] when :on_tstring, :on_dstring then tk[:text][1..-2] else raise RDoc::Error, "Name or symbol expected (got #{tk})" end end ## # Marks containers between +container+ and +ancestor+ as ignored def suppress_parents container, ancestor # :nodoc: while container and container != ancestor do container.suppress unless container.documented? container = container.parent end end ## # Look for directives in a normal comment block: # # # :stopdoc: # # Don't display comment from this point forward # # This routine modifies its +comment+ parameter. def look_for_directives_in container, comment @preprocess.handle comment, container do |directive, param| case directive when 'method', 'singleton-method', 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then false # handled elsewhere when 'section' then break unless container.kind_of?(RDoc::Context) container.set_current_section param, comment.dup comment.text = '' break end end comment.remove_private end ## # Adds useful info about the parser to +message+ def make_message message prefix = "#{@file_name}:".dup tk = peek_tk prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk "#{prefix} #{message}" end ## # Creates a comment with the correct format def new_comment comment, line_no = nil c = RDoc::Comment.new comment, @top_level, :ruby c.line = line_no c.format = @markup c end ## # Creates an RDoc::Attr for the name following +tk+, setting the comment to # +comment+. def parse_attr(context, single, tk, comment) line_no = tk[:line_no] args = parse_symbol_arg 1 if args.size > 0 then name = args[0] rw = "R" skip_tkspace_without_nl tk = get_tk if :on_comma == tk[:kind] then rw = "RW" if get_bool else unget_tk tk end att = create_attr context, single, name, rw, comment att.line = line_no read_documentation_modifiers att, RDoc::ATTR_MODIFIERS else warn "'attr' ignored - looks like a variable" end end ## # Creates an RDoc::Attr for each attribute listed after +tk+, setting the # comment for each to +comment+. def parse_attr_accessor(context, single, tk, comment) line_no = tk[:line_no] args = parse_symbol_arg rw = "?" tmp = RDoc::CodeObject.new read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS # TODO In most other places we let the context keep track of document_self # and add found items appropriately but here we do not. I'm not sure why. return if @track_visibility and not tmp.document_self case tk[:text] when "attr_reader" then rw = "R" when "attr_writer" then rw = "W" when "attr_accessor" then rw = "RW" else rw = '?' end for name in args att = create_attr context, single, name, rw, comment att.line = line_no end end ## # Parses an +alias+ in +context+ with +comment+ def parse_alias(context, single, tk, comment) line_no = tk[:line_no] skip_tkspace if :on_lparen === peek_tk[:kind] then get_tk skip_tkspace end new_name = get_symbol_or_name skip_tkspace if :on_comma === peek_tk[:kind] then get_tk skip_tkspace end begin old_name = get_symbol_or_name rescue RDoc::Error return end al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, single == SINGLE) record_location al al.line = line_no read_documentation_modifiers al, RDoc::ATTR_MODIFIERS context.add_alias al @stats.add_alias al al end ## # Extracts call parameters from the token stream. def parse_call_parameters(tk) end_token = case tk[:kind] when :on_lparen :on_rparen when :on_rparen return "" else :on_nl end nest = 0 loop do break if tk.nil? case tk[:kind] when :on_semicolon break when :on_lparen nest += 1 when end_token if end_token == :on_rparen nest -= 1 break if RDoc::Parser::RipperStateLex.end?(tk) and nest <= 0 else break if RDoc::Parser::RipperStateLex.end?(tk) end when :on_comment, :on_embdoc unget_tk(tk) break when :on_op if tk[:text] =~ /^(.{1,2})?=$/ unget_tk(tk) break end end tk = get_tk end get_tkread_clean "\n", " " end ## # Parses a class in +context+ with +comment+ def parse_class container, single, tk, comment line_no = tk[:line_no] declaration_context = container container, name_t, given_name, = get_class_or_module container if name_t[:kind] == :on_const cls = parse_class_regular container, declaration_context, single, name_t, given_name, comment elsif name_t[:kind] == :on_op && name_t[:text] == '<<' case name = skip_parentheses { get_class_specification } when 'self', container.name read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS parse_statements container, SINGLE return # don't update line else cls = parse_class_singleton container, name, comment end else warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}" return end cls.line = line_no # after end modifiers read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS cls end ## # Parses and creates a regular class def parse_class_regular container, declaration_context, single, # :nodoc: name_t, given_name, comment superclass = '::Object' if given_name =~ /^::/ then declaration_context = @top_level given_name = $' end tk = peek_tk if tk[:kind] == :on_op && tk[:text] == '<' then get_tk skip_tkspace superclass = get_class_specification superclass = '(unknown)' if superclass.empty? end cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass cls = declaration_context.add_class cls_type, given_name, superclass cls.ignore unless container.document_children read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS record_location cls cls.add_comment comment, @top_level @top_level.add_to_classes_or_modules cls @stats.add_class cls suppress_parents container, declaration_context unless cls.document_self parse_statements cls cls end ## # Parses a singleton class in +container+ with the given +name+ and # +comment+. def parse_class_singleton container, name, comment # :nodoc: other = @store.find_class_named name unless other then if name =~ /^::/ then name = $' container = @top_level end other = container.add_module RDoc::NormalModule, name record_location other # class << $gvar other.ignore if name.empty? other.add_comment comment, @top_level end # notify :nodoc: all if not a constant-named class/module # (and remove any comment) unless name =~ /\A(::)?[A-Z]/ then other.document_self = nil other.document_children = false other.clear_comment end @top_level.add_to_classes_or_modules other @stats.add_class other read_documentation_modifiers other, RDoc::CLASS_MODIFIERS parse_statements(other, SINGLE) other end ## # Parses a constant in +context+ with +comment+. If +ignore_constants+ is # true, no found constants will be added to RDoc. def parse_constant container, tk, comment, ignore_constants = false line_no = tk[:line_no] name = tk[:text] skip_tkspace_without_nl return unless name =~ /^\w+$/ new_modules = [] if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then unget_tk tk container, name_t, _, new_modules = get_class_or_module container, true name = name_t[:text] end is_array_or_hash = false if peek_tk && :on_lbracket == peek_tk[:kind] get_tk nest = 1 while bracket_tk = get_tk case bracket_tk[:kind] when :on_lbracket nest += 1 when :on_rbracket nest -= 1 break if nest == 0 end end skip_tkspace_without_nl is_array_or_hash = true end unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then return false end get_tk unless ignore_constants new_modules.each do |prev_c, new_module| prev_c.add_module_by_normal_module new_module new_module.ignore unless prev_c.document_children @top_level.add_to_classes_or_modules new_module end end value = '' con = RDoc::Constant.new name, value, comment body = parse_constant_body container, con, is_array_or_hash return unless body con.value = body record_location con con.line = line_no read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS return if is_array_or_hash @stats.add_constant con container.add_constant con true end def parse_constant_body container, constant, is_array_or_hash # :nodoc: nest = 0 rhs_name = ''.dup get_tkread tk = get_tk body = nil loop do break if tk.nil? if :on_semicolon == tk[:kind] then break if nest <= 0 elsif [:on_tlambeg, :on_lparen, :on_lbrace, :on_lbracket].include?(tk[:kind]) then nest += 1 elsif (:on_kw == tk[:kind] && 'def' == tk[:text]) then nest += 1 elsif (:on_kw == tk[:kind] && %w{do if unless case begin}.include?(tk[:text])) then if (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) == 0 nest += 1 end elsif [:on_rparen, :on_rbrace, :on_rbracket].include?(tk[:kind]) || (:on_kw == tk[:kind] && 'end' == tk[:text]) then nest -= 1 elsif (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then unget_tk tk if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then body = get_tkread_clean(/^[ \t]+/, '') read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS break else read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS end elsif :on_const == tk[:kind] then rhs_name << tk[:text] next_tk = peek_tk if nest <= 0 and (next_tk.nil? || :on_nl == next_tk[:kind]) then create_module_alias container, constant, rhs_name unless is_array_or_hash break end elsif :on_nl == tk[:kind] then if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then unget_tk tk break end elsif :on_op == tk[:kind] && '::' == tk[:text] rhs_name << '::' end tk = get_tk end body ? body : get_tkread_clean(/^[ \t]+/, '') end ## # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for # :method: or :attr: directives in +comment+. def parse_comment container, tk, comment return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc' column = tk[:char_no] line_no = comment.line.nil? ? tk[:line_no] : comment.line comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') singleton = !!$~ co = if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then line_no += $`.count("\n") parse_comment_ghost container, comment.text, $1, column, line_no, comment elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then parse_comment_attr container, $1, $3, comment end if co then co.singleton = singleton co.line = line_no end true end ## # Parse a comment that is describing an attribute in +container+ with the # given +name+ and +comment+. def parse_comment_attr container, type, name, comment # :nodoc: return if name.empty? rw = case type when 'attr_reader' then 'R' when 'attr_writer' then 'W' else 'RW' end create_attr container, NORMAL, name, rw, comment end def parse_comment_ghost container, text, name, column, line_no, # :nodoc: comment name = nil if name.empty? meth = RDoc::GhostMethod.new get_tkread, name record_location meth meth.start_collecting_tokens indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") meth.add_tokens [position_comment, newline, indent] meth.params = if text.sub!(/^#\s+:?args?:\s*(.*?)\s*$/i, '') then $1 else '' end comment.normalize comment.extract_call_seq meth return unless meth.name container.add_method meth meth.comment = comment @stats.add_method meth meth end ## # Creates an RDoc::Method on +container+ from +comment+ if there is a # Signature section in the comment def parse_comment_tomdoc container, tk, comment return unless signature = RDoc::TomDoc.signature(comment) column = tk[:char_no] line_no = tk[:line_no] name, = signature.split %r%[ \(]%, 2 meth = RDoc::GhostMethod.new get_tkread, name record_location meth meth.line = line_no meth.start_collecting_tokens indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") meth.add_tokens [position_comment, newline, indent] meth.call_seq = signature comment.normalize return unless meth.name container.add_method meth meth.comment = comment @stats.add_method meth end ## # Parses an +include+ or +extend+, indicated by the +klass+ and adds it to # +container+ # with +comment+ def parse_extend_or_include klass, container, comment # :nodoc: loop do skip_tkspace_comment name = get_included_module_with_optional_parens unless name.empty? then obj = container.add klass, name, comment record_location obj end return if peek_tk.nil? || :on_comma != peek_tk[:kind] get_tk end end ## # Parses an +included+ with a block feature of ActiveSupport::Concern. def parse_included_with_activesupport_concern container, comment # :nodoc: skip_tkspace_without_nl tk = get_tk unless tk[:kind] == :on_lbracket || (tk[:kind] == :on_kw && tk[:text] == 'do') unget_tk tk return nil # should be a block end parse_statements container container end ## # Parses identifiers that can create new methods or change visibility. # # Returns true if the comment was not consumed. def parse_identifier container, single, tk, comment # :nodoc: case tk[:text] when 'private', 'protected', 'public', 'private_class_method', 'public_class_method', 'module_function' then parse_visibility container, single, tk return true when 'private_constant', 'public_constant' parse_constant_visibility container, single, tk return true when 'attr' then parse_attr container, single, tk, comment when /^attr_(reader|writer|accessor)$/ then parse_attr_accessor container, single, tk, comment when 'alias_method' then parse_alias container, single, tk, comment when 'require', 'include' then # ignore else if comment.text =~ /\A#\#$/ then case comment.text when /^# +:?attr(_reader|_writer|_accessor)?:/ then parse_meta_attr container, single, tk, comment else method = parse_meta_method container, single, tk, comment method.params = container.params if container.params method.block_params = container.block_params if container.block_params end end end false end ## # Parses a meta-programmed attribute and creates an RDoc::Attr. # # To create foo and bar attributes on class C with comment "My attributes": # # class C # # ## # # :attr: # # # # My attributes # # my_attr :foo, :bar # # end # # To create a foo attribute on class C with comment "My attribute": # # class C # # ## # # :attr: foo # # # # My attribute # # my_attr :foo, :bar # # end def parse_meta_attr(context, single, tk, comment) args = parse_symbol_arg rw = "?" # If nodoc is given, don't document any of them tmp = RDoc::CodeObject.new read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i if regexp =~ comment.text then comment.text = comment.text.sub(regexp, '') rw = case $1 when 'attr_reader' then 'R' when 'attr_writer' then 'W' else 'RW' end name = $3 unless $3.empty? end if name then att = create_attr context, single, name, rw, comment else args.each do |attr_name| att = create_attr context, single, attr_name, rw, comment end end att end ## # Parses a meta-programmed method def parse_meta_method(container, single, tk, comment) column = tk[:char_no] line_no = tk[:line_no] start_collecting_tokens add_token tk add_token_listener self skip_tkspace_without_nl comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') singleton = !!$~ name = parse_meta_method_name comment, tk return unless name meth = RDoc::MetaMethod.new get_tkread, name record_location meth meth.line = line_no meth.singleton = singleton remove_token_listener self meth.start_collecting_tokens indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") meth.add_tokens [position_comment, newline, indent] meth.add_tokens @token_stream parse_meta_method_params container, single, meth, tk, comment meth.comment = comment @stats.add_method meth meth end ## # Parses the name of a metaprogrammed method. +comment+ is used to # determine the name while +tk+ is used in an error message if the name # cannot be determined. def parse_meta_method_name comment, tk # :nodoc: if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then return $1 unless $1.empty? end name_t = get_tk if :on_symbol == name_t[:kind] then name_t[:text][1..-1] elsif :on_tstring == name_t[:kind] then name_t[:text][1..-2] elsif :on_op == name_t[:kind] && '=' == name_t[:text] then # ignore remove_token_listener self nil else warn "unknown name token #{name_t.inspect} for meta-method '#{tk[:text]}'" 'unknown' end end ## # Parses the parameters and block for a meta-programmed method. def parse_meta_method_params container, single, meth, tk, comment # :nodoc: token_listener meth do meth.params = '' look_for_directives_in meth, comment comment.normalize comment.extract_call_seq meth container.add_method meth last_tk = tk while tk = get_tk do if :on_semicolon == tk[:kind] then break elsif :on_nl == tk[:kind] then break unless last_tk and :on_comma == last_tk[:kind] elsif :on_sp == tk[:kind] then # expression continues elsif :on_kw == tk[:kind] && 'do' == tk[:text] then parse_statements container, single, meth break else last_tk = tk end end end end ## # Parses a normal method defined by +def+ def parse_method(container, single, tk, comment) singleton = nil added_container = false name = nil column = tk[:char_no] line_no = tk[:line_no] start_collecting_tokens add_token tk token_listener self do prev_container = container name, container, singleton = parse_method_name container added_container = container != prev_container end return unless name meth = RDoc::AnyMethod.new get_tkread, name look_for_directives_in meth, comment meth.singleton = single == SINGLE ? true : singleton record_location meth meth.line = line_no meth.start_collecting_tokens indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) token[:text] = "# File #{@top_level.relative_name}, line #{line_no}" newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") meth.add_tokens [token, newline, indent] meth.add_tokens @token_stream parse_method_params_and_body container, single, meth, added_container comment.normalize comment.extract_call_seq meth meth.comment = comment # after end modifiers read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS @stats.add_method meth end ## # Parses the parameters and body of +meth+ def parse_method_params_and_body container, single, meth, added_container token_listener meth do parse_method_parameters meth if meth.document_self or not @track_visibility then container.add_method meth elsif added_container then container.document_self = false end # Having now read the method parameters and documentation modifiers, we # now know whether we have to rename #initialize to ::new if meth.name == "initialize" && !meth.singleton then if meth.dont_rename_initialize then meth.visibility = :protected else meth.singleton = true meth.name = "new" meth.visibility = :public end end parse_statements container, single, meth end end ## # Parses a method that needs to be ignored. def parse_method_dummy container dummy = RDoc::Context.new dummy.parent = container dummy.store = container.store skip_method dummy end ## # Parses the name of a method in +container+. # # Returns the method name, the container it is in (for def Foo.name) and if # it is a singleton or regular method. def parse_method_name container # :nodoc: skip_tkspace name_t = get_tk back_tk = skip_tkspace_without_nl singleton = false dot = get_tk if dot[:kind] == :on_period || (dot[:kind] == :on_op && dot[:text] == '::') then singleton = true name, container = parse_method_name_singleton container, name_t else unget_tk dot back_tk.reverse_each do |token| unget_tk token end name = parse_method_name_regular container, name_t end return name, container, singleton end ## # For the given +container+ and initial name token +name_t+ the method name # is parsed from the token stream for a regular method. def parse_method_name_regular container, name_t # :nodoc: if :on_op == name_t[:kind] && (%w{* & [] []= <<}.include?(name_t[:text])) then name_t[:text] else unless [:on_kw, :on_const, :on_ident].include?(name_t[:kind]) then warn "expected method name token, . or ::, got #{name_t.inspect}" skip_method container return end name_t[:text] end end ## # For the given +container+ and initial name token +name_t+ the method name # and the new +container+ (if necessary) are parsed from the token stream # for a singleton method. def parse_method_name_singleton container, name_t # :nodoc: skip_tkspace name_t2 = get_tk if (:on_kw == name_t[:kind] && 'self' == name_t[:text]) || (:on_op == name_t[:kind] && '%' == name_t[:text]) then # NOTE: work around '[' being consumed early if :on_lbracket == name_t2[:kind] get_tk name = '[]' else name = name_t2[:text] end elsif :on_const == name_t[:kind] then name = name_t2[:text] container = get_method_container container, name_t return unless container name elsif :on_ident == name_t[:kind] || :on_ivar == name_t[:kind] || :on_gvar == name_t[:kind] then parse_method_dummy container name = nil elsif (:on_kw == name_t[:kind]) && ('true' == name_t[:text] || 'false' == name_t[:text] || 'nil' == name_t[:text]) then klass_name = "#{name_t[:text].capitalize}Class" container = @store.find_class_named klass_name container ||= @top_level.add_class RDoc::NormalClass, klass_name name = name_t2[:text] else warn "unexpected method name token #{name_t.inspect}" # break skip_method container name = nil end return name, container end ## # Extracts +yield+ parameters from +method+ def parse_method_or_yield_parameters(method = nil, modifiers = RDoc::METHOD_MODIFIERS) skip_tkspace_without_nl tk = get_tk end_token = get_end_token tk return '' unless end_token nest = 0 continue = false while tk != nil do case tk[:kind] when :on_semicolon then break if nest == 0 when :on_lbracket then nest += 1 when :on_rbracket then nest -= 1 when :on_lbrace then nest += 1 when :on_rbrace then nest -= 1 if nest <= 0 # we might have a.each { |i| yield i } unget_tk(tk) if nest < 0 break end when :on_lparen then nest += 1 when end_token[:kind] then if end_token[:kind] == :on_rparen nest -= 1 break if nest <= 0 else break end when :on_rparen then nest -= 1 when :on_comment, :on_embdoc then @read.pop if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and (!continue or (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) != 0) then if method && method.block_params.nil? then unget_tk tk read_documentation_modifiers method, modifiers end break if !continue and nest <= 0 end when :on_comma then continue = true when :on_ident then continue = false if continue end tk = get_tk end get_tkread_clean(/\s+/, ' ') end ## # Capture the method's parameters. Along the way, look for a comment # containing: # # # yields: .... # # and add this as the block_params for the method def parse_method_parameters method res = parse_method_or_yield_parameters method res = "(#{res})" unless res =~ /\A\(/ method.params = res unless method.params return if method.block_params skip_tkspace_without_nl read_documentation_modifiers method, RDoc::METHOD_MODIFIERS end ## # Parses an RDoc::NormalModule in +container+ with +comment+ def parse_module container, single, tk, comment container, name_t, = get_class_or_module container name = name_t[:text] mod = container.add_module RDoc::NormalModule, name mod.ignore unless container.document_children record_location mod read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS mod.add_comment comment, @top_level parse_statements mod # after end modifiers read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS @stats.add_module mod end ## # Parses an RDoc::Require in +context+ containing +comment+ def parse_require(context, comment) skip_tkspace_comment tk = get_tk if :on_lparen == tk[:kind] then skip_tkspace_comment tk = get_tk end name = tk[:text][1..-2] if :on_tstring == tk[:kind] if name then @top_level.add_require RDoc::Require.new(name, comment) else unget_tk tk end end ## # Parses a rescue def parse_rescue skip_tkspace_without_nl while tk = get_tk case tk[:kind] when :on_nl, :on_semicolon, :on_comment then break when :on_comma then skip_tkspace_without_nl get_tk if :on_nl == peek_tk[:kind] end skip_tkspace_without_nl end end ## # Retrieve comment body without =begin/=end def retrieve_comment_body(tk) if :on_embdoc == tk[:kind] tk[:text].gsub(/\A=begin.*\n/, '').gsub(/=end\n?\z/, '') else tk[:text] end end ## # The core of the Ruby parser. def parse_statements(container, single = NORMAL, current_method = nil, comment = new_comment('')) raise 'no' unless RDoc::Comment === comment comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding nest = 1 save_visibility = container.visibility non_comment_seen = true while tk = get_tk do keep_comment = false try_parse_comment = false non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) case tk[:kind] when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] skip_tkspace tk = get_tk else past_tokens = @read.size > 1 ? @read[0..-2] : [] nl_position = 0 past_tokens.reverse.each_with_index do |read_tk, i| if read_tk =~ /^\n$/ then nl_position = (past_tokens.size - 1) - i break elsif read_tk =~ /^#.*\n$/ then nl_position = ((past_tokens.size - 1) - i) + 1 break end end comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ } unless comment_only_line then tk = get_tk end end if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then if non_comment_seen then # Look for RDoc in a comment about to be thrown away non_comment_seen = parse_comment container, tk, comment unless comment.empty? comment = '' comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding end line_no = nil while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do comment_body = retrieve_comment_body(tk) line_no = tk[:line_no] if comment.empty? comment += comment_body comment << "\n" unless comment_body =~ /\n\z/ if comment_body.size > 1 && comment_body =~ /\n\z/ then skip_tkspace_without_nl # leading spaces end tk = get_tk end comment = new_comment comment, line_no unless comment.empty? then look_for_directives_in container, comment if container.done_documenting then throw :eof if RDoc::TopLevel === container container.ongoing_visibility = save_visibility end end keep_comment = true else non_comment_seen = true end unget_tk tk keep_comment = true container.current_line_visibility = nil when :on_kw then case tk[:text] when 'class' then parse_class container, single, tk, comment when 'module' then parse_module container, single, tk, comment when 'def' then parse_method container, single, tk, comment when 'alias' then parse_alias container, single, tk, comment unless current_method when 'yield' then if current_method.nil? then warn "Warning: yield outside of method" if container.document_self else parse_yield container, single, tk, current_method end when 'until', 'while' then if (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) == 0 nest += 1 skip_optional_do_after_expression end # Until and While can have a 'do', which shouldn't increase the nesting. # We can't solve the general case, but we can handle most occurrences by # ignoring a do at the end of a line. # 'for' is trickier when 'for' then nest += 1 skip_for_variable skip_optional_do_after_expression when 'case', 'do', 'if', 'unless', 'begin' then if (tk[:state] & RDoc::Parser::RipperStateLex::EXPR_LABEL) == 0 nest += 1 end when 'super' then current_method.calls_super = true if current_method when 'rescue' then parse_rescue when 'end' then nest -= 1 if nest == 0 then container.ongoing_visibility = save_visibility parse_comment container, tk, comment unless comment.empty? return end end when :on_const then unless parse_constant container, tk, comment, current_method then try_parse_comment = true end when :on_ident then if nest == 1 and current_method.nil? then keep_comment = parse_identifier container, single, tk, comment end case tk[:text] when "require" then parse_require container, comment when "include" then parse_extend_or_include RDoc::Include, container, comment when "extend" then parse_extend_or_include RDoc::Extend, container, comment when "included" then parse_included_with_activesupport_concern container, comment end else try_parse_comment = nest == 1 end if try_parse_comment then non_comment_seen = parse_comment container, tk, comment unless comment.empty? keep_comment = false end unless keep_comment then comment = new_comment '' comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding container.params = nil container.block_params = nil end consume_trailing_spaces end container.params = nil container.block_params = nil end ## # Parse up to +no+ symbol arguments def parse_symbol_arg(no = nil) skip_tkspace_comment tk = get_tk if tk[:kind] == :on_lparen parse_symbol_arg_paren no else parse_symbol_arg_space no, tk end end ## # Parses up to +no+ symbol arguments surrounded by () and places them in # +args+. def parse_symbol_arg_paren no # :nodoc: args = [] loop do skip_tkspace_comment if tk1 = parse_symbol_in_arg args.push tk1 break if no and args.size >= no end skip_tkspace_comment case (tk2 = get_tk)[:kind] when :on_rparen break when :on_comma else warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC break end end args end ## # Parses up to +no+ symbol arguments separated by spaces and places them in # +args+. def parse_symbol_arg_space no, tk # :nodoc: args = [] unget_tk tk if tk = parse_symbol_in_arg args.push tk return args if no and args.size >= no end loop do skip_tkspace_without_nl tk1 = get_tk if tk1.nil? || :on_comma != tk1[:kind] then unget_tk tk1 break end skip_tkspace_comment if tk = parse_symbol_in_arg args.push tk break if no and args.size >= no end end args end ## # Returns symbol text from the next token def parse_symbol_in_arg tk = get_tk if :on_symbol == tk[:kind] then tk[:text].sub(/^:/, '') elsif :on_tstring == tk[:kind] then tk[:text][1..-2] elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then nil # ignore else warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC nil end end ## # Parses statements in the top-level +container+ def parse_top_level_statements container comment = collect_first_comment look_for_directives_in container, comment throw :eof if container.done_documenting @markup = comment.format # HACK move if to RDoc::Context#comment= container.comment = comment if container.document_self unless comment.empty? parse_statements container, NORMAL, nil, comment end ## # Determines the visibility in +container+ from +tk+ def parse_visibility(container, single, tk) vis_type, vis, singleton = get_visibility_information tk, single skip_tkspace_comment false ptk = peek_tk # Ryan Davis suggested the extension to ignore modifiers, because he # often writes # # protected unless $TESTING # if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then container.ongoing_visibility = vis elsif :on_kw == ptk[:kind] && 'def' == ptk[:text] container.current_line_visibility = vis else update_visibility container, vis_type, vis, singleton end end ## # Parses a Module#private_constant or Module#public_constant call from +tk+. def parse_constant_visibility(container, single, tk) args = parse_symbol_arg case tk[:text] when 'private_constant' vis = :private when 'public_constant' vis = :public else raise RDoc::Error, 'Unreachable' end container.set_constant_visibility_for args, vis end ## # Determines the block parameter for +context+ def parse_yield(context, single, tk, method) return if method.block_params get_tkread method.block_params = parse_method_or_yield_parameters end ## # Directives are modifier comments that can appear after class, module, or # method names. For example: # # def fred # :yields: a, b # # or: # # class MyClass # :nodoc: # # We return the directive name and any parameters as a two element array if # the name is in +allowed+. A directive can be found anywhere up to the end # of the current line. def read_directive allowed tokens = [] while tk = get_tk do tokens << tk if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then return elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then return unless tk[:text] =~ /\s*:?([\w-]+):\s*(.*)/ directive = $1.downcase return [directive, $2] if allowed.include? directive return end end ensure unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then tokens.reverse_each do |token| unget_tk token end end end ## # Handles directives following the definition for +context+ (any # RDoc::CodeObject) if the directives are +allowed+ at this point. # # See also RDoc::Markup::PreProcess#handle_directive def read_documentation_modifiers context, allowed skip_tkspace_without_nl directive, value = read_directive allowed return unless directive @preprocess.handle_directive '', directive, value, context do |dir, param| if %w[notnew not_new not-new].include? dir then context.dont_rename_initialize = true true end end end ## # Records the location of this +container+ in the file for this parser and # adds it to the list of classes and modules in the file. def record_location container # :nodoc: case container when RDoc::ClassModule then @top_level.add_to_classes_or_modules container end container.record_location @top_level end ## # Scans this Ruby file for Ruby constructs def scan reset catch :eof do begin parse_top_level_statements @top_level rescue StandardError => e if @content.include?('<%') and @content.include?('%>') then # Maybe, this is ERB. $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:" $stderr.puts @file_name return end if @scanner_point >= @scanner.size now_line_no = @scanner[@scanner.size - 1][:line_no] else now_line_no = peek_tk[:line_no] end first_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no } last_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 } last_tk_index = last_tk_index ? last_tk_index - 1 : @scanner.size - 1 code = @scanner[first_tk_index..last_tk_index].map{ |t| t[:text] }.join $stderr.puts <<-EOF #{self.class} failure around line #{now_line_no} of #{@file_name} EOF unless code.empty? then $stderr.puts code $stderr.puts end raise e end end @top_level end ## # while, until, and for have an optional do def skip_optional_do_after_expression skip_tkspace_without_nl tk = get_tk b_nest = 0 nest = 0 loop do break unless tk case tk[:kind] when :on_semicolon, :on_nl, :on_ignored_nl then break if b_nest.zero? when :on_lparen then nest += 1 when :on_rparen then nest -= 1 when :on_kw then case tk[:text] when 'begin' b_nest += 1 when 'end' b_nest -= 1 when 'do' break if nest.zero? end when :on_comment, :on_embdoc then if b_nest.zero? and "\n" == tk[:text][-1] then break end end tk = get_tk end skip_tkspace_without_nl get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text] end ## # skip the var [in] part of a 'for' statement def skip_for_variable skip_tkspace_without_nl get_tk skip_tkspace_without_nl tk = get_tk unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text] end ## # Skips the next method in +container+ def skip_method container meth = RDoc::AnyMethod.new "", "anon" parse_method_parameters meth parse_statements container, false, meth end ## # Skip spaces until a comment is found def skip_tkspace_comment(skip_nl = true) loop do skip_nl ? skip_tkspace : skip_tkspace_without_nl next_tk = peek_tk return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind]) get_tk end end ## # Updates visibility in +container+ from +vis_type+ and +vis+. def update_visibility container, vis_type, vis, singleton # :nodoc: new_methods = [] case vis_type when 'module_function' then args = parse_symbol_arg container.set_visibility_for args, :private, false container.methods_matching args do |m| s_m = m.dup record_location s_m s_m.singleton = true new_methods << s_m end when 'public_class_method', 'private_class_method' then args = parse_symbol_arg container.methods_matching args, true do |m| if m.parent != container then m = m.dup record_location m new_methods << m end m.visibility = vis end else args = parse_symbol_arg container.set_visibility_for args, vis, singleton end new_methods.each do |method| case method when RDoc::AnyMethod then container.add_method method when RDoc::Attr then container.add_attribute method end method.visibility = vis end end ## # Prints +message+ to +$stderr+ unless we're being quiet def warn message @options.warn make_message message end end PK!h\;!;! changelog.rbnu[# frozen_string_literal: true ## # A ChangeLog file parser. # # This parser converts a ChangeLog into an RDoc::Markup::Document. When # viewed as HTML a ChangeLog page will have an entry for each day's entries in # the sidebar table of contents. # # This parser is meant to parse the MRI ChangeLog, but can be used to parse any # {GNU style Change # Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. class RDoc::Parser::ChangeLog < RDoc::Parser include RDoc::Parser::Text parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) ## # Attaches the +continuation+ of the previous line to the +entry_body+. # # Continued function listings are joined together as a single entry. # Continued descriptions are joined to make a single paragraph. def continue_entry_body entry_body, continuation return unless last = entry_body.last if last =~ /\)\s*\z/ and continuation =~ /\A\(/ then last.sub!(/\)\s*\z/, ',') continuation = continuation.sub(/\A\(/, '') end if last =~ /\s\z/ then last << continuation else last << ' ' + continuation end end ## # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. def create_document groups doc = RDoc::Markup::Document.new doc.omit_headings_below = 2 doc.file = @top_level doc << RDoc::Markup::Heading.new(1, File.basename(@file_name)) doc << RDoc::Markup::BlankLine.new groups.sort_by do |day,| day end.reverse_each do |day, entries| doc << RDoc::Markup::Heading.new(2, day.dup) doc << RDoc::Markup::BlankLine.new doc.concat create_entries entries end doc end ## # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given # +entries+. def create_entries entries out = [] entries.each do |entry, items| out << RDoc::Markup::Heading.new(3, entry) out << RDoc::Markup::BlankLine.new out << create_items(items) end out end ## # Returns an RDoc::Markup::List containing the given +items+ in the # ChangeLog def create_items items list = RDoc::Markup::List.new :NOTE items.each do |item| item =~ /\A(.*?(?:\([^)]+\))?):\s*/ title = $1 body = $' paragraph = RDoc::Markup::Paragraph.new body list_item = RDoc::Markup::ListItem.new title, paragraph list << list_item end list end ## # Groups +entries+ by date. def group_entries entries @time_cache ||= {} entries.group_by do |title, _| begin time = @time_cache[title] (time || parse_date(title)).strftime '%Y-%m-%d' rescue NoMethodError, ArgumentError time, = title.split ' ', 2 parse_date(time).strftime '%Y-%m-%d' end end end ## # Parse date in ISO-8601, RFC-2822, or default of Git def parse_date(date) case date when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ Time.new($6, $1, $2, $3, $4, $5) else raise ArgumentError, "bad date: #{date}" end end ## # Parses the entries in the ChangeLog. # # Returns an Array of each ChangeLog entry in order of parsing. # # A ChangeLog entry is an Array containing the ChangeLog title (date and # committer) and an Array of ChangeLog items (file and function changed with # description). # # An example result would be: # # [ 'Tue Dec 4 08:33:46 2012 Eric Hodel ', # [ 'README.EXT: Converted to RDoc format', # 'README.EXT.ja: ditto']] def parse_entries @time_cache ||= {} if /\A((?:.*\n){,3})commit\s/ =~ @content class << self; prepend Git; end parse_info($1) return parse_entries end entries = [] entry_name = nil entry_body = [] @content.each_line do |line| case line when /^\s*$/ then next when /^\w.*/ then entries << [entry_name, entry_body] if entry_name entry_name = $& begin time = parse_date entry_name @time_cache[entry_name] = time rescue ArgumentError entry_name = nil end entry_body = [] when /^(\t| {8})?\*\s*(.*)/ then # "\t* file.c (func): ..." entry_body << $2.dup when /^(\t| {8})?\s*(\(.*)/ then # "\t(func): ..." entry = $2 if entry_body.last =~ /:/ then entry_body << entry.dup else continue_entry_body entry_body, entry end when /^(\t| {8})?\s*(.*)/ then continue_entry_body entry_body, $2 end end entries << [entry_name, entry_body] if entry_name entries.reject! do |(entry,_)| entry == nil end entries end ## # Converts the ChangeLog into an RDoc::Markup::Document def scan @time_cache = {} entries = parse_entries grouped_entries = group_entries entries doc = create_document grouped_entries @top_level.comment = doc @top_level end module Git def parse_info(info) /^\s*base-url\s*=\s*(.*\S)/ =~ info @base_url = $1 end def parse_entries entries = [] @content.scan(/^commit\s+(\h{20})\h*\n((?:.+\n)*)\n((?: {4}.*\n+)*)/) do entry_name, header, entry_body = $1, $2, $3.gsub(/^ {4}/, '') # header = header.scan(/^ *(\S+?): +(.*)/).to_h # date = header["CommitDate"] || header["Date"] date = header[/^ *(?:Author)?Date: +(.*)/, 1] author = header[/^ *Author: +(.*)/, 1] begin time = parse_date(header[/^ *CommitDate: +(.*)/, 1] || date) @time_cache[entry_name] = time author.sub!(/\s*<(.*)>/, '') email = $1 entries << [entry_name, [author, email, date, entry_body]] rescue ArgumentError end end entries end def create_entries entries # git log entries have no strictly itemized style like the old # style, just assume Markdown. entries.map do |commit, entry| LogEntry.new(@base_url, commit, *entry) end end LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do HEADING_LEVEL = 3 def initialize(base, commit, author, email, date, contents) case contents when String contents = RDoc::Markdown.parse(contents).parts.each do |body| case body when RDoc::Markup::Heading body.level += HEADING_LEVEL + 1 end end case first = contents[0] when RDoc::Markup::Paragraph contents[0] = RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) end end super end def level HEADING_LEVEL end def aref "label-#{commit}" end def label context = nil aref end def text case base when nil "#{date}" when /%s/ "{#{date}}[#{base % commit}]" else "{#{date}}[#{base}#{commit}]" end + " {#{author}}[mailto:#{email}]" end def accept visitor visitor.accept_heading self begin if visitor.respond_to?(:code_object=) code_object = visitor.code_object visitor.code_object = self end contents.each do |body| body.accept visitor end ensure if visitor.respond_to?(:code_object) visitor.code_object = code_object end end end def pretty_print q # :nodoc: q.group(2, '[log_entry: ', ']') do q.text commit q.text ',' q.breakable q.group(2, '[date: ', ']') { q.text date } q.text ',' q.breakable q.group(2, '[author: ', ']') { q.text author } q.text ',' q.breakable q.group(2, '[email: ', ']') { q.text email } q.text ',' q.breakable q.pp contents end end end end end PK!hlexer.rbnu[# frozen_string_literal: true # :markup: markdown require "strscan" require_relative "../../polyfill/append_as_bytes" require_relative "../../polyfill/scan_byte" module Prism module Translation class Parser # Accepts a list of prism tokens and converts them into the expected # format for the parser gem. class Lexer # These tokens are always skipped TYPES_ALWAYS_SKIP = Set.new(%i[IGNORED_NEWLINE __END__ EOF]) private_constant :TYPES_ALWAYS_SKIP # The direct translating of types between the two lexers. TYPES = { # These tokens should never appear in the output of the lexer. MISSING: nil, NOT_PROVIDED: nil, EMBDOC_END: nil, EMBDOC_LINE: nil, # These tokens have more or less direct mappings. AMPERSAND: :tAMPER2, AMPERSAND_AMPERSAND: :tANDOP, AMPERSAND_AMPERSAND_EQUAL: :tOP_ASGN, AMPERSAND_DOT: :tANDDOT, AMPERSAND_EQUAL: :tOP_ASGN, BACK_REFERENCE: :tBACK_REF, BACKTICK: :tXSTRING_BEG, BANG: :tBANG, BANG_EQUAL: :tNEQ, BANG_TILDE: :tNMATCH, BRACE_LEFT: :tLCURLY, BRACE_RIGHT: :tRCURLY, BRACKET_LEFT: :tLBRACK2, BRACKET_LEFT_ARRAY: :tLBRACK, BRACKET_LEFT_RIGHT: :tAREF, BRACKET_LEFT_RIGHT_EQUAL: :tASET, BRACKET_RIGHT: :tRBRACK, CARET: :tCARET, CARET_EQUAL: :tOP_ASGN, CHARACTER_LITERAL: :tCHARACTER, CLASS_VARIABLE: :tCVAR, COLON: :tCOLON, COLON_COLON: :tCOLON2, COMMA: :tCOMMA, COMMENT: :tCOMMENT, CONSTANT: :tCONSTANT, DOT: :tDOT, DOT_DOT: :tDOT2, DOT_DOT_DOT: :tDOT3, EMBDOC_BEGIN: :tCOMMENT, EMBEXPR_BEGIN: :tSTRING_DBEG, EMBEXPR_END: :tSTRING_DEND, EMBVAR: :tSTRING_DVAR, EQUAL: :tEQL, EQUAL_EQUAL: :tEQ, EQUAL_EQUAL_EQUAL: :tEQQ, EQUAL_GREATER: :tASSOC, EQUAL_TILDE: :tMATCH, FLOAT: :tFLOAT, FLOAT_IMAGINARY: :tIMAGINARY, FLOAT_RATIONAL: :tRATIONAL, FLOAT_RATIONAL_IMAGINARY: :tIMAGINARY, GLOBAL_VARIABLE: :tGVAR, GREATER: :tGT, GREATER_EQUAL: :tGEQ, GREATER_GREATER: :tRSHFT, GREATER_GREATER_EQUAL: :tOP_ASGN, HEREDOC_START: :tSTRING_BEG, HEREDOC_END: :tSTRING_END, IDENTIFIER: :tIDENTIFIER, INSTANCE_VARIABLE: :tIVAR, INTEGER: :tINTEGER, INTEGER_IMAGINARY: :tIMAGINARY, INTEGER_RATIONAL: :tRATIONAL, INTEGER_RATIONAL_IMAGINARY: :tIMAGINARY, KEYWORD_ALIAS: :kALIAS, KEYWORD_AND: :kAND, KEYWORD_BEGIN: :kBEGIN, KEYWORD_BEGIN_UPCASE: :klBEGIN, KEYWORD_BREAK: :kBREAK, KEYWORD_CASE: :kCASE, KEYWORD_CLASS: :kCLASS, KEYWORD_DEF: :kDEF, KEYWORD_DEFINED: :kDEFINED, KEYWORD_DO: :kDO, KEYWORD_DO_LOOP: :kDO_COND, KEYWORD_END: :kEND, KEYWORD_END_UPCASE: :klEND, KEYWORD_ENSURE: :kENSURE, KEYWORD_ELSE: :kELSE, KEYWORD_ELSIF: :kELSIF, KEYWORD_FALSE: :kFALSE, KEYWORD_FOR: :kFOR, KEYWORD_IF: :kIF, KEYWORD_IF_MODIFIER: :kIF_MOD, KEYWORD_IN: :kIN, KEYWORD_MODULE: :kMODULE, KEYWORD_NEXT: :kNEXT, KEYWORD_NIL: :kNIL, KEYWORD_NOT: :kNOT, KEYWORD_OR: :kOR, KEYWORD_REDO: :kREDO, KEYWORD_RESCUE: :kRESCUE, KEYWORD_RESCUE_MODIFIER: :kRESCUE_MOD, KEYWORD_RETRY: :kRETRY, KEYWORD_RETURN: :kRETURN, KEYWORD_SELF: :kSELF, KEYWORD_SUPER: :kSUPER, KEYWORD_THEN: :kTHEN, KEYWORD_TRUE: :kTRUE, KEYWORD_UNDEF: :kUNDEF, KEYWORD_UNLESS: :kUNLESS, KEYWORD_UNLESS_MODIFIER: :kUNLESS_MOD, KEYWORD_UNTIL: :kUNTIL, KEYWORD_UNTIL_MODIFIER: :kUNTIL_MOD, KEYWORD_WHEN: :kWHEN, KEYWORD_WHILE: :kWHILE, KEYWORD_WHILE_MODIFIER: :kWHILE_MOD, KEYWORD_YIELD: :kYIELD, KEYWORD___ENCODING__: :k__ENCODING__, KEYWORD___FILE__: :k__FILE__, KEYWORD___LINE__: :k__LINE__, LABEL: :tLABEL, LABEL_END: :tLABEL_END, LAMBDA_BEGIN: :tLAMBEG, LESS: :tLT, LESS_EQUAL: :tLEQ, LESS_EQUAL_GREATER: :tCMP, LESS_LESS: :tLSHFT, LESS_LESS_EQUAL: :tOP_ASGN, METHOD_NAME: :tFID, MINUS: :tMINUS, MINUS_EQUAL: :tOP_ASGN, MINUS_GREATER: :tLAMBDA, NEWLINE: :tNL, NUMBERED_REFERENCE: :tNTH_REF, PARENTHESIS_LEFT: :tLPAREN2, PARENTHESIS_LEFT_PARENTHESES: :tLPAREN_ARG, PARENTHESIS_RIGHT: :tRPAREN, PERCENT: :tPERCENT, PERCENT_EQUAL: :tOP_ASGN, PERCENT_LOWER_I: :tQSYMBOLS_BEG, PERCENT_LOWER_W: :tQWORDS_BEG, PERCENT_UPPER_I: :tSYMBOLS_BEG, PERCENT_UPPER_W: :tWORDS_BEG, PERCENT_LOWER_X: :tXSTRING_BEG, PLUS: :tPLUS, PLUS_EQUAL: :tOP_ASGN, PIPE_EQUAL: :tOP_ASGN, PIPE: :tPIPE, PIPE_PIPE: :tOROP, PIPE_PIPE_EQUAL: :tOP_ASGN, QUESTION_MARK: :tEH, REGEXP_BEGIN: :tREGEXP_BEG, REGEXP_END: :tSTRING_END, SEMICOLON: :tSEMI, SLASH: :tDIVIDE, SLASH_EQUAL: :tOP_ASGN, STAR: :tSTAR2, STAR_EQUAL: :tOP_ASGN, STAR_STAR: :tPOW, STAR_STAR_EQUAL: :tOP_ASGN, STRING_BEGIN: :tSTRING_BEG, STRING_CONTENT: :tSTRING_CONTENT, STRING_END: :tSTRING_END, SYMBOL_BEGIN: :tSYMBEG, TILDE: :tTILDE, UAMPERSAND: :tAMPER, UCOLON_COLON: :tCOLON3, UDOT_DOT: :tBDOT2, UDOT_DOT_DOT: :tBDOT3, UMINUS: :tUMINUS, UMINUS_NUM: :tUNARY_NUM, UPLUS: :tUPLUS, USTAR: :tSTAR, USTAR_STAR: :tDSTAR, WORDS_SEP: :tSPACE } # These constants represent flags in our lex state. We really, really # don't want to be using them and we really, really don't want to be # exposing them as part of our public API. Unfortunately, we don't have # another way of matching the exact tokens that the parser gem expects # without them. We should find another way to do this, but in the # meantime we'll hide them from the documentation and mark them as # private constants. EXPR_BEG = 0x1 # :nodoc: EXPR_LABEL = 0x400 # :nodoc: # It is used to determine whether `do` is of the token type `kDO` or `kDO_LAMBDA`. # # NOTE: In edge cases like `-> (foo = -> (bar) {}) do end`, please note that `kDO` is still returned # instead of `kDO_LAMBDA`, which is expected: https://github.com/ruby/prism/pull/3046 LAMBDA_TOKEN_TYPES = Set.new([:kDO_LAMBDA, :tLAMBDA, :tLAMBEG]) # The `PARENTHESIS_LEFT` token in Prism is classified as either `tLPAREN` or `tLPAREN2` in the Parser gem. # The following token types are listed as those classified as `tLPAREN`. LPAREN_CONVERSION_TOKEN_TYPES = Set.new([ :kBREAK, :tCARET, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3, :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS, :tLCURLY ]) # Types of tokens that are allowed to continue a method call with comments in-between. # For these, the parser gem doesn't emit a newline token after the last comment. COMMENT_CONTINUATION_TYPES = Set.new([:COMMENT, :AMPERSAND_DOT, :DOT]) private_constant :COMMENT_CONTINUATION_TYPES # Heredocs are complex and require us to keep track of a bit of info to refer to later HeredocData = Struct.new(:identifier, :common_whitespace, keyword_init: true) private_constant :TYPES, :EXPR_BEG, :EXPR_LABEL, :LAMBDA_TOKEN_TYPES, :LPAREN_CONVERSION_TOKEN_TYPES, :HeredocData # The Parser::Source::Buffer that the tokens were lexed from. attr_reader :source_buffer # An array of tuples that contain prism tokens and their associated lex # state when they were lexed. attr_reader :lexed # A hash that maps offsets in bytes to offsets in characters. attr_reader :offset_cache # Initialize the lexer with the given source buffer, prism tokens, and # offset cache. def initialize(source_buffer, lexed, offset_cache) @source_buffer = source_buffer @lexed = lexed @offset_cache = offset_cache end Range = ::Parser::Source::Range # :nodoc: private_constant :Range # Convert the prism tokens into the expected format for the parser gem. def to_a tokens = [] index = 0 length = lexed.length heredoc_stack = [] quote_stack = [] # The parser gem emits the newline tokens for comments out of order. This saves # that token location to emit at a later time to properly line everything up. # https://github.com/whitequark/parser/issues/1025 comment_newline_location = nil while index < length token, state = lexed[index] index += 1 next if TYPES_ALWAYS_SKIP.include?(token.type) type = TYPES.fetch(token.type) value = token.value location = range(token.location.start_offset, token.location.end_offset) case type when :kDO nearest_lambda_token = tokens.reverse_each.find do |token| LAMBDA_TOKEN_TYPES.include?(token.first) end if nearest_lambda_token&.first == :tLAMBDA type = :kDO_LAMBDA end when :tCHARACTER value.delete_prefix!("?") # Character literals behave similar to double-quoted strings. We can use the same escaping mechanism. value = unescape_string(value, "?") when :tCOMMENT if token.type == :EMBDOC_BEGIN while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1) value += next_token.value index += 1 end value += next_token.value location = range(token.location.start_offset, next_token.location.end_offset) index += 1 else is_at_eol = value.chomp!.nil? location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1)) prev_token, _ = lexed[index - 2] if index - 2 >= 0 next_token, _ = lexed[index] is_inline_comment = prev_token&.location&.start_line == token.location.start_line if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type) tokens << [:tCOMMENT, [value, location]] nl_location = range(token.location.end_offset - 1, token.location.end_offset) tokens << [:tNL, [nil, nl_location]] next elsif is_inline_comment && next_token&.type == :COMMENT comment_newline_location = range(token.location.end_offset - 1, token.location.end_offset) elsif comment_newline_location && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type) tokens << [:tCOMMENT, [value, location]] tokens << [:tNL, [nil, comment_newline_location]] comment_newline_location = nil next end end when :tNL next_token, _ = lexed[index] # Newlines after comments are emitted out of order. if next_token&.type == :COMMENT comment_newline_location = location next end value = nil when :tFLOAT value = parse_float(value) when :tIMAGINARY value = parse_complex(value) when :tINTEGER if value.start_with?("+") tokens << [:tUNARY_NUM, ["+", range(token.location.start_offset, token.location.start_offset + 1)]] location = range(token.location.start_offset + 1, token.location.end_offset) end value = parse_integer(value) when :tLABEL value.chomp!(":") when :tLABEL_END value.chomp!(":") when :tLCURLY type = :tLBRACE if state == EXPR_BEG | EXPR_LABEL when :tLPAREN2 type = :tLPAREN if tokens.empty? || LPAREN_CONVERSION_TOKEN_TYPES.include?(tokens.dig(-1, 0)) when :tNTH_REF value = parse_integer(value.delete_prefix("$")) when :tOP_ASGN value.chomp!("=") when :tRATIONAL value = parse_rational(value) when :tSPACE location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value)) value = nil when :tSTRING_BEG next_token, _ = lexed[index] next_next_token, _ = lexed[index + 1] basic_quotes = value == '"' || value == "'" if basic_quotes && next_token&.type == :STRING_END next_location = token.location.join(next_token.location) type = :tSTRING value = "" location = range(next_location.start_offset, next_location.end_offset) index += 1 elsif value.start_with?("'", '"', "%") if next_token&.type == :STRING_CONTENT && next_next_token&.type == :STRING_END string_value = next_token.value if simplify_string?(string_value, value) next_location = token.location.join(next_next_token.location) if percent_array?(value) value = percent_array_unescape(string_value) else value = unescape_string(string_value, value) end type = :tSTRING location = range(next_location.start_offset, next_location.end_offset) index += 2 tokens << [type, [value, location]] next end end quote_stack.push(value) elsif token.type == :HEREDOC_START quote = value[2] == "-" || value[2] == "~" ? value[3] : value[2] heredoc_type = value[2] == "-" || value[2] == "~" ? value[2] : "" heredoc = HeredocData.new( identifier: value.match(/<<[-~]?["'`]?(?.*?)["'`]?\z/)[:heredoc_identifier], common_whitespace: 0, ) if quote == "`" type = :tXSTRING_BEG end # The parser gem trims whitespace from squiggly heredocs. We must record # the most common whitespace to later remove. if heredoc_type == "~" || heredoc_type == "`" heredoc.common_whitespace = calculate_heredoc_whitespace(index) end if quote == "'" || quote == '"' || quote == "`" value = "<<#{quote}" else value = '<<"' end heredoc_stack.push(heredoc) quote_stack.push(value) end when :tSTRING_CONTENT is_percent_array = percent_array?(quote_stack.last) if (lines = token.value.lines).one? # Prism usually emits a single token for strings with line continuations. # For squiggly heredocs they are not joined so we do that manually here. current_string = +"" current_length = 0 start_offset = token.location.start_offset while token.type == :STRING_CONTENT current_length += token.value.bytesize # Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line. prev_token, _ = lexed[index - 2] if index - 2 >= 0 is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line # The parser gem only removes indentation when the heredoc is not nested not_nested = heredoc_stack.size == 1 if is_percent_array value = percent_array_unescape(token.value) elsif is_first_token_on_line && not_nested && (current_heredoc = heredoc_stack.last).common_whitespace > 0 value = trim_heredoc_whitespace(token.value, current_heredoc) end current_string << unescape_string(value, quote_stack.last) relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I") 0 # the last backslash escapes the newline else token.value[/(\\{1,})\n/, 1]&.length || 0 end if relevant_backslash_count.even? || !interpolation?(quote_stack.last) tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]] break end token, _ = lexed[index] index += 1 end else # When the parser gem encounters a line continuation inside of a multiline string, # it emits a single string node. The backslash (and remaining newline) is removed. current_line = +"" adjustment = 0 start_offset = token.location.start_offset emit = false lines.each.with_index do |line, index| chomped_line = line.chomp backslash_count = chomped_line[/\\{1,}\z/]&.length || 0 is_interpolation = interpolation?(quote_stack.last) if backslash_count.odd? && (is_interpolation || is_percent_array) if is_percent_array current_line << percent_array_unescape(line) adjustment += 1 else chomped_line.delete_suffix!("\\") current_line << chomped_line adjustment += 2 end # If the string ends with a line continuation emit the remainder emit = index == lines.count - 1 else current_line << line emit = true end if emit end_offset = start_offset + current_line.bytesize + adjustment tokens << [:tSTRING_CONTENT, [unescape_string(current_line, quote_stack.last), range(start_offset, end_offset)]] start_offset = end_offset current_line = +"" adjustment = 0 end end end next when :tSTRING_DVAR value = nil when :tSTRING_END if token.type == :HEREDOC_END && value.end_with?("\n") newline_length = value.end_with?("\r\n") ? 2 : 1 value = heredoc_stack.pop.identifier location = range(token.location.start_offset, token.location.end_offset - newline_length) elsif token.type == :REGEXP_END value = value[0] location = range(token.location.start_offset, token.location.start_offset + 1) end if percent_array?(quote_stack.pop) prev_token, _ = lexed[index - 2] if index - 2 >= 0 empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type) ends_with_whitespace = prev_token&.type == :WORDS_SEP # parser always emits a space token after content in a percent array, even if no actual whitespace is present. if !empty && !ends_with_whitespace tokens << [:tSPACE, [nil, range(token.location.start_offset, token.location.start_offset)]] end end when :tSYMBEG if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END next_location = token.location.join(next_token.location) type = :tSYMBOL value = next_token.value value = { "~@" => "~", "!@" => "!" }.fetch(value, value) location = range(next_location.start_offset, next_location.end_offset) index += 1 else quote_stack.push(value) end when :tFID if !tokens.empty? && tokens.dig(-1, 0) == :kDEF type = :tIDENTIFIER end when :tXSTRING_BEG if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type) # self.`() type = :tBACK_REF2 end quote_stack.push(value) when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP index += 1 end quote_stack.push(value) when :tREGEXP_BEG quote_stack.push(value) end tokens << [type, [value, location]] if token.type == :REGEXP_END tokens << [:tREGEXP_OPT, [token.value[1..], range(token.location.start_offset + 1, token.location.end_offset)]] end end tokens end private # Creates a new parser range, taking prisms byte offsets into account def range(start_offset, end_offset) Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end # Parse an integer from the string representation. def parse_integer(value) Integer(value) rescue ArgumentError 0 end # Parse a float from the string representation. def parse_float(value) Float(value) rescue ArgumentError 0.0 end # Parse a complex from the string representation. def parse_complex(value) value.chomp!("i") if value.end_with?("r") Complex(0, parse_rational(value)) elsif value.start_with?(/0[BbOoDdXx]/) Complex(0, parse_integer(value)) else Complex(0, value) end rescue ArgumentError 0i end # Parse a rational from the string representation. def parse_rational(value) value.chomp!("r") if value.start_with?(/0[BbOoDdXx]/) Rational(parse_integer(value)) else Rational(value) end rescue ArgumentError 0r end # Wonky heredoc tab/spaces rules. # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L10548-L10558 def calculate_heredoc_whitespace(heredoc_token_index) next_token_index = heredoc_token_index nesting_level = 0 previous_line = -1 result = Float::MAX while (next_token = lexed[next_token_index]&.first) next_token_index += 1 next_next_token, _ = lexed[next_token_index] first_token_on_line = next_token.location.start_column == 0 # String content inside nested heredocs and interpolation is ignored if next_token.type == :HEREDOC_START || next_token.type == :EMBEXPR_BEGIN # When interpolation is the first token of a line there is no string # content to check against. There will be no common whitespace. if nesting_level == 0 && first_token_on_line result = 0 end nesting_level += 1 elsif next_token.type == :HEREDOC_END || next_token.type == :EMBEXPR_END nesting_level -= 1 # When we encountered the matching heredoc end, we can exit break if nesting_level == -1 elsif next_token.type == :STRING_CONTENT && nesting_level == 0 && first_token_on_line common_whitespace = 0 next_token.value[/^\s*/].each_char do |char| if char == "\t" common_whitespace = (common_whitespace / 8 + 1) * 8; else common_whitespace += 1 end end is_first_token_on_line = next_token.location.start_line != previous_line # Whitespace is significant if followed by interpolation whitespace_only = common_whitespace == next_token.value.length && next_next_token&.location&.start_line != next_token.location.start_line if is_first_token_on_line && !whitespace_only && common_whitespace < result result = common_whitespace previous_line = next_token.location.start_line end end end result end # Wonky heredoc tab/spaces rules. # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L16528-L16545 def trim_heredoc_whitespace(string, heredoc) trimmed_whitespace = 0 trimmed_characters = 0 while (string[trimmed_characters] == "\t" || string[trimmed_characters] == " ") && trimmed_whitespace < heredoc.common_whitespace if string[trimmed_characters] == "\t" trimmed_whitespace = (trimmed_whitespace / 8 + 1) * 8; break if trimmed_whitespace > heredoc.common_whitespace else trimmed_whitespace += 1 end trimmed_characters += 1 end string[trimmed_characters..] end # Escape sequences that have special and should appear unescaped in the resulting string. ESCAPES = { "a" => "\a", "b" => "\b", "e" => "\e", "f" => "\f", "n" => "\n", "r" => "\r", "s" => "\s", "t" => "\t", "v" => "\v", "\\" => "\\" }.freeze private_constant :ESCAPES # When one of these delimiters is encountered, then the other # one is allowed to be escaped as well. DELIMITER_SYMETRY = { "[" => "]", "(" => ")", "{" => "}", "<" => ">" }.freeze private_constant :DELIMITER_SYMETRY # https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/lexer-strings.rl#L14 REGEXP_META_CHARACTERS = ["\\", "$", "(", ")", "*", "+", ".", "<", ">", "?", "[", "]", "^", "{", "|", "}"] private_constant :REGEXP_META_CHARACTERS # Apply Ruby string escaping rules def unescape_string(string, quote) # In single-quoted heredocs, everything is taken literally. return string if quote == "<<'" # OPTIMIZATION: Assume that few strings need escaping to speed up the common case. return string unless string.include?("\\") # Enclosing character for the string. `"` for `"foo"`, `{` for `%w{foo}`, etc. delimiter = quote[-1] if regexp?(quote) # Should be escaped handled to single-quoted heredocs. The only character that is # allowed to be escaped is the delimiter, except when that also has special meaning # in the regexp. Since all the symetry delimiters have special meaning, they don't need # to be considered separately. if REGEXP_META_CHARACTERS.include?(delimiter) string else # There can never be an even amount of backslashes. It would be a syntax error. string.gsub(/\\(#{Regexp.escape(delimiter)})/, '\1') end elsif interpolation?(quote) # Appending individual escape sequences may force the string out of its intended # encoding. Start out with binary and force it back later. result = "".b scanner = StringScanner.new(string) while (skipped = scanner.skip_until(/\\/)) # Append what was just skipped over, excluding the found backslash. result.append_as_bytes(string.byteslice(scanner.pos - skipped, skipped - 1)) escape_read(result, scanner, false, false) end # Add remaining chars result.append_as_bytes(string.byteslice(scanner.pos..)) result.force_encoding(source_buffer.source.encoding) else delimiters = Regexp.escape("#{delimiter}#{DELIMITER_SYMETRY[delimiter]}") string.gsub(/\\([\\#{delimiters}])/, '\1') end end # Certain strings are merged into a single string token. def simplify_string?(value, quote) case quote when "'" # Only simplify 'foo' !value.include?("\n") when '"' # Simplify when every line ends with a line continuation, or it is the last line value.lines.all? do |line| !line.end_with?("\n") || line[/(\\*)$/, 1]&.length&.odd? end else # %q and similar are never simplified false end end # Escape a byte value, given the control and meta flags. def escape_build(value, control, meta) value &= 0x9f if control value |= 0x80 if meta value end # Read an escape out of the string scanner, given the control and meta # flags, and push the unescaped value into the result. def escape_read(result, scanner, control, meta) if scanner.skip("\n") # Line continuation elsif (value = ESCAPES[scanner.peek(1)]) # Simple single-character escape sequences like \n result.append_as_bytes(value) scanner.pos += 1 elsif (value = scanner.scan(/[0-7]{1,3}/)) # \nnn result.append_as_bytes(escape_build(value.to_i(8), control, meta)) elsif (value = scanner.scan(/x[0-9a-fA-F]{1,2}/)) # \xnn result.append_as_bytes(escape_build(value[1..].to_i(16), control, meta)) elsif (value = scanner.scan(/u[0-9a-fA-F]{4}/)) # \unnnn result.append_as_bytes(value[1..].hex.chr(Encoding::UTF_8)) elsif scanner.skip("u{}") # https://github.com/whitequark/parser/issues/856 elsif (value = scanner.scan(/u{.*?}/)) # \u{nnnn ...} value[2..-2].split.each do |unicode| result.append_as_bytes(unicode.hex.chr(Encoding::UTF_8)) end elsif (value = scanner.scan(/c\\?(?=[[:print:]])|C-\\?(?=[[:print:]])/)) # \cx or \C-x where x is an ASCII printable character escape_read(result, scanner, true, meta) elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/)) # \M-x where x is an ASCII printable character escape_read(result, scanner, control, true) elsif (byte = scanner.scan_byte) # Something else after an escape. if control && byte == 0x3f # ASCII '?' result.append_as_bytes(escape_build(0x7f, false, meta)) else result.append_as_bytes(escape_build(byte, control, meta)) end end end # In a percent array, certain whitespace can be preceeded with a backslash, # causing the following characters to be part of the previous element. def percent_array_unescape(string) string.gsub(/(\\)+[ \f\n\r\t\v]/) do |full_match| full_match.delete_prefix!("\\") if Regexp.last_match[1].length.odd? full_match end end # For %-arrays whitespace, the parser gem only considers whitespace before the newline. def percent_array_leading_whitespace(string) return 1 if string.start_with?("\n") leading_whitespace = 0 string.each_char do |c| break if c == "\n" leading_whitespace += 1 end leading_whitespace end # Determine if characters preceeded by a backslash should be escaped or not def interpolation?(quote) !quote.end_with?("'") && !quote.start_with?("%q", "%w", "%i", "%s") end # Regexp allow interpolation but are handled differently during unescaping def regexp?(quote) quote == "/" || quote.start_with?("%r") end # Determine if the string is part of a %-style array. def percent_array?(quote) quote.start_with?("%w", "%W", "%i", "%I") end end end end end PK!׫z builder.rbnu[# frozen_string_literal: true # :markup: markdown module Prism module Translation class Parser # A builder that knows how to convert more modern Ruby syntax # into whitequark/parser gem's syntax tree. class Builder < ::Parser::Builders::Default # It represents the `it` block argument, which is not yet implemented in the Parser gem. def itarg n(:itarg, [:it], nil) end # The following three lines have been added to support the `it` block parameter syntax in the source code below. # # if args.type == :itarg # block_type = :itblock # args = :it # # https://github.com/whitequark/parser/blob/v3.3.7.1/lib/parser/builders/default.rb#L1122-L1155 def block(method_call, begin_t, args, body, end_t) _receiver, _selector, *call_args = *method_call if method_call.type == :yield diagnostic :error, :block_given_to_yield, nil, method_call.loc.keyword, [loc(begin_t)] end last_arg = call_args.last if last_arg && (last_arg.type == :block_pass || last_arg.type == :forwarded_args) diagnostic :error, :block_and_blockarg, nil, last_arg.loc.expression, [loc(begin_t)] end if args.type == :itarg block_type = :itblock args = :it elsif args.type == :numargs block_type = :numblock args = args.children[0] else block_type = :block end if [:send, :csend, :index, :super, :zsuper, :lambda].include?(method_call.type) n(block_type, [ method_call, args, body ], block_map(method_call.loc.expression, begin_t, end_t)) else # Code like "return foo 1 do end" is reduced in a weird sequence. # Here, method_call is actually (return). actual_send, = *method_call block = n(block_type, [ actual_send, args, body ], block_map(actual_send.loc.expression, begin_t, end_t)) n(method_call.type, [ block ], method_call.loc.with_expression(join_exprs(method_call, block))) end end end end end end PK!R&(&( compiler.rbnu[# frozen_string_literal: true # :markup: markdown module Prism module Translation class Parser # A visitor that knows how to convert a prism syntax tree into the # whitequark/parser gem's syntax tree. class Compiler < ::Prism::Compiler # Raised when the tree is malformed or there is a bug in the compiler. class CompilationError < StandardError end # The Parser::Base instance that is being used to build the AST. attr_reader :parser # The Parser::Builders::Default instance that is being used to build the # AST. attr_reader :builder # The Parser::Source::Buffer instance that is holding a reference to the # source code. attr_reader :source_buffer # The offset cache that is used to map between byte and character # offsets in the file. attr_reader :offset_cache # The types of values that can be forwarded in the current scope. attr_reader :forwarding # Whether or not the current node is in a destructure. attr_reader :in_destructure # Whether or not the current node is in a pattern. attr_reader :in_pattern # Initialize a new compiler with the given parser, offset cache, and # options. def initialize(parser, offset_cache, forwarding: [], in_destructure: false, in_pattern: false) @parser = parser @builder = parser.builder @source_buffer = parser.source_buffer @offset_cache = offset_cache @forwarding = forwarding @in_destructure = in_destructure @in_pattern = in_pattern end # alias foo bar # ^^^^^^^^^^^^^ def visit_alias_method_node(node) builder.alias(token(node.keyword_loc), visit(node.new_name), visit(node.old_name)) end # alias $foo $bar # ^^^^^^^^^^^^^^^ def visit_alias_global_variable_node(node) builder.alias(token(node.keyword_loc), visit(node.new_name), visit(node.old_name)) end # foo => bar | baz # ^^^^^^^^^ def visit_alternation_pattern_node(node) builder.match_alt(visit(node.left), token(node.operator_loc), visit(node.right)) end # a and b # ^^^^^^^ def visit_and_node(node) builder.logical_op(:and, visit(node.left), token(node.operator_loc), visit(node.right)) end # [] # ^^ def visit_array_node(node) if node.opening&.start_with?("%w", "%W", "%i", "%I") elements = node.elements.flat_map do |element| if element.is_a?(StringNode) if element.content.include?("\n") string_nodes_from_line_continuations(element.unescaped, element.content, element.content_loc.start_offset, node.opening) else [builder.string_internal([element.unescaped, srange(element.content_loc)])] end elsif element.is_a?(InterpolatedStringNode) builder.string_compose( token(element.opening_loc), string_nodes_from_interpolation(element, node.opening), token(element.closing_loc) ) else [visit(element)] end end else elements = visit_all(node.elements) end builder.array(token(node.opening_loc), elements, token(node.closing_loc)) end # foo => [bar] # ^^^^^ def visit_array_pattern_node(node) elements = [*node.requireds] elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) elements.concat(node.posts) visited = visit_all(elements) if node.rest.is_a?(ImplicitRestNode) visited[-1] = builder.match_with_trailing_comma(visited[-1], token(node.rest.location)) end if node.constant if visited.empty? builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc)), token(node.closing_loc)) else builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc)) end else builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc)) end end # foo(bar) # ^^^ def visit_arguments_node(node) visit_all(node.arguments) end # { a: 1 } # ^^^^ def visit_assoc_node(node) key = node.key if node.value.is_a?(ImplicitNode) if in_pattern if key.is_a?(SymbolNode) if key.opening.nil? builder.match_hash_var([key.unescaped, srange(key.location)]) else builder.match_hash_var_from_str(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc)) end else builder.match_hash_var_from_str(token(key.opening_loc), visit_all(key.parts), token(key.closing_loc)) end else value = node.value.value implicit_value = if value.is_a?(CallNode) builder.call_method(nil, nil, [value.name, srange(value.message_loc)]) elsif value.is_a?(ConstantReadNode) builder.const([value.name, srange(key.value_loc)]) else builder.ident([value.name, srange(key.value_loc)]).updated(:lvar) end builder.pair_keyword([key.unescaped, srange(key)], implicit_value) end elsif node.operator_loc builder.pair(visit(key), token(node.operator_loc), visit(node.value)) elsif key.is_a?(SymbolNode) && key.opening_loc.nil? builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value)) else parts = if key.is_a?(SymbolNode) [builder.string_internal([key.unescaped, srange(key.value_loc)])] else visit_all(key.parts) end builder.pair_quoted(token(key.opening_loc), parts, token(key.closing_loc), visit(node.value)) end end # def foo(**); bar(**); end # ^^ # # { **foo } # ^^^^^ def visit_assoc_splat_node(node) if in_pattern builder.match_rest(token(node.operator_loc), token(node.value&.location)) elsif node.value.nil? && forwarding.include?(:**) builder.forwarded_kwrestarg(token(node.operator_loc)) else builder.kwsplat(token(node.operator_loc), visit(node.value)) end end # $+ # ^^ def visit_back_reference_read_node(node) builder.back_ref(token(node.location)) end # begin end # ^^^^^^^^^ def visit_begin_node(node) rescue_bodies = [] if (rescue_clause = node.rescue_clause) begin find_start_offset = (rescue_clause.reference&.location || rescue_clause.exceptions.last&.location || rescue_clause.keyword_loc).end_offset find_end_offset = ( rescue_clause.statements&.location&.start_offset || rescue_clause.subsequent&.location&.start_offset || node.else_clause&.location&.start_offset || node.ensure_clause&.location&.start_offset || node.end_keyword_loc&.start_offset || find_start_offset + 1 ) rescue_bodies << builder.rescue_body( token(rescue_clause.keyword_loc), rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, token(rescue_clause.operator_loc), visit(rescue_clause.reference), srange_find(find_start_offset, find_end_offset, ";"), visit(rescue_clause.statements) ) end until (rescue_clause = rescue_clause.subsequent).nil? end begin_body = builder.begin_body( visit(node.statements), rescue_bodies, token(node.else_clause&.else_keyword_loc), visit(node.else_clause), token(node.ensure_clause&.ensure_keyword_loc), visit(node.ensure_clause&.statements) ) if node.begin_keyword_loc builder.begin_keyword(token(node.begin_keyword_loc), begin_body, token(node.end_keyword_loc)) else begin_body end end # foo(&bar) # ^^^^ def visit_block_argument_node(node) builder.block_pass(token(node.operator_loc), visit(node.expression)) end # foo { |; bar| } # ^^^ def visit_block_local_variable_node(node) builder.shadowarg(token(node.location)) end # A block on a keyword or method call. def visit_block_node(node) raise CompilationError, "Cannot directly compile block nodes" end # def foo(&bar); end # ^^^^ def visit_block_parameter_node(node) builder.blockarg(token(node.operator_loc), token(node.name_loc)) end # A block's parameters. def visit_block_parameters_node(node) [*visit(node.parameters)].concat(visit_all(node.locals)) end # break # ^^^^^ # # break foo # ^^^^^^^^^ def visit_break_node(node) builder.keyword_cmd(:break, token(node.keyword_loc), nil, visit(node.arguments) || [], nil) end # foo # ^^^ # # foo.bar # ^^^^^^^ # # foo.bar() {} # ^^^^^^^^^^^^ def visit_call_node(node) name = node.name arguments = node.arguments&.arguments || [] block = node.block if block.is_a?(BlockArgumentNode) arguments = [*arguments, block] block = nil end if node.call_operator_loc.nil? case name when :-@ case (receiver = node.receiver).type when :integer_node, :float_node, :rational_node, :imaginary_node return visit(numeric_negate(node.message_loc, receiver)) end when :! return visit_block(builder.not_op(token(node.message_loc), token(node.opening_loc), visit(node.receiver), token(node.closing_loc)), block) when :=~ if (receiver = node.receiver).is_a?(RegularExpressionNode) return builder.match_op(visit(receiver), token(node.message_loc), visit(node.arguments.arguments.first)) end when :[] return visit_block(builder.index(visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc)), block) when :[]= if node.message != "[]=" && node.arguments && block.nil? && !node.safe_navigation? arguments = node.arguments.arguments[...-1] arguments << node.block if node.block return visit_block( builder.assign( builder.index_asgn( visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc), ), srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="), visit(node.arguments.arguments.last) ), block ) end end end message_loc = node.message_loc call_operator_loc = node.call_operator_loc call_operator = [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)] if call_operator_loc visit_block( if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? builder.assign( builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="), visit(node.arguments.arguments.last) ) else builder.call_method( visit(node.receiver), call_operator, message_loc ? [node.name, srange(message_loc)] : nil, token(node.opening_loc), visit_all(arguments), token(node.closing_loc) ) end, block ) end # foo.bar += baz # ^^^^^^^^^^^^^^^ def visit_call_operator_write_node(node) call_operator_loc = node.call_operator_loc builder.op_assign( builder.call_method( visit(node.receiver), call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], node.message_loc ? [node.read_name, srange(node.message_loc)] : nil, nil, [], nil ), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # foo.bar &&= baz # ^^^^^^^^^^^^^^^ def visit_call_and_write_node(node) call_operator_loc = node.call_operator_loc builder.op_assign( builder.call_method( visit(node.receiver), call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], node.message_loc ? [node.read_name, srange(node.message_loc)] : nil, nil, [], nil ), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo.bar ||= baz # ^^^^^^^^^^^^^^^ def visit_call_or_write_node(node) call_operator_loc = node.call_operator_loc builder.op_assign( builder.call_method( visit(node.receiver), call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], node.message_loc ? [node.read_name, srange(node.message_loc)] : nil, nil, [], nil ), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo.bar, = 1 # ^^^^^^^ def visit_call_target_node(node) call_operator_loc = node.call_operator_loc builder.attr_asgn( visit(node.receiver), call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)], token(node.message_loc) ) end # foo => bar => baz # ^^^^^^^^^^ def visit_capture_pattern_node(node) builder.match_as(visit(node.value), token(node.operator_loc), visit(node.target)) end # case foo; when bar; end # ^^^^^^^^^^^^^^^^^^^^^^^ def visit_case_node(node) builder.case( token(node.case_keyword_loc), visit(node.predicate), visit_all(node.conditions), token(node.else_clause&.else_keyword_loc), visit(node.else_clause), token(node.end_keyword_loc) ) end # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ def visit_case_match_node(node) builder.case_match( token(node.case_keyword_loc), visit(node.predicate), visit_all(node.conditions), token(node.else_clause&.else_keyword_loc), visit(node.else_clause), token(node.end_keyword_loc) ) end # class Foo; end # ^^^^^^^^^^^^^^ def visit_class_node(node) builder.def_class( token(node.class_keyword_loc), visit(node.constant_path), token(node.inheritance_operator_loc), visit(node.superclass), node.body&.accept(copy_compiler(forwarding: [])), token(node.end_keyword_loc) ) end # @@foo # ^^^^^ def visit_class_variable_read_node(node) builder.cvar(token(node.location)) end # @@foo = 1 # ^^^^^^^^^ def visit_class_variable_write_node(node) builder.assign( builder.assignable(builder.cvar(token(node.name_loc))), token(node.operator_loc), visit(node.value) ) end # @@foo += bar # ^^^^^^^^^^^^ def visit_class_variable_operator_write_node(node) builder.op_assign( builder.assignable(builder.cvar(token(node.name_loc))), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # @@foo &&= bar # ^^^^^^^^^^^^^ def visit_class_variable_and_write_node(node) builder.op_assign( builder.assignable(builder.cvar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # @@foo ||= bar # ^^^^^^^^^^^^^ def visit_class_variable_or_write_node(node) builder.op_assign( builder.assignable(builder.cvar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # @@foo, = bar # ^^^^^ def visit_class_variable_target_node(node) builder.assignable(builder.cvar(token(node.location))) end # Foo # ^^^ def visit_constant_read_node(node) builder.const([node.name, srange(node.location)]) end # Foo = 1 # ^^^^^^^ # # Foo, Bar = 1 # ^^^ ^^^ def visit_constant_write_node(node) builder.assign(builder.assignable(builder.const([node.name, srange(node.name_loc)])), token(node.operator_loc), visit(node.value)) end # Foo += bar # ^^^^^^^^^^^ def visit_constant_operator_write_node(node) builder.op_assign( builder.assignable(builder.const([node.name, srange(node.name_loc)])), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # Foo &&= bar # ^^^^^^^^^^^^ def visit_constant_and_write_node(node) builder.op_assign( builder.assignable(builder.const([node.name, srange(node.name_loc)])), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # Foo ||= bar # ^^^^^^^^^^^^ def visit_constant_or_write_node(node) builder.op_assign( builder.assignable(builder.const([node.name, srange(node.name_loc)])), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # Foo, = bar # ^^^ def visit_constant_target_node(node) builder.assignable(builder.const([node.name, srange(node.location)])) end # Foo::Bar # ^^^^^^^^ def visit_constant_path_node(node) if node.parent.nil? builder.const_global( token(node.delimiter_loc), [node.name, srange(node.name_loc)] ) else builder.const_fetch( visit(node.parent), token(node.delimiter_loc), [node.name, srange(node.name_loc)] ) end end # Foo::Bar = 1 # ^^^^^^^^^^^^ # # Foo::Foo, Bar::Bar = 1 # ^^^^^^^^ ^^^^^^^^ def visit_constant_path_write_node(node) builder.assign( builder.assignable(visit(node.target)), token(node.operator_loc), visit(node.value) ) end # Foo::Bar += baz # ^^^^^^^^^^^^^^^ def visit_constant_path_operator_write_node(node) builder.op_assign( builder.assignable(visit(node.target)), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # Foo::Bar &&= baz # ^^^^^^^^^^^^^^^^ def visit_constant_path_and_write_node(node) builder.op_assign( builder.assignable(visit(node.target)), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # Foo::Bar ||= baz # ^^^^^^^^^^^^^^^^ def visit_constant_path_or_write_node(node) builder.op_assign( builder.assignable(visit(node.target)), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # Foo::Bar, = baz # ^^^^^^^^ def visit_constant_path_target_node(node) builder.assignable(visit_constant_path_node(node)) end # def foo; end # ^^^^^^^^^^^^ # # def self.foo; end # ^^^^^^^^^^^^^^^^^ def visit_def_node(node) if node.equal_loc if node.receiver builder.def_endless_singleton( token(node.def_keyword_loc), visit(node.receiver.is_a?(ParenthesesNode) ? node.receiver.body : node.receiver), token(node.operator_loc), token(node.name_loc), builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), token(node.equal_loc), node.body&.accept(copy_compiler(forwarding: find_forwarding(node.parameters))) ) else builder.def_endless_method( token(node.def_keyword_loc), token(node.name_loc), builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), token(node.equal_loc), node.body&.accept(copy_compiler(forwarding: find_forwarding(node.parameters))) ) end elsif node.receiver builder.def_singleton( token(node.def_keyword_loc), visit(node.receiver.is_a?(ParenthesesNode) ? node.receiver.body : node.receiver), token(node.operator_loc), token(node.name_loc), builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), node.body&.accept(copy_compiler(forwarding: find_forwarding(node.parameters))), token(node.end_keyword_loc) ) else builder.def_method( token(node.def_keyword_loc), token(node.name_loc), builder.args(token(node.lparen_loc), visit(node.parameters) || [], token(node.rparen_loc), false), node.body&.accept(copy_compiler(forwarding: find_forwarding(node.parameters))), token(node.end_keyword_loc) ) end end # defined? a # ^^^^^^^^^^ # # defined?(a) # ^^^^^^^^^^^ def visit_defined_node(node) # Very weird circumstances here where something like: # # defined? # (1) # # gets parsed in Ruby as having only the `1` expression but in parser # it gets parsed as having a begin. In this case we need to synthesize # that begin to match parser's behavior. if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n") builder.keyword_cmd( :defined?, token(node.keyword_loc), nil, [ builder.begin( token(node.lparen_loc), visit(node.value), token(node.rparen_loc) ) ], nil ) else builder.keyword_cmd( :defined?, token(node.keyword_loc), token(node.lparen_loc), [visit(node.value)], token(node.rparen_loc) ) end end # if foo then bar else baz end # ^^^^^^^^^^^^ def visit_else_node(node) visit(node.statements) end # "foo #{bar}" # ^^^^^^ def visit_embedded_statements_node(node) builder.begin( token(node.opening_loc), visit(node.statements), token(node.closing_loc) ) end # "foo #@bar" # ^^^^^ def visit_embedded_variable_node(node) visit(node.variable) end # begin; foo; ensure; bar; end # ^^^^^^^^^^^^ def visit_ensure_node(node) raise CompilationError, "Cannot directly compile ensure nodes" end # false # ^^^^^ def visit_false_node(node) builder.false(token(node.location)) end # foo => [*, bar, *] # ^^^^^^^^^^^ def visit_find_pattern_node(node) elements = [node.left, *node.requireds, node.right] if node.constant builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.find_pattern(nil, visit_all(elements), nil), token(node.closing_loc)) else builder.find_pattern(token(node.opening_loc), visit_all(elements), token(node.closing_loc)) end end # 1.0 # ^^^ def visit_float_node(node) visit_numeric(node, builder.float([node.value, srange(node.location)])) end # for foo in bar do end # ^^^^^^^^^^^^^^^^^^^^^ def visit_for_node(node) builder.for( token(node.for_keyword_loc), visit(node.index), token(node.in_keyword_loc), visit(node.collection), if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";") end, visit(node.statements), token(node.end_keyword_loc) ) end # def foo(...); bar(...); end # ^^^ def visit_forwarding_arguments_node(node) builder.forwarded_args(token(node.location)) end # def foo(...); end # ^^^ def visit_forwarding_parameter_node(node) builder.forward_arg(token(node.location)) end # super # ^^^^^ # # super {} # ^^^^^^^^ def visit_forwarding_super_node(node) visit_block( builder.keyword_cmd( :zsuper, ["super", srange_offsets(node.location.start_offset, node.location.start_offset + 5)] ), node.block ) end # $foo # ^^^^ def visit_global_variable_read_node(node) builder.gvar(token(node.location)) end # $foo = 1 # ^^^^^^^^ def visit_global_variable_write_node(node) builder.assign( builder.assignable(builder.gvar(token(node.name_loc))), token(node.operator_loc), visit(node.value) ) end # $foo += bar # ^^^^^^^^^^^ def visit_global_variable_operator_write_node(node) builder.op_assign( builder.assignable(builder.gvar(token(node.name_loc))), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # $foo &&= bar # ^^^^^^^^^^^^ def visit_global_variable_and_write_node(node) builder.op_assign( builder.assignable(builder.gvar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # $foo ||= bar # ^^^^^^^^^^^^ def visit_global_variable_or_write_node(node) builder.op_assign( builder.assignable(builder.gvar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # $foo, = bar # ^^^^ def visit_global_variable_target_node(node) builder.assignable(builder.gvar([node.slice, srange(node.location)])) end # {} # ^^ def visit_hash_node(node) builder.associate( token(node.opening_loc), visit_all(node.elements), token(node.closing_loc) ) end # foo => {} # ^^ def visit_hash_pattern_node(node) elements = [*node.elements, *node.rest] if node.constant builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.hash_pattern(nil, visit_all(elements), nil), token(node.closing_loc)) else builder.hash_pattern(token(node.opening_loc), visit_all(elements), token(node.closing_loc)) end end # if foo then bar end # ^^^^^^^^^^^^^^^^^^^ # # bar if foo # ^^^^^^^^^^ # # foo ? bar : baz # ^^^^^^^^^^^^^^^ def visit_if_node(node) if !node.if_keyword_loc builder.ternary( visit(node.predicate), token(node.then_keyword_loc), visit(node.statements), token(node.subsequent.else_keyword_loc), visit(node.subsequent) ) elsif node.if_keyword_loc.start_offset == node.location.start_offset builder.condition( token(node.if_keyword_loc), visit(node.predicate), if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";") end, visit(node.statements), case node.subsequent when IfNode token(node.subsequent.if_keyword_loc) when ElseNode token(node.subsequent.else_keyword_loc) end, visit(node.subsequent), if node.if_keyword != "elsif" token(node.end_keyword_loc) end ) else builder.condition_mod( visit(node.statements), visit(node.subsequent), token(node.if_keyword_loc), visit(node.predicate) ) end end # 1i # ^^ def visit_imaginary_node(node) visit_numeric(node, builder.complex([Complex(0, node.numeric.value), srange(node.location)])) end # { foo: } # ^^^^ def visit_implicit_node(node) raise CompilationError, "Cannot directly compile implicit nodes" end # foo { |bar,| } # ^ def visit_implicit_rest_node(node) raise CompilationError, "Cannot compile implicit rest nodes" end # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ def visit_in_node(node) pattern = nil guard = nil case node.pattern when IfNode pattern = within_pattern { |compiler| node.pattern.statements.accept(compiler) } guard = builder.if_guard(token(node.pattern.if_keyword_loc), visit(node.pattern.predicate)) when UnlessNode pattern = within_pattern { |compiler| node.pattern.statements.accept(compiler) } guard = builder.unless_guard(token(node.pattern.keyword_loc), visit(node.pattern.predicate)) else pattern = within_pattern { |compiler| node.pattern.accept(compiler) } end builder.in_pattern( token(node.in_loc), pattern, guard, if (then_loc = node.then_loc) token(then_loc) else srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";") end, visit(node.statements) ) end # foo[bar] += baz # ^^^^^^^^^^^^^^^ def visit_index_operator_write_node(node) arguments = node.arguments&.arguments || [] arguments << node.block if node.block builder.op_assign( builder.index( visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc) ), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # foo[bar] &&= baz # ^^^^^^^^^^^^^^^^ def visit_index_and_write_node(node) arguments = node.arguments&.arguments || [] arguments << node.block if node.block builder.op_assign( builder.index( visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc) ), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo[bar] ||= baz # ^^^^^^^^^^^^^^^^ def visit_index_or_write_node(node) arguments = node.arguments&.arguments || [] arguments << node.block if node.block builder.op_assign( builder.index( visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc) ), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo[bar], = 1 # ^^^^^^^^ def visit_index_target_node(node) builder.index_asgn( visit(node.receiver), token(node.opening_loc), visit_all(node.arguments&.arguments || []), token(node.closing_loc), ) end # @foo # ^^^^ def visit_instance_variable_read_node(node) builder.ivar(token(node.location)) end # @foo = 1 # ^^^^^^^^ def visit_instance_variable_write_node(node) builder.assign( builder.assignable(builder.ivar(token(node.name_loc))), token(node.operator_loc), visit(node.value) ) end # @foo += bar # ^^^^^^^^^^^ def visit_instance_variable_operator_write_node(node) builder.op_assign( builder.assignable(builder.ivar(token(node.name_loc))), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # @foo &&= bar # ^^^^^^^^^^^^ def visit_instance_variable_and_write_node(node) builder.op_assign( builder.assignable(builder.ivar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # @foo ||= bar # ^^^^^^^^^^^^ def visit_instance_variable_or_write_node(node) builder.op_assign( builder.assignable(builder.ivar(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # @foo, = bar # ^^^^ def visit_instance_variable_target_node(node) builder.assignable(builder.ivar(token(node.location))) end # 1 # ^ def visit_integer_node(node) visit_numeric(node, builder.integer([node.value, srange(node.location)])) end # /foo #{bar}/ # ^^^^^^^^^^^^ def visit_interpolated_regular_expression_node(node) builder.regexp_compose( token(node.opening_loc), string_nodes_from_interpolation(node, node.opening), [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)], builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)]) ) end # if /foo #{bar}/ then end # ^^^^^^^^^^^^ alias visit_interpolated_match_last_line_node visit_interpolated_regular_expression_node # "foo #{bar}" # ^^^^^^^^^^^^ def visit_interpolated_string_node(node) if node.heredoc? return visit_heredoc(node) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) } end builder.string_compose( token(node.opening_loc), string_nodes_from_interpolation(node, node.opening), token(node.closing_loc) ) end # :"foo #{bar}" # ^^^^^^^^^^^^^ def visit_interpolated_symbol_node(node) builder.symbol_compose( token(node.opening_loc), string_nodes_from_interpolation(node, node.opening), token(node.closing_loc) ) end # `foo #{bar}` # ^^^^^^^^^^^^ def visit_interpolated_x_string_node(node) if node.heredoc? return visit_heredoc(node) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } end builder.xstring_compose( token(node.opening_loc), string_nodes_from_interpolation(node, node.opening), token(node.closing_loc) ) end # -> { it } # ^^ def visit_it_local_variable_read_node(node) builder.ident([:it, srange(node.location)]).updated(:lvar) end # -> { it } # ^^^^^^^^^ def visit_it_parameters_node(node) # FIXME: The builder _should_ always be a subclass of the prism builder. # Currently RuboCop passes in its own builder that always inherits from the # parser builder (which is lacking the `itarg` method). Once rubocop-ast # opts in to use the custom prism builder a warning can be emitted when # it is not the expected class, and eventually raise. # https://github.com/rubocop/rubocop-ast/pull/354 if builder.is_a?(Translation::Parser::Builder) builder.itarg else builder.args(nil, [], nil, false) end end # foo(bar: baz) # ^^^^^^^^ def visit_keyword_hash_node(node) builder.associate(nil, visit_all(node.elements), nil) end # def foo(**bar); end # ^^^^^ # # def foo(**); end # ^^ def visit_keyword_rest_parameter_node(node) builder.kwrestarg( token(node.operator_loc), node.name ? [node.name, srange(node.name_loc)] : nil ) end # -> {} # ^^^^^ def visit_lambda_node(node) parameters = node.parameters implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode) builder.block( builder.call_lambda(token(node.operator_loc)), [node.opening, srange(node.opening_loc)], if parameters.nil? builder.args(nil, [], nil, false) elsif implicit_parameters visit(node.parameters) else builder.args( token(node.parameters.opening_loc), visit(node.parameters), token(node.parameters.closing_loc), false ) end, visit(node.body), [node.closing, srange(node.closing_loc)] ) end # foo # ^^^ def visit_local_variable_read_node(node) builder.ident([node.name, srange(node.location)]).updated(:lvar) end # foo = 1 # ^^^^^^^ def visit_local_variable_write_node(node) builder.assign( builder.assignable(builder.ident(token(node.name_loc))), token(node.operator_loc), visit(node.value) ) end # foo += bar # ^^^^^^^^^^ def visit_local_variable_operator_write_node(node) builder.op_assign( builder.assignable(builder.ident(token(node.name_loc))), [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)], visit(node.value) ) end # foo &&= bar # ^^^^^^^^^^^ def visit_local_variable_and_write_node(node) builder.op_assign( builder.assignable(builder.ident(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo ||= bar # ^^^^^^^^^^^ def visit_local_variable_or_write_node(node) builder.op_assign( builder.assignable(builder.ident(token(node.name_loc))), [node.operator_loc.slice.chomp("="), srange(node.operator_loc)], visit(node.value) ) end # foo, = bar # ^^^ def visit_local_variable_target_node(node) if in_pattern builder.assignable(builder.match_var([node.name, srange(node.location)])) else builder.assignable(builder.ident(token(node.location))) end end # foo in bar # ^^^^^^^^^^ def visit_match_predicate_node(node) builder.match_pattern_p( visit(node.value), token(node.operator_loc), within_pattern { |compiler| node.pattern.accept(compiler) } ) end # foo => bar # ^^^^^^^^^^ def visit_match_required_node(node) builder.match_pattern( visit(node.value), token(node.operator_loc), within_pattern { |compiler| node.pattern.accept(compiler) } ) end # /(?foo)/ =~ bar # ^^^^^^^^^^^^^^^^^^^^ def visit_match_write_node(node) builder.match_op( visit(node.call.receiver), token(node.call.message_loc), visit(node.call.arguments.arguments.first) ) end # A node that is missing from the syntax tree. This is only used in the # case of a syntax error. The parser gem doesn't have such a concept, so # we invent our own here. def visit_missing_node(node) ::AST::Node.new(:missing, [], location: ::Parser::Source::Map.new(srange(node.location))) end # module Foo; end # ^^^^^^^^^^^^^^^ def visit_module_node(node) builder.def_module( token(node.module_keyword_loc), visit(node.constant_path), node.body&.accept(copy_compiler(forwarding: [])), token(node.end_keyword_loc) ) end # foo, bar = baz # ^^^^^^^^ def visit_multi_target_node(node) builder.multi_lhs( token(node.lparen_loc), visit_all(multi_target_elements(node)), token(node.rparen_loc) ) end # foo, bar = baz # ^^^^^^^^^^^^^^ def visit_multi_write_node(node) elements = multi_target_elements(node) if elements.length == 1 && elements.first.is_a?(MultiTargetNode) && !node.rest elements = multi_target_elements(elements.first) end builder.multi_assign( builder.multi_lhs( token(node.lparen_loc), visit_all(elements), token(node.rparen_loc) ), token(node.operator_loc), visit(node.value) ) end # next # ^^^^ # # next foo # ^^^^^^^^ def visit_next_node(node) builder.keyword_cmd( :next, token(node.keyword_loc), nil, visit(node.arguments) || [], nil ) end # nil # ^^^ def visit_nil_node(node) builder.nil(token(node.location)) end # def foo(**nil); end # ^^^^^ def visit_no_keywords_parameter_node(node) if in_pattern builder.match_nil_pattern(token(node.operator_loc), token(node.keyword_loc)) else builder.kwnilarg(token(node.operator_loc), token(node.keyword_loc)) end end # -> { _1 + _2 } # ^^^^^^^^^^^^^^ def visit_numbered_parameters_node(node) builder.numargs(node.maximum) end # $1 # ^^ def visit_numbered_reference_read_node(node) builder.nth_ref([node.number, srange(node.location)]) end # def foo(bar: baz); end # ^^^^^^^^ def visit_optional_keyword_parameter_node(node) builder.kwoptarg([node.name, srange(node.name_loc)], visit(node.value)) end # def foo(bar = 1); end # ^^^^^^^ def visit_optional_parameter_node(node) builder.optarg(token(node.name_loc), token(node.operator_loc), visit(node.value)) end # a or b # ^^^^^^ def visit_or_node(node) builder.logical_op(:or, visit(node.left), token(node.operator_loc), visit(node.right)) end # def foo(bar, *baz); end # ^^^^^^^^^ def visit_parameters_node(node) params = [] if node.requireds.any? node.requireds.each do |required| params << if required.is_a?(RequiredParameterNode) visit(required) else required.accept(copy_compiler(in_destructure: true)) end end end params.concat(visit_all(node.optionals)) if node.optionals.any? params << visit(node.rest) if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) if node.posts.any? node.posts.each do |post| params << if post.is_a?(RequiredParameterNode) visit(post) else post.accept(copy_compiler(in_destructure: true)) end end end params.concat(visit_all(node.keywords)) if node.keywords.any? params << visit(node.keyword_rest) if !node.keyword_rest.nil? params << visit(node.block) if !node.block.nil? params end # () # ^^ # # (1) # ^^^ def visit_parentheses_node(node) builder.begin( token(node.opening_loc), visit(node.body), token(node.closing_loc) ) end # foo => ^(bar) # ^^^^^^ def visit_pinned_expression_node(node) parts = node.expression.accept(copy_compiler(in_pattern: false)) # Don't treat * and similar as match_rest expression = builder.begin(token(node.lparen_loc), parts, token(node.rparen_loc)) builder.pin(token(node.operator_loc), expression) end # foo = 1 and bar => ^foo # ^^^^ def visit_pinned_variable_node(node) builder.pin(token(node.operator_loc), visit(node.variable)) end # END {} def visit_post_execution_node(node) builder.postexe( token(node.keyword_loc), token(node.opening_loc), visit(node.statements), token(node.closing_loc) ) end # BEGIN {} def visit_pre_execution_node(node) builder.preexe( token(node.keyword_loc), token(node.opening_loc), visit(node.statements), token(node.closing_loc) ) end # The top-level program node. def visit_program_node(node) visit(node.statements) end # 0..5 # ^^^^ def visit_range_node(node) if node.exclude_end? builder.range_exclusive( visit(node.left), token(node.operator_loc), visit(node.right) ) else builder.range_inclusive( visit(node.left), token(node.operator_loc), visit(node.right) ) end end # if foo .. bar; end # ^^^^^^^^^^ alias visit_flip_flop_node visit_range_node # 1r # ^^ def visit_rational_node(node) visit_numeric(node, builder.rational([node.value, srange(node.location)])) end # redo # ^^^^ def visit_redo_node(node) builder.keyword_cmd(:redo, token(node.location)) end # /foo/ # ^^^^^ def visit_regular_expression_node(node) parts = if node.content == "" [] elsif node.content.include?("\n") string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) else [builder.string_internal([node.unescaped, srange(node.content_loc)])] end builder.regexp_compose( token(node.opening_loc), parts, [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)], builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)]) ) end # if /foo/ then end # ^^^^^ alias visit_match_last_line_node visit_regular_expression_node # def foo(bar:); end # ^^^^ def visit_required_keyword_parameter_node(node) builder.kwarg([node.name, srange(node.name_loc)]) end # def foo(bar); end # ^^^ def visit_required_parameter_node(node) builder.arg(token(node.location)) end # foo rescue bar # ^^^^^^^^^^^^^^ def visit_rescue_modifier_node(node) builder.begin_body( visit(node.expression), [ builder.rescue_body( token(node.keyword_loc), nil, nil, nil, nil, visit(node.rescue_expression) ) ] ) end # begin; rescue; end # ^^^^^^^ def visit_rescue_node(node) raise CompilationError, "Cannot directly compile rescue nodes" end # def foo(*bar); end # ^^^^ # # def foo(*); end # ^ def visit_rest_parameter_node(node) builder.restarg(token(node.operator_loc), token(node.name_loc)) end # retry # ^^^^^ def visit_retry_node(node) builder.keyword_cmd(:retry, token(node.location)) end # return # ^^^^^^ # # return 1 # ^^^^^^^^ def visit_return_node(node) builder.keyword_cmd( :return, token(node.keyword_loc), nil, visit(node.arguments) || [], nil ) end # self # ^^^^ def visit_self_node(node) builder.self(token(node.location)) end # A shareable constant. def visit_shareable_constant_node(node) visit(node.write) end # class << self; end # ^^^^^^^^^^^^^^^^^^ def visit_singleton_class_node(node) builder.def_sclass( token(node.class_keyword_loc), token(node.operator_loc), visit(node.expression), node.body&.accept(copy_compiler(forwarding: [])), token(node.end_keyword_loc) ) end # __ENCODING__ # ^^^^^^^^^^^^ def visit_source_encoding_node(node) builder.accessible(builder.__ENCODING__(token(node.location))) end # __FILE__ # ^^^^^^^^ def visit_source_file_node(node) builder.accessible(builder.__FILE__(token(node.location))) end # __LINE__ # ^^^^^^^^ def visit_source_line_node(node) builder.accessible(builder.__LINE__(token(node.location))) end # foo(*bar) # ^^^^ # # def foo((bar, *baz)); end # ^^^^ # # def foo(*); bar(*); end # ^ def visit_splat_node(node) if node.expression.nil? && forwarding.include?(:*) builder.forwarded_restarg(token(node.operator_loc)) elsif in_destructure builder.restarg(token(node.operator_loc), token(node.expression&.location)) elsif in_pattern builder.match_rest(token(node.operator_loc), token(node.expression&.location)) else builder.splat(token(node.operator_loc), visit(node.expression)) end end # A list of statements. def visit_statements_node(node) builder.compstmt(visit_all(node.body)) end # "foo" # ^^^^^ def visit_string_node(node) if node.heredoc? visit_heredoc(node.to_interpolated) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) } elsif node.opening == "?" builder.character([node.unescaped, srange(node.location)]) elsif node.opening&.start_with?("%") && node.unescaped.empty? builder.string_compose(token(node.opening_loc), [], token(node.closing_loc)) else parts = if node.content.include?("\n") string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) else [builder.string_internal([node.unescaped, srange(node.content_loc)])] end builder.string_compose( token(node.opening_loc), parts, token(node.closing_loc) ) end end # super(foo) # ^^^^^^^^^^ def visit_super_node(node) arguments = node.arguments&.arguments || [] block = node.block if block.is_a?(BlockArgumentNode) arguments = [*arguments, block] block = nil end visit_block( builder.keyword_cmd( :super, token(node.keyword_loc), token(node.lparen_loc), visit_all(arguments), token(node.rparen_loc) ), block ) end # :foo # ^^^^ def visit_symbol_node(node) if node.closing_loc.nil? if node.opening_loc.nil? builder.symbol_internal([node.unescaped, srange(node.location)]) else builder.symbol([node.unescaped, srange(node.location)]) end else parts = if node.value == "" [] elsif node.value.include?("\n") string_nodes_from_line_continuations(node.unescaped, node.value, node.value_loc.start_offset, node.opening) else [builder.string_internal([node.unescaped, srange(node.value_loc)])] end builder.symbol_compose( token(node.opening_loc), parts, token(node.closing_loc) ) end end # true # ^^^^ def visit_true_node(node) builder.true(token(node.location)) end # undef foo # ^^^^^^^^^ def visit_undef_node(node) builder.undef_method(token(node.keyword_loc), visit_all(node.names)) end # unless foo; bar end # ^^^^^^^^^^^^^^^^^^^ # # bar unless foo # ^^^^^^^^^^^^^^ def visit_unless_node(node) if node.keyword_loc.start_offset == node.location.start_offset builder.condition( token(node.keyword_loc), visit(node.predicate), if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";") end, visit(node.else_clause), token(node.else_clause&.else_keyword_loc), visit(node.statements), token(node.end_keyword_loc) ) else builder.condition_mod( visit(node.else_clause), visit(node.statements), token(node.keyword_loc), visit(node.predicate) ) end end # until foo; bar end # ^^^^^^^^^^^^^^^^^^ # # bar until foo # ^^^^^^^^^^^^^ def visit_until_node(node) if node.location.start_offset == node.keyword_loc.start_offset builder.loop( :until, token(node.keyword_loc), visit(node.predicate), if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") end, visit(node.statements), token(node.closing_loc) ) else builder.loop_mod( :until, visit(node.statements), token(node.keyword_loc), visit(node.predicate) ) end end # case foo; when bar; end # ^^^^^^^^^^^^^ def visit_when_node(node) builder.when( token(node.keyword_loc), visit_all(node.conditions), if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";") end, visit(node.statements) ) end # while foo; bar end # ^^^^^^^^^^^^^^^^^^ # # bar while foo # ^^^^^^^^^^^^^ def visit_while_node(node) if node.location.start_offset == node.keyword_loc.start_offset builder.loop( :while, token(node.keyword_loc), visit(node.predicate), if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") end, visit(node.statements), token(node.closing_loc) ) else builder.loop_mod( :while, visit(node.statements), token(node.keyword_loc), visit(node.predicate) ) end end # `foo` # ^^^^^ def visit_x_string_node(node) if node.heredoc? return visit_heredoc(node.to_interpolated) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } end parts = if node.content == "" [] elsif node.content.include?("\n") string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) else [builder.string_internal([node.unescaped, srange(node.content_loc)])] end builder.xstring_compose( token(node.opening_loc), parts, token(node.closing_loc) ) end # yield # ^^^^^ # # yield 1 # ^^^^^^^ def visit_yield_node(node) builder.keyword_cmd( :yield, token(node.keyword_loc), token(node.lparen_loc), visit(node.arguments) || [], token(node.rparen_loc) ) end private # Initialize a new compiler with the given option overrides, used to # visit a subtree with the given options. def copy_compiler(forwarding: self.forwarding, in_destructure: self.in_destructure, in_pattern: self.in_pattern) Compiler.new(parser, offset_cache, forwarding: forwarding, in_destructure: in_destructure, in_pattern: in_pattern) end # When *, **, &, or ... are used as an argument in a method call, we # check if they were allowed by the current context. To determine that # we build this lookup table. def find_forwarding(node) return [] if node.nil? forwarding = [] forwarding << :* if node.rest.is_a?(RestParameterNode) && node.rest.name.nil? forwarding << :** if node.keyword_rest.is_a?(KeywordRestParameterNode) && node.keyword_rest.name.nil? forwarding << :& if !node.block.nil? && node.block.name.nil? forwarding |= [:&, :"..."] if node.keyword_rest.is_a?(ForwardingParameterNode) forwarding end # Returns the set of targets for a MultiTargetNode or a MultiWriteNode. def multi_target_elements(node) elements = [*node.lefts] elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) elements.concat(node.rights) elements end # Negate the value of a numeric node. This is a special case where you # have a negative sign on one line and then a number on the next line. # In normal Ruby, this will always be a method call. The parser gem, # however, marks this as a numeric literal. We have to massage the tree # here to get it into the correct form. def numeric_negate(message_loc, receiver) case receiver.type when :integer_node, :float_node receiver.copy(value: -receiver.value, location: message_loc.join(receiver.location)) when :rational_node receiver.copy(numerator: -receiver.numerator, location: message_loc.join(receiver.location)) when :imaginary_node receiver.copy(numeric: numeric_negate(message_loc, receiver.numeric), location: message_loc.join(receiver.location)) end end # Blocks can have a special set of parameters that automatically expand # when given arrays if they have a single required parameter and no # other parameters. def procarg0?(parameters) parameters && parameters.requireds.length == 1 && parameters.optionals.empty? && parameters.rest.nil? && parameters.posts.empty? && parameters.keywords.empty? && parameters.keyword_rest.nil? && parameters.block.nil? end # Locations in the parser gem AST are generated using this class. We # store a reference to its constant to make it slightly faster to look # up. Range = ::Parser::Source::Range # Constructs a new source range from the given start and end offsets. def srange(location) Range.new(source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset]) if location end # Constructs a new source range from the given start and end offsets. def srange_offsets(start_offset, end_offset) Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end # Constructs a new source range by finding the given character between # the given start offset and end offset. If the needle is not found, it # returns nil. Importantly it does not search past newlines or comments. # # Note that end_offset is allowed to be nil, in which case this will # search until the end of the string. def srange_find(start_offset, end_offset, character) if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/]) final_offset = start_offset + match.bytesize [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])] end end # Transform a location into a token that the parser gem expects. def token(location) [location.slice, Range.new(source_buffer, offset_cache[location.start_offset], offset_cache[location.end_offset])] if location end # Visit a block node on a call. def visit_block(call, block) if block parameters = block.parameters implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode) builder.block( call, token(block.opening_loc), if parameters.nil? builder.args(nil, [], nil, false) elsif implicit_parameters visit(parameters) else builder.args( token(parameters.opening_loc), if procarg0?(parameters.parameters) parameter = parameters.parameters.requireds.first visited = parameter.is_a?(RequiredParameterNode) ? visit(parameter) : parameter.accept(copy_compiler(in_destructure: true)) [builder.procarg0(visited)].concat(visit_all(parameters.locals)) else visit(parameters) end, token(parameters.closing_loc), false ) end, visit(block.body), token(block.closing_loc) ) else call end end # Visit a heredoc that can be either a string or an xstring. def visit_heredoc(node) children = Array.new indented = false # If this is a dedenting heredoc, then we need to insert the opening # content into the children as well. if node.opening.start_with?("<<~") && node.parts.length > 0 && !node.parts.first.is_a?(StringNode) location = node.parts.first.location location = location.copy(start_offset: location.start_offset - location.start_line_slice.bytesize) children << builder.string_internal(token(location)) indented = true end node.parts.each do |part| pushing = if part.is_a?(StringNode) && part.content.include?("\n") string_nodes_from_line_continuations(part.unescaped, part.content, part.location.start_offset, node.opening) else [visit(part)] end pushing.each do |child| if child.type == :str && child.children.last == "" # nothing elsif child.type == :str && children.last && children.last.type == :str && !children.last.children.first.end_with?("\n") appendee = children[-1] location = appendee.loc location = location.with_expression(location.expression.join(child.loc.expression)) children[-1] = appendee.updated(:str, ["#{appendee.children.first}#{child.children.first}"], location: location) else children << child end end end closing = node.closing closing_t = [closing.chomp, srange_offsets(node.closing_loc.start_offset, node.closing_loc.end_offset - (closing[/\s+$/]&.length || 0))] composed = yield children, closing_t composed = composed.updated(nil, children[1..-1]) if indented composed end # Visit a numeric node and account for the optional sign. def visit_numeric(node, value) if (slice = node.slice).match?(/^[+-]/) builder.unary_num( [slice[0].to_sym, srange_offsets(node.location.start_offset, node.location.start_offset + 1)], value ) else value end end # Within the given block, track that we're within a pattern. def within_pattern begin parser.pattern_variables.push yield copy_compiler(in_pattern: true) ensure parser.pattern_variables.pop end end # When the content of a string node is split across multiple lines, the # parser gem creates individual string nodes for each line the content is part of. def string_nodes_from_interpolation(node, opening) node.parts.flat_map do |part| if part.type == :string_node && part.content.include?("\n") && part.opening_loc.nil? string_nodes_from_line_continuations(part.unescaped, part.content, part.content_loc.start_offset, opening) else visit(part) end end end # Create parser string nodes from a single prism node. The parser gem # "glues" strings together when a line continuation is encountered. def string_nodes_from_line_continuations(unescaped, escaped, start_offset, opening) unescaped = unescaped.lines escaped = escaped.lines percent_array = opening&.start_with?("%w", "%W", "%i", "%I") regex = opening == "/" || opening&.start_with?("%r") # Non-interpolating strings if opening&.end_with?("'") || opening&.start_with?("%q", "%s", "%w", "%i") current_length = 0 current_line = +"" escaped.filter_map.with_index do |escaped_line, index| unescaped_line = unescaped.fetch(index, "") current_length += escaped_line.bytesize current_line << unescaped_line # Glue line continuations together. Only %w and %i arrays can contain these. if percent_array && escaped_line[/(\\)*\n$/, 1]&.length&.odd? next unless index == escaped.count - 1 end s = builder.string_internal([current_line, srange_offsets(start_offset, start_offset + current_length)]) start_offset += escaped_line.bytesize current_line = +"" current_length = 0 s end else escaped_lengths = [] normalized_lengths = [] # Keeps track of where an unescaped line should start a new token. An unescaped # \n would otherwise be indistinguishable from the actual newline at the end of # of the line. The parser gem only emits a new string node at "real" newlines, # line continuations don't start a new node as well. do_next_tokens = [] escaped .chunk_while { |before, after| before[/(\\*)\r?\n$/, 1]&.length&.odd? || false } .each do |lines| escaped_lengths << lines.sum(&:bytesize) unescaped_lines_count = if regex 0 # Will always be preserved as is else lines.sum do |line| count = line.scan(/(\\*)n/).count { |(backslashes)| backslashes&.length&.odd? } count -= 1 if !line.end_with?("\n") && count > 0 count end end extra = 1 extra = lines.count if percent_array # Account for line continuations in percent arrays normalized_lengths.concat(Array.new(unescaped_lines_count + extra, 0)) normalized_lengths[-1] = lines.sum { |line| line.bytesize } do_next_tokens.concat(Array.new(unescaped_lines_count + extra, false)) do_next_tokens[-1] = true end current_line = +"" current_normalized_length = 0 emitted_count = 0 unescaped.filter_map.with_index do |unescaped_line, index| current_line << unescaped_line current_normalized_length += normalized_lengths.fetch(index, 0) if do_next_tokens[index] inner_part = builder.string_internal([current_line, srange_offsets(start_offset, start_offset + current_normalized_length)]) start_offset += escaped_lengths.fetch(emitted_count, 0) current_line = +"" current_normalized_length = 0 emitted_count += 1 inner_part else nil end end end end end end end end PK!Sʘʘ prism_ruby.rbnu[# frozen_string_literal: true require 'prism' require_relative 'ripper_state_lex' # Unlike lib/rdoc/parser/ruby.rb, this file is not based on rtags and does not contain code from # rtags.rb - # ruby-lex.rb - ruby lexcal analyzer # ruby-token.rb - ruby tokens # Parse and collect document from Ruby source code. # RDoc::Parser::PrismRuby is compatible with RDoc::Parser::Ruby and aims to replace it. class RDoc::Parser::PrismRuby < RDoc::Parser parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER'] attr_accessor :visibility attr_reader :container, :singleton def initialize(top_level, content, options, stats) super content = handle_tab_width(content) @size = 0 @token_listeners = nil content = RDoc::Encoding.remove_magic_comment content @content = content @markup = @options.markup @track_visibility = :nodoc != @options.visibility @encoding = @options.encoding @module_nesting = [[top_level, false]] @container = top_level @visibility = :public @singleton = false @in_proc_block = false end # Suppress `extend` and `include` within block # because they might be a metaprogramming block # example: `Module.new { include M }` `M.module_eval { include N }` def with_in_proc_block @in_proc_block = true yield @in_proc_block = false end # Dive into another container def with_container(container, singleton: false) old_container = @container old_visibility = @visibility old_singleton = @singleton old_in_proc_block = @in_proc_block @visibility = :public @container = container @singleton = singleton @in_proc_block = false unless singleton # Need to update module parent chain to emulate Module.nesting. # This mechanism is inaccurate and needs to be fixed. container.parent = old_container end @module_nesting.push([container, singleton]) yield container ensure @container = old_container @visibility = old_visibility @singleton = old_singleton @in_proc_block = old_in_proc_block @module_nesting.pop end # Records the location of this +container+ in the file for this parser and # adds it to the list of classes and modules in the file. def record_location(container) # :nodoc: case container when RDoc::ClassModule then @top_level.add_to_classes_or_modules container end container.record_location @top_level end # Scans this Ruby file for Ruby constructs def scan @tokens = RDoc::Parser::RipperStateLex.parse(@content) @lines = @content.lines result = Prism.parse(@content) @program_node = result.value @line_nodes = {} prepare_line_nodes(@program_node) prepare_comments(result.comments) return if @top_level.done_documenting @first_non_meta_comment_start_line = nil if (_line_no, start_line = @unprocessed_comments.first) @first_non_meta_comment_start_line = start_line if start_line < @program_node.location.start_line end @program_node.accept(RDocVisitor.new(self, @top_level, @store)) process_comments_until(@lines.size + 1) end def should_document?(code_object) # :nodoc: return true unless @track_visibility return false if code_object.parent&.document_children == false code_object.document_self end # Assign AST node to a line. # This is used to show meta-method source code in the documentation. def prepare_line_nodes(node) # :nodoc: case node when Prism::CallNode, Prism::DefNode @line_nodes[node.location.start_line] ||= node end node.compact_child_nodes.each do |child| prepare_line_nodes(child) end end # Prepares comments for processing. Comments are grouped into consecutive. # Consecutive comment is linked to the next non-blank line. # # Example: # 01| class A # modifier comment 1 # 02| def foo; end # modifier comment 2 # 03| # 04| # consecutive comment 1 start_line: 4 # 05| # consecutive comment 1 linked to line: 7 # 06| # 07| # consecutive comment 2 start_line: 7 # 08| # consecutive comment 2 linked to line: 10 # 09| # 10| def bar; end # consecutive comment 2 linked to this line # 11| end def prepare_comments(comments) current = [] consecutive_comments = [current] @modifier_comments = {} comments.each do |comment| if comment.is_a? Prism::EmbDocComment consecutive_comments << [comment] << (current = []) elsif comment.location.start_line_slice.match?(/\S/) text = comment.slice text = RDoc::Encoding.change_encoding(text, @encoding) if @encoding @modifier_comments[comment.location.start_line] = text elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line current << comment else consecutive_comments << (current = [comment]) end end consecutive_comments.reject!(&:empty?) # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n" # 1| class A # 2| # comment_start_line # 3| # comment # 4| # 5| def f; end # comment linked to this line # 6| end @unprocessed_comments = consecutive_comments.map! do |comments| start_line = comments.first.location.start_line line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1) texts = comments.map do |c| c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice end text = texts.join("\n") text = RDoc::Encoding.change_encoding(text, @encoding) if @encoding line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/) [line_no, start_line, text] end # The first comment is special. It defines markup for the rest of the comments. _, first_comment_start_line, first_comment_text = @unprocessed_comments.first if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) } _text, directives = @preprocess.parse_comment(first_comment_text, first_comment_start_line, :ruby) markup, = directives['markup'] @markup = markup.downcase if markup end end # Creates an RDoc::Method on +container+ from +comment+ if there is a # Signature section in the comment def parse_comment_tomdoc(container, comment, line_no, start_line) return unless signature = RDoc::TomDoc.signature(comment) name, = signature.split %r%[ \(]%, 2 meth = RDoc::GhostMethod.new comment.text, name record_location(meth) meth.line = start_line meth.call_seq = signature return unless meth.name meth.start_collecting_tokens(:ruby) node = @line_nodes[line_no] tokens = node ? visible_tokens_from_location(node.location) : [file_line_comment_token(start_line)] tokens.each { |token| meth.token_stream << token } container.add_method meth meth.comment = comment @stats.add_method meth end def has_modifier_nodoc?(line_no) # :nodoc: @modifier_comments[line_no]&.match?(/\A#\s*:nodoc:/) end def handle_modifier_directive(code_object, line_no) # :nodoc: if (comment_text = @modifier_comments[line_no]) _text, directives = @preprocess.parse_comment(comment_text, line_no, :ruby) handle_code_object_directives(code_object, directives) end end def call_node_name_arguments(call_node) # :nodoc: return [] unless call_node.arguments call_node.arguments.arguments.map do |arg| case arg when Prism::SymbolNode arg.value when Prism::StringNode arg.unescaped end end || [] end # Handles meta method comments def handle_meta_method_comment(comment, directives, node) handle_code_object_directives(@container, directives) is_call_node = node.is_a?(Prism::CallNode) singleton_method = false visibility = @visibility attributes = rw = line_no = method_name = nil directives.each do |directive, (param, line)| case directive when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor' attributes = [param] if param attributes ||= call_node_name_arguments(node) if is_call_node rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R' when 'method' method_name = param if param line_no = line when 'singleton-method' method_name = param if param line_no = line singleton_method = true visibility = :public end end if attributes attributes.each do |attr| a = RDoc::Attr.new(@container, attr, rw, comment, singleton: @singleton) a.store = @store a.line = line_no record_location(a) @container.add_attribute(a) a.visibility = visibility end elsif line_no || node method_name ||= call_node_name_arguments(node).first if is_call_node if node tokens = visible_tokens_from_location(node.location) line_no = node.location.start_line else tokens = [file_line_comment_token(line_no)] end internal_add_method( method_name, @container, comment: comment, directives: directives, dont_rename_initialize: false, line_no: line_no, visibility: visibility, singleton: @singleton || singleton_method, params: nil, calls_super: false, block_params: nil, tokens: tokens, ) end end INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST = %w[ method singleton-method attr attr_reader attr_writer attr_accessor ].freeze private_constant :INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST def normal_comment_treat_as_ghost_method_for_now?(directives, line_no) # :nodoc: # Meta method comment should start with `##` but some comments does not follow this rule. # For now, RDoc accepts them as a meta method comment if there is no node linked to it. !@line_nodes[line_no] && INVALID_GHOST_METHOD_ACCEPT_DIRECTIVE_LIST.any? { |directive| directives.has_key?(directive) } end def handle_standalone_consecutive_comment_directive(comment, directives, start_with_sharp_sharp, line_no, start_line) # :nodoc: if start_with_sharp_sharp && start_line != @first_non_meta_comment_start_line node = @line_nodes[line_no] handle_meta_method_comment(comment, directives, node) elsif normal_comment_treat_as_ghost_method_for_now?(directives, line_no) && start_line != @first_non_meta_comment_start_line handle_meta_method_comment(comment, directives, nil) else handle_code_object_directives(@container, directives) end end # Processes consecutive comments that were not linked to any documentable code until the given line number def process_comments_until(line_no_until) while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until line_no, start_line, text = @unprocessed_comments.shift if @markup == 'tomdoc' comment = RDoc::Comment.new(text, @top_level, :ruby) comment.format = 'tomdoc' parse_comment_tomdoc(@container, comment, line_no, start_line) @preprocess.run_post_processes(comment, @container) elsif (comment_text, directives = parse_comment_text_to_directives(text, start_line)) handle_standalone_consecutive_comment_directive(comment_text, directives, text.start_with?(/#\#$/), line_no, start_line) end end end # Skips all undocumentable consecutive comments until the given line number. # Undocumentable comments are comments written inside `def` or inside undocumentable class/module def skip_comments_until(line_no_until) while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until @unprocessed_comments.shift end end # Returns consecutive comment linked to the given line number def consecutive_comment(line_no) return unless @unprocessed_comments.first&.first == line_no _line_no, start_line, text = @unprocessed_comments.shift parse_comment_text_to_directives(text, start_line) end # Parses comment text and retuns a pair of RDoc::Comment and directives def parse_comment_text_to_directives(comment_text, start_line) # :nodoc: comment_text, directives = @preprocess.parse_comment(comment_text, start_line, :ruby) comment = RDoc::Comment.new(comment_text, @top_level, :ruby) comment.normalized = true comment.line = start_line markup, = directives['markup'] comment.format = markup&.downcase || @markup if (section, = directives['section']) # If comment has :section:, it is not a documentable comment for a code object @container.set_current_section(section, comment.dup) return end @preprocess.run_post_processes(comment, @container) [comment, directives] end def slice_tokens(start_pos, end_pos) # :nodoc: start_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> start_pos) >= 0 } end_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> end_pos) >= 0 } tokens = @tokens[start_index...end_index] tokens.pop if tokens.last&.kind == :on_nl tokens end def file_line_comment_token(line_no) # :nodoc: position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no - 1, 0, :on_comment) position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" position_comment end # Returns tokens from the given location def visible_tokens_from_location(location) position_comment = file_line_comment_token(location.start_line) newline_token = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") indent_token = RDoc::Parser::RipperStateLex::Token.new(location.start_line, 0, :on_sp, ' ' * location.start_character_column) tokens = slice_tokens( [location.start_line, location.start_character_column], [location.end_line, location.end_character_column] ) [position_comment, newline_token, indent_token, *tokens] end # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` def change_method_visibility(names, visibility, singleton: @singleton) new_methods = [] @container.methods_matching(names, singleton) do |m| if m.parent != @container m = m.dup record_location(m) new_methods << m else m.visibility = visibility end end new_methods.each do |method| case method when RDoc::AnyMethod then @container.add_method(method) when RDoc::Attr then @container.add_attribute(method) end method.visibility = visibility end end # Handles `module_function :foo, :bar` def change_method_to_module_function(names) @container.set_visibility_for(names, :private, false) new_methods = [] @container.methods_matching(names) do |m| s_m = m.dup record_location(s_m) s_m.singleton = true new_methods << s_m end new_methods.each do |method| case method when RDoc::AnyMethod then @container.add_method(method) when RDoc::Attr then @container.add_attribute(method) end method.visibility = :public end end def handle_code_object_directives(code_object, directives) # :nodoc: directives.each do |directive, (param)| @preprocess.handle_directive('', directive, param, code_object) end end # Handles `alias foo bar` and `alias_method :foo, :bar` def add_alias_method(old_name, new_name, line_no) comment, directives = consecutive_comment(line_no) handle_code_object_directives(@container, directives) if directives visibility = @container.find_method(old_name, @singleton)&.visibility || :public a = RDoc::Alias.new(nil, old_name, new_name, comment, singleton: @singleton) handle_modifier_directive(a, line_no) a.store = @store a.line = line_no record_location(a) if should_document?(a) @container.add_alias(a) @container.find_method(new_name, @singleton)&.visibility = visibility end end # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b` def add_attributes(names, rw, line_no) comment, directives = consecutive_comment(line_no) handle_code_object_directives(@container, directives) if directives return unless @container.document_children names.each do |symbol| a = RDoc::Attr.new(nil, symbol.to_s, rw, comment, singleton: @singleton) a.store = @store a.line = line_no record_location(a) handle_modifier_directive(a, line_no) @container.add_attribute(a) if should_document?(a) a.visibility = visibility # should set after adding to container end end def add_includes_extends(names, rdoc_class, line_no) # :nodoc: return if @in_proc_block comment, directives = consecutive_comment(line_no) handle_code_object_directives(@container, directives) if directives names.each do |name| ie = @container.add(rdoc_class, name, '') ie.store = @store ie.line = line_no ie.comment = comment record_location(ie) end end # Handle `include Foo, Bar` def add_includes(names, line_no) # :nodoc: add_includes_extends(names, RDoc::Include, line_no) end # Handle `extend Foo, Bar` def add_extends(names, line_no) # :nodoc: add_includes_extends(names, RDoc::Extend, line_no) end # Adds a method defined by `def` syntax def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:) return if @in_proc_block receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container comment, directives = consecutive_comment(start_line) handle_code_object_directives(@container, directives) if directives internal_add_method( method_name, receiver, comment: comment, directives: directives, modifier_comment_lines: [start_line, args_end_line, end_line].uniq, line_no: start_line, visibility: visibility, singleton: singleton, params: params, calls_super: calls_super, block_params: block_params, tokens: tokens ) end private def internal_add_method(method_name, container, comment:, dont_rename_initialize: false, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:) # :nodoc: meth = RDoc::AnyMethod.new(nil, method_name, singleton: singleton) meth.comment = comment handle_code_object_directives(meth, directives) if directives modifier_comment_lines&.each do |line| handle_modifier_directive(meth, line) end return unless should_document?(meth) if directives && (call_seq, = directives['call-seq']) meth.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") if call_seq end meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq meth.name ||= 'unknown' meth.store = @store meth.line = line_no container.add_method(meth) # should add after setting singleton and before setting visibility meth.visibility = visibility meth.params ||= params || '()' meth.calls_super = calls_super meth.block_params ||= block_params if block_params record_location(meth) meth.start_collecting_tokens(:ruby) tokens.each do |token| meth.token_stream << token end # Rename after add_method to register duplicated 'new' and 'initialize' # defined in c and ruby just like the old parser did. if !dont_rename_initialize && method_name == 'initialize' && !singleton if meth.dont_rename_initialize meth.visibility = :protected else meth.name = 'new' meth.singleton = true meth.visibility = :public end end end # Find or create module or class from a given module name. # If module or class does not exist, creates a module or a class according to `create_mode` argument. def find_or_create_module_path(module_name, create_mode) root_name, *path, name = module_name.split('::') add_module = ->(mod, name, mode) { case mode when :class mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } when :module mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store } end } if root_name.empty? mod = @top_level else @module_nesting.reverse_each do |nesting, singleton| next if singleton mod = nesting.find_module_named(root_name) break if mod # If a constant is found and it is not a module or class, RDoc can't document about it. # Return an anonymous module to avoid wrong document creation. return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name) end last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton } return mod || add_module.call(last_nesting, root_name, create_mode) unless name mod ||= add_module.call(last_nesting, root_name, :module) end path.each do |name| mod = mod.find_module_named(name) || add_module.call(mod, name, :module) end mod.find_module_named(name) || add_module.call(mod, name, create_mode) end # Resolves constant path to a full path by searching module nesting def resolve_constant_path(constant_path) owner_name, path = constant_path.split('::', 2) return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar mod = nil @module_nesting.reverse_each do |nesting, singleton| next if singleton mod = nesting.find_module_named(owner_name) break if mod end mod ||= @top_level.find_module_named(owner_name) [mod.full_name, path].compact.join('::') if mod end # Returns a pair of owner module and constant name from a given constant path. # Creates owner module if it does not exist. def find_or_create_constant_owner_name(constant_path) const_path, colon, name = constant_path.rpartition('::') if colon.empty? # class Foo # Within `class C` or `module C`, owner is C(== current container) # Within `class <z,_takes_ascii..func..sasciiz5ISO-8601 strings should contain only ASCII characters)getattr isinstancesix text_typeencodeUnicodeEncodeError raise_from ValueError)selfrargskwargsemsgfs ` rfuncz_takes_ascii..funcs988:: fcm , , 3 3w//% 3 3 3Mz#22222222 3qv///////sA B$BBr)r$r%s` r _takes_asciir&s3 1XX 0 0 0 0X 0 KrceZdZddZedZedZedZeddZdZ d Z e j d Z d Zd Zd ZdZdZddZdS)r Nc|Nt|dkst|dks|dvrtd|d}||_dS)z :param sep: A single character that separates date and time portions. If ``None``, the parser will accept any single character. For strict ISO-8601 adherence, pass ``'T'``. N 0123456789z7Separator must be a single, non-numeric ASCII characterr)lenordrr_sep)rseps r__init__zisoparser.__init__+se ?CA SSC<4G4G "3444**W%%C rc||\}}t||krP|j|||dz|jkr$||||dzdz }nt dt|dkr+|ddkrd|d<t |t dzSt |S)u+ Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. An ISO-8601 datetime string consists of a date portion, followed optionally by a time portion - the date and time portions are separated by a single character separator, which is ``T`` in the official standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be combined with a time portion. Supported date formats are: Common: - ``YYYY`` - ``YYYY-MM`` or ``YYYYMM`` - ``YYYY-MM-DD`` or ``YYYYMMDD`` Uncommon: - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day The ISO week and day numbering follows the same logic as :func:`datetime.date.isocalendar`. Supported time formats are: - ``hh`` - ``hh:mm`` or ``hhmm`` - ``hh:mm:ss`` or ``hhmmss`` - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) Midnight is a special case for `hh`, as the standard supports both 00:00 and 24:00 as a representation. The decimal separator can be either a dot or a comma. .. caution:: Support for fractional components other than seconds is part of the ISO-8601 standard, but is not currently implemented in this parser. Supported time zone offset formats are: - `Z` (UTC) - `±HH:MM` - `±HHMM` - `±HH` Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, with the exception of UTC, which will be represented as :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. :param dt_str: A string or stream containing only an ISO-8601 datetime string :return: Returns a :class:`datetime.datetime` representing the string. Unspecified components default to their lowest value. .. warning:: As of version 2.7.0, the strictness of the parser should not be considered a stable part of the contract. Any valid ISO-8601 string that parses correctly with the default settings will continue to parse correctly in future versions, but invalid strings that currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not guaranteed to continue failing in future versions if they encode a valid date. .. versionadded:: 2.7.0 Nr)z&String contains unknown ISO componentsrdays)_parse_isodater,r._parse_isotimerrr)rdt_str componentsposs rr zisoparser.isoparse;sV--f55 C v;;  y F3sQw;$749$D$Dd11&q2BCCC  !IJJJ z??Q  :a=B#6#6JqMZ(9!+<+<+<< <$$rc||\}}|t|kr8tdd|dzt |S)z Parse the date portion of an ISO string. :param datestr: The string portion of an ISO string, without a separator :return: Returns a :class:`datetime.date` object zString contains unknown ISO zcomponents: {!r}r)r6r,rformatdecoder)rdatestrr9r:s r parse_isodatezisoparser.parse_isodatesr--g66 C W  ;/66w~~g7N7NOOPQQ QZ  rc`||}|ddkrd|d<t|S)z Parse the time portion of an ISO string. :param timestr: The time portion of an ISO string, without a separator :return: Returns a :class:`datetime.time` object rr3)r7r)rtimestrr9s r parse_isotimezisoparser.parse_isotimes:((11 a=B  JqMZ  rTc0|||S)a Parse a valid ISO time zone string. See :func:`isoparser.isoparse` for details on supported formats. :param tzstr: A string representing an ISO time zone offset :param zero_as_utc: Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones :return: Returns :class:`dateutil.tz.tzoffset` for offsets and :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is specified) offsets equivalent to UTC. ) zero_as_utc) _parse_tzstr)rtzstrrDs r parse_tzstrzisoparser.parse_tzstrs$  K @@@r-:s [\.,]([0-9]+)cx ||S#t$r||cYSwxYwr)_parse_isodate_commonr_parse_isodate_uncommon)rr8s rr6zisoparser._parse_isodatesP 8--f55 5 8 8 8//77 7 7 7 8s 99cRt|}gd}|dkrtdt|dd|d<d}||kr||fS|||dz|jk}|r|dz }||z dkrtdt|||dz|d<|dz }||kr|r||fStd|r*|||dz|jkrtd |dz }||z dkrtd t|||dz|d<||dzfS) N)r)r)r)ISO string too shortrr)zInvalid common monthzInvalid ISO formatzInvalid separator in ISO stringzInvalid common day)r,rint _DATE_SEP)rr8len_strr9r:has_seps rrKzisoparser._parse_isodate_commonsf++YY Q;;344 4F1Q3K(( 1  '>>s? "S1W%7   1HC S=1  344 4F3sQw;/00 1  q '>> 7!3& !5666  c#'k"dn44 !BCCC 1HC S=1  122 2F3sQw;/00 1 37""rct|dkrtdt|dd}|dd|jk}d|z}|||dzdkr|dz }t|||dz}|dz }d}t||krM|||dz|jk|krtd||z }t|||dz}|dz }||||}nt||z d krtd t|||d z}|d z }|dks|d t j|zkr&td d ||zt|ddt|dz z}|j |j |j g} | |fS)NrNrOrr)WrPz"Inconsistent use of dash separatorr2zInvalid ordinal dayimz {} for year {}r4) r,rrQrR_calculate_weekdatecalendarisleapr<rryearmonthday) rr8r[rTr:weeknodayno base_date ordinal_dayr9s rrLz!isoparser._parse_isodate_uncommons v;;??344 46!A#;1+/'k #cAg+ $ & & 1HCC!G ,--F 1HCE6{{S  3sQw;'4>9gEE$%IJJJwF3sQw;/00q00vuEEII6{{S 1$$ !6777fSq[122K 1HCQ+xt7L7L1L"M"M !6!2!9!9+t!L!L"MNNNT1a((9+/+J+J+JJInioy}E 3rcd|cxkrdks$ntd|d|cxkrdks$ntd|t|dd}|t|ddz z }|dz d z|dz z}|t| zS) a Calculate the day of corresponding to the ISO year-week-day calendar. This function is effectively the inverse of :func:`datetime.date.isocalendar`. :param year: The year in the ISO calendar :param week: The week in the ISO calendar - range is [1, 53] :param day: The day in the ISO calendar - range is [1 (MON), 7 (SUN)] :return: Returns a :class:`datetime.date` r6zInvalid week: {}zInvalid weekday: {}r)rNrPr4)rr<rr isocalendar)rr[weekr]jan_4week_1 week_offsets rrXzisoparser._calculate_weekdate)s&4}}}}"}}}}/66t<<== =3{{{{{{{{299#>>?? ?T1a  (9(9(;(;A(>(BCCCCax1na0  {33333rct|}gd}d}d}|dkrtdd}||kr^|dkrW|dz }|||dzd vr$|||d|d<|}n|dkr|||dz|jkrd }|dz }n2|dkr,|r*|||dz|jkrtd |dz }|d kr"t |||dz||<|dz }|d kr|j||d}|s|ddd }t |dd t|z zz||<|t|z }||kr|dkW||krtd|ddkr0td|ddDrtd|S)N)rrrrNrrPzISO time too shortFrVr)s-+ZzTz#Inconsistent use of colon separatorr2 zUnused components in ISO stringr3c3"K|] }|dkV dS)rNr).0 components r z+isoparser._parse_isotime..zs&CCi9>CCCCCCrrNz#Hour may only be 24 at 24:00:00.000) r,rrE _TIME_SEPrQ_FRACTION_REGEXmatchgroupany) rrArSr9r:comprTfracus_strs rr7zisoparser._parse_isotimeJs<g,,'''  Q;;122 2Gmmq AIDs37{#w..!%!2!27344=!A!A 2qyyWSQY/4>AAqw3s1u9%77$%JKKKqaxx#&ws37{';#<#< 4 qqyy+11'#$$-@@Arr*#&v;;a#f++o1F#F 4 s4::<<(((=Gmmq@ ==>?? ? a=B  CC:ac?CCCCC H !FGGGrcP|dks|dkr tjSt|dvrtd|dddkrd}n |ddd krd}ntd t |dd }t|d krd}n,t ||d d |jkrd nd d}|r|dkr|dkr tjS|d krtd|dkrtdtjd||dz|zzdzS)NZz>r2rVrmz0Time zone offset must be 1, 3, 5 or 6 charactersrr)rHrl+zTime zone offset requires signr2rN;z#Invalid minutes in time zone offsetz!Invalid hours in time zone offset<)rUTCr,rrQrstzoffset)rrFrDmulthoursminutess rrEzisoparser._parse_tzstrsK D==ETMM6M u::Y & &OPP P 1:  DD 1Q3Z4  DD=>> >E!A#J u::??GG%eAaCjDN&B&B K KLMMG  I5A::'Q,,6M|| !FGGGrzz !DEEE;tTURZ'-A%BR%GHH Hrr)T)__name__ __module__ __qualname__r0r&r r?rBrGrRrsrecompilertr6rKrLrXr7rErrrr r *s( V%V%\V%p!!\!  ! !\ !AAA\A(II bj!233O888 '#'#'#R***X444B333jIIIIIIr)__doc__rrrrrYdateutilr functoolsr rr__all__r&objectr DEFAULT_ISOPARSERr rrrrs544444444444 { #(rIrIrIrIrIrIrIrIj IKK  %rPK!v:gg g $__pycache__/__init__.cpython-311.pycnu[ iddlmZmZmZmZddlmZmZddlmZddlmZddl m Z m Z gdZ dZ dZ dd lmZmZdd lmZmZe eZe eZe eZe eZd S) )parseparser parserinfo ParserError) DEFAULTPARSERDEFAULTTZPARSER)UnknownTimezoneWarning)__doc__) isoparserisoparse)rrrr r rr cddlm}ddldj|fd}|S)N)wrapszo{name} is a private function and may break without warning, it will be moved and or renamed in future versions.namecJt|i|SN)warnDeprecationWarning)argskwargsfmsgwarningss x/builddir/build/BUILD/imunify360-venv-2.6.2/opt/imunify360/venv/lib/python3.11/site-packages/dateutil/parser/__init__.pydeprecated_funcz2__deprecated_private_func..deprecated_funcs- c-...q$!&!!!) functoolsrrformat__name__)rrrrrs` @@r__deprecated_private_funcr!szOOO AC **!** % %C U1XX""""""X" rcddldjGfddj_S)Nrzl{name} is a private class and may break without warning, it will be moved and or renamed in future versions.rc4eZdZjZfdZxZS)0__deprecate_private_class..private_classcttt|j|i|dSr)rrsuper__init__)selfrr __class__r private_classrs rr'z9__deprecate_private_class..private_class.__init__.s@ MM#1 2 2 2 /E- & & / @ @ @ @ @ @r)r __module__ __qualname__r r' __classcell__)r)crr*rs@rr*r$+s[) A A A A A A A A A A A Arr*)rrr )r.rr*rs`@@@r__deprecate_private_classr/$sOOO AC **!** % %CAAAAAAAAAAAAAZM r)_timelex _resultbase) _tzparser_parsetzN)_parserrrrrrrr r r r __all__r!r/r0r1r2r3rrr7s";;;;;;;;;;;;33333333++++++******** % % %   &+*******(((((((( $ $X . . % %i 0 0 '' 44 $ $X . .rPK!<#__pycache__/_parser.cpython-311.pycnu[ idZddlmZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl Z ddl m Z mZddlmZddlmZd d lmZd d lmZgd ZGd deZGddeZGddeZGddeZGddeZeZddZGddeZeZ dZ!Gdde"Z#Gdde$Z%dS) a This module offers a generic date/time string parser which is able to parse most known formats to represent a date and/or time. This module attempts to be forgiving with regards to unlikely input formats, returning a datetime object even for dates which are ambiguous. If an element of a date/time stamp is omitted, the following rules are applied: - If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is specified. - If a time zone is omitted, a timezone-naive datetime is returned. If any other elements are missing, they are taken from the :class:`datetime.datetime` object passed to the parameter ``default``. If this results in a day number exceeding the valid number of days per month, the value falls back to the end of the month. Additional resources about date/time string formats can be found below: - `A summary of the international standard date and time notation `_ - `W3C Date and Time Formats `_ - `Time Formats (Planetary Rings Node) `_ - `CPAN ParseDate module `_ - `Java SimpleDateFormat Class `_ )unicode_literalsN) monthrange)StringIO) integer_types text_type)Decimal)warn) relativedelta)tz)parse parserinfo ParserErrorceZdZejdZdZdZdZdZ dZ e dZ e dZ e d Ze d Zd S) _timelexz([.,])cdt|ttfr|}t|trt |}n>t |dd-td|j j ||_ g|_ g|_ d|_dS)Nreadz8Parser must be a string or character stream, not {itype})itypeF) isinstancebytes bytearraydecoderrgetattr TypeErrorformat __class____name__instream charstack tokenstackeof)selfrs w/builddir/build/BUILD/imunify360-venv-2.6.2/opt/imunify360/venv/lib/python3.11/site-packages/dateutil/parser/_parser.py__init__z_timelex.__init__>s h 2 3 3 )((H h * * Q))HH Xvt , , 4&&,f83E3Nf&O&OQQ Q! cX|jr|jdSd}d}d}|jsQ|jr|jd}n@|jd}|dkr |jd}|dk |s d|_n|sO|}||rd}n||rd}n||rd }nn|dkrId}||r||z }ng|d kr ||z }d }nX|j |nD|dkr^||r||z }n|d ks|d krt|d kr||z }d}n|j |n|d krgd}|d ks||r||z }n||r|dd kr||z }d}n|j |ns|dkre|d ks||r||z }nD||r|dd kr||z }d }n|j |n|jQ|dvrp|s#| d dks |ddvrK|j |}|d}|ddD]}|r|j ||dkr/| d dkr|d d }|S)a This function breaks the time string into lexical units (tokens), which can be parsed by the parser. Lexical units are demarcated by changes in the character set, so any continuous string of letters is considered one unit, any continuous string of numbers is considered one unit. The main complication arises from the fact that dots ('.') can be used both as separators (e.g. "Sep.20.2009") or decimal points (e.g. "4:30:21.447"). As such, it is necessary to read the full context of any dot-separated strings before breaking it into tokens; as such, this function maintains a "token stack", for when the ambiguous context demands that multiple tokens be parsed at once. rFNTa0 .a.,r 0.)r-r/z.,)r popr!rrriswordisnumisspaceappendlencount_split_decimalsplitreplace)r" seenletterstokenstatenextcharltoks r# get_tokenz_timelex.get_tokenMs ? *?&&q)) ) (I  ~ 5>--a00=--a00&((#}11!44H&((= : !;;x((EEZZ))EE\\(++E## ;;x((X%EE__X%E EEN))(333#::h''X%EE__SSZZ1__X%E EEN))(333$# s??dkk(&;&;?X%EEZZ))eBi3.>.>X%E EEN))(333$s??djj&:&:?X%EE[[**uRyC/?/?X%E EEN))(333S(I V \ ! !{ !ekk#6F6F6J6J',RyD'8'8#))%00AaDEu 0 00O**3/// D==U[[--22MM#s++E r%c|SNr"s r#__iter__z_timelex.__iter__s r%c@|}|t|SrC)rA StopIteration)r"r<s r#__next__z_timelex.__next__s!   =  r%c*|SrC)rIrEs r#nextz _timelex.nexts}}r%c2t||SrC)list)clsss r#r9z_timelex.splitsCCFF||r%c*|S)z5 Whether or not the next character is part of a word )isalpharNr>s r#r2z_timelex.isword!!!r%c*|S)z0 Whether the next character is part of a number )isdigitrRs r#r3z_timelex.isnumrSr%c*|S)z* Whether the next character is whitespace )r4rRs r#r4z_timelex.isspacerSr%N)r __module__ __qualname__recompiler8r$rArFrIrK classmethodr9r2r3r4rDr%r#rr:sRZ))N   kkkZ[""["""["""["""r%rc&eZdZdZdZdZdZdS) _resultbasec<|jD]}t||ddSrC) __slots__setattr)r"attrs r#r$z_resultbase.__init__s2N & &D D$ % % % % & &r%cg}|jD];}t||}|'||dt|<|dd|dS)N=(, ))r_rr5reprjoin)r" classnamer?ravalues r#_reprz_resultbase._reprsn N 8 8DD$''E DDD$u+++6777$99diillll33r%cDtfdjDS)Nc3<K|]}t|duVdSrC)r).0rar"s r# z&_resultbase.__len__..sF00D$''t3000000r%)sumr_rEs`r#__len__z_resultbase.__len__s=0000 $00000 1r%c@||jjSrC)rkrrrEs r#__repr__z_resultbase.__repr__szz$.1222r%N)rrWrXr$rkrqrsrDr%r#r]r]sP&&&44411133333r%r]ceZdZdZgdZgdZgdZgdZddgZgdZ d gZ iZ dd Z d Z d ZdZdZdZdZdZdZdZddZdZdS)ra Class which handles what inputs are accepted. Subclass this to customize the language and acceptable values for each parameter. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (``True``) or month (``False``). If ``yearfirst`` is set to ``True``, this distinguishes between YDM and YMD. Default is ``False``. :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If ``True``, the first number is taken to be the year, otherwise the last number is taken to be the year. Default is ``False``. )r+r,r.;-/'atonandadmtofstndrdth))MonMonday)TueTuesday)Wed Wednesday)ThuThursday)FriFriday)SatSaturday)SunSunday) )JanJanuary)FebFebruary)MarMarch)AprApril)Mayr)JunJune)JulJuly)AugAugust)SepSept September)OctOctober)NovNovember)DecDecember))hhourhours)r}minuteminutes)rOsecondseconds)amr))pmp)UTCGMTZzrFc2||j|_||j|_||j|_||j|_||j |_ ||j |_ ||j |_||_||_t#jj|_|jdzdz|_dS)Nd)_convertJUMP_jumpWEEKDAYS _weekdaysMONTHS_monthsHMS_hmsAMPM_ampmUTCZONE_utczonePERTAIN_pertaindayfirst yearfirsttime localtimetm_year_year_century)r"rrs r#r$zparserinfo.__init__&s]]49-- t}55}}T[11 MM$(++ ]]49--  dl33  dl33   "^%%-  c)C/ r%ci}t|D]N\}}t|tr|D]}|||<7|||<O|SrC) enumeratertuplelower)r"lstdctivs r#rzparserinfo._convert5sscNN # #DAq!U## #''A%&C NN'"#AGGII r%c8||jvSrC)rrr"names r#jumpzparserinfo.jump?szz||tz))r%cf |j|S#t$rYnwxYwdSrC)rrKeyErrorrs r#weekdayzparserinfo.weekdayBsA >$**,,/ /    D ts ! ..cl |j|dzS#t$rYnwxYwdSNr')rrrrs r#monthzparserinfo.monthIsF < -1 1    D ts !$ 11cd |j|S#t$rYdSwxYwrC)rrrrs r#hmszparserinfo.hmsPs> 9TZZ\\* *   44  ! //cd |j|S#t$rYdSwxYwrC)rrrrs r#ampmzparserinfo.ampmVs> :djjll+ +   44 rc8||jvSrC)rrrs r#pertainzparserinfo.pertain\zz||t},,r%c8||jvSrC)rrrs r#utczonezparserinfo.utczone_rr%cL||jvrdS|j|S)Nr)rTZOFFSETgetrs r#tzoffsetzparserinfo.tzoffsetbs* 4= 1}  &&&r%c|dksJ|dkr3|s1||jz }||jdzkr|dz}n||jdz kr|dz }|S)zt Converts two-digit years to year within [-50, 49] range of self._year (current local time) rr2)rr)r"yearcentury_specifieds r# convertyearzparserinfo.convertyearhsfqyyyy #::/: DM !DtzB&&  R''  r%c2|j%||j|j|_|jdkr|jr|jdks |jdkrd|_d|_n3|jdkr(|jr!||jrd|_dS)NrrrrT)rrrrtznamer)r"ress r#validatezparserinfo.validate|s 8 ''#2GHHCH \Q  sz jC3:#4#4CJCLL \Q  3: $,,sz2J2J CLtr%N)FF)F)rrWrX__doc__rrrrrrrrr$rrrrrrrrrrrrDr%r#rrsF$ $ $ $D###H # # #F ' ' 'C   D&&&GfGH 0 0 0 0***  ------''' (     r%rc~eZdZfdZedZedZedZdZd fd Z dZ d Z xZ S) _ymdct|j|j|i|d|_d|_d|_d|_dS)NF)superrr$rdstridxmstridxystridx)r"argskwargsrs r#r$z _ymd.__init__sH,dnd##,d=f===!&   r%c|jduSrC)rrEs r#has_yearz _ymd.has_year|4''r%c|jduSrC)rrEs r# has_monthz_ymd.has_monthrr%c|jduSrC)rrEs r#has_dayz _ymd.has_dayrr%c.|jrdS|jsd|cxkodkncS|js1||j}d|cxkot d|dkncS||j}||j}d|cxkot ||dkncS)NFr'i)r rrrrr)r"rjrrs r# could_be_dayz_ymd.could_be_days < <5 <######## # <&E;;;;D%!8!8!;;;;; ;&E %D;;;;D%!8!8!;;;;; ;r%Nct|drD|r/t|dkrd|_|dvrt |d}n"|dkrd|_|dvrt |d}t |j|t||dkr/|j rt dt|d z |_ dS|d kr/|j rt d t|d z |_ dS|dkr/|j rt d t|d z |_dSdS) Nrqr T)NYrrMzMonth is already setr'DzDay is already setzYear is already set)hasattrrUr6r ValueErrorrrr5intrrr rrr)r"vallabelrs r#r5z _ymd.appends[ 3 " " {{}} SA)-& ++$U+++ 3YY%)D "K'' '''E dnd##**3s88444 C<<~ 9 !7888t99q=DLLL c\\| 7 !5666t99q=DLLL c\\} 8 !6777t99q=DLLL\r%ctdkr|tdkrifdtdD}fddD}t|t|cxkrdksnJ|d}|d}||<ttksJfdD}|d |d |d fS) z Try to resolve the identities of year/month/day elements using ystridx, mstridx, and dstridx, if enough of these are specified. r c@g|]}|v|SrD)valuesrnxstridss r# z._ymd._resolve_from_stridxs..s+GGGQav}}.F.Fq.F.F.Fr%cg|]}|v| SrDrDrs r#rz._ymd._resolve_from_stridxs..sAAA&1r%)yr}dr'rc.i|]}||SrDrD)rnkeyr"rs r# z._ymd._resolve_from_stridxs..s$888#sD%888r%rr}r )r6ranger)r"rmissingr"routs`` r#_resolve_from_stridxsz_ymd._resolve_from_stridxss  t99>>c&kkQ..GGGG%((GGGGAAAAoAAACw<<3s880000q000000a&C!*CF3K4yyCKK''''88888888 cggcllCGGCLL99r%cbt|}d\}}}d|jfd|jfd|jff}d|D}t|t|cxkrdks(nt|dkr(t|dkr||S|j}|dkrt d |d ks|:|dkr4|||}||d z } n|d} |d ks| | d kr| }n2| }n.|dkr@|dd kr|\}}n|d d kr|\}}n|r|d d kr|\}}n|\}}n|dkr|dkr|d d kr|\}}}n|\}}}n|d kr(|dd ks|r|dd kr|\}}}n|\}}}n|dkr|d d kr|\}}}n{|\}}}nt|dd ks%|jdks|r4|d d kr(|dd kr|r|dd kr|\}}}n.|\}}}n'|dd ks|r|d d kr|\}}}n|\}}}|||fS) N)NNNrr}r ci|] \}}||| SrCrD)rnr"rs r#r#z$_ymd.resolve_ymd..sEEExsCS_#s___r%rrr zMore than three YMD valuesr'r  )r6rrrr'r) r"rrlen_ymdrrdayrrothers r# resolve_ymdz_ymd.resolve_ymdsd))-eS % % %'FE6EEE IIV ( ( ( (q ( ( ( (TaCKK1$4$4--f55 5, Q;;9:: : \\g1gll"W Wq[)Q{{go2:: DDC \\Aw||" eea2" tt "d1gmm! UU" ss \\!||7R<<'+$E4'+$E3A7R<>> from dateutil.parser import parse >>> from dateutil.tz import gettz >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) This parameter is ignored if ``ignoretz`` is set. :param \*\*kwargs: Keyword arguments as passed to ``_parse()``. :return: Returns a :class:`datetime.datetime` object or, if the ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the first element being a :class:`datetime.datetime` object, the second a tuple containing the fuzzy tokens. :raises ParserError: Raised for invalid or unknown string format, if the provided :class:`tzinfo` is not in a valid format, or if an invalid date would be created. :raises TypeError: Raised for non-string or character stream input. :raises OverflowError: Raised if the parsed date exceeds the largest valid C integer on your system. Nr)rrr microsecondzUnknown string format: %sz"String does not contain a date: %sz: %sfuzzy_with_tokensF) datetimenowr:_parserr6 _build_naiversix raise_fromstr_build_tzawarer) r"timestrdefaultignoretztzinfosrrskipped_tokensretes r#r z parser.parse<sQ@ ?'++--551Q=>A6OOG*dk'<>> from dateutil.parser import parse >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) TNrrr')rvrwr rr+r+rvrPr0r':<rdrf)NN)$r5rrrLrr9rr6floatr_parse_numeric_tokenrrr5rrUrr?rr _ampm_validr _adjust_ampm_could_be_tznamerrrrr.rrr, IndexErrorr_recombine_skippedr)r"rArrfuzzyr8r5rr? skipped_idxsymdlen_lr value_reprrjsepr val_is_ampmsignallen_li hour_offset min_offsetrr,rEs r#r;z parser._parsesR  Ey  }H  Illnn NN7 # # ffA H e))qT !!*--EE!!!! EEE!$11!Qc3NNAA\\!A$''3 LL1..E"'CKKZZ!%%1 JJqt,,EJJuc***1uu}}QU8z11"#AE(CJJqQx000 1uu}}1q5S # 1QU8 4 4 4 !QFAA!eemm!a%Aa!eH0K0K0K0K0K0K0K0K0K"ll1QU8441L !Qx//11%(+Aa!eH '*4+;+;E+B+B'C'C # 4 5 5 5 5!%FAYYqt__0 IIadOOE"&"2"238SXu"M"MK"/#'#4#4SXu#E#E#(/$++A...**38SZqQRtTT;+!"1CJ#'==#<#!a%'+ << 33.*.CJX)adj.@.@$QqTS[1F 1q5]]F{{&)!AE(2A2,&7&7 %(1q5!""%6%6 Q1QU8s??&)!AE(mm %(1q5]] Q1&)!AE(2A2,&7&7 %& (111#)[4-?*r/-Q#RCLA  IIaAh//&45a!eHOOa!eHOOQq1uX.. 11#(CJ26!a%BB/&'q1uX QFAA))AaD//+U+$W---!''***Qye))~ #y(CC D%$'$9C !CHCICGGJ'   :: }}S!! :  !44Q EENn--- -9 s7.X(>B X( BX(BV X((X=<X=c ||} ||}n9#t$r,} tjt d| Yd} ~ nd} ~ wwxYwt |} t |} t |dkr| dvr|j~|dz| ks-||dzdkrf|||dzH||} t| dd|_| dkrt| dd|_ n| dks%| dkr|| d dkr||} |scd ||vrY| | dd| | dd| | ddnt| dd|_t| dd|_ | | dd\|_ |_n| d vr||} | | ddd | | dd| | dd | d krZt| d d |_t| d d|_ | dkrt| dd|_ n||||dO||||d} ||||| \}}|||||n|dz| kr||dzdkrt||_|||dz}||\|_ |_ |dz| kr?||dzdkr0| ||dz\|_ |_|dz }|dz }n|dz| krU||dzdvrG||dz}| ||dz| kr|||dzs||dzr| ||dznE|||dz}|| |dnt |dz| kri||dz|krZ|||dz}|| |dn| ||dz|dz }|dz }|dz }nj|dz| ks|||dzr|dz| krj|||dzLt|}|||||dz|_|dz }n| ||dz }n|||dz\d|cxkrdkrOnnLt|}|||||dz|_|dz }n;||r| |n|st |S)NzUnknown numeric tokenr)r rNr'rRr rNr,)r*rrj r*T) allow_jump)rvrwr,rr) _to_decimal Exceptionr=r>rr6rrrrfindr5_parsemsrr7 _find_hms_idx _parse_hms _assign_hms_parse_min_secrrUrrrYr )r"tokensidxr5r_rr]rarjrGrer`rOhms_idxrrbrs r#rWzparser._parse_numeric_tokenksC[  C$$Z00EE C C C N:&=>> B B B B B B B B CZF  HHMMf.. H  1W  S1Wo$$hhvcAg''/s A1RaR5zzCH{{ 122ZZ q[[VaZZF3K,<,.%s*@@QQ&00@@@@@@r%)r6allr5r)r"rrrr<s r#rZzparser._could_be_tzname soD 0$0D 0E a0@@%@@@@@/TY..  1r%cd}|r|d}||rd}n2tdd|cxkrdksn|rd}ntd|S)z For fuzzy parsing, 'a' or 'am' (both valid English words) may erroneously trigger the AM/PM flag. Deal with that here. TNFz%No hour specified with AM or PM flag.rr*z)Invalid hour specified for 12-hour clock.)r)r"rrr]rcs r#rXzparser._ampm_valid(s    T%K < J#  !HIIIdb N#  !LMMMr%cF|dkr |dkr|dz }n|dkr|dkrd}|S)Nr*r'rrD)r"rrs r#rYzparser._adjust_ampmDs9 "99 BJDD RZZDAIID r%c^t|}d}|dz}|rtd|z}||fS)Nr'rT)r)r"rjrr sec_remainders r#rvzparser._parse_min_secKsBU  -m+,,Fr%c|d}|}nD||kr|||}|}n |||dz}|}||fSr)r)r"rxrwr5ryrnew_idxs r#rtzparser._parse_hmsWse ?CGG s]]((6'?++CGG((6'?++a/CG~r%cd|vrt|dfS|d\}}t|t|ddddfS)z9Parse a I[.F] seconds value into (seconds, microseconds).r,rrir*N)rr9ljust)r"rjrfs r#rrzparser._parsemsms` e  u::q= ;;s##DAqq663qwwq#rr2333 3r%c t|}|std |S#t$r2}d|z}t jt||Yd}~dSd}~wwxYw)Nz*Converted decimal value is infinite or NaNzCould not convert %s to decimal)r is_finiterrpr=r>)r"r decimal_valuerGmsgs r#rozparser._to_decimalus !#CLLM!**,, O !MNNN O !  / / /3c9C N:c??A . . . . . . . . . /s27 A3'A..A3cnt|r |||}n||}t|tjs||}ndt|t rt j|}n:t|trt j ||}ntd|S)Nz9Offset must be tzinfo subclass, tz string, or int offset.) callablerrr9tzinforr tzstrrrr)r"rDrrtzdatars r# _build_tzinfozparser._build_tzinfos G   )WVX..FF[[((F fho . . .&.FF  * * .Xf%%FF  . . .[00FF-.. . r%ct|s |r]|j|vrT|||j|j}||}|||j}nb|jr|jt jvr|tj}|||j}||jkr3|j|j j vr |tj }n|jdkr!|tj }n|jr4|tj|j|j}nN|js |js|}n=|jr6tj d|jt|}|S)N)rrztzname {tzname} identified but not understood. Pass `tzinfos` argument in order to correctly return a timezone-aware datetime. In a future version, this will raise an exception.)r)category)rrrrr:_assign_tznamerr tzlocalr5rrwarningsr rUnknownTimezoneWarning)r"naiverrDrawares r#r@zparser._build_tzawares W  # '# cjG.C.C''S\JJFMMM00E''sz::EE Z CJ$+55MMM66E''sz::E #*,,J$)"333 RV 44 \Q  MMM00EE \ MMSZ)N)NMOOEE CL EE Z  M'(.vSZv'@'@#9  ; ; ; ; E r%ci}dD]}t||}||||<d|vrr|j|jn|j}|j|jn|j}|j|jn|j}|t ||dkrt ||d|d<|jdi|} |j$|js| tj|jz} | S)N)rrr,rrrr7r,r')rrD)rrrr,rr:rr ) r"rrBreplrarjcyearcmonthcdayrs r#r<zparser._build_naives8 # #DC&&E "T   %(H$4GLL#(E&)i&7W]]SYF"%'/7;;swDj//222(77:U ''$'' ; "37 "M7 LLLLE r%c||kr0tj|d}||kr|S|S)Nr')fold)rr enfold)r"dtrnew_dts r#rzparser._assign_tznamesF 99;;& Yr***F}}&((  r%cg}tt|D]M\}}|dkr'|dz ||dz kr|d||z|d<2|||N|S)z >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] >>> skipped_idxs = [0, 1, 2, 5] >>> _recombine_skipped(tokens, skipped_idxs) ["foo bar", "baz"] rr'r0)rsortedr5)r"rwr^rErrxs r#r\zparser._recombine_skippeds| 4 455 3 3FAs1uuqLQ$777%3B%7&+%Er""%%fSk2222r%rC)NFN)NNFF)rrWrXr$r r]rLr;rWrsrurZrXrYrvrtrrrorr@r<rr\rDr%r#r3r38s))))&*&*WWWWrGGGGG+GGG DI!&OOOObAAAF   DFFF 1118    ,444 ! ! !$$&&&P2r%r3c `|rt|j|fi|Stj|fi|S)a Parse a string in one of the supported formats, using the ``parserinfo`` parameters. :param timestr: A string containing a date/time stamp. :param parserinfo: A :class:`parserinfo` object containing parameters for the parser. If ``None``, the default arguments to the :class:`parserinfo` constructor are used. The ``**kwargs`` parameter takes the following keyword arguments: :param default: The default datetime object, if this is a datetime object and not ``None``, elements specified in ``timestr`` replace elements in the default object. :param ignoretz: If set ``True``, time zones in parsed strings are ignored and a naive :class:`datetime` object is returned. :param tzinfos: Additional time zone names / aliases which may be present in the string. This argument maps time zone names (and optionally offsets from those time zones) to time zones. This parameter can be a dictionary with timezone aliases mapping time zone names to time zones or a function taking two parameters (``tzname`` and ``tzoffset``) and returning a time zone. The timezones to which the names are mapped can be an integer offset from UTC in seconds or a :class:`tzinfo` object. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> from dateutil.parser import parse >>> from dateutil.tz import gettz >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) This parameter is ignored if ``ignoretz`` is set. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (``True``) or month (``False``). If ``yearfirst`` is set to ``True``, this distinguishes between YDM and YMD. If set to ``None``, this value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If ``True``, the first number is taken to be the year, otherwise the last number is taken to be the year. If this is set to ``None``, the value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param fuzzy: Whether to allow fuzzy parsing, allowing for string like "Today is January 1, 2047 at 8:21:00AM". :param fuzzy_with_tokens: If ``True``, ``fuzzy`` is automatically set to True, and the parser will return a tuple where the first element is the parsed :class:`datetime.datetime` datetimestamp and the second element is a tuple containing the portions of the string which were ignored: .. doctest:: >>> from dateutil.parser import parse >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) :return: Returns a :class:`datetime.datetime` object or, if the ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the first element being a :class:`datetime.datetime` object, the second a tuple containing the fuzzy tokens. :raises ParserError: Raised for invalid or unknown string formats, if the provided :class:`tzinfo` is not in a valid format, or if an invalid date would be created. :raises OverflowError: Raised if the parsed date exceeds the largest valid C integer on your system. )r3r DEFAULTPARSER)rArrs r#r r sI~6'vj!!'::6:::"755f555r%c0eZdZGddeZdZdS) _tzparserc>eZdZgdZGddeZdZdZdS)_tzparser._result)stdabbr stdoffsetdstabbr dstoffsetstartendceZdZgdZdS)_tzparser._result._attr)rweekrydayjydayr,rNrKrDr%r#_attrrbs999IIIr%rc,|dS)N)rkrEs r#rsz_tzparser._result.__repr__fs::b>> !r%ct|||_||_dSrC)r]r$rrrrEs r#r$z_tzparser._result.__init__is8   & & &DJzz||DHHHr%N)rrWrXr_r]rrsr$rDr%r#rLr]sm%%%  9 9 9 9 9K 9 9 9 " " " $ $ $ $ $r%rLc |}dtjd|Dt} t }d}||kr`|}||kr/d|Ds|dz }||krd|D||kr|js%d}d|||_n$d}d|||_t|D]}| ||}||kr|d vs|dd vrv|d vr-d |d k} | ||dz }nd } t |} | dkrTt||t|dddzt|dddzz| zn|dz|krp|dzdkrat||t|dzt|dzdzz| z| ||dz }n;| dkr3t||t|dddz| zndS| ||dz }|jrn nn||k`||kr7t||D]}|dkrd|<|dksJ|dz }||krnd dcxkrdkr nnd|dDs|j |jfD]:} t|| _| ||dz }|dkr6t|dzd z} | ||dz }nt|} | ||dz }| r(| | _t|dz dz| _nt|| _| ||dz }t|| _| ||dz }<||krs|dvr-d|d k} | ||dz }nd} | ||jt|| zz|_t-d|zdzdzdzd zd!zt.jn ddkr|d d"dkrjd#|dDsU|j |jfD]=} |d$kr6| ||dz }t|| _n+|d%kr| ||dz }t|| _| ||dz }|d&vsJ| ||dz }t|| _| jd'krd | _| ||dz }|d&vsJ| ||dz }t|dz dz| _nt|dz| _| ||dz }||kr|d"kr| ||dz }t |} | dkrGt|dddzt|dddzz| _n|dz|kr|dzdkrt|dzt|dzdzz| _| ||dz }|dz|krL|dzdkr=| ||dz }| xjt|z c_n/| dkr&t|dddz| _ndS| ||dz }||ks|dksJ|dz }?||ksJn#t6t8t:f$rYdSwxYwt=t||} fd(| D ddh |_!|S))Ncg|]}||SrDrDrs r#rz#_tzparser.parse..ps I I I1q IQ I I Ir%z([,:.]|[a-zA-Z]+|[0-9]+)rcg|]}|dv| S)z0123456789:,-+rDrs r#rz#_tzparser.parse..zs0)C)C)Cq,-1A,A,A*+,A,A,Ar%r'rrrrO 0123456789)r'r0rPr0rNr rSrTrRrur.rj c0g|]}|dkr |D]}|dv| S)r.z 0123456789+-rDrnrrs r#rz#_tzparser.parse..sC>>>Q188 !,48Qn%<%<%<%<%<%.sT<<z"_tzparser.parse..*s$?$?$?aQqT$?$?$?r%)"rLrYr9rMr6rrhrr$r5r`rr7rrrrrr,rrrr r DeprecatedTzFormatWarningrrr[rAssertionErrorset differenceissubsetrJ)r"rr used_idxsr`rjoffattriirdrerrj unused_idxsr?s @r#r z_tzparser.parsensp llnn I I!4>4Q4:--&-QqTS[%9F%,,Q///FAA%'F!$QqT!Q;;#C3qtBQBx==43G36qtABBx==23E4FIO3PQQQQUU]]qQx3#C%(1YY%5%(1q5]]R%7&8;A%BCCC&,,Q///FAA#q[[#C$'!RaRMMD$86$ACCCC$(4!((+++Q{ee))j5yyq%##Ats{{"!ts{{{{QEzzqwws||((((q(((((>>!ABB%>>>))SW-A!!A$iiAG$$Q'''FAts{{ #Aa!eH  2!((+++Q #AaD $$Q'''FA*!&%(1YY]a$7 #AaD $$Q'''FA 1YYAF$$Q'''FAAu99tz))!(1!5!((+++Q!"$$Q'''%(]S1YY5G%GCM-5LMJKNNJ J + + -/,H JJJJ ''#,,!##!"" C(8(8A(=(=<)SW-<<Ats{{!((+++Q"%ad))1!((+++Q"%ad))!((+++Q tz1111!((+++Q!$QqT6Q;;%'AF!((+++Q tz1111!((+++Q%(1YY]a$7 "%QqTQ$$Q'''FA5yyQqTS[[!((+++Q!$QqT!Q;;&)!A$rr(mmd&:&)!A$qrr(mmb&8'9AFFUU]]qQx3%(1YY%5Aa!eH 8J%JAF%,,Q///FA 1uu}}1q5S ) 0 0 3 3 3 !Q !#ad)) 3#q[[&)!A$rr(mmd&:AFF#'44!((+++Q::14FAAEzzzzJ7   44 %,,''229== $?$?$?$?;$?$?$?$H$H#c$S$S S s I cXc=cc.-c.N)rrWrXr]rLr rDr%r#rr[sR$$$$$+$$$"}}}}}r%rc6t|SrC)DEFAULTTZPARSERr )rs r#_parsetzr1s   ' ''r%c(eZdZdZfdZdZxZS)rzException subclass used for any failure to parse a datetime string. This is a subclass of :py:exc:`ValueError`, and should be raised any time earlier versions of ``dateutil`` would have raised ``ValueError``. .. versionadded:: 2.8.1 c |jd|jddzS#ttf$r*tt|cYSwxYw)Nrr')rrr[rr__str__)r"rs r#rzParserError.__str__=se 69Q<$)ABB-/ /:& 6 6 6d++3355 5 5 5 6s 8AAcndd|jD}|jjd|dS)Nrec3 K|] }d|zV dS)z'%s'NrD)rnargs r#roz'ParserError.__repr__..Ds&;;##;;;;;;r%rdrf)rhrrr)r"rs r#rszParserError.__repr__Cs>yy;;;;;;;>222DDD99r%)rrWrXrrrsr0r1s@r#rr5sQ66666 :::::::r%rceZdZdZdS)rzhRaised when the parser finds a timezone it cannot parse into a tzinfo. .. versionadded:: 2.7.0 N)rrWrXrrDr%r#rrHsr%rrC)&r __future__rr9rYr~rrcalendarriorr=rrdecimalrr rr r __all__objectrr]rrMrr3rr rrrrrRuntimeWarningrrDr%r#rs:('''''  (((((((( 0 0 0 ^"^"^"^"^"v^"^"^"B33333&333,VVVVVVVVrk k k k k 4k k k \x x x x x Vx x x v b6b6b6b6JPPPPPPPPf)++(((:::::*:::&^r%PK!`R۬ _parser.pynu[# -*- coding: utf-8 -*- """ This module offers a generic date/time string parser which is able to parse most known formats to represent a date and/or time. This module attempts to be forgiving with regards to unlikely input formats, returning a datetime object even for dates which are ambiguous. If an element of a date/time stamp is omitted, the following rules are applied: - If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is specified. - If a time zone is omitted, a timezone-naive datetime is returned. If any other elements are missing, they are taken from the :class:`datetime.datetime` object passed to the parameter ``default``. If this results in a day number exceeding the valid number of days per month, the value falls back to the end of the month. Additional resources about date/time string formats can be found below: - `A summary of the international standard date and time notation `_ - `W3C Date and Time Formats `_ - `Time Formats (Planetary Rings Node) `_ - `CPAN ParseDate module `_ - `Java SimpleDateFormat Class `_ """ from __future__ import unicode_literals import datetime import re import string import time import warnings from calendar import monthrange from io import StringIO import six from six import integer_types, text_type from decimal import Decimal from warnings import warn from .. import relativedelta from .. import tz __all__ = ["parse", "parserinfo", "ParserError"] # TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth # making public and/or figuring out if there is something we can # take off their plate. class _timelex(object): # Fractional seconds are sometimes split by a comma _split_decimal = re.compile("([.,])") def __init__(self, instream): if isinstance(instream, (bytes, bytearray)): instream = instream.decode() if isinstance(instream, text_type): instream = StringIO(instream) elif getattr(instream, 'read', None) is None: raise TypeError('Parser must be a string or character stream, not ' '{itype}'.format(itype=instream.__class__.__name__)) self.instream = instream self.charstack = [] self.tokenstack = [] self.eof = False def get_token(self): """ This function breaks the time string into lexical units (tokens), which can be parsed by the parser. Lexical units are demarcated by changes in the character set, so any continuous string of letters is considered one unit, any continuous string of numbers is considered one unit. The main complication arises from the fact that dots ('.') can be used both as separators (e.g. "Sep.20.2009") or decimal points (e.g. "4:30:21.447"). As such, it is necessary to read the full context of any dot-separated strings before breaking it into tokens; as such, this function maintains a "token stack", for when the ambiguous context demands that multiple tokens be parsed at once. """ if self.tokenstack: return self.tokenstack.pop(0) seenletters = False token = None state = None while not self.eof: # We only realize that we've reached the end of a token when we # find a character that's not part of the current token - since # that character may be part of the next token, it's stored in the # charstack. if self.charstack: nextchar = self.charstack.pop(0) else: nextchar = self.instream.read(1) while nextchar == '\x00': nextchar = self.instream.read(1) if not nextchar: self.eof = True break elif not state: # First character of the token - determines if we're starting # to parse a word, a number or something else. token = nextchar if self.isword(nextchar): state = 'a' elif self.isnum(nextchar): state = '0' elif self.isspace(nextchar): token = ' ' break # emit token else: break # emit token elif state == 'a': # If we've already started reading a word, we keep reading # letters until we find something that's not part of a word. seenletters = True if self.isword(nextchar): token += nextchar elif nextchar == '.': token += nextchar state = 'a.' else: self.charstack.append(nextchar) break # emit token elif state == '0': # If we've already started reading a number, we keep reading # numbers until we find something that doesn't fit. if self.isnum(nextchar): token += nextchar elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): token += nextchar state = '0.' else: self.charstack.append(nextchar) break # emit token elif state == 'a.': # If we've seen some letters and a dot separator, continue # parsing, and the tokens will be broken up later. seenletters = True if nextchar == '.' or self.isword(nextchar): token += nextchar elif self.isnum(nextchar) and token[-1] == '.': token += nextchar state = '0.' else: self.charstack.append(nextchar) break # emit token elif state == '0.': # If we've seen at least one dot separator, keep going, we'll # break up the tokens later. if nextchar == '.' or self.isnum(nextchar): token += nextchar elif self.isword(nextchar) and token[-1] == '.': token += nextchar state = 'a.' else: self.charstack.append(nextchar) break # emit token if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or token[-1] in '.,')): l = self._split_decimal.split(token) token = l[0] for tok in l[1:]: if tok: self.tokenstack.append(tok) if state == '0.' and token.count('.') == 0: token = token.replace(',', '.') return token def __iter__(self): return self def __next__(self): token = self.get_token() if token is None: raise StopIteration return token def next(self): return self.__next__() # Python 2.x support @classmethod def split(cls, s): return list(cls(s)) @classmethod def isword(cls, nextchar): """ Whether or not the next character is part of a word """ return nextchar.isalpha() @classmethod def isnum(cls, nextchar): """ Whether the next character is part of a number """ return nextchar.isdigit() @classmethod def isspace(cls, nextchar): """ Whether the next character is whitespace """ return nextchar.isspace() class _resultbase(object): def __init__(self): for attr in self.__slots__: setattr(self, attr, None) def _repr(self, classname): l = [] for attr in self.__slots__: value = getattr(self, attr) if value is not None: l.append("%s=%s" % (attr, repr(value))) return "%s(%s)" % (classname, ", ".join(l)) def __len__(self): return (sum(getattr(self, attr) is not None for attr in self.__slots__)) def __repr__(self): return self._repr(self.__class__.__name__) class parserinfo(object): """ Class which handles what inputs are accepted. Subclass this to customize the language and acceptable values for each parameter. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (``True``) or month (``False``). If ``yearfirst`` is set to ``True``, this distinguishes between YDM and YMD. Default is ``False``. :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If ``True``, the first number is taken to be the year, otherwise the last number is taken to be the year. Default is ``False``. """ # m from a.m/p.m, t from ISO T separator JUMP = [" ", ".", ",", ";", "-", "/", "'", "at", "on", "and", "ad", "m", "t", "of", "st", "nd", "rd", "th"] WEEKDAYS = [("Mon", "Monday"), ("Tue", "Tuesday"), # TODO: "Tues" ("Wed", "Wednesday"), ("Thu", "Thursday"), # TODO: "Thurs" ("Fri", "Friday"), ("Sat", "Saturday"), ("Sun", "Sunday")] MONTHS = [("Jan", "January"), ("Feb", "February"), # TODO: "Febr" ("Mar", "March"), ("Apr", "April"), ("May", "May"), ("Jun", "June"), ("Jul", "July"), ("Aug", "August"), ("Sep", "Sept", "September"), ("Oct", "October"), ("Nov", "November"), ("Dec", "December")] HMS = [("h", "hour", "hours"), ("m", "minute", "minutes"), ("s", "second", "seconds")] AMPM = [("am", "a"), ("pm", "p")] UTCZONE = ["UTC", "GMT", "Z", "z"] PERTAIN = ["of"] TZOFFSET = {} # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", # "Anno Domini", "Year of Our Lord"] def __init__(self, dayfirst=False, yearfirst=False): self._jump = self._convert(self.JUMP) self._weekdays = self._convert(self.WEEKDAYS) self._months = self._convert(self.MONTHS) self._hms = self._convert(self.HMS) self._ampm = self._convert(self.AMPM) self._utczone = self._convert(self.UTCZONE) self._pertain = self._convert(self.PERTAIN) self.dayfirst = dayfirst self.yearfirst = yearfirst self._year = time.localtime().tm_year self._century = self._year // 100 * 100 def _convert(self, lst): dct = {} for i, v in enumerate(lst): if isinstance(v, tuple): for v in v: dct[v.lower()] = i else: dct[v.lower()] = i return dct def jump(self, name): return name.lower() in self._jump def weekday(self, name): try: return self._weekdays[name.lower()] except KeyError: pass return None def month(self, name): try: return self._months[name.lower()] + 1 except KeyError: pass return None def hms(self, name): try: return self._hms[name.lower()] except KeyError: return None def ampm(self, name): try: return self._ampm[name.lower()] except KeyError: return None def pertain(self, name): return name.lower() in self._pertain def utczone(self, name): return name.lower() in self._utczone def tzoffset(self, name): if name in self._utczone: return 0 return self.TZOFFSET.get(name) def convertyear(self, year, century_specified=False): """ Converts two-digit years to year within [-50, 49] range of self._year (current local time) """ # Function contract is that the year is always positive assert year >= 0 if year < 100 and not century_specified: # assume current century to start year += self._century if year >= self._year + 50: # if too far in future year -= 100 elif year < self._year - 50: # if too far in past year += 100 return year def validate(self, res): # move to info if res.year is not None: res.year = self.convertyear(res.year, res.century_specified) if ((res.tzoffset == 0 and not res.tzname) or (res.tzname == 'Z' or res.tzname == 'z')): res.tzname = "UTC" res.tzoffset = 0 elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): res.tzoffset = 0 return True class _ymd(list): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.century_specified = False self.dstridx = None self.mstridx = None self.ystridx = None @property def has_year(self): return self.ystridx is not None @property def has_month(self): return self.mstridx is not None @property def has_day(self): return self.dstridx is not None def could_be_day(self, value): if self.has_day: return False elif not self.has_month: return 1 <= value <= 31 elif not self.has_year: # Be permissive, assume leap year month = self[self.mstridx] return 1 <= value <= monthrange(2000, month)[1] else: month = self[self.mstridx] year = self[self.ystridx] return 1 <= value <= monthrange(year, month)[1] def append(self, val, label=None): if hasattr(val, '__len__'): if val.isdigit() and len(val) > 2: self.century_specified = True if label not in [None, 'Y']: # pragma: no cover raise ValueError(label) label = 'Y' elif val > 100: self.century_specified = True if label not in [None, 'Y']: # pragma: no cover raise ValueError(label) label = 'Y' super(self.__class__, self).append(int(val)) if label == 'M': if self.has_month: raise ValueError('Month is already set') self.mstridx = len(self) - 1 elif label == 'D': if self.has_day: raise ValueError('Day is already set') self.dstridx = len(self) - 1 elif label == 'Y': if self.has_year: raise ValueError('Year is already set') self.ystridx = len(self) - 1 def _resolve_from_stridxs(self, strids): """ Try to resolve the identities of year/month/day elements using ystridx, mstridx, and dstridx, if enough of these are specified. """ if len(self) == 3 and len(strids) == 2: # we can back out the remaining stridx value missing = [x for x in range(3) if x not in strids.values()] key = [x for x in ['y', 'm', 'd'] if x not in strids] assert len(missing) == len(key) == 1 key = key[0] val = missing[0] strids[key] = val assert len(self) == len(strids) # otherwise this should not be called out = {key: self[strids[key]] for key in strids} return (out.get('y'), out.get('m'), out.get('d')) def resolve_ymd(self, yearfirst, dayfirst): len_ymd = len(self) year, month, day = (None, None, None) strids = (('y', self.ystridx), ('m', self.mstridx), ('d', self.dstridx)) strids = {key: val for key, val in strids if val is not None} if (len(self) == len(strids) > 0 or (len(self) == 3 and len(strids) == 2)): return self._resolve_from_stridxs(strids) mstridx = self.mstridx if len_ymd > 3: raise ValueError("More than three YMD values") elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): # One member, or two members with a month string if mstridx is not None: month = self[mstridx] # since mstridx is 0 or 1, self[mstridx-1] always # looks up the other element other = self[mstridx - 1] else: other = self[0] if len_ymd > 1 or mstridx is None: if other > 31: year = other else: day = other elif len_ymd == 2: # Two members with numbers if self[0] > 31: # 99-01 year, month = self elif self[1] > 31: # 01-99 month, year = self elif dayfirst and self[1] <= 12: # 13-01 day, month = self else: # 01-13 month, day = self elif len_ymd == 3: # Three members if mstridx == 0: if self[1] > 31: # Apr-2003-25 month, year, day = self else: month, day, year = self elif mstridx == 1: if self[0] > 31 or (yearfirst and self[2] <= 31): # 99-Jan-01 year, month, day = self else: # 01-Jan-01 # Give precedence to day-first, since # two-digit years is usually hand-written. day, month, year = self elif mstridx == 2: # WTF!? if self[1] > 31: # 01-99-Jan day, year, month = self else: # 99-01-Jan year, day, month = self else: if (self[0] > 31 or self.ystridx == 0 or (yearfirst and self[1] <= 12 and self[2] <= 31)): # 99-01-01 if dayfirst and self[2] <= 12: year, day, month = self else: year, month, day = self elif self[0] > 12 or (dayfirst and self[1] <= 12): # 13-01-01 day, month, year = self else: # 01-13-01 month, day, year = self return year, month, day class parser(object): def __init__(self, info=None): self.info = info or parserinfo() def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, **kwargs): """ Parse the date/time string into a :class:`datetime.datetime` object. :param timestr: Any date/time string using the supported formats. :param default: The default datetime object, if this is a datetime object and not ``None``, elements specified in ``timestr`` replace elements in the default object. :param ignoretz: If set ``True``, time zones in parsed strings are ignored and a naive :class:`datetime.datetime` object is returned. :param tzinfos: Additional time zone names / aliases which may be present in the string. This argument maps time zone names (and optionally offsets from those time zones) to time zones. This parameter can be a dictionary with timezone aliases mapping time zone names to time zones or a function taking two parameters (``tzname`` and ``tzoffset``) and returning a time zone. The timezones to which the names are mapped can be an integer offset from UTC in seconds or a :class:`tzinfo` object. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> from dateutil.parser import parse >>> from dateutil.tz import gettz >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) This parameter is ignored if ``ignoretz`` is set. :param \\*\\*kwargs: Keyword arguments as passed to ``_parse()``. :return: Returns a :class:`datetime.datetime` object or, if the ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the first element being a :class:`datetime.datetime` object, the second a tuple containing the fuzzy tokens. :raises ParserError: Raised for invalid or unknown string format, if the provided :class:`tzinfo` is not in a valid format, or if an invalid date would be created. :raises TypeError: Raised for non-string or character stream input. :raises OverflowError: Raised if the parsed date exceeds the largest valid C integer on your system. """ if default is None: default = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) res, skipped_tokens = self._parse(timestr, **kwargs) if res is None: raise ParserError("Unknown string format: %s", timestr) if len(res) == 0: raise ParserError("String does not contain a date: %s", timestr) try: ret = self._build_naive(res, default) except ValueError as e: six.raise_from(ParserError(str(e) + ": %s", timestr), e) if not ignoretz: ret = self._build_tzaware(ret, res, tzinfos) if kwargs.get('fuzzy_with_tokens', False): return ret, skipped_tokens else: return ret class _result(_resultbase): __slots__ = ["year", "month", "day", "weekday", "hour", "minute", "second", "microsecond", "tzname", "tzoffset", "ampm","any_unused_tokens"] def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, fuzzy_with_tokens=False): """ Private method which performs the heavy lifting of parsing, called from ``parse()``, which passes on its ``kwargs`` to this function. :param timestr: The string to parse. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (``True``) or month (``False``). If ``yearfirst`` is set to ``True``, this distinguishes between YDM and YMD. If set to ``None``, this value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If ``True``, the first number is taken to be the year, otherwise the last number is taken to be the year. If this is set to ``None``, the value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param fuzzy: Whether to allow fuzzy parsing, allowing for string like "Today is January 1, 2047 at 8:21:00AM". :param fuzzy_with_tokens: If ``True``, ``fuzzy`` is automatically set to True, and the parser will return a tuple where the first element is the parsed :class:`datetime.datetime` datetimestamp and the second element is a tuple containing the portions of the string which were ignored: .. doctest:: >>> from dateutil.parser import parse >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) """ if fuzzy_with_tokens: fuzzy = True info = self.info if dayfirst is None: dayfirst = info.dayfirst if yearfirst is None: yearfirst = info.yearfirst res = self._result() l = _timelex.split(timestr) # Splits the timestr into tokens skipped_idxs = [] # year/month/day list ymd = _ymd() len_l = len(l) i = 0 try: while i < len_l: # Check if it's a number value_repr = l[i] try: value = float(value_repr) except ValueError: value = None if value is not None: # Numeric token i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) # Check weekday elif info.weekday(l[i]) is not None: value = info.weekday(l[i]) res.weekday = value # Check month name elif info.month(l[i]) is not None: value = info.month(l[i]) ymd.append(value, 'M') if i + 1 < len_l: if l[i + 1] in ('-', '/'): # Jan-01[-99] sep = l[i + 1] ymd.append(l[i + 2]) if i + 3 < len_l and l[i + 3] == sep: # Jan-01-99 ymd.append(l[i + 4]) i += 2 i += 2 elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and info.pertain(l[i + 2])): # Jan of 01 # In this case, 01 is clearly year if l[i + 4].isdigit(): # Convert it here to become unambiguous value = int(l[i + 4]) year = str(info.convertyear(value)) ymd.append(year, 'Y') else: # Wrong guess pass # TODO: not hit in tests i += 4 # Check am/pm elif info.ampm(l[i]) is not None: value = info.ampm(l[i]) val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) if val_is_ampm: res.hour = self._adjust_ampm(res.hour, value) res.ampm = value elif fuzzy: skipped_idxs.append(i) # Check for a timezone name elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): res.tzname = l[i] res.tzoffset = info.tzoffset(res.tzname) # Check for something like GMT+3, or BRST+3. Notice # that it doesn't mean "I am 3 hours after GMT", but # "my time +3 is GMT". If found, we reverse the # logic so that timezone parsing code will get it # right. if i + 1 < len_l and l[i + 1] in ('+', '-'): l[i + 1] = ('+', '-')[l[i + 1] == '+'] res.tzoffset = None if info.utczone(res.tzname): # With something like GMT+3, the timezone # is *not* GMT. res.tzname = None # Check for a numbered timezone elif res.hour is not None and l[i] in ('+', '-'): signal = (-1, 1)[l[i] == '+'] len_li = len(l[i + 1]) # TODO: check that l[i + 1] is integer? if len_li == 4: # -0300 hour_offset = int(l[i + 1][:2]) min_offset = int(l[i + 1][2:]) elif i + 2 < len_l and l[i + 2] == ':': # -03:00 hour_offset = int(l[i + 1]) min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? i += 2 elif len_li <= 2: # -[0]3 hour_offset = int(l[i + 1][:2]) min_offset = 0 else: raise ValueError(timestr) res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) # Look for a timezone name between parenthesis if (i + 5 < len_l and info.jump(l[i + 2]) and l[i + 3] == '(' and l[i + 5] == ')' and 3 <= len(l[i + 4]) and self._could_be_tzname(res.hour, res.tzname, None, l[i + 4])): # -0300 (BRST) res.tzname = l[i + 4] i += 4 i += 1 # Check jumps elif not (info.jump(l[i]) or fuzzy): raise ValueError(timestr) else: skipped_idxs.append(i) i += 1 # Process year/month/day year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) res.century_specified = ymd.century_specified res.year = year res.month = month res.day = day except (IndexError, ValueError): return None, None if not info.validate(res): return None, None if fuzzy_with_tokens: skipped_tokens = self._recombine_skipped(l, skipped_idxs) return res, tuple(skipped_tokens) else: return res, None def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): # Token is a number value_repr = tokens[idx] try: value = self._to_decimal(value_repr) except Exception as e: six.raise_from(ValueError('Unknown numeric token'), e) len_li = len(value_repr) len_l = len(tokens) if (len(ymd) == 3 and len_li in (2, 4) and res.hour is None and (idx + 1 >= len_l or (tokens[idx + 1] != ':' and info.hms(tokens[idx + 1]) is None))): # 19990101T23[59] s = tokens[idx] res.hour = int(s[:2]) if len_li == 4: res.minute = int(s[2:]) elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): # YYMMDD or HHMMSS[.ss] s = tokens[idx] if not ymd and '.' not in tokens[idx]: ymd.append(s[:2]) ymd.append(s[2:4]) ymd.append(s[4:]) else: # 19990101T235959[.59] # TODO: Check if res attributes already set. res.hour = int(s[:2]) res.minute = int(s[2:4]) res.second, res.microsecond = self._parsems(s[4:]) elif len_li in (8, 12, 14): # YYYYMMDD s = tokens[idx] ymd.append(s[:4], 'Y') ymd.append(s[4:6]) ymd.append(s[6:8]) if len_li > 8: res.hour = int(s[8:10]) res.minute = int(s[10:12]) if len_li > 12: res.second = int(s[12:]) elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: # HH[ ]h or MM[ ]m or SS[.ss][ ]s hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) if hms is not None: # TODO: checking that hour/minute/second are not # already set? self._assign_hms(res, value_repr, hms) elif idx + 2 < len_l and tokens[idx + 1] == ':': # HH:MM[:SS[.ss]] res.hour = int(value) value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? (res.minute, res.second) = self._parse_min_sec(value) if idx + 4 < len_l and tokens[idx + 3] == ':': res.second, res.microsecond = self._parsems(tokens[idx + 4]) idx += 2 idx += 2 elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): sep = tokens[idx + 1] ymd.append(value_repr) if idx + 2 < len_l and not info.jump(tokens[idx + 2]): if tokens[idx + 2].isdigit(): # 01-01[-01] ymd.append(tokens[idx + 2]) else: # 01-Jan[-01] value = info.month(tokens[idx + 2]) if value is not None: ymd.append(value, 'M') else: raise ValueError() if idx + 3 < len_l and tokens[idx + 3] == sep: # We have three members value = info.month(tokens[idx + 4]) if value is not None: ymd.append(value, 'M') else: ymd.append(tokens[idx + 4]) idx += 2 idx += 1 idx += 1 elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: # 12 am hour = int(value) res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) idx += 1 else: # Year, month or day ymd.append(value) idx += 1 elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): # 12am hour = int(value) res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) idx += 1 elif ymd.could_be_day(value): ymd.append(value) elif not fuzzy: raise ValueError() return idx def _find_hms_idx(self, idx, tokens, info, allow_jump): len_l = len(tokens) if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: # There is an "h", "m", or "s" label following this token. We take # assign the upcoming label to the current token. # e.g. the "12" in 12h" hms_idx = idx + 1 elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and info.hms(tokens[idx+2]) is not None): # There is a space and then an "h", "m", or "s" label. # e.g. the "12" in "12 h" hms_idx = idx + 2 elif idx > 0 and info.hms(tokens[idx-1]) is not None: # There is a "h", "m", or "s" preceding this token. Since neither # of the previous cases was hit, there is no label following this # token, so we use the previous label. # e.g. the "04" in "12h04" hms_idx = idx-1 elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and info.hms(tokens[idx-2]) is not None): # If we are looking at the final token, we allow for a # backward-looking check to skip over a space. # TODO: Are we sure this is the right condition here? hms_idx = idx - 2 else: hms_idx = None return hms_idx def _assign_hms(self, res, value_repr, hms): # See GH issue #427, fixing float rounding value = self._to_decimal(value_repr) if hms == 0: # Hour res.hour = int(value) if value % 1: res.minute = int(60*(value % 1)) elif hms == 1: (res.minute, res.second) = self._parse_min_sec(value) elif hms == 2: (res.second, res.microsecond) = self._parsems(value_repr) def _could_be_tzname(self, hour, tzname, tzoffset, token): return (hour is not None and tzname is None and tzoffset is None and len(token) <= 5 and (all(x in string.ascii_uppercase for x in token) or token in self.info.UTCZONE)) def _ampm_valid(self, hour, ampm, fuzzy): """ For fuzzy parsing, 'a' or 'am' (both valid English words) may erroneously trigger the AM/PM flag. Deal with that here. """ val_is_ampm = True # If there's already an AM/PM flag, this one isn't one. if fuzzy and ampm is not None: val_is_ampm = False # If AM/PM is found and hour is not, raise a ValueError if hour is None: if fuzzy: val_is_ampm = False else: raise ValueError('No hour specified with AM or PM flag.') elif not 0 <= hour <= 12: # If AM/PM is found, it's a 12 hour clock, so raise # an error for invalid range if fuzzy: val_is_ampm = False else: raise ValueError('Invalid hour specified for 12-hour clock.') return val_is_ampm def _adjust_ampm(self, hour, ampm): if hour < 12 and ampm == 1: hour += 12 elif hour == 12 and ampm == 0: hour = 0 return hour def _parse_min_sec(self, value): # TODO: Every usage of this function sets res.second to the return # value. Are there any cases where second will be returned as None and # we *don't* want to set res.second = None? minute = int(value) second = None sec_remainder = value % 1 if sec_remainder: second = int(60 * sec_remainder) return (minute, second) def _parse_hms(self, idx, tokens, info, hms_idx): # TODO: Is this going to admit a lot of false-positives for when we # just happen to have digits and "h", "m" or "s" characters in non-date # text? I guess hex hashes won't have that problem, but there's plenty # of random junk out there. if hms_idx is None: hms = None new_idx = idx elif hms_idx > idx: hms = info.hms(tokens[hms_idx]) new_idx = hms_idx else: # Looking backwards, increment one. hms = info.hms(tokens[hms_idx]) + 1 new_idx = idx return (new_idx, hms) # ------------------------------------------------------------------ # Handling for individual tokens. These are kept as methods instead # of functions for the sake of customizability via subclassing. def _parsems(self, value): """Parse a I[.F] seconds value into (seconds, microseconds).""" if "." not in value: return int(value), 0 else: i, f = value.split(".") return int(i), int(f.ljust(6, "0")[:6]) def _to_decimal(self, val): try: decimal_value = Decimal(val) # See GH 662, edge case, infinite value should not be converted # via `_to_decimal` if not decimal_value.is_finite(): raise ValueError("Converted decimal value is infinite or NaN") except Exception as e: msg = "Could not convert %s to decimal" % val six.raise_from(ValueError(msg), e) else: return decimal_value # ------------------------------------------------------------------ # Post-Parsing construction of datetime output. These are kept as # methods instead of functions for the sake of customizability via # subclassing. def _build_tzinfo(self, tzinfos, tzname, tzoffset): if callable(tzinfos): tzdata = tzinfos(tzname, tzoffset) else: tzdata = tzinfos.get(tzname) # handle case where tzinfo is paased an options that returns None # eg tzinfos = {'BRST' : None} if isinstance(tzdata, datetime.tzinfo) or tzdata is None: tzinfo = tzdata elif isinstance(tzdata, text_type): tzinfo = tz.tzstr(tzdata) elif isinstance(tzdata, integer_types): tzinfo = tz.tzoffset(tzname, tzdata) else: raise TypeError("Offset must be tzinfo subclass, tz string, " "or int offset.") return tzinfo def _build_tzaware(self, naive, res, tzinfos): if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) aware = naive.replace(tzinfo=tzinfo) aware = self._assign_tzname(aware, res.tzname) elif res.tzname and res.tzname in time.tzname: aware = naive.replace(tzinfo=tz.tzlocal()) # Handle ambiguous local datetime aware = self._assign_tzname(aware, res.tzname) # This is mostly relevant for winter GMT zones parsed in the UK if (aware.tzname() != res.tzname and res.tzname in self.info.UTCZONE): aware = aware.replace(tzinfo=tz.UTC) elif res.tzoffset == 0: aware = naive.replace(tzinfo=tz.UTC) elif res.tzoffset: aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) elif not res.tzname and not res.tzoffset: # i.e. no timezone information was found. aware = naive elif res.tzname: # tz-like string was parsed but we don't know what to do # with it warnings.warn("tzname {tzname} identified but not understood. " "Pass `tzinfos` argument in order to correctly " "return a timezone-aware datetime. In a future " "version, this will raise an " "exception.".format(tzname=res.tzname), category=UnknownTimezoneWarning) aware = naive return aware def _build_naive(self, res, default): repl = {} for attr in ("year", "month", "day", "hour", "minute", "second", "microsecond"): value = getattr(res, attr) if value is not None: repl[attr] = value if 'day' not in repl: # If the default day exceeds the last day of the month, fall back # to the end of the month. cyear = default.year if res.year is None else res.year cmonth = default.month if res.month is None else res.month cday = default.day if res.day is None else res.day if cday > monthrange(cyear, cmonth)[1]: repl['day'] = monthrange(cyear, cmonth)[1] naive = default.replace(**repl) if res.weekday is not None and not res.day: naive = naive + relativedelta.relativedelta(weekday=res.weekday) return naive def _assign_tzname(self, dt, tzname): if dt.tzname() != tzname: new_dt = tz.enfold(dt, fold=1) if new_dt.tzname() == tzname: return new_dt return dt def _recombine_skipped(self, tokens, skipped_idxs): """ >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] >>> skipped_idxs = [0, 1, 2, 5] >>> _recombine_skipped(tokens, skipped_idxs) ["foo bar", "baz"] """ skipped_tokens = [] for i, idx in enumerate(sorted(skipped_idxs)): if i > 0 and idx - 1 == skipped_idxs[i - 1]: skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] else: skipped_tokens.append(tokens[idx]) return skipped_tokens DEFAULTPARSER = parser() def parse(timestr, parserinfo=None, **kwargs): """ Parse a string in one of the supported formats, using the ``parserinfo`` parameters. :param timestr: A string containing a date/time stamp. :param parserinfo: A :class:`parserinfo` object containing parameters for the parser. If ``None``, the default arguments to the :class:`parserinfo` constructor are used. The ``**kwargs`` parameter takes the following keyword arguments: :param default: The default datetime object, if this is a datetime object and not ``None``, elements specified in ``timestr`` replace elements in the default object. :param ignoretz: If set ``True``, time zones in parsed strings are ignored and a naive :class:`datetime` object is returned. :param tzinfos: Additional time zone names / aliases which may be present in the string. This argument maps time zone names (and optionally offsets from those time zones) to time zones. This parameter can be a dictionary with timezone aliases mapping time zone names to time zones or a function taking two parameters (``tzname`` and ``tzoffset``) and returning a time zone. The timezones to which the names are mapped can be an integer offset from UTC in seconds or a :class:`tzinfo` object. .. doctest:: :options: +NORMALIZE_WHITESPACE >>> from dateutil.parser import parse >>> from dateutil.tz import gettz >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) This parameter is ignored if ``ignoretz`` is set. :param dayfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the day (``True``) or month (``False``). If ``yearfirst`` is set to ``True``, this distinguishes between YDM and YMD. If set to ``None``, this value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param yearfirst: Whether to interpret the first value in an ambiguous 3-integer date (e.g. 01/05/09) as the year. If ``True``, the first number is taken to be the year, otherwise the last number is taken to be the year. If this is set to ``None``, the value is retrieved from the current :class:`parserinfo` object (which itself defaults to ``False``). :param fuzzy: Whether to allow fuzzy parsing, allowing for string like "Today is January 1, 2047 at 8:21:00AM". :param fuzzy_with_tokens: If ``True``, ``fuzzy`` is automatically set to True, and the parser will return a tuple where the first element is the parsed :class:`datetime.datetime` datetimestamp and the second element is a tuple containing the portions of the string which were ignored: .. doctest:: >>> from dateutil.parser import parse >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) :return: Returns a :class:`datetime.datetime` object or, if the ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the first element being a :class:`datetime.datetime` object, the second a tuple containing the fuzzy tokens. :raises ParserError: Raised for invalid or unknown string formats, if the provided :class:`tzinfo` is not in a valid format, or if an invalid date would be created. :raises OverflowError: Raised if the parsed date exceeds the largest valid C integer on your system. """ if parserinfo: return parser(parserinfo).parse(timestr, **kwargs) else: return DEFAULTPARSER.parse(timestr, **kwargs) class _tzparser(object): class _result(_resultbase): __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", "start", "end"] class _attr(_resultbase): __slots__ = ["month", "week", "weekday", "yday", "jyday", "day", "time"] def __repr__(self): return self._repr("") def __init__(self): _resultbase.__init__(self) self.start = self._attr() self.end = self._attr() def parse(self, tzstr): res = self._result() l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] used_idxs = list() try: len_l = len(l) i = 0 while i < len_l: # BRST+3[BRDT[+2]] j = i while j < len_l and not [x for x in l[j] if x in "0123456789:,-+"]: j += 1 if j != i: if not res.stdabbr: offattr = "stdoffset" res.stdabbr = "".join(l[i:j]) else: offattr = "dstoffset" res.dstabbr = "".join(l[i:j]) for ii in range(j): used_idxs.append(ii) i = j if (i < len_l and (l[i] in ('+', '-') or l[i][0] in "0123456789")): if l[i] in ('+', '-'): # Yes, that's right. See the TZ variable # documentation. signal = (1, -1)[l[i] == '+'] used_idxs.append(i) i += 1 else: signal = -1 len_li = len(l[i]) if len_li == 4: # -0300 setattr(res, offattr, (int(l[i][:2]) * 3600 + int(l[i][2:]) * 60) * signal) elif i + 1 < len_l and l[i + 1] == ':': # -03:00 setattr(res, offattr, (int(l[i]) * 3600 + int(l[i + 2]) * 60) * signal) used_idxs.append(i) i += 2 elif len_li <= 2: # -[0]3 setattr(res, offattr, int(l[i][:2]) * 3600 * signal) else: return None used_idxs.append(i) i += 1 if res.dstabbr: break else: break if i < len_l: for j in range(i, len_l): if l[j] == ';': l[j] = ',' assert l[i] == ',' i += 1 if i >= len_l: pass elif (8 <= l.count(',') <= 9 and not [y for x in l[i:] if x != ',' for y in x if y not in "0123456789+-"]): # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] for x in (res.start, res.end): x.month = int(l[i]) used_idxs.append(i) i += 2 if l[i] == '-': value = int(l[i + 1]) * -1 used_idxs.append(i) i += 1 else: value = int(l[i]) used_idxs.append(i) i += 2 if value: x.week = value x.weekday = (int(l[i]) - 1) % 7 else: x.day = int(l[i]) used_idxs.append(i) i += 2 x.time = int(l[i]) used_idxs.append(i) i += 2 if i < len_l: if l[i] in ('-', '+'): signal = (-1, 1)[l[i] == "+"] used_idxs.append(i) i += 1 else: signal = 1 used_idxs.append(i) res.dstoffset = (res.stdoffset + int(l[i]) * signal) # This was a made-up format that is not in normal use warn(('Parsed time zone "%s"' % tzstr) + 'is in a non-standard dateutil-specific format, which ' + 'is now deprecated; support for parsing this format ' + 'will be removed in future versions. It is recommended ' + 'that you switch to a standard format like the GNU ' + 'TZ variable format.', tz.DeprecatedTzFormatWarning) elif (l.count(',') == 2 and l[i:].count('/') <= 2 and not [y for x in l[i:] if x not in (',', '/', 'J', 'M', '.', '-', ':') for y in x if y not in "0123456789"]): for x in (res.start, res.end): if l[i] == 'J': # non-leap year day (1 based) used_idxs.append(i) i += 1 x.jyday = int(l[i]) elif l[i] == 'M': # month[-.]week[-.]weekday used_idxs.append(i) i += 1 x.month = int(l[i]) used_idxs.append(i) i += 1 assert l[i] in ('-', '.') used_idxs.append(i) i += 1 x.week = int(l[i]) if x.week == 5: x.week = -1 used_idxs.append(i) i += 1 assert l[i] in ('-', '.') used_idxs.append(i) i += 1 x.weekday = (int(l[i]) - 1) % 7 else: # year day (zero based) x.yday = int(l[i]) + 1 used_idxs.append(i) i += 1 if i < len_l and l[i] == '/': used_idxs.append(i) i += 1 # start time len_li = len(l[i]) if len_li == 4: # -0300 x.time = (int(l[i][:2]) * 3600 + int(l[i][2:]) * 60) elif i + 1 < len_l and l[i + 1] == ':': # -03:00 x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 used_idxs.append(i) i += 2 if i + 1 < len_l and l[i + 1] == ':': used_idxs.append(i) i += 2 x.time += int(l[i]) elif len_li <= 2: # -[0]3 x.time = (int(l[i][:2]) * 3600) else: return None used_idxs.append(i) i += 1 assert i == len_l or l[i] == ',' i += 1 assert i >= len_l except (IndexError, ValueError, AssertionError): return None unused_idxs = set(range(len_l)).difference(used_idxs) res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) return res DEFAULTTZPARSER = _tzparser() def _parsetz(tzstr): return DEFAULTTZPARSER.parse(tzstr) class ParserError(ValueError): """Exception subclass used for any failure to parse a datetime string. This is a subclass of :py:exc:`ValueError`, and should be raised any time earlier versions of ``dateutil`` would have raised ``ValueError``. .. versionadded:: 2.8.1 """ def __str__(self): try: return self.args[0] % self.args[1:] except (TypeError, IndexError): return super(ParserError, self).__str__() def __repr__(self): args = ", ".join("'%s'" % arg for arg in self.args) return "%s(%s)" % (self.__class__.__name__, args) class UnknownTimezoneWarning(RuntimeWarning): """Raised when the parser finds a timezone it cannot parse into a tzinfo. .. versionadded:: 2.7.0 """ # vim:ts=4:sw=4:et PK!oÿ33 isoparser.pynu[# -*- coding: utf-8 -*- """ This module offers a parser for ISO-8601 strings It is intended to support all valid date, time and datetime formats per the ISO-8601 specification. ..versionadded:: 2.7.0 """ from datetime import datetime, timedelta, time, date import calendar from dateutil import tz from functools import wraps import re import six __all__ = ["isoparse", "isoparser"] def _takes_ascii(f): @wraps(f) def func(self, str_in, *args, **kwargs): # If it's a stream, read the whole thing str_in = getattr(str_in, 'read', lambda: str_in)() # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII if isinstance(str_in, six.text_type): # ASCII is the same in UTF-8 try: str_in = str_in.encode('ascii') except UnicodeEncodeError as e: msg = 'ISO-8601 strings should contain only ASCII characters' six.raise_from(ValueError(msg), e) return f(self, str_in, *args, **kwargs) return func class isoparser(object): def __init__(self, sep=None): """ :param sep: A single character that separates date and time portions. If ``None``, the parser will accept any single character. For strict ISO-8601 adherence, pass ``'T'``. """ if sep is not None: if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): raise ValueError('Separator must be a single, non-numeric ' + 'ASCII character') sep = sep.encode('ascii') self._sep = sep @_takes_ascii def isoparse(self, dt_str): """ Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. An ISO-8601 datetime string consists of a date portion, followed optionally by a time portion - the date and time portions are separated by a single character separator, which is ``T`` in the official standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be combined with a time portion. Supported date formats are: Common: - ``YYYY`` - ``YYYY-MM`` or ``YYYYMM`` - ``YYYY-MM-DD`` or ``YYYYMMDD`` Uncommon: - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day The ISO week and day numbering follows the same logic as :func:`datetime.date.isocalendar`. Supported time formats are: - ``hh`` - ``hh:mm`` or ``hhmm`` - ``hh:mm:ss`` or ``hhmmss`` - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) Midnight is a special case for `hh`, as the standard supports both 00:00 and 24:00 as a representation. The decimal separator can be either a dot or a comma. .. caution:: Support for fractional components other than seconds is part of the ISO-8601 standard, but is not currently implemented in this parser. Supported time zone offset formats are: - `Z` (UTC) - `±HH:MM` - `±HHMM` - `±HH` Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, with the exception of UTC, which will be represented as :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. :param dt_str: A string or stream containing only an ISO-8601 datetime string :return: Returns a :class:`datetime.datetime` representing the string. Unspecified components default to their lowest value. .. warning:: As of version 2.7.0, the strictness of the parser should not be considered a stable part of the contract. Any valid ISO-8601 string that parses correctly with the default settings will continue to parse correctly in future versions, but invalid strings that currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not guaranteed to continue failing in future versions if they encode a valid date. .. versionadded:: 2.7.0 """ components, pos = self._parse_isodate(dt_str) if len(dt_str) > pos: if self._sep is None or dt_str[pos:pos + 1] == self._sep: components += self._parse_isotime(dt_str[pos + 1:]) else: raise ValueError('String contains unknown ISO components') if len(components) > 3 and components[3] == 24: components[3] = 0 return datetime(*components) + timedelta(days=1) return datetime(*components) @_takes_ascii def parse_isodate(self, datestr): """ Parse the date portion of an ISO string. :param datestr: The string portion of an ISO string, without a separator :return: Returns a :class:`datetime.date` object """ components, pos = self._parse_isodate(datestr) if pos < len(datestr): raise ValueError('String contains unknown ISO ' + 'components: {!r}'.format(datestr.decode('ascii'))) return date(*components) @_takes_ascii def parse_isotime(self, timestr): """ Parse the time portion of an ISO string. :param timestr: The time portion of an ISO string, without a separator :return: Returns a :class:`datetime.time` object """ components = self._parse_isotime(timestr) if components[0] == 24: components[0] = 0 return time(*components) @_takes_ascii def parse_tzstr(self, tzstr, zero_as_utc=True): """ Parse a valid ISO time zone string. See :func:`isoparser.isoparse` for details on supported formats. :param tzstr: A string representing an ISO time zone offset :param zero_as_utc: Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones :return: Returns :class:`dateutil.tz.tzoffset` for offsets and :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is specified) offsets equivalent to UTC. """ return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) # Constants _DATE_SEP = b'-' _TIME_SEP = b':' _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') def _parse_isodate(self, dt_str): try: return self._parse_isodate_common(dt_str) except ValueError: return self._parse_isodate_uncommon(dt_str) def _parse_isodate_common(self, dt_str): len_str = len(dt_str) components = [1, 1, 1] if len_str < 4: raise ValueError('ISO string too short') # Year components[0] = int(dt_str[0:4]) pos = 4 if pos >= len_str: return components, pos has_sep = dt_str[pos:pos + 1] == self._DATE_SEP if has_sep: pos += 1 # Month if len_str - pos < 2: raise ValueError('Invalid common month') components[1] = int(dt_str[pos:pos + 2]) pos += 2 if pos >= len_str: if has_sep: return components, pos else: raise ValueError('Invalid ISO format') if has_sep: if dt_str[pos:pos + 1] != self._DATE_SEP: raise ValueError('Invalid separator in ISO string') pos += 1 # Day if len_str - pos < 2: raise ValueError('Invalid common day') components[2] = int(dt_str[pos:pos + 2]) return components, pos + 2 def _parse_isodate_uncommon(self, dt_str): if len(dt_str) < 4: raise ValueError('ISO string too short') # All ISO formats start with the year year = int(dt_str[0:4]) has_sep = dt_str[4:5] == self._DATE_SEP pos = 4 + has_sep # Skip '-' if it's there if dt_str[pos:pos + 1] == b'W': # YYYY-?Www-?D? pos += 1 weekno = int(dt_str[pos:pos + 2]) pos += 2 dayno = 1 if len(dt_str) > pos: if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: raise ValueError('Inconsistent use of dash separator') pos += has_sep dayno = int(dt_str[pos:pos + 1]) pos += 1 base_date = self._calculate_weekdate(year, weekno, dayno) else: # YYYYDDD or YYYY-DDD if len(dt_str) - pos < 3: raise ValueError('Invalid ordinal day') ordinal_day = int(dt_str[pos:pos + 3]) pos += 3 if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): raise ValueError('Invalid ordinal day' + ' {} for year {}'.format(ordinal_day, year)) base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) components = [base_date.year, base_date.month, base_date.day] return components, pos def _calculate_weekdate(self, year, week, day): """ Calculate the day of corresponding to the ISO year-week-day calendar. This function is effectively the inverse of :func:`datetime.date.isocalendar`. :param year: The year in the ISO calendar :param week: The week in the ISO calendar - range is [1, 53] :param day: The day in the ISO calendar - range is [1 (MON), 7 (SUN)] :return: Returns a :class:`datetime.date` """ if not 0 < week < 54: raise ValueError('Invalid week: {}'.format(week)) if not 0 < day < 8: # Range is 1-7 raise ValueError('Invalid weekday: {}'.format(day)) # Get week 1 for the specific year: jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) # Now add the specific number of weeks and days to get what we want week_offset = (week - 1) * 7 + (day - 1) return week_1 + timedelta(days=week_offset) def _parse_isotime(self, timestr): len_str = len(timestr) components = [0, 0, 0, 0, None] pos = 0 comp = -1 if len_str < 2: raise ValueError('ISO time too short') has_sep = False while pos < len_str and comp < 5: comp += 1 if timestr[pos:pos + 1] in b'-+Zz': # Detect time zone boundary components[-1] = self._parse_tzstr(timestr[pos:]) pos = len_str break if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP: has_sep = True pos += 1 elif comp == 2 and has_sep: if timestr[pos:pos+1] != self._TIME_SEP: raise ValueError('Inconsistent use of colon separator') pos += 1 if comp < 3: # Hour, minute, second components[comp] = int(timestr[pos:pos + 2]) pos += 2 if comp == 3: # Fraction of a second frac = self._FRACTION_REGEX.match(timestr[pos:]) if not frac: continue us_str = frac.group(1)[:6] # Truncate to microseconds components[comp] = int(us_str) * 10**(6 - len(us_str)) pos += len(frac.group()) if pos < len_str: raise ValueError('Unused components in ISO string') if components[0] == 24: # Standard supports 00:00 and 24:00 as representations of midnight if any(component != 0 for component in components[1:4]): raise ValueError('Hour may only be 24 at 24:00:00.000') return components def _parse_tzstr(self, tzstr, zero_as_utc=True): if tzstr == b'Z' or tzstr == b'z': return tz.UTC if len(tzstr) not in {3, 5, 6}: raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') if tzstr[0:1] == b'-': mult = -1 elif tzstr[0:1] == b'+': mult = 1 else: raise ValueError('Time zone offset requires sign') hours = int(tzstr[1:3]) if len(tzstr) == 3: minutes = 0 else: minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) if zero_as_utc and hours == 0 and minutes == 0: return tz.UTC else: if minutes > 59: raise ValueError('Invalid minutes in time zone offset') if hours > 23: raise ValueError('Invalid hours in time zone offset') return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) DEFAULT_ISOPARSER = isoparser() isoparse = DEFAULT_ISOPARSER.isoparse PK!o __init__.pynu[# -*- coding: utf-8 -*- from ._parser import parse, parser, parserinfo, ParserError from ._parser import DEFAULTPARSER, DEFAULTTZPARSER from ._parser import UnknownTimezoneWarning from ._parser import __doc__ from .isoparser import isoparser, isoparse __all__ = ['parse', 'parser', 'parserinfo', 'isoparse', 'isoparser', 'ParserError', 'UnknownTimezoneWarning'] ### # Deprecate portions of the private interface so that downstream code that # is improperly relying on it is given *some* notice. def __deprecated_private_func(f): from functools import wraps import warnings msg = ('{name} is a private function and may break without warning, ' 'it will be moved and or renamed in future versions.') msg = msg.format(name=f.__name__) @wraps(f) def deprecated_func(*args, **kwargs): warnings.warn(msg, DeprecationWarning) return f(*args, **kwargs) return deprecated_func def __deprecate_private_class(c): import warnings msg = ('{name} is a private class and may break without warning, ' 'it will be moved and or renamed in future versions.') msg = msg.format(name=c.__name__) class private_class(c): __doc__ = c.__doc__ def __init__(self, *args, **kwargs): warnings.warn(msg, DeprecationWarning) super(private_class, self).__init__(*args, **kwargs) private_class.__name__ = c.__name__ return private_class from ._parser import _timelex, _resultbase from ._parser import _tzparser, _parsetz _timelex = __deprecate_private_class(_timelex) _tzparser = __deprecate_private_class(_tzparser) _resultbase = __deprecate_private_class(_resultbase) _parsetz = __deprecated_private_func(_parsetz) PK! !s(s(Makefilenu[ SHELL = /bin/sh # V=0 quiet, V=1 verbose. other values don't work. V = 1 Q1 = $(V:1=) Q = $(Q1:0=@) ECHO1 = $(V:1=@ :) ECHO = $(ECHO1:0=@ echo) NULLCMD = : #### Start of system configuration section. #### top_srcdir = $(topdir)/. srcdir = $(top_srcdir)/ext/json/parser topdir = ../../.. hdrdir = $(top_srcdir)/include arch_hdrdir = $(extout)/include/$(arch) PATH_SEPARATOR = : VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby RUBYLIB = RUBYOPT = - prefix = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME) rubyarchprefix = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/lib64/ruby rubylibprefix = $(exec_prefix)/share/ruby exec_prefix = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr vendorarchhdrdir = $(vendorhdrdir)/$(arch) sitearchhdrdir = $(sitehdrdir)/$(arch) rubyarchhdrdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/include vendorhdrdir = $(rubyhdrdir)/vendor_ruby sitehdrdir = $(rubyhdrdir)/site_ruby rubyhdrdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/include vendorarchdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/lib64/ruby/vendor_ruby vendorlibdir = $(vendordir)/$(ruby_version_dir_name) vendordir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/share/ruby/vendor_ruby sitearchdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/local/lib64/ruby/site_ruby sitelibdir = $(sitedir)/$(ruby_version_dir_name) sitedir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/local/share/ruby/site_ruby rubyarchdir = $(rubyarchprefix)/$(ruby_version_dir_name) rubylibdir = $(rubylibprefix)/$(ruby_version_dir_name) sitearchincludedir = $(includedir)/$(sitearch) archincludedir = $(includedir)/$(arch) sitearchlibdir = $(libdir)/$(sitearch) archlibdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/lib64 ridir = $(datarootdir)/$(RI_BASE_NAME) mandir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/share/man localedir = $(datarootdir)/locale libdir = $(exec_prefix)/lib64 psdir = $(docdir) pdfdir = $(docdir) dvidir = $(docdir) htmldir = $(docdir) infodir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/share/info docdir = $(datarootdir)/doc/$(PACKAGE) oldincludedir = $(DESTDIR)/usr/include includedir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/include localstatedir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/var sharedstatedir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/var/lib sysconfdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/etc datadir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/share datarootdir = $(prefix)/share libexecdir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/libexec sbindir = $(DESTDIR)/opt/cpanel/ea-ruby27/root/usr/sbin bindir = $(exec_prefix)/bin archdir = $(rubyarchdir) CC_WRAPPER = CC = gcc CXX = g++ LIBRUBY = $(LIBRUBY_SO) LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS) empty = OUTFLAG = -o $(empty) COUTFLAG = -o $(empty) CSRCFLAG = $(empty) RUBY_EXTCONF_H = extconf.h cflags = $(optflags) $(debugflags) $(warnflags) cxxflags = optflags = -O3 debugflags = -ggdb3 warnflags = -Wall -Wextra -Wdeprecated-declarations -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable cppflags = CCDLFLAGS = -fPIC CFLAGS = $(CCDLFLAGS) -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC $(ARCH_FLAG) INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir) -I$(srcdir) DEFS = CPPFLAGS = -DRUBY_EXTCONF_H=\"$(RUBY_EXTCONF_H)\" $(DEFS) $(cppflags) CXXFLAGS = $(CCDLFLAGS) -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection $(ARCH_FLAG) ldflags = -L. -Wl,-rpath=/opt/cpanel/ea-ruby27/root/usr/lib64 -fstack-protector-strong -rdynamic -Wl,-export-dynamic dldflags = -Wl,-rpath=/opt/cpanel/ea-ruby27/root/usr/lib64 ARCH_FLAG = -m64 DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) LDSHARED = $(CC) -shared LDSHAREDXX = $(CXX) -shared AR = ar EXEEXT = RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) RUBY_SO_NAME = ruby RUBYW_INSTALL_NAME = RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version_dir_name) RUBYW_BASE_NAME = rubyw RUBY_BASE_NAME = ruby arch = x86_64-linux sitearch = $(arch) ruby_version = 2.7.0 ruby = $(topdir)/miniruby -I'$(topdir)' -I'$(top_srcdir)/lib' -I'$(extout)/$(arch)' -I'$(extout)/common' RUBY = $(ruby) BUILTRUBY = $(topdir)/miniruby ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h $(RUBY_EXTCONF_H) RM = rm -f RM_RF = $(RUBY) -run -e rm -- -rf RMDIRS = rmdir --ignore-fail-on-non-empty -p MAKEDIRS = /usr/bin/mkdir -p INSTALL = /usr/bin/install -c INSTALL_PROG = $(INSTALL) -m 0755 INSTALL_DATA = $(INSTALL) -m 644 COPY = cp TOUCH = exit > #### End of system configuration section. #### preload = EXTSO = libpath = . $(topdir) LIBPATH = -L. -L$(topdir) DEFFILE = CLEANFILES = mkmf.log DISTCLEANFILES = DISTCLEANDIRS = extout = $(topdir)/.ext extout_prefix = $(extout)$(target_prefix)/ target_prefix = /json/ext LOCAL_LIBS = LIBS = $(LIBRUBYARG_SHARED) -lm -lc ORIG_SRCS = parser.c SRCS = $(ORIG_SRCS) OBJS = parser.o HDRS = $(srcdir)/parser.h LOCAL_HDRS = TARGET = parser TARGET_NAME = parser TARGET_ENTRY = Init_$(TARGET_NAME) DLLIB = $(TARGET).so EXTSTATIC = STATIC_LIB = TIMESTAMP_DIR = $(extout)/.timestamp BINDIR = $(extout)/bin RUBYCOMMONDIR = $(extout)/common RUBYLIBDIR = $(RUBYCOMMONDIR)$(target_prefix) RUBYARCHDIR = $(extout)/$(arch)$(target_prefix) HDRDIR = $(extout)/include/ruby$(target_prefix) ARCHHDRDIR = $(extout)/include/$(arch)/ruby$(target_prefix) TARGET_SO_DIR = $(RUBYARCHDIR)/ TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) CLEANLIBS = $(TARGET_SO) CLEANOBJS = *.o *.bak all: install static: all .PHONY: all install static install-so install-rb .PHONY: clean clean-so clean-static clean-rb clean-static:: clean-rb-default:: clean-rb:: clean-so:: clean: clean-so clean-static clean-rb-default clean-rb -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time distclean-rb-default:: distclean-rb:: distclean-so:: distclean-static:: distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true realclean: distclean install: install-so install-rb install-so: $(TARGET_SO) clean-so:: -$(Q)$(RM) $(TARGET_SO) $(TIMESTAMP_DIR)/$(arch)/.json.-.ext.time -$(Q)$(RMDIRS) $(TARGET_SO_DIR) 2> /dev/null || true clean-static:: -$(Q)$(RM) $(STATIC_LIB) install-rb: pre-install-rb do-install-rb install-rb-default install-rb-default: pre-install-rb-default do-install-rb-default pre-install-rb: Makefile pre-install-rb-default: Makefile do-install-rb: do-install-rb-default: pre-install-rb-default: @$(NULLCMD) $(TIMESTAMP_DIR)/$(arch)/.json.-.ext.time: $(Q) $(MAKEDIRS) $(@D) $(TARGET_SO_DIR) $(Q) $(TOUCH) $@ site-install: site-install-so site-install-rb site-install-so: install-so site-install-rb: install-rb .SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S .cc.o: $(ECHO) compiling $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .cc.S: $(ECHO) translating $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< .mm.o: $(ECHO) compiling $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .mm.S: $(ECHO) translating $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< .cxx.o: $(ECHO) compiling $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .cxx.S: $(ECHO) translating $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< .cpp.o: $(ECHO) compiling $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .cpp.S: $(ECHO) translating $(<) $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< .c.o: $(ECHO) compiling $(<) $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .c.S: $(ECHO) translating $(<) $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< .m.o: $(ECHO) compiling $(<) $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< .m.S: $(ECHO) translating $(<) $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< $(TARGET_SO): $(OBJS) Makefile $(TIMESTAMP_DIR)/$(arch)/.json.-.ext.time $(ECHO) linking shared-object json/ext/$(DLLIB) -$(Q)$(RM) $(@) $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) ### $(OBJS): $(RUBY_EXTCONF_H) $(OBJS): $(ruby_headers) parser.o: parser.c parser.h $(srcdir)/../fbuffer/fbuffer.h # AUTOGENERATED DEPENDENCIES START parser.o: $(RUBY_EXTCONF_H) parser.o: $(arch_hdrdir)/ruby/config.h parser.o: $(hdrdir)/ruby.h parser.o: $(hdrdir)/ruby/assert.h parser.o: $(hdrdir)/ruby/backward.h parser.o: $(hdrdir)/ruby/defines.h parser.o: $(hdrdir)/ruby/encoding.h parser.o: $(hdrdir)/ruby/intern.h parser.o: $(hdrdir)/ruby/missing.h parser.o: $(hdrdir)/ruby/onigmo.h parser.o: $(hdrdir)/ruby/oniguruma.h parser.o: $(hdrdir)/ruby/ruby.h parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h parser.o: parser.c parser.o: parser.h parser.o: parser.rl # AUTOGENERATED DEPENDENCIES END PK!(_vv extconf.rbnu[# frozen_string_literal: false require 'mkmf' have_func("rb_enc_raise", "ruby.h") create_makefile 'json/ext/parser' PK!(dependnu[$(OBJS): $(ruby_headers) parser.o: parser.c parser.h $(srcdir)/../fbuffer/fbuffer.h # AUTOGENERATED DEPENDENCIES START parser.o: $(RUBY_EXTCONF_H) parser.o: $(arch_hdrdir)/ruby/config.h parser.o: $(hdrdir)/ruby.h parser.o: $(hdrdir)/ruby/assert.h parser.o: $(hdrdir)/ruby/backward.h parser.o: $(hdrdir)/ruby/defines.h parser.o: $(hdrdir)/ruby/encoding.h parser.o: $(hdrdir)/ruby/intern.h parser.o: $(hdrdir)/ruby/missing.h parser.o: $(hdrdir)/ruby/onigmo.h parser.o: $(hdrdir)/ruby/oniguruma.h parser.o: $(hdrdir)/ruby/ruby.h parser.o: $(hdrdir)/ruby/st.h parser.o: $(hdrdir)/ruby/subst.h parser.o: $(srcdir)/../fbuffer/fbuffer.h parser.o: parser.c parser.o: parser.h parser.o: parser.rl # AUTOGENERATED DEPENDENCIES END PK!@GG extconf.hnu[#ifndef EXTCONF_H #define EXTCONF_H #define HAVE_RB_ENC_RAISE 1 #endif PK!./%% prereq.mknu[RAGEL = ragel .SUFFIXES: .rl .rl.c: $(RAGEL) -G2 $< $(BASERUBY) -pli -e '$$_.sub!(/[ \t]+$$/, "")' \ -e '$$_.sub!(/^static const int (JSON_.*=.*);$$/, "enum {\\1};")' \ -e '$$_ = "/* This file is automatically generated from parser.rl by using ragel */" + $$_ if $$. == 1' $@ parser.c: PK!parser.cnu[/* This file is automatically generated from parser.rl by using ragel */ #line 1 "parser.rl" #include "../fbuffer/fbuffer.h" #include "parser.h" #if defined HAVE_RUBY_ENCODING_H # define EXC_ENCODING rb_utf8_encoding(), # ifndef HAVE_RB_ENC_RAISE static void enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) { va_list args; VALUE mesg; va_start(args, fmt); mesg = rb_enc_vsprintf(enc, fmt, args); va_end(args); rb_exc_raise(rb_exc_new3(exc, mesg)); } # define rb_enc_raise enc_raise # endif #else # define EXC_ENCODING /* nothing */ # define rb_enc_raise rb_raise #endif /* unicode */ static const signed char digit_values[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static UTF32 unescape_unicode(const unsigned char *p) { signed char b; UTF32 result = 0; b = digit_values[p[0]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[1]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[2]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[3]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; return result; } static int convert_UTF32_to_UTF8(char *buf, UTF32 ch) { int len = 1; if (ch <= 0x7F) { buf[0] = (char) ch; } else if (ch <= 0x07FF) { buf[0] = (char) ((ch >> 6) | 0xC0); buf[1] = (char) ((ch & 0x3F) | 0x80); len++; } else if (ch <= 0xFFFF) { buf[0] = (char) ((ch >> 12) | 0xE0); buf[1] = (char) (((ch >> 6) & 0x3F) | 0x80); buf[2] = (char) ((ch & 0x3F) | 0x80); len += 2; } else if (ch <= 0x1fffff) { buf[0] =(char) ((ch >> 18) | 0xF0); buf[1] =(char) (((ch >> 12) & 0x3F) | 0x80); buf[2] =(char) (((ch >> 6) & 0x3F) | 0x80); buf[3] =(char) ((ch & 0x3F) | 0x80); len += 3; } else { buf[0] = '?'; } return len; } static VALUE mJSON, mExt, cParser, eParserError, eNestingError; static VALUE CNaN, CInfinity, CMinusInfinity; static VALUE cBigDecimal = Qundef; static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions, i_chr, i_max_nesting, i_allow_nan, i_symbolize_names, i_object_class, i_array_class, i_decimal_class, i_key_p, i_deep_const_get, i_match, i_match_string, i_aset, i_aref, i_leftshift, i_new, i_BigDecimal; #line 126 "parser.rl" #line 108 "parser.c" enum {JSON_object_start = 1}; enum {JSON_object_first_final = 27}; enum {JSON_object_error = 0}; enum {JSON_object_en_main = 1}; #line 167 "parser.rl" static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; VALUE last_name = Qnil; VALUE object_class = json->object_class; if (json->max_nesting && current_nesting > json->max_nesting) { rb_raise(eNestingError, "nesting of %d is too deep", current_nesting); } *result = NIL_P(object_class) ? rb_hash_new() : rb_class_new_instance(0, 0, object_class); #line 132 "parser.c" { cs = JSON_object_start; } #line 182 "parser.rl" #line 139 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: if ( (*p) == 123 ) goto st2; goto st0; st0: cs = 0; goto _out; st2: if ( ++p == pe ) goto _test_eof2; case 2: switch( (*p) ) { case 13: goto st2; case 32: goto st2; case 34: goto tr2; case 47: goto st23; case 125: goto tr4; } if ( 9 <= (*p) && (*p) <= 10 ) goto st2; goto st0; tr2: #line 149 "parser.rl" { char *np; json->parsing_name = 1; np = JSON_parse_string(json, p, pe, &last_name); json->parsing_name = 0; if (np == NULL) { p--; {p++; cs = 3; goto _out;} } else {p = (( np))-1;} } goto st3; st3: if ( ++p == pe ) goto _test_eof3; case 3: #line 180 "parser.c" switch( (*p) ) { case 13: goto st3; case 32: goto st3; case 47: goto st4; case 58: goto st8; } if ( 9 <= (*p) && (*p) <= 10 ) goto st3; goto st0; st4: if ( ++p == pe ) goto _test_eof4; case 4: switch( (*p) ) { case 42: goto st5; case 47: goto st7; } goto st0; st5: if ( ++p == pe ) goto _test_eof5; case 5: if ( (*p) == 42 ) goto st6; goto st5; st6: if ( ++p == pe ) goto _test_eof6; case 6: switch( (*p) ) { case 42: goto st6; case 47: goto st3; } goto st5; st7: if ( ++p == pe ) goto _test_eof7; case 7: if ( (*p) == 10 ) goto st3; goto st7; st8: if ( ++p == pe ) goto _test_eof8; case 8: switch( (*p) ) { case 13: goto st8; case 32: goto st8; case 34: goto tr11; case 45: goto tr11; case 47: goto st19; case 73: goto tr11; case 78: goto tr11; case 91: goto tr11; case 102: goto tr11; case 110: goto tr11; case 116: goto tr11; case 123: goto tr11; } if ( (*p) > 10 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr11; } else if ( (*p) >= 9 ) goto st8; goto st0; tr11: #line 134 "parser.rl" { VALUE v = Qnil; char *np = JSON_parse_value(json, p, pe, &v, current_nesting); if (np == NULL) { p--; {p++; cs = 9; goto _out;} } else { if (NIL_P(json->object_class)) { rb_hash_aset(*result, last_name, v); } else { rb_funcall(*result, i_aset, 2, last_name, v); } {p = (( np))-1;} } } goto st9; st9: if ( ++p == pe ) goto _test_eof9; case 9: #line 267 "parser.c" switch( (*p) ) { case 13: goto st9; case 32: goto st9; case 44: goto st10; case 47: goto st15; case 125: goto tr4; } if ( 9 <= (*p) && (*p) <= 10 ) goto st9; goto st0; st10: if ( ++p == pe ) goto _test_eof10; case 10: switch( (*p) ) { case 13: goto st10; case 32: goto st10; case 34: goto tr2; case 47: goto st11; } if ( 9 <= (*p) && (*p) <= 10 ) goto st10; goto st0; st11: if ( ++p == pe ) goto _test_eof11; case 11: switch( (*p) ) { case 42: goto st12; case 47: goto st14; } goto st0; st12: if ( ++p == pe ) goto _test_eof12; case 12: if ( (*p) == 42 ) goto st13; goto st12; st13: if ( ++p == pe ) goto _test_eof13; case 13: switch( (*p) ) { case 42: goto st13; case 47: goto st10; } goto st12; st14: if ( ++p == pe ) goto _test_eof14; case 14: if ( (*p) == 10 ) goto st10; goto st14; st15: if ( ++p == pe ) goto _test_eof15; case 15: switch( (*p) ) { case 42: goto st16; case 47: goto st18; } goto st0; st16: if ( ++p == pe ) goto _test_eof16; case 16: if ( (*p) == 42 ) goto st17; goto st16; st17: if ( ++p == pe ) goto _test_eof17; case 17: switch( (*p) ) { case 42: goto st17; case 47: goto st9; } goto st16; st18: if ( ++p == pe ) goto _test_eof18; case 18: if ( (*p) == 10 ) goto st9; goto st18; tr4: #line 157 "parser.rl" { p--; {p++; cs = 27; goto _out;} } goto st27; st27: if ( ++p == pe ) goto _test_eof27; case 27: #line 363 "parser.c" goto st0; st19: if ( ++p == pe ) goto _test_eof19; case 19: switch( (*p) ) { case 42: goto st20; case 47: goto st22; } goto st0; st20: if ( ++p == pe ) goto _test_eof20; case 20: if ( (*p) == 42 ) goto st21; goto st20; st21: if ( ++p == pe ) goto _test_eof21; case 21: switch( (*p) ) { case 42: goto st21; case 47: goto st8; } goto st20; st22: if ( ++p == pe ) goto _test_eof22; case 22: if ( (*p) == 10 ) goto st8; goto st22; st23: if ( ++p == pe ) goto _test_eof23; case 23: switch( (*p) ) { case 42: goto st24; case 47: goto st26; } goto st0; st24: if ( ++p == pe ) goto _test_eof24; case 24: if ( (*p) == 42 ) goto st25; goto st24; st25: if ( ++p == pe ) goto _test_eof25; case 25: switch( (*p) ) { case 42: goto st25; case 47: goto st2; } goto st24; st26: if ( ++p == pe ) goto _test_eof26; case 26: if ( (*p) == 10 ) goto st2; goto st26; } _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof11: cs = 11; goto _test_eof; _test_eof12: cs = 12; goto _test_eof; _test_eof13: cs = 13; goto _test_eof; _test_eof14: cs = 14; goto _test_eof; _test_eof15: cs = 15; goto _test_eof; _test_eof16: cs = 16; goto _test_eof; _test_eof17: cs = 17; goto _test_eof; _test_eof18: cs = 18; goto _test_eof; _test_eof27: cs = 27; goto _test_eof; _test_eof19: cs = 19; goto _test_eof; _test_eof20: cs = 20; goto _test_eof; _test_eof21: cs = 21; goto _test_eof; _test_eof22: cs = 22; goto _test_eof; _test_eof23: cs = 23; goto _test_eof; _test_eof24: cs = 24; goto _test_eof; _test_eof25: cs = 25; goto _test_eof; _test_eof26: cs = 26; goto _test_eof; _test_eof: {} _out: {} } #line 183 "parser.rl" if (cs >= JSON_object_first_final) { if (json->create_additions) { VALUE klassname; if (NIL_P(json->object_class)) { klassname = rb_hash_aref(*result, json->create_id); } else { klassname = rb_funcall(*result, i_aref, 1, json->create_id); } if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { *result = rb_funcall(klass, i_json_create, 1, *result); } } } return p + 1; } else { return NULL; } } #line 486 "parser.c" enum {JSON_value_start = 1}; enum {JSON_value_first_final = 29}; enum {JSON_value_error = 0}; enum {JSON_value_en_main = 1}; #line 283 "parser.rl" static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; #line 502 "parser.c" { cs = JSON_value_start; } #line 290 "parser.rl" #line 509 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { st1: if ( ++p == pe ) goto _test_eof1; case 1: switch( (*p) ) { case 13: goto st1; case 32: goto st1; case 34: goto tr2; case 45: goto tr3; case 47: goto st6; case 73: goto st10; case 78: goto st17; case 91: goto tr7; case 102: goto st19; case 110: goto st23; case 116: goto st26; case 123: goto tr11; } if ( (*p) > 10 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr3; } else if ( (*p) >= 9 ) goto st1; goto st0; st0: cs = 0; goto _out; tr2: #line 235 "parser.rl" { char *np = JSON_parse_string(json, p, pe, result); if (np == NULL) { p--; {p++; cs = 29; goto _out;} } else {p = (( np))-1;} } goto st29; tr3: #line 240 "parser.rl" { char *np; if(pe > p + 8 && !strncmp(MinusInfinity, p, 9)) { if (json->allow_nan) { *result = CMinusInfinity; {p = (( p + 10))-1;} p--; {p++; cs = 29; goto _out;} } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); } } np = JSON_parse_float(json, p, pe, result); if (np != NULL) {p = (( np))-1;} np = JSON_parse_integer(json, p, pe, result); if (np != NULL) {p = (( np))-1;} p--; {p++; cs = 29; goto _out;} } goto st29; tr7: #line 258 "parser.rl" { char *np; np = JSON_parse_array(json, p, pe, result, current_nesting + 1); if (np == NULL) { p--; {p++; cs = 29; goto _out;} } else {p = (( np))-1;} } goto st29; tr11: #line 264 "parser.rl" { char *np; np = JSON_parse_object(json, p, pe, result, current_nesting + 1); if (np == NULL) { p--; {p++; cs = 29; goto _out;} } else {p = (( np))-1;} } goto st29; tr25: #line 228 "parser.rl" { if (json->allow_nan) { *result = CInfinity; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p - 8); } } goto st29; tr27: #line 221 "parser.rl" { if (json->allow_nan) { *result = CNaN; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p - 2); } } goto st29; tr31: #line 215 "parser.rl" { *result = Qfalse; } goto st29; tr34: #line 212 "parser.rl" { *result = Qnil; } goto st29; tr37: #line 218 "parser.rl" { *result = Qtrue; } goto st29; st29: if ( ++p == pe ) goto _test_eof29; case 29: #line 270 "parser.rl" { p--; {p++; cs = 29; goto _out;} } #line 629 "parser.c" switch( (*p) ) { case 13: goto st29; case 32: goto st29; case 47: goto st2; } if ( 9 <= (*p) && (*p) <= 10 ) goto st29; goto st0; st2: if ( ++p == pe ) goto _test_eof2; case 2: switch( (*p) ) { case 42: goto st3; case 47: goto st5; } goto st0; st3: if ( ++p == pe ) goto _test_eof3; case 3: if ( (*p) == 42 ) goto st4; goto st3; st4: if ( ++p == pe ) goto _test_eof4; case 4: switch( (*p) ) { case 42: goto st4; case 47: goto st29; } goto st3; st5: if ( ++p == pe ) goto _test_eof5; case 5: if ( (*p) == 10 ) goto st29; goto st5; st6: if ( ++p == pe ) goto _test_eof6; case 6: switch( (*p) ) { case 42: goto st7; case 47: goto st9; } goto st0; st7: if ( ++p == pe ) goto _test_eof7; case 7: if ( (*p) == 42 ) goto st8; goto st7; st8: if ( ++p == pe ) goto _test_eof8; case 8: switch( (*p) ) { case 42: goto st8; case 47: goto st1; } goto st7; st9: if ( ++p == pe ) goto _test_eof9; case 9: if ( (*p) == 10 ) goto st1; goto st9; st10: if ( ++p == pe ) goto _test_eof10; case 10: if ( (*p) == 110 ) goto st11; goto st0; st11: if ( ++p == pe ) goto _test_eof11; case 11: if ( (*p) == 102 ) goto st12; goto st0; st12: if ( ++p == pe ) goto _test_eof12; case 12: if ( (*p) == 105 ) goto st13; goto st0; st13: if ( ++p == pe ) goto _test_eof13; case 13: if ( (*p) == 110 ) goto st14; goto st0; st14: if ( ++p == pe ) goto _test_eof14; case 14: if ( (*p) == 105 ) goto st15; goto st0; st15: if ( ++p == pe ) goto _test_eof15; case 15: if ( (*p) == 116 ) goto st16; goto st0; st16: if ( ++p == pe ) goto _test_eof16; case 16: if ( (*p) == 121 ) goto tr25; goto st0; st17: if ( ++p == pe ) goto _test_eof17; case 17: if ( (*p) == 97 ) goto st18; goto st0; st18: if ( ++p == pe ) goto _test_eof18; case 18: if ( (*p) == 78 ) goto tr27; goto st0; st19: if ( ++p == pe ) goto _test_eof19; case 19: if ( (*p) == 97 ) goto st20; goto st0; st20: if ( ++p == pe ) goto _test_eof20; case 20: if ( (*p) == 108 ) goto st21; goto st0; st21: if ( ++p == pe ) goto _test_eof21; case 21: if ( (*p) == 115 ) goto st22; goto st0; st22: if ( ++p == pe ) goto _test_eof22; case 22: if ( (*p) == 101 ) goto tr31; goto st0; st23: if ( ++p == pe ) goto _test_eof23; case 23: if ( (*p) == 117 ) goto st24; goto st0; st24: if ( ++p == pe ) goto _test_eof24; case 24: if ( (*p) == 108 ) goto st25; goto st0; st25: if ( ++p == pe ) goto _test_eof25; case 25: if ( (*p) == 108 ) goto tr34; goto st0; st26: if ( ++p == pe ) goto _test_eof26; case 26: if ( (*p) == 114 ) goto st27; goto st0; st27: if ( ++p == pe ) goto _test_eof27; case 27: if ( (*p) == 117 ) goto st28; goto st0; st28: if ( ++p == pe ) goto _test_eof28; case 28: if ( (*p) == 101 ) goto tr37; goto st0; } _test_eof1: cs = 1; goto _test_eof; _test_eof29: cs = 29; goto _test_eof; _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof11: cs = 11; goto _test_eof; _test_eof12: cs = 12; goto _test_eof; _test_eof13: cs = 13; goto _test_eof; _test_eof14: cs = 14; goto _test_eof; _test_eof15: cs = 15; goto _test_eof; _test_eof16: cs = 16; goto _test_eof; _test_eof17: cs = 17; goto _test_eof; _test_eof18: cs = 18; goto _test_eof; _test_eof19: cs = 19; goto _test_eof; _test_eof20: cs = 20; goto _test_eof; _test_eof21: cs = 21; goto _test_eof; _test_eof22: cs = 22; goto _test_eof; _test_eof23: cs = 23; goto _test_eof; _test_eof24: cs = 24; goto _test_eof; _test_eof25: cs = 25; goto _test_eof; _test_eof26: cs = 26; goto _test_eof; _test_eof27: cs = 27; goto _test_eof; _test_eof28: cs = 28; goto _test_eof; _test_eof: {} _out: {} } #line 291 "parser.rl" if (cs >= JSON_value_first_final) { return p; } else { return NULL; } } #line 880 "parser.c" enum {JSON_integer_start = 1}; enum {JSON_integer_first_final = 3}; enum {JSON_integer_error = 0}; enum {JSON_integer_en_main = 1}; #line 307 "parser.rl" static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; #line 896 "parser.c" { cs = JSON_integer_start; } #line 314 "parser.rl" json->memo = p; #line 904 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: switch( (*p) ) { case 45: goto st2; case 48: goto st3; } if ( 49 <= (*p) && (*p) <= 57 ) goto st5; goto st0; st0: cs = 0; goto _out; st2: if ( ++p == pe ) goto _test_eof2; case 2: if ( (*p) == 48 ) goto st3; if ( 49 <= (*p) && (*p) <= 57 ) goto st5; goto st0; st3: if ( ++p == pe ) goto _test_eof3; case 3: if ( 48 <= (*p) && (*p) <= 57 ) goto st0; goto tr4; tr4: #line 304 "parser.rl" { p--; {p++; cs = 4; goto _out;} } goto st4; st4: if ( ++p == pe ) goto _test_eof4; case 4: #line 945 "parser.c" goto st0; st5: if ( ++p == pe ) goto _test_eof5; case 5: if ( 48 <= (*p) && (*p) <= 57 ) goto st5; goto tr4; } _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof: {} _out: {} } #line 316 "parser.rl" if (cs >= JSON_integer_first_final) { long len = p - json->memo; fbuffer_clear(json->fbuffer); fbuffer_append(json->fbuffer, json->memo, len); fbuffer_append_char(json->fbuffer, '\0'); *result = rb_cstr2inum(FBUFFER_PTR(json->fbuffer), 10); return p + 1; } else { return NULL; } } #line 979 "parser.c" enum {JSON_float_start = 1}; enum {JSON_float_first_final = 8}; enum {JSON_float_error = 0}; enum {JSON_float_en_main = 1}; #line 341 "parser.rl" static int is_bigdecimal_class(VALUE obj) { if (cBigDecimal == Qundef) { if (rb_const_defined(rb_cObject, i_BigDecimal)) { cBigDecimal = rb_const_get_at(rb_cObject, i_BigDecimal); } else { return 0; } } return obj == cBigDecimal; } static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; #line 1008 "parser.c" { cs = JSON_float_start; } #line 361 "parser.rl" json->memo = p; #line 1016 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: switch( (*p) ) { case 45: goto st2; case 48: goto st3; } if ( 49 <= (*p) && (*p) <= 57 ) goto st7; goto st0; st0: cs = 0; goto _out; st2: if ( ++p == pe ) goto _test_eof2; case 2: if ( (*p) == 48 ) goto st3; if ( 49 <= (*p) && (*p) <= 57 ) goto st7; goto st0; st3: if ( ++p == pe ) goto _test_eof3; case 3: switch( (*p) ) { case 46: goto st4; case 69: goto st5; case 101: goto st5; } goto st0; st4: if ( ++p == pe ) goto _test_eof4; case 4: if ( 48 <= (*p) && (*p) <= 57 ) goto st8; goto st0; st8: if ( ++p == pe ) goto _test_eof8; case 8: switch( (*p) ) { case 69: goto st5; case 101: goto st5; } if ( (*p) > 46 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st8; } else if ( (*p) >= 45 ) goto st0; goto tr9; tr9: #line 335 "parser.rl" { p--; {p++; cs = 9; goto _out;} } goto st9; st9: if ( ++p == pe ) goto _test_eof9; case 9: #line 1081 "parser.c" goto st0; st5: if ( ++p == pe ) goto _test_eof5; case 5: switch( (*p) ) { case 43: goto st6; case 45: goto st6; } if ( 48 <= (*p) && (*p) <= 57 ) goto st10; goto st0; st6: if ( ++p == pe ) goto _test_eof6; case 6: if ( 48 <= (*p) && (*p) <= 57 ) goto st10; goto st0; st10: if ( ++p == pe ) goto _test_eof10; case 10: switch( (*p) ) { case 69: goto st0; case 101: goto st0; } if ( (*p) > 46 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st10; } else if ( (*p) >= 45 ) goto st0; goto tr9; st7: if ( ++p == pe ) goto _test_eof7; case 7: switch( (*p) ) { case 46: goto st4; case 69: goto st5; case 101: goto st5; } if ( 48 <= (*p) && (*p) <= 57 ) goto st7; goto st0; } _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof: {} _out: {} } #line 363 "parser.rl" if (cs >= JSON_float_first_final) { long len = p - json->memo; fbuffer_clear(json->fbuffer); fbuffer_append(json->fbuffer, json->memo, len); fbuffer_append_char(json->fbuffer, '\0'); if (NIL_P(json->decimal_class)) { *result = rb_float_new(rb_cstr_to_dbl(FBUFFER_PTR(json->fbuffer), 1)); } else { VALUE text; text = rb_str_new2(FBUFFER_PTR(json->fbuffer)); if (is_bigdecimal_class(json->decimal_class)) { *result = rb_funcall(Qnil, i_BigDecimal, 1, text); } else { *result = rb_funcall(json->decimal_class, i_new, 1, text); } } return p + 1; } else { return NULL; } } #line 1168 "parser.c" enum {JSON_array_start = 1}; enum {JSON_array_first_final = 17}; enum {JSON_array_error = 0}; enum {JSON_array_en_main = 1}; #line 416 "parser.rl" static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; VALUE array_class = json->array_class; if (json->max_nesting && current_nesting > json->max_nesting) { rb_raise(eNestingError, "nesting of %d is too deep", current_nesting); } *result = NIL_P(array_class) ? rb_ary_new() : rb_class_new_instance(0, 0, array_class); #line 1190 "parser.c" { cs = JSON_array_start; } #line 429 "parser.rl" #line 1197 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: if ( (*p) == 91 ) goto st2; goto st0; st0: cs = 0; goto _out; st2: if ( ++p == pe ) goto _test_eof2; case 2: switch( (*p) ) { case 13: goto st2; case 32: goto st2; case 34: goto tr2; case 45: goto tr2; case 47: goto st13; case 73: goto tr2; case 78: goto tr2; case 91: goto tr2; case 93: goto tr4; case 102: goto tr2; case 110: goto tr2; case 116: goto tr2; case 123: goto tr2; } if ( (*p) > 10 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr2; } else if ( (*p) >= 9 ) goto st2; goto st0; tr2: #line 393 "parser.rl" { VALUE v = Qnil; char *np = JSON_parse_value(json, p, pe, &v, current_nesting); if (np == NULL) { p--; {p++; cs = 3; goto _out;} } else { if (NIL_P(json->array_class)) { rb_ary_push(*result, v); } else { rb_funcall(*result, i_leftshift, 1, v); } {p = (( np))-1;} } } goto st3; st3: if ( ++p == pe ) goto _test_eof3; case 3: #line 1256 "parser.c" switch( (*p) ) { case 13: goto st3; case 32: goto st3; case 44: goto st4; case 47: goto st9; case 93: goto tr4; } if ( 9 <= (*p) && (*p) <= 10 ) goto st3; goto st0; st4: if ( ++p == pe ) goto _test_eof4; case 4: switch( (*p) ) { case 13: goto st4; case 32: goto st4; case 34: goto tr2; case 45: goto tr2; case 47: goto st5; case 73: goto tr2; case 78: goto tr2; case 91: goto tr2; case 102: goto tr2; case 110: goto tr2; case 116: goto tr2; case 123: goto tr2; } if ( (*p) > 10 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr2; } else if ( (*p) >= 9 ) goto st4; goto st0; st5: if ( ++p == pe ) goto _test_eof5; case 5: switch( (*p) ) { case 42: goto st6; case 47: goto st8; } goto st0; st6: if ( ++p == pe ) goto _test_eof6; case 6: if ( (*p) == 42 ) goto st7; goto st6; st7: if ( ++p == pe ) goto _test_eof7; case 7: switch( (*p) ) { case 42: goto st7; case 47: goto st4; } goto st6; st8: if ( ++p == pe ) goto _test_eof8; case 8: if ( (*p) == 10 ) goto st4; goto st8; st9: if ( ++p == pe ) goto _test_eof9; case 9: switch( (*p) ) { case 42: goto st10; case 47: goto st12; } goto st0; st10: if ( ++p == pe ) goto _test_eof10; case 10: if ( (*p) == 42 ) goto st11; goto st10; st11: if ( ++p == pe ) goto _test_eof11; case 11: switch( (*p) ) { case 42: goto st11; case 47: goto st3; } goto st10; st12: if ( ++p == pe ) goto _test_eof12; case 12: if ( (*p) == 10 ) goto st3; goto st12; tr4: #line 408 "parser.rl" { p--; {p++; cs = 17; goto _out;} } goto st17; st17: if ( ++p == pe ) goto _test_eof17; case 17: #line 1363 "parser.c" goto st0; st13: if ( ++p == pe ) goto _test_eof13; case 13: switch( (*p) ) { case 42: goto st14; case 47: goto st16; } goto st0; st14: if ( ++p == pe ) goto _test_eof14; case 14: if ( (*p) == 42 ) goto st15; goto st14; st15: if ( ++p == pe ) goto _test_eof15; case 15: switch( (*p) ) { case 42: goto st15; case 47: goto st2; } goto st14; st16: if ( ++p == pe ) goto _test_eof16; case 16: if ( (*p) == 10 ) goto st2; goto st16; } _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof11: cs = 11; goto _test_eof; _test_eof12: cs = 12; goto _test_eof; _test_eof17: cs = 17; goto _test_eof; _test_eof13: cs = 13; goto _test_eof; _test_eof14: cs = 14; goto _test_eof; _test_eof15: cs = 15; goto _test_eof; _test_eof16: cs = 16; goto _test_eof; _test_eof: {} _out: {} } #line 430 "parser.rl" if(cs >= JSON_array_first_final) { return p + 1; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); return NULL; } } static VALUE json_string_unescape(VALUE result, char *string, char *stringEnd) { char *p = string, *pe = string, *unescape; int unescape_len; char buf[4]; while (pe < stringEnd) { if (*pe == '\\') { unescape = (char *) "?"; unescape_len = 1; if (pe > p) rb_str_buf_cat(result, p, pe - p); switch (*++pe) { case 'n': unescape = (char *) "\n"; break; case 'r': unescape = (char *) "\r"; break; case 't': unescape = (char *) "\t"; break; case '"': unescape = (char *) "\""; break; case '\\': unescape = (char *) "\\"; break; case 'b': unescape = (char *) "\b"; break; case 'f': unescape = (char *) "\f"; break; case 'u': if (pe > stringEnd - 4) { rb_enc_raise( EXC_ENCODING eParserError, "%u: incomplete unicode character escape sequence at '%s'", __LINE__, p ); } else { UTF32 ch = unescape_unicode((unsigned char *) ++pe); pe += 3; if (UNI_SUR_HIGH_START == (ch & 0xFC00)) { pe++; if (pe > stringEnd - 6) { rb_enc_raise( EXC_ENCODING eParserError, "%u: incomplete surrogate pair at '%s'", __LINE__, p ); } if (pe[0] == '\\' && pe[1] == 'u') { UTF32 sur = unescape_unicode((unsigned char *) pe + 2); ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF)); pe += 5; } else { unescape = (char *) "?"; break; } } unescape_len = convert_UTF32_to_UTF8(buf, ch); unescape = buf; } break; default: p = pe; continue; } rb_str_buf_cat(result, unescape, unescape_len); p = ++pe; } else { pe++; } } rb_str_buf_cat(result, p, pe - p); return result; } #line 1508 "parser.c" enum {JSON_string_start = 1}; enum {JSON_string_first_final = 8}; enum {JSON_string_error = 0}; enum {JSON_string_en_main = 1}; #line 537 "parser.rl" static int match_i(VALUE regexp, VALUE klass, VALUE memo) { if (regexp == Qundef) return ST_STOP; if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) && RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { rb_ary_push(memo, klass); return ST_STOP; } return ST_CONTINUE; } static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; VALUE match_string; *result = rb_str_buf_new(0); #line 1538 "parser.c" { cs = JSON_string_start; } #line 558 "parser.rl" json->memo = p; #line 1546 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: if ( (*p) == 34 ) goto st2; goto st0; st0: cs = 0; goto _out; st2: if ( ++p == pe ) goto _test_eof2; case 2: switch( (*p) ) { case 34: goto tr2; case 92: goto st3; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st2; tr2: #line 523 "parser.rl" { *result = json_string_unescape(*result, json->memo + 1, p); if (NIL_P(*result)) { p--; {p++; cs = 8; goto _out;} } else { FORCE_UTF8(*result); {p = (( p + 1))-1;} } } #line 534 "parser.rl" { p--; {p++; cs = 8; goto _out;} } goto st8; st8: if ( ++p == pe ) goto _test_eof8; case 8: #line 1589 "parser.c" goto st0; st3: if ( ++p == pe ) goto _test_eof3; case 3: if ( (*p) == 117 ) goto st4; if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st2; st4: if ( ++p == pe ) goto _test_eof4; case 4: if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st5; } else if ( (*p) > 70 ) { if ( 97 <= (*p) && (*p) <= 102 ) goto st5; } else goto st5; goto st0; st5: if ( ++p == pe ) goto _test_eof5; case 5: if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st6; } else if ( (*p) > 70 ) { if ( 97 <= (*p) && (*p) <= 102 ) goto st6; } else goto st6; goto st0; st6: if ( ++p == pe ) goto _test_eof6; case 6: if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st7; } else if ( (*p) > 70 ) { if ( 97 <= (*p) && (*p) <= 102 ) goto st7; } else goto st7; goto st0; st7: if ( ++p == pe ) goto _test_eof7; case 7: if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st2; } else if ( (*p) > 70 ) { if ( 97 <= (*p) && (*p) <= 102 ) goto st2; } else goto st2; goto st0; } _test_eof2: cs = 2; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof: {} _out: {} } #line 560 "parser.rl" if (json->create_additions && RTEST(match_string = json->match_string)) { VALUE klass; VALUE memo = rb_ary_new2(2); rb_ary_push(memo, *result); rb_hash_foreach(match_string, match_i, memo); klass = rb_ary_entry(memo, 1); if (RTEST(klass)) { *result = rb_funcall(klass, i_json_create, 1, *result); } } if (json->symbolize_names && json->parsing_name) { *result = rb_str_intern(*result); } else if (RB_TYPE_P(*result, T_STRING)) { rb_str_resize(*result, RSTRING_LEN(*result)); } if (cs >= JSON_string_first_final) { return p + 1; } else { return NULL; } } /* * Document-class: JSON::Ext::Parser * * This is the JSON parser implemented as a C extension. It can be configured * to be used by setting * * JSON.parser = JSON::Ext::Parser * * with the method parser= in JSON. * */ static VALUE convert_encoding(VALUE source) { #ifdef HAVE_RUBY_ENCODING_H rb_encoding *enc = rb_enc_get(source); if (enc == rb_ascii8bit_encoding()) { if (OBJ_FROZEN(source)) { source = rb_str_dup(source); } FORCE_UTF8(source); } else { source = rb_str_conv_enc(source, rb_enc_get(source), rb_utf8_encoding()); } #endif return source; } /* * call-seq: new(source, opts => {}) * * Creates a new JSON::Ext::Parser instance for the string _source_. * * Creates a new JSON::Ext::Parser instance for the string _source_. * * It will be configured by the _opts_ hash. _opts_ can have the following * keys: * * _opts_ can have the following keys: * * *max_nesting*: The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, it * defaults to 100. * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in * defiance of RFC 4627 to be parsed by the Parser. This option defaults to * false. * * *symbolize_names*: If set to true, returns symbols for the names * (keys) in a JSON object. Otherwise strings are returned, which is * also the default. It's not possible to use this option in * conjunction with the *create_additions* option. * * *create_additions*: If set to false, the Parser doesn't create * additions even if a matching class and create_id was found. This option * defaults to false. * * *object_class*: Defaults to Hash * * *array_class*: Defaults to Array */ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) { VALUE source, opts; GET_PARSER_INIT; if (json->Vsource) { rb_raise(rb_eTypeError, "already initialized instance"); } #ifdef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH rb_scan_args(argc, argv, "1:", &source, &opts); #else rb_scan_args(argc, argv, "11", &source, &opts); #endif if (!NIL_P(opts)) { #ifndef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash"); if (NIL_P(opts)) { rb_raise(rb_eArgError, "opts needs to be like a hash"); } else { #endif VALUE tmp = ID2SYM(i_max_nesting); if (option_given_p(opts, tmp)) { VALUE max_nesting = rb_hash_aref(opts, tmp); if (RTEST(max_nesting)) { Check_Type(max_nesting, T_FIXNUM); json->max_nesting = FIX2INT(max_nesting); } else { json->max_nesting = 0; } } else { json->max_nesting = 100; } tmp = ID2SYM(i_allow_nan); if (option_given_p(opts, tmp)) { json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->allow_nan = 0; } tmp = ID2SYM(i_symbolize_names); if (option_given_p(opts, tmp)) { json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->symbolize_names = 0; } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { json->create_additions = RTEST(rb_hash_aref(opts, tmp)); } else { json->create_additions = 0; } if (json->symbolize_names && json->create_additions) { rb_raise(rb_eArgError, "options :symbolize_names and :create_additions cannot be " " used in conjunction"); } tmp = ID2SYM(i_create_id); if (option_given_p(opts, tmp)) { json->create_id = rb_hash_aref(opts, tmp); } else { json->create_id = rb_funcall(mJSON, i_create_id, 0); } tmp = ID2SYM(i_object_class); if (option_given_p(opts, tmp)) { json->object_class = rb_hash_aref(opts, tmp); } else { json->object_class = Qnil; } tmp = ID2SYM(i_array_class); if (option_given_p(opts, tmp)) { json->array_class = rb_hash_aref(opts, tmp); } else { json->array_class = Qnil; } tmp = ID2SYM(i_decimal_class); if (option_given_p(opts, tmp)) { json->decimal_class = rb_hash_aref(opts, tmp); } else { json->decimal_class = Qnil; } tmp = ID2SYM(i_match_string); if (option_given_p(opts, tmp)) { VALUE match_string = rb_hash_aref(opts, tmp); json->match_string = RTEST(match_string) ? match_string : Qnil; } else { json->match_string = Qnil; } #ifndef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH } #endif } else { json->max_nesting = 100; json->allow_nan = 0; json->create_additions = 0; json->create_id = rb_funcall(mJSON, i_create_id, 0); json->object_class = Qnil; json->array_class = Qnil; json->decimal_class = Qnil; } source = convert_encoding(StringValue(source)); StringValue(source); json->len = RSTRING_LEN(source); json->source = RSTRING_PTR(source);; json->Vsource = source; return self; } #line 1852 "parser.c" enum {JSON_start = 1}; enum {JSON_first_final = 10}; enum {JSON_error = 0}; enum {JSON_en_main = 1}; #line 760 "parser.rl" /* * call-seq: parse() * * Parses the current JSON text _source_ and returns the complete data * structure as a result. */ static VALUE cParser_parse(VALUE self) { char *p, *pe; int cs = EVIL; VALUE result = Qnil; GET_PARSER; #line 1877 "parser.c" { cs = JSON_start; } #line 776 "parser.rl" p = json->source; pe = p + json->len; #line 1886 "parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { st1: if ( ++p == pe ) goto _test_eof1; case 1: switch( (*p) ) { case 13: goto st1; case 32: goto st1; case 34: goto tr2; case 45: goto tr2; case 47: goto st6; case 73: goto tr2; case 78: goto tr2; case 91: goto tr2; case 102: goto tr2; case 110: goto tr2; case 116: goto tr2; case 123: goto tr2; } if ( (*p) > 10 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr2; } else if ( (*p) >= 9 ) goto st1; goto st0; st0: cs = 0; goto _out; tr2: #line 752 "parser.rl" { char *np = JSON_parse_value(json, p, pe, &result, 0); if (np == NULL) { p--; {p++; cs = 10; goto _out;} } else {p = (( np))-1;} } goto st10; st10: if ( ++p == pe ) goto _test_eof10; case 10: #line 1930 "parser.c" switch( (*p) ) { case 13: goto st10; case 32: goto st10; case 47: goto st2; } if ( 9 <= (*p) && (*p) <= 10 ) goto st10; goto st0; st2: if ( ++p == pe ) goto _test_eof2; case 2: switch( (*p) ) { case 42: goto st3; case 47: goto st5; } goto st0; st3: if ( ++p == pe ) goto _test_eof3; case 3: if ( (*p) == 42 ) goto st4; goto st3; st4: if ( ++p == pe ) goto _test_eof4; case 4: switch( (*p) ) { case 42: goto st4; case 47: goto st10; } goto st3; st5: if ( ++p == pe ) goto _test_eof5; case 5: if ( (*p) == 10 ) goto st10; goto st5; st6: if ( ++p == pe ) goto _test_eof6; case 6: switch( (*p) ) { case 42: goto st7; case 47: goto st9; } goto st0; st7: if ( ++p == pe ) goto _test_eof7; case 7: if ( (*p) == 42 ) goto st8; goto st7; st8: if ( ++p == pe ) goto _test_eof8; case 8: switch( (*p) ) { case 42: goto st8; case 47: goto st1; } goto st7; st9: if ( ++p == pe ) goto _test_eof9; case 9: if ( (*p) == 10 ) goto st1; goto st9; } _test_eof1: cs = 1; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof: {} _out: {} } #line 779 "parser.rl" if (cs >= JSON_first_final && p == pe) { return result; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); return Qnil; } } static void JSON_mark(void *ptr) { JSON_Parser *json = ptr; rb_gc_mark_maybe(json->Vsource); rb_gc_mark_maybe(json->create_id); rb_gc_mark_maybe(json->object_class); rb_gc_mark_maybe(json->array_class); rb_gc_mark_maybe(json->decimal_class); rb_gc_mark_maybe(json->match_string); } static void JSON_free(void *ptr) { JSON_Parser *json = ptr; fbuffer_free(json->fbuffer); ruby_xfree(json); } static size_t JSON_memsize(const void *ptr) { const JSON_Parser *json = ptr; return sizeof(*json) + FBUFFER_CAPA(json->fbuffer); } #ifdef NEW_TYPEDDATA_WRAPPER static const rb_data_type_t JSON_Parser_type = { "JSON/Parser", {JSON_mark, JSON_free, JSON_memsize,}, #ifdef RUBY_TYPED_FREE_IMMEDIATELY 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, #endif }; #endif static VALUE cJSON_parser_s_allocate(VALUE klass) { JSON_Parser *json; VALUE obj = TypedData_Make_Struct(klass, JSON_Parser, &JSON_Parser_type, json); json->fbuffer = fbuffer_alloc(0); return obj; } /* * call-seq: source() * * Returns a copy of the current _source_ string, that was used to construct * this Parser. */ static VALUE cParser_source(VALUE self) { GET_PARSER; return rb_str_dup(json->Vsource); } void Init_parser(void) { #undef rb_intern rb_require("json/common"); mJSON = rb_define_module("JSON"); mExt = rb_define_module_under(mJSON, "Ext"); cParser = rb_define_class_under(mExt, "Parser", rb_cObject); eParserError = rb_path2class("JSON::ParserError"); eNestingError = rb_path2class("JSON::NestingError"); rb_gc_register_mark_object(eParserError); rb_gc_register_mark_object(eNestingError); rb_define_alloc_func(cParser, cJSON_parser_s_allocate); rb_define_method(cParser, "initialize", cParser_initialize, -1); rb_define_method(cParser, "parse", cParser_parse, 0); rb_define_method(cParser, "source", cParser_source, 0); CNaN = rb_const_get(mJSON, rb_intern("NaN")); rb_gc_register_mark_object(CNaN); CInfinity = rb_const_get(mJSON, rb_intern("Infinity")); rb_gc_register_mark_object(CInfinity); CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity")); rb_gc_register_mark_object(CMinusInfinity); i_json_creatable_p = rb_intern("json_creatable?"); i_json_create = rb_intern("json_create"); i_create_id = rb_intern("create_id"); i_create_additions = rb_intern("create_additions"); i_chr = rb_intern("chr"); i_max_nesting = rb_intern("max_nesting"); i_allow_nan = rb_intern("allow_nan"); i_symbolize_names = rb_intern("symbolize_names"); i_object_class = rb_intern("object_class"); i_array_class = rb_intern("array_class"); i_decimal_class = rb_intern("decimal_class"); i_match = rb_intern("match"); i_match_string = rb_intern("match_string"); i_key_p = rb_intern("key?"); i_deep_const_get = rb_intern("deep_const_get"); i_aset = rb_intern("[]="); i_aref = rb_intern("[]"); i_leftshift = rb_intern("<<"); i_new = rb_intern("new"); i_BigDecimal = rb_intern("BigDecimal"); } /* * Local variables: * mode: c * c-file-style: ruby * indent-tabs-mode: nil * End: */ PK! hh parser.rlnu[#include "../fbuffer/fbuffer.h" #include "parser.h" #if defined HAVE_RUBY_ENCODING_H # define EXC_ENCODING rb_utf8_encoding(), # ifndef HAVE_RB_ENC_RAISE static void enc_raise(rb_encoding *enc, VALUE exc, const char *fmt, ...) { va_list args; VALUE mesg; va_start(args, fmt); mesg = rb_enc_vsprintf(enc, fmt, args); va_end(args); rb_exc_raise(rb_exc_new3(exc, mesg)); } # define rb_enc_raise enc_raise # endif #else # define EXC_ENCODING /* nothing */ # define rb_enc_raise rb_raise #endif /* unicode */ static const signed char digit_values[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static UTF32 unescape_unicode(const unsigned char *p) { signed char b; UTF32 result = 0; b = digit_values[p[0]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[1]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[2]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; b = digit_values[p[3]]; if (b < 0) return UNI_REPLACEMENT_CHAR; result = (result << 4) | (unsigned char)b; return result; } static int convert_UTF32_to_UTF8(char *buf, UTF32 ch) { int len = 1; if (ch <= 0x7F) { buf[0] = (char) ch; } else if (ch <= 0x07FF) { buf[0] = (char) ((ch >> 6) | 0xC0); buf[1] = (char) ((ch & 0x3F) | 0x80); len++; } else if (ch <= 0xFFFF) { buf[0] = (char) ((ch >> 12) | 0xE0); buf[1] = (char) (((ch >> 6) & 0x3F) | 0x80); buf[2] = (char) ((ch & 0x3F) | 0x80); len += 2; } else if (ch <= 0x1fffff) { buf[0] =(char) ((ch >> 18) | 0xF0); buf[1] =(char) (((ch >> 12) & 0x3F) | 0x80); buf[2] =(char) (((ch >> 6) & 0x3F) | 0x80); buf[3] =(char) ((ch & 0x3F) | 0x80); len += 3; } else { buf[0] = '?'; } return len; } static VALUE mJSON, mExt, cParser, eParserError, eNestingError; static VALUE CNaN, CInfinity, CMinusInfinity; static VALUE cBigDecimal = Qundef; static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions, i_chr, i_max_nesting, i_allow_nan, i_symbolize_names, i_object_class, i_array_class, i_decimal_class, i_key_p, i_deep_const_get, i_match, i_match_string, i_aset, i_aref, i_leftshift, i_new, i_BigDecimal; %%{ machine JSON_common; cr = '\n'; cr_neg = [^\n]; ws = [ \t\r\n]; c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/'; cpp_comment = '//' cr_neg* cr; comment = c_comment | cpp_comment; ignore = ws | comment; name_separator = ':'; value_separator = ','; Vnull = 'null'; Vfalse = 'false'; Vtrue = 'true'; VNaN = 'NaN'; VInfinity = 'Infinity'; VMinusInfinity = '-Infinity'; begin_value = [nft\"\-\[\{NI] | digit; begin_object = '{'; end_object = '}'; begin_array = '['; end_array = ']'; begin_string = '"'; begin_name = begin_string; begin_number = digit | '-'; }%% %%{ machine JSON_object; include JSON_common; write data; action parse_value { VALUE v = Qnil; char *np = JSON_parse_value(json, fpc, pe, &v, current_nesting); if (np == NULL) { fhold; fbreak; } else { if (NIL_P(json->object_class)) { rb_hash_aset(*result, last_name, v); } else { rb_funcall(*result, i_aset, 2, last_name, v); } fexec np; } } action parse_name { char *np; json->parsing_name = 1; np = JSON_parse_string(json, fpc, pe, &last_name); json->parsing_name = 0; if (np == NULL) { fhold; fbreak; } else fexec np; } action exit { fhold; fbreak; } pair = ignore* begin_name >parse_name ignore* name_separator ignore* begin_value >parse_value; next_pair = ignore* value_separator pair; main := ( begin_object (pair (next_pair)*)? ignore* end_object ) @exit; }%% static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; VALUE last_name = Qnil; VALUE object_class = json->object_class; if (json->max_nesting && current_nesting > json->max_nesting) { rb_raise(eNestingError, "nesting of %d is too deep", current_nesting); } *result = NIL_P(object_class) ? rb_hash_new() : rb_class_new_instance(0, 0, object_class); %% write init; %% write exec; if (cs >= JSON_object_first_final) { if (json->create_additions) { VALUE klassname; if (NIL_P(json->object_class)) { klassname = rb_hash_aref(*result, json->create_id); } else { klassname = rb_funcall(*result, i_aref, 1, json->create_id); } if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { *result = rb_funcall(klass, i_json_create, 1, *result); } } } return p + 1; } else { return NULL; } } %%{ machine JSON_value; include JSON_common; write data; action parse_null { *result = Qnil; } action parse_false { *result = Qfalse; } action parse_true { *result = Qtrue; } action parse_nan { if (json->allow_nan) { *result = CNaN; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p - 2); } } action parse_infinity { if (json->allow_nan) { *result = CInfinity; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p - 8); } } action parse_string { char *np = JSON_parse_string(json, fpc, pe, result); if (np == NULL) { fhold; fbreak; } else fexec np; } action parse_number { char *np; if(pe > fpc + 8 && !strncmp(MinusInfinity, fpc, 9)) { if (json->allow_nan) { *result = CMinusInfinity; fexec p + 10; fhold; fbreak; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); } } np = JSON_parse_float(json, fpc, pe, result); if (np != NULL) fexec np; np = JSON_parse_integer(json, fpc, pe, result); if (np != NULL) fexec np; fhold; fbreak; } action parse_array { char *np; np = JSON_parse_array(json, fpc, pe, result, current_nesting + 1); if (np == NULL) { fhold; fbreak; } else fexec np; } action parse_object { char *np; np = JSON_parse_object(json, fpc, pe, result, current_nesting + 1); if (np == NULL) { fhold; fbreak; } else fexec np; } action exit { fhold; fbreak; } main := ignore* ( Vnull @parse_null | Vfalse @parse_false | Vtrue @parse_true | VNaN @parse_nan | VInfinity @parse_infinity | begin_number >parse_number | begin_string >parse_string | begin_array >parse_array | begin_object >parse_object ) ignore* %*exit; }%% static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; %% write init; %% write exec; if (cs >= JSON_value_first_final) { return p; } else { return NULL; } } %%{ machine JSON_integer; write data; action exit { fhold; fbreak; } main := '-'? ('0' | [1-9][0-9]*) (^[0-9]? @exit); }%% static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; %% write init; json->memo = p; %% write exec; if (cs >= JSON_integer_first_final) { long len = p - json->memo; fbuffer_clear(json->fbuffer); fbuffer_append(json->fbuffer, json->memo, len); fbuffer_append_char(json->fbuffer, '\0'); *result = rb_cstr2inum(FBUFFER_PTR(json->fbuffer), 10); return p + 1; } else { return NULL; } } %%{ machine JSON_float; include JSON_common; write data; action exit { fhold; fbreak; } main := '-'? ( (('0' | [1-9][0-9]*) '.' [0-9]+ ([Ee] [+\-]?[0-9]+)?) | (('0' | [1-9][0-9]*) ([Ee] [+\-]?[0-9]+)) ) (^[0-9Ee.\-]? @exit ); }%% static int is_bigdecimal_class(VALUE obj) { if (cBigDecimal == Qundef) { if (rb_const_defined(rb_cObject, i_BigDecimal)) { cBigDecimal = rb_const_get_at(rb_cObject, i_BigDecimal); } else { return 0; } } return obj == cBigDecimal; } static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; %% write init; json->memo = p; %% write exec; if (cs >= JSON_float_first_final) { long len = p - json->memo; fbuffer_clear(json->fbuffer); fbuffer_append(json->fbuffer, json->memo, len); fbuffer_append_char(json->fbuffer, '\0'); if (NIL_P(json->decimal_class)) { *result = rb_float_new(rb_cstr_to_dbl(FBUFFER_PTR(json->fbuffer), 1)); } else { VALUE text; text = rb_str_new2(FBUFFER_PTR(json->fbuffer)); if (is_bigdecimal_class(json->decimal_class)) { *result = rb_funcall(Qnil, i_BigDecimal, 1, text); } else { *result = rb_funcall(json->decimal_class, i_new, 1, text); } } return p + 1; } else { return NULL; } } %%{ machine JSON_array; include JSON_common; write data; action parse_value { VALUE v = Qnil; char *np = JSON_parse_value(json, fpc, pe, &v, current_nesting); if (np == NULL) { fhold; fbreak; } else { if (NIL_P(json->array_class)) { rb_ary_push(*result, v); } else { rb_funcall(*result, i_leftshift, 1, v); } fexec np; } } action exit { fhold; fbreak; } next_element = value_separator ignore* begin_value >parse_value; main := begin_array ignore* ((begin_value >parse_value ignore*) (ignore* next_element ignore*)*)? end_array @exit; }%% static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) { int cs = EVIL; VALUE array_class = json->array_class; if (json->max_nesting && current_nesting > json->max_nesting) { rb_raise(eNestingError, "nesting of %d is too deep", current_nesting); } *result = NIL_P(array_class) ? rb_ary_new() : rb_class_new_instance(0, 0, array_class); %% write init; %% write exec; if(cs >= JSON_array_first_final) { return p + 1; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); return NULL; } } static VALUE json_string_unescape(VALUE result, char *string, char *stringEnd) { char *p = string, *pe = string, *unescape; int unescape_len; char buf[4]; while (pe < stringEnd) { if (*pe == '\\') { unescape = (char *) "?"; unescape_len = 1; if (pe > p) rb_str_buf_cat(result, p, pe - p); switch (*++pe) { case 'n': unescape = (char *) "\n"; break; case 'r': unescape = (char *) "\r"; break; case 't': unescape = (char *) "\t"; break; case '"': unescape = (char *) "\""; break; case '\\': unescape = (char *) "\\"; break; case 'b': unescape = (char *) "\b"; break; case 'f': unescape = (char *) "\f"; break; case 'u': if (pe > stringEnd - 4) { rb_enc_raise( EXC_ENCODING eParserError, "%u: incomplete unicode character escape sequence at '%s'", __LINE__, p ); } else { UTF32 ch = unescape_unicode((unsigned char *) ++pe); pe += 3; if (UNI_SUR_HIGH_START == (ch & 0xFC00)) { pe++; if (pe > stringEnd - 6) { rb_enc_raise( EXC_ENCODING eParserError, "%u: incomplete surrogate pair at '%s'", __LINE__, p ); } if (pe[0] == '\\' && pe[1] == 'u') { UTF32 sur = unescape_unicode((unsigned char *) pe + 2); ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF)); pe += 5; } else { unescape = (char *) "?"; break; } } unescape_len = convert_UTF32_to_UTF8(buf, ch); unescape = buf; } break; default: p = pe; continue; } rb_str_buf_cat(result, unescape, unescape_len); p = ++pe; } else { pe++; } } rb_str_buf_cat(result, p, pe - p); return result; } %%{ machine JSON_string; include JSON_common; write data; action parse_string { *result = json_string_unescape(*result, json->memo + 1, p); if (NIL_P(*result)) { fhold; fbreak; } else { FORCE_UTF8(*result); fexec p + 1; } } action exit { fhold; fbreak; } main := '"' ((^([\"\\] | 0..0x1f) | '\\'[\"\\/bfnrt] | '\\u'[0-9a-fA-F]{4} | '\\'^([\"\\/bfnrtu]|0..0x1f))* %parse_string) '"' @exit; }%% static int match_i(VALUE regexp, VALUE klass, VALUE memo) { if (regexp == Qundef) return ST_STOP; if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) && RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { rb_ary_push(memo, klass); return ST_STOP; } return ST_CONTINUE; } static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; VALUE match_string; *result = rb_str_buf_new(0); %% write init; json->memo = p; %% write exec; if (json->create_additions && RTEST(match_string = json->match_string)) { VALUE klass; VALUE memo = rb_ary_new2(2); rb_ary_push(memo, *result); rb_hash_foreach(match_string, match_i, memo); klass = rb_ary_entry(memo, 1); if (RTEST(klass)) { *result = rb_funcall(klass, i_json_create, 1, *result); } } if (json->symbolize_names && json->parsing_name) { *result = rb_str_intern(*result); } else if (RB_TYPE_P(*result, T_STRING)) { rb_str_resize(*result, RSTRING_LEN(*result)); } if (cs >= JSON_string_first_final) { return p + 1; } else { return NULL; } } /* * Document-class: JSON::Ext::Parser * * This is the JSON parser implemented as a C extension. It can be configured * to be used by setting * * JSON.parser = JSON::Ext::Parser * * with the method parser= in JSON. * */ static VALUE convert_encoding(VALUE source) { #ifdef HAVE_RUBY_ENCODING_H rb_encoding *enc = rb_enc_get(source); if (enc == rb_ascii8bit_encoding()) { if (OBJ_FROZEN(source)) { source = rb_str_dup(source); } FORCE_UTF8(source); } else { source = rb_str_conv_enc(source, rb_enc_get(source), rb_utf8_encoding()); } #endif return source; } /* * call-seq: new(source, opts => {}) * * Creates a new JSON::Ext::Parser instance for the string _source_. * * Creates a new JSON::Ext::Parser instance for the string _source_. * * It will be configured by the _opts_ hash. _opts_ can have the following * keys: * * _opts_ can have the following keys: * * *max_nesting*: The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, it * defaults to 100. * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in * defiance of RFC 4627 to be parsed by the Parser. This option defaults to * false. * * *symbolize_names*: If set to true, returns symbols for the names * (keys) in a JSON object. Otherwise strings are returned, which is * also the default. It's not possible to use this option in * conjunction with the *create_additions* option. * * *create_additions*: If set to false, the Parser doesn't create * additions even if a matching class and create_id was found. This option * defaults to false. * * *object_class*: Defaults to Hash * * *array_class*: Defaults to Array */ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) { VALUE source, opts; GET_PARSER_INIT; if (json->Vsource) { rb_raise(rb_eTypeError, "already initialized instance"); } #ifdef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH rb_scan_args(argc, argv, "1:", &source, &opts); #else rb_scan_args(argc, argv, "11", &source, &opts); #endif if (!NIL_P(opts)) { #ifndef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash"); if (NIL_P(opts)) { rb_raise(rb_eArgError, "opts needs to be like a hash"); } else { #endif VALUE tmp = ID2SYM(i_max_nesting); if (option_given_p(opts, tmp)) { VALUE max_nesting = rb_hash_aref(opts, tmp); if (RTEST(max_nesting)) { Check_Type(max_nesting, T_FIXNUM); json->max_nesting = FIX2INT(max_nesting); } else { json->max_nesting = 0; } } else { json->max_nesting = 100; } tmp = ID2SYM(i_allow_nan); if (option_given_p(opts, tmp)) { json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->allow_nan = 0; } tmp = ID2SYM(i_symbolize_names); if (option_given_p(opts, tmp)) { json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->symbolize_names = 0; } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { json->create_additions = RTEST(rb_hash_aref(opts, tmp)); } else { json->create_additions = 0; } if (json->symbolize_names && json->create_additions) { rb_raise(rb_eArgError, "options :symbolize_names and :create_additions cannot be " " used in conjunction"); } tmp = ID2SYM(i_create_id); if (option_given_p(opts, tmp)) { json->create_id = rb_hash_aref(opts, tmp); } else { json->create_id = rb_funcall(mJSON, i_create_id, 0); } tmp = ID2SYM(i_object_class); if (option_given_p(opts, tmp)) { json->object_class = rb_hash_aref(opts, tmp); } else { json->object_class = Qnil; } tmp = ID2SYM(i_array_class); if (option_given_p(opts, tmp)) { json->array_class = rb_hash_aref(opts, tmp); } else { json->array_class = Qnil; } tmp = ID2SYM(i_decimal_class); if (option_given_p(opts, tmp)) { json->decimal_class = rb_hash_aref(opts, tmp); } else { json->decimal_class = Qnil; } tmp = ID2SYM(i_match_string); if (option_given_p(opts, tmp)) { VALUE match_string = rb_hash_aref(opts, tmp); json->match_string = RTEST(match_string) ? match_string : Qnil; } else { json->match_string = Qnil; } #ifndef HAVE_RB_SCAN_ARGS_OPTIONAL_HASH } #endif } else { json->max_nesting = 100; json->allow_nan = 0; json->create_additions = 0; json->create_id = rb_funcall(mJSON, i_create_id, 0); json->object_class = Qnil; json->array_class = Qnil; json->decimal_class = Qnil; } source = convert_encoding(StringValue(source)); StringValue(source); json->len = RSTRING_LEN(source); json->source = RSTRING_PTR(source);; json->Vsource = source; return self; } %%{ machine JSON; write data; include JSON_common; action parse_value { char *np = JSON_parse_value(json, fpc, pe, &result, 0); if (np == NULL) { fhold; fbreak; } else fexec np; } main := ignore* ( begin_value >parse_value ) ignore*; }%% /* * call-seq: parse() * * Parses the current JSON text _source_ and returns the complete data * structure as a result. */ static VALUE cParser_parse(VALUE self) { char *p, *pe; int cs = EVIL; VALUE result = Qnil; GET_PARSER; %% write init; p = json->source; pe = p + json->len; %% write exec; if (cs >= JSON_first_final && p == pe) { return result; } else { rb_enc_raise(EXC_ENCODING eParserError, "%u: unexpected token at '%s'", __LINE__, p); return Qnil; } } static void JSON_mark(void *ptr) { JSON_Parser *json = ptr; rb_gc_mark_maybe(json->Vsource); rb_gc_mark_maybe(json->create_id); rb_gc_mark_maybe(json->object_class); rb_gc_mark_maybe(json->array_class); rb_gc_mark_maybe(json->decimal_class); rb_gc_mark_maybe(json->match_string); } static void JSON_free(void *ptr) { JSON_Parser *json = ptr; fbuffer_free(json->fbuffer); ruby_xfree(json); } static size_t JSON_memsize(const void *ptr) { const JSON_Parser *json = ptr; return sizeof(*json) + FBUFFER_CAPA(json->fbuffer); } #ifdef NEW_TYPEDDATA_WRAPPER static const rb_data_type_t JSON_Parser_type = { "JSON/Parser", {JSON_mark, JSON_free, JSON_memsize,}, #ifdef RUBY_TYPED_FREE_IMMEDIATELY 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, #endif }; #endif static VALUE cJSON_parser_s_allocate(VALUE klass) { JSON_Parser *json; VALUE obj = TypedData_Make_Struct(klass, JSON_Parser, &JSON_Parser_type, json); json->fbuffer = fbuffer_alloc(0); return obj; } /* * call-seq: source() * * Returns a copy of the current _source_ string, that was used to construct * this Parser. */ static VALUE cParser_source(VALUE self) { GET_PARSER; return rb_str_dup(json->Vsource); } void Init_parser(void) { #undef rb_intern rb_require("json/common"); mJSON = rb_define_module("JSON"); mExt = rb_define_module_under(mJSON, "Ext"); cParser = rb_define_class_under(mExt, "Parser", rb_cObject); eParserError = rb_path2class("JSON::ParserError"); eNestingError = rb_path2class("JSON::NestingError"); rb_gc_register_mark_object(eParserError); rb_gc_register_mark_object(eNestingError); rb_define_alloc_func(cParser, cJSON_parser_s_allocate); rb_define_method(cParser, "initialize", cParser_initialize, -1); rb_define_method(cParser, "parse", cParser_parse, 0); rb_define_method(cParser, "source", cParser_source, 0); CNaN = rb_const_get(mJSON, rb_intern("NaN")); rb_gc_register_mark_object(CNaN); CInfinity = rb_const_get(mJSON, rb_intern("Infinity")); rb_gc_register_mark_object(CInfinity); CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity")); rb_gc_register_mark_object(CMinusInfinity); i_json_creatable_p = rb_intern("json_creatable?"); i_json_create = rb_intern("json_create"); i_create_id = rb_intern("create_id"); i_create_additions = rb_intern("create_additions"); i_chr = rb_intern("chr"); i_max_nesting = rb_intern("max_nesting"); i_allow_nan = rb_intern("allow_nan"); i_symbolize_names = rb_intern("symbolize_names"); i_object_class = rb_intern("object_class"); i_array_class = rb_intern("array_class"); i_decimal_class = rb_intern("decimal_class"); i_match = rb_intern("match"); i_match_string = rb_intern("match_string"); i_key_p = rb_intern("key?"); i_deep_const_get = rb_intern("deep_const_get"); i_aset = rb_intern("[]="); i_aref = rb_intern("[]"); i_leftshift = rb_intern("<<"); i_new = rb_intern("new"); i_BigDecimal = rb_intern("BigDecimal"); } /* * Local variables: * mode: c * c-file-style: ruby * indent-tabs-mode: nil * End: */ PK!CV parser.hnu[#ifndef _PARSER_H_ #define _PARSER_H_ #include "ruby.h" #ifndef HAVE_RUBY_RE_H #include "re.h" #endif #ifdef HAVE_RUBY_ST_H #include "ruby/st.h" #else #include "st.h" #endif #define option_given_p(opts, key) RTEST(rb_funcall(opts, i_key_p, 1, key)) /* unicode */ typedef unsigned long UTF32; /* at least 32 bits */ typedef unsigned short UTF16; /* at least 16 bits */ typedef unsigned char UTF8; /* typically 8 bits */ #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD #define UNI_SUR_HIGH_START (UTF32)0xD800 #define UNI_SUR_HIGH_END (UTF32)0xDBFF #define UNI_SUR_LOW_START (UTF32)0xDC00 #define UNI_SUR_LOW_END (UTF32)0xDFFF typedef struct JSON_ParserStruct { VALUE Vsource; char *source; long len; char *memo; VALUE create_id; int max_nesting; int allow_nan; int parsing_name; int symbolize_names; VALUE object_class; VALUE array_class; VALUE decimal_class; int create_additions; VALUE match_string; FBuffer *fbuffer; } JSON_Parser; #define GET_PARSER \ GET_PARSER_INIT; \ if (!json->Vsource) rb_raise(rb_eTypeError, "uninitialized instance") #define GET_PARSER_INIT \ JSON_Parser *json; \ TypedData_Get_Struct(self, JSON_Parser, &JSON_Parser_type, json) #define MinusInfinity "-Infinity" #define EVIL 0x666 static UTF32 unescape_unicode(const unsigned char *p); static int convert_UTF32_to_UTF8(char *buf, UTF32 ch); static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting); static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting); static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result); static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result); static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting); static VALUE json_string_unescape(VALUE result, char *string, char *stringEnd); static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result); static VALUE convert_encoding(VALUE source); static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self); static VALUE cParser_parse(VALUE self); static void JSON_mark(void *json); static void JSON_free(void *json); static VALUE cJSON_parser_s_allocate(VALUE klass); static VALUE cParser_source(VALUE self); #ifndef ZALLOC #define ZALLOC(type) ((type *)ruby_zalloc(sizeof(type))) static inline void *ruby_zalloc(size_t n) { void *p = ruby_xmalloc(n); memset(p, 0, n); return p; } #endif #ifdef TypedData_Make_Struct static const rb_data_type_t JSON_Parser_type; #define NEW_TYPEDDATA_WRAPPER 1 #else #define TypedData_Make_Struct(klass, type, ignore, json) Data_Make_Struct(klass, type, NULL, JSON_free, json) #define TypedData_Get_Struct(self, JSON_Parser, ignore, json) Data_Get_Struct(self, JSON_Parser, json) #endif #endif PK!p88parser.onu[ELF>8@@<; /12.3-4H Hx=WHx0HH WHxHH WHxHH øf.HG`H@Hhff.@USHHHo`H}HtHHH[]ff.SHH?H{ H{8H{@H{HH{X[fAWIAVAUIATUSH1H8H$dH%(HD$(1H$IEL{I99A?"1KPunI}S4t C0@uHtH7IOHDH\$(dH3%(H8[]A\A]A^A_DLcXItIuIHH5LLLHAIUH5HHL$HT$HIEfHMMfL9AF<"t4<\uMfL9AFHDHH9uE1H|$HdH3<%(LmHX[]A\A]A^A_Ã0 wHCH9c H5HEAN,HLc IEHCH9s{niHCH9\{fRHCH9E{i;HCH9.{n$HCH9{i HCH9{tHCH9{yAV,kHLcIEHCH9{aHCH9{NAF,HLcIEmf.A@IV@ljD$AF(9H=H51HCH9E1{a HCH9{lHCH9{sHCH9{eIELcHCH9E1{rHCH9{uHCH9u{egIELcVAF(E`IV8HD$ A9B:H=DH51fLHHL/IHLDHCH9K*tR/t8 HH9ufHHXH9@<*t0@ tHD$(HD$HD$0HD$DD$HL$HLHD$(LkIH,I~@Ht$(I}#Ht$0HL$H5ID$HPH9t%H,t>o t YHHPH9uID X LLzL9JA/]HILDfD0 HL$EHLHD$(IHXI~8Ht$ HT$(I}4Ht$0HL$H5HT$8IGLxL9P,t5G t KLLxL9u< t <LIL9B< t~<" tHH9uHH9<*t*u H/6}V ,$IMf`LH)ID$HD$L.ID$IT$IF`ID$I~HHx{H=4IIFHHD$HD$H;"HL$0H5Ld$0IEIlLI^H9`IGH9tAW+t-uIGH9tAW0 HH9Ee.0 vfD u7HAH9#Q*tB/t8 @HH9uHH9*t/HH98*u@HL`L9@0< v@HLxL9@0@ wpE1HHHH(  c0  8  @  H  P  #X  S` wh  p  x p <&p### & $### &p># #*&p]# #]# D&p# ###]#i&p# ##C#&p# ####0&p###&p## # # &pG# ##G#M )&]w####Y&p####]#]# }$$<( +op<op2<< $+"8"8"8"8"8"8"8"8"8"8"8"8%8s  .: /5)ptr0 )len15 2535 h#    )len! | "  #  $ p( % p, & p0 ' p4 ( 8 ) @ * H + pP , X -#`:.R)*T  d` 5P*` *Z +Z*Z *Z$ *Z2 *[ *[ *[ *\ *^  *^ *^. *^; +_ *_ *_! *_. *`  *` *`* *`; *a  *a *a& *a6 *a> *b  *b *b ,<l,<m-<%-<;-<pQ-<qg-<}-<-<-<-<-<-<<-<= .K/YU0U /Yt0U /Y0T /Y0T /Y0U /Y0U 1Y1Y/Y)0T 2W0T 0Q 0R 20T 0Q 0R020T 0Q 0R0/Z0U 1Z1Y/Z 0U 1Z1Y/ZB0U 1Z1Y/Z{0U /Z0U /Z0U /Z0U /Z0U /Z0U /Z50U /ZT0U /Zs0U /Z0U /Z0U /Z0U /Z0U /Z0U /Z-0U /ZL0U /Zk0U /Z0U /Z0U 3Z0U 4E5E#6G)77Q<87,99:obj: ;9:3"Z0UU0Th0Q ?R@VN%=!W=W= W3/Z0U 7&)Aptr&((U9(5B 5?CptrC9!DvR")=R1;Z3;Z0UvE;Z0UUB >CptrC91GZ1GZ1GZ1GZ1GZFGZ7Vt"8":p :pe :cspG P9HIst1cJItr2~Ist6Kst0{HJJIst2JKst3Ist5JIst4JJJKst7Ist9JIst8JJL!!:np3>90Ts0Qv0Rw0X0/TZ "0UU0T 1aZ/nZK"0Ts0Q 0R 0Xv1zZ3Z0T M@.8%p82^8>G 99;"):tmp;#9wGP.9w3Z0Uv0Q10R|;#9/Z#0Uv0T~/Z#0U~0TE3Z0U~;Y$9wGP.9w3Z0Uv0Q10R|;$9wGP.9w3Z0Uv0Q10R|;!%9wGP.9w3Z0Uv0Q10R|;%9wGP.9w3Z0Uv0Q10R|L%9#w6#e.9#w3Z0Q00R0;N&9wGP.9w3Z0Uv0Q10R|;&9wGP.9w3Z0Uv0Q10R|;'9wGP.9w3Z0Uv0Q10R|;z'9wGP.9w3Z0Uv0Q10R|L'93Z0Uv0T~1Z1Z1Z1Z1Z/Z(0Uv0T~1Z1Z1Z1Z/Zp(0Uv0T~/Z(0Uv0T~/Z(0Uv0T~/Z(0Uv0T~/Z(0T /Z)0Uv0T~3Z0Uv0T~;{)9w6e.9w3Z0Q00R0NR,=LS=?SO2S=%S=S= S=R=R=R=R=R>?YS?dS?qS?~S?SPS?S?S?S?SPSQSRS+?SRT>+?T/Z+0U/Z"+0U 3Z0U /Z]+0U 1Z/Z+0U 3Z0U RTu,?TD5V! L,=TV=aV=GVS?nV3Z0U0T80Q11 [3Z0U 1[1&[/3[,0U/3[,0U3@[0U|0T10Q1Nq.-=.>?./M[3-0Uv1Y[1aZ/M[e-0Uv/f[-0Uv0Q~/r[-0Uv1aZ3[0Uv/TZ-0U}0T /[-0U|/[.0U|/Z2.0T 1zZ P. 5@. e.Tj.U.4T.5T%UencW.7' 38'-Cp'9Cpe'B8'M^:cs) p9* H|Kst2Kst0H}JtItr2!Ist36Kst8JuJvIst4?JwIst5LJxIst6YJyIst7fJzL~A19293L%098wG8P.98w3Z0Q10R/[00U2/[00U~/['10U|0T 0Q~3[0U~0T1N;4 R3=g4=Z4=M4>?t4?4?4?4P4R42?4V4:2?43"Q0UuNP(c2= Q=P>?Q/"Q{20Uu1aZ3nZ0Ts0Q 0R 0X/[20Uv0T/[20Uv/[30Uv0T0Qw1aZ3nZ0Ts0Q 0R 0X/[i30U01[1aZ1[1[1zZ4p;4555*W46 w6 e.6 wX6 w6 P.6 w445)575EUp Upe6&6 pUbuf XUchFXUsur'F465,Yp8YpeA5L^5XpUcs p6 JKst2Kst0JJuKtr2JTKtr4JKst3JvKst4Kst9*JwKst5 JxKst6Kst8#JyKst7JzJ{J|J3JCJ}J:J~JJJJJ]JmJJdJJXUvUnpX6w6P.6w4dS85d,Ypd8YpedA5dL^Ucsf pJqKst2Kst3Kst7ZKst0JrJgJhKst4Kst5:JiKst8"JjKtr90Kst9QJkJlKst6EJLJmJnJoXUlenm|X6tW'86ww6wP.6wwX6yw6yP.6yw4W ps8YobjW&45>955.Yp5:Ype5C55N^Ucs7 pJKst2Kst3Kst5Kst0JJJKtr4Kst42JJXUlen>|7,ZM8,Cp8CpeA8L^8Xp:cs pJaIst1JCItr2Ztr3Ist6HHItr7HHH-HIst0Jb[H[[[[JDKst2}JEKst3Kst5JFKst4JGJHJIKst7Ist9JJIst8JKJLJMHJNHJOHJPHJQHJRHJSJTHJUJVHJWH JXHJYJZHJ[H&J\J]H4J^H;J_L =\np3.0U~0Ts0Qv0R};E\np]6B=6=6=6=6>? 7^7Q!7Q*7^37Q<7^E7^N7^W7Q`7Qi7^r7Q{7^7^7^7^7^7Q7^7^7^7^7_7?7N]Rn ?=jRNQo @=Q=Q=Q`Qo =Q=Q=Q]Us f@=)V=V=V?8^8Q8Q8^8Q8^8^8^9^ 9^9^9^%9_.9?/9N]R? C=jRNQ@ D=Q=Q=Q`Qo =Q=Q=Q]Us qD=)V=V=V? 5?,5^95^B5^K5^T5^]5^f5Qo5Qx5^5^5^5Q5^5Q5^5^5Q5^5Q5^5^5^5^5Q6^6Q6^#6^,6^56^>6^G6^P6QY6^b6Qk6^t6^}6R6OGP6?6R6 G?6P6?63Z0Q10R/>9AG0U~0T0Qv0R0X~1[/ZwG0T 0Q~/U\G0U00T01aZ/nZG0Ts0Q 0R 0X1b\; M:np ?MPM?M^M^M^M^M^MQMQMQN^ N^NQN^%N^-N^6NQ>N^FNQON^WN^`N^iNQrNQzN^N^N^NQN^NQN^N^NQN^NQN^N^N^N^OQ O^OQO^$O^-O^6O^>O^GO^POQYO^bOQkO^tO^}O^O^OQO^OQO^O^ORPK?PV$PJ?)PP5P?AP3Z0Q10RVNPpK?OPR[PK?`PPlP?xP3Z0Q10R|RP]K?PaP?P3Z0Uv0Q00R0bP ?PPP?P3Z0Uv0Q10R|1ZROmLPO?ORO3L?OPO? P3Z0Q20R/>9_L0U~0Qv0R0X|1n\VO0L?O3.0U~0T0Qv0R~/ZL0T 0Q|/U\L0U00T01 [1zZ/nZ?M0Tv0Q 1aZ1aZcPd-ep9epeBdM^dYpfcs p+ + Jgst2gst0JJgtr2JKtr4bgst3Jgst4gst8Jgst5gst7Jgst6JJJhJlgst9JJJBJJ"JJ+J;JJ2JJJJKJ[JJRJJhJJJuJJJ|JJJJJJJJJWOfnpWPfvfnpX+w+P+wX+WNP+w+P.+wX+WP+w+P.+wWP+w+e.+wX+w+P.+w P 5Pc@ p"Qebuf@(ech@3FflenB pi-FVwQjp-4wQU\b/]k0 FQl Qefb*#d3lo Qefbo%#do5elenoK5m^ ]Rnfb^'#o^95k`5/{\IR0Ts0Q13\0T1lY vRefbY$#lS RefbS##cI#RdI-5ffbK#4 pCT5 p5 )5 p5 p5 p5 p5 p5 p5 CTYfmt 5 -pUi p6 p6 p6 pUvar ^6 6 6 w6 p6 p6 Jg WT6 X6 W$TUptr! ^X6A pX6F p^4 piTYfmt "4 pTYfmt $Uidx w4 pTYfmt !4 pTYfmt #Uidx w4 pUYfmt "Uidx w4 p=UYfmt $Uidx w4 p]UYfmt 4 p}UYfmt "4y pUYfmty 4r pUYfmtr 4k pUYfmtk !4d pUYfmtd !4C5V5CYsrc.(Yn:)4CV5&V52|5@)Ucnt )X6 )Umax)4pVYa)Yb')Ymax1)Yc>VUc2 V)p>C.Wd>Cd>pd>)pCdWdEd.d)q=4X=?DEX=Sa3Z0T /TZ&X0UU0T Fr[q3Y=3=3=3;X?3a3?43Z0U|0Q00R0D3:Y=3=3=3V4%Y?4P4@?,4/[cY0U}0T03Z0Uv0Qs0Rw3[0U}0T|1zZrrrrrrrrrrssrrrstrrer3rRrru1:ryr r-rr0rr! rrSsrsrsrVs@sPr.sTr$rr=r#vrrrrrssrs?r7ss% U: ; 9 I$ >  7I&I$ >  I I !I/  : ; 9  : ; 9 I8 : ; 9 <4: ; 9 I?<!&4: ;9 I?<5I: ; 9 I> I: ;9 ( (((   : ;9  : ;9 I8  : ;9  : ;9 I : ;9  : ;9 I8 : ;9 I! : ;9 I 8 "'#I$: ;9 I% : ;9 &'I'> I: ; 9 ( : ; 9 ) : ; 9 I8 *4: ; 9 I+4: ; 9 I,> I: ; 9 -> I: ;9 ..?: ;9 '@B/10B112314.: ;9 'I 5: ;9 I64: ;9 I7.: ;9 'I@B8: ;9 IB94: ;9 IB:4: ;9 IB; U<1RBUX YW =1B> U?41B@1RBX Y W A: ;9 IB.: ;9 '@BC: ;9 IBD1RBX YW EB1FB1G4: ;9 IH : ;9 I : ;9 J : ;9 K : ;9 L M.: ;9 'IU@BN1RBUX YW O1P41Q 1R 1US T!I/ U4: ;9 IV 1W X Y: ;9 IZ : ; 9 [ : ; 9 \4: ; 9 IB]1RBUX Y W ^ 1_ 1U`1RBUX Y W a41b 1c.: ; 9 'I d: ; 9 Ie: ; 9 If4: ; 9 Ig : ; 9 h : ; 9 i.: ; 9 'I@Bj: ; 9 Ik4: ; 9 IBl.: ; 9 ' m.: ; 9 '@Bn: ; 9 IBo: ; 9 IBp.?: ; 9 'I 4q.1@Br.?<n: ;9 s.?<n: ; 9 t.?<nu6v.?<n: ; UUVPSPPSP 0 Pv 0PUUSUUUSUUVUSUUSUUUSPSPSVSQSQSQSQSVVV f111 fPPPPpP U\U\U\U\U\U\U\\|UT_T__Q]P]P]]VVPSSPSSP^P^P^P^P^P^P^P^P^^^11111111PP^111111111111111111111111001111111111111111P00222222200000001111111\\1\\\U1\RR\\|\10 0 10 0 10 0 10!0101001000!012012001200100!      TR  R"00000QQQ00QQQQ#1111111P^P^^P^^^00PPP0P00UU p3$"8| $ &3$"8| $ &3$"8PVPVPVP\18U1PVVVP^^^USUSUSUSUT_r_^\_\_^_\_\R\_\R\R\_\QwQwQwQwQwQwQR]R] f1VV1V181V1V1V1\PP^11\\\\\\\\RT#T#T#T#T#T#RT#VVVVVVVVR_Y______R_RYRYrUrYrYYYw_RwrrrrrRYw#  T      11111111PZQZPZZZPZPZPZPZZZPZPTTTTT14111213UUU^U^U^U^U^U^U^U^U^TSPSS\|\SsPsPsPsPsPsPsPsSsPsPsSsPsPsPsPSsPsPsPSPSPSPSsPsPsPSQSSSSSSSSSSSsPsQVQVQVQVQVQVQVQVQVR]R]R]XXX~1X|XXX|XX~1|X~1X~1X|XX|XXX|XXXXX|XXXXXX~1X~1XXXXXXX f11M11P|]]]]]]]]]]VVVVVVVVVVS_P_PSP_P_S__P_^^^^^^^^^^ f1111111sP~ss~P~s\\PP~SS\\~S\~S||"U~S||"U~S||"U0\P\\\P~P~1111^^^^^^]]]]]]VQVVVQVS\SSP\S\\\ f1 f11111|s__VV|s__SSVV_SV_Svv"U_Svv"U_Svv"U0~qQPUQ~U~~U~~~]]]]]VVVVVSSQ_PQPQ_Q_QqQQ_S_S^^^^^ f f1A1 f11Q~QQP\\P\\\110_\\\X#\\\\]]]]]]]VVVQVVVVSSQ_Q___S_RPTP_P_Q_TpTtTtTtTP_PrPpPpPpP_U^U^^U^^^^ f f11K f11K1Q~8QQP11PV110011P_P_22PPQ ur"Q ur"Q0Pur"8$8&P(ur"8$8&4$ur"8$8&!P=ur"8$8&4$ur"8$8&!4$ur"8$8&!PU\UU\TVTTV|SUUPPUUVUVUT\T\TQ]Q]Q0000]\V11<(# ../fbuffer/usr/include/bits../../.././include/ruby/usr/lib/gcc/x86_64-redhat-linux/8/include/usr/include/bits/types/usr/includeparser.rlfbuffer.hparser.cstring_fortified.hruby.hstddef.htypes.hstruct_FILE.hFILE.hstdio.hsys_errlist.hunistd.hgetopt_core.hmath.hst.hintern.honigmo.hencoding.hparser.hdefines.h -KK J=JYK J=JYK J= u  XKJvK zJ JXYI= X rK ><YK~t2*J=X xJJxJ. w t fֺJJ#kf J  I =! t< fJ  JXw  wJ < =    J$(JJ$JZ$H=7XK0 J * zt/f!-!$DH4<0KDW4J$LJHQ<VJ;J$<!> |   ?"X|X$J u%t-< u=I%K$I%=,W uf  af  K  Z r9JtY F` x X uXn KY JX lX r.].X T< S JtJ | gX"XXtJ t   "XJX< JX< JX< JX<<xJ |=JW&K"J$|X&=$W K  <   <   <   < sJ>"X|X%J u=I&K%I&=-W u < vt <====wXt tX|"84XfXN?H<4fZ Y! WY.. vX] 1<V   lp    u K .. X X p#!f 2 Ky  < y  [q Kt1V   p&I$y'%z%#z&$o*(< @'u)L'z#F!t%'< [. X!>$XXp) mtX< X] 1 X< Z   X< n| <J~>&< } J.X uIK% { .JJJJJJJ {  t JJ { J   } J~J.J J .tJ J|<JJJ { J{<JJ {  } JJ I t<  }X = J .J.w<J yJ ~J|<JJ {  }f35J XRJZJ[  <}J 13J XJ5J <y x    ?XXXJX Ky<JX@  <J &y   x ~J   }   1XX gXX<XX J< ?J .JwJ v.JxXJjJ  ~J .J[ J[ J[tJ[ vfJ[   yt$x$X%~X zJ 2<tJ < y8{J <$GJ {X ~J j<  ~EtH BEB E(A0A8Ip 8A0A(B BBBF ,BDD V ABE =HX E 8 FBA A(I@R (A ABBE $\QEMD uDALpFLB E(G0A8Da 8A0A(B BBBH $zRx ,H,BBB B(A0A8D 8A0A(B BBBA (\VEHD0p AAA H   0 )  ?1Yt234V;VLYVuq`q5>1 P bj x  =H.E Z m 0u   Q ` p. p< @D hP `b xu  X P H ( `#`,, ; E J X d k 8|   #%#V%(  "#%')+12345607$).38= BG LQ@V\xbh'n{tDz^ 04@JZgs "(.4:@hFLR0Xdt,-./ (4?M^o}  ) 1 ; H X c y          ) < R ] j z        %    + 9 T i s  .annobin_parser.c.annobin_parser.c_end.annobin_parser.c.hot.annobin_parser.c_end.hot.annobin_parser.c.unlikely.annobin_parser.c_end.unlikely.annobin_parser.c.startup.annobin_parser.c_end.startup.annobin_parser.c.exit.annobin_parser.c_end.exit.annobin_unescape_unicode.start.annobin_unescape_unicode.endunescape_unicodedigit_values.annobin_JSON_memsize.start.annobin_JSON_memsize.endJSON_memsize.annobin_JSON_free.start.annobin_JSON_free.endJSON_free.annobin_JSON_mark.start.annobin_JSON_mark.endJSON_mark.annobin_JSON_parse_string.start.annobin_JSON_parse_string.endJSON_parse_stringmatch_ii_json_createeParserError.annobin_fbuffer_inc_capa.start.annobin_fbuffer_inc_capa.endfbuffer_inc_capa.annobin_cParser_source.start.annobin_cParser_source.endcParser_sourceJSON_Parser_type.annobin_match_i.start.annobin_match_i.endi_json_creatable_pi_match.annobin_cJSON_parser_s_allocate.start.annobin_cJSON_parser_s_allocate.endcJSON_parser_s_allocate.annobin_cParser_initialize.start.annobin_cParser_initialize.endcParser_initializei_max_nestingi_key_pi_allow_nani_symbolize_namesi_create_additionsi_create_idi_object_classi_array_classi_decimal_classi_match_stringmJSONcParser_initialize.cold.8.annobin_JSON_parse_value.start.annobin_JSON_parse_value.endJSON_parse_valueCMinusInfinityCInfinityCNaNeNestingErrori_leftshifti_arefi_deep_const_geti_asetcBigDecimali_BigDecimali_new.annobin_cParser_parse.start.annobin_cParser_parse.endcParser_parse.annobin_Init_parser.start.annobin_Init_parser.endcParser.LC7.LC0.LC1.LC2.LC4.LC5.LC3.LC6.LC8.LC9.LC10.LC16.LC17.LC11.LC21.LC19.LC20.LC22.LC23.LC24.LC25.LC26.LC27.LC28.LC29.LC30.LC31.LC32.LC33.LC34.LC35.LC36.LC37.LC38.LC39.LC40.LC41.LC42.LC43.LC44.LC45.LC46.LC47.LC48.LC49.LC50.LC51.LC52.LC53.LC12.LC13.LC14.LC15.text.group.text.hot.group.text.unlikely.group.text.startup.group.text.exit.group_GLOBAL_OFFSET_TABLE_ruby_xfreerb_gc_mark_mayberb_str_buf_newrb_ary_new_caparb_ary_pushrb_hash_foreachrb_ary_entryrb_funcallvrb_str_catrb_str_internrb_utf8_encodingrb_enc_associaterb_str_resize__stack_chk_failrb_enc_raiseruby_xrealloc2ruby_xmalloc2rb_check_typeddatarb_str_duprb_eTypeErrorrb_raiserb_data_typed_object_zallocruby_xmallocrb_keyword_given_prb_warnrb_id2symrb_hash_arefrb_string_valuerb_enc_getrb_ascii8bit_encodingrb_str_conv_encrb_empty_keyword_given_prb_free_tmp_bufferrb_error_arityrb_alloc_tmp_buffer_with_countrb_hash_newrb_eArgErrorrb_extract_keywordsrb_check_typerb_fix2intrb_check_hash_typerb_class_new_instancerb_ary_newrb_cstr2inumrb_str_new_cstrmemcpyrb_hash_asetrb_cstr_to_dblrb_float_newrb_cObjectrb_const_definedrb_const_get_atInit_parserrb_requirerb_define_modulerb_define_module_underrb_define_class_underrb_path2classrb_gc_register_mark_objectrb_define_alloc_funcrb_internrb_const_getrb_define_methodM.  7UpHqXxrstuvw!5qxya_*z ! V h ,q    _   _& A o {v  l  <  d  < & \+ = <J d ti { <  |  <   T  < ' L, > <K f Dk } <  $  <      $ 4     .|@E|{7_w*|7*}OM~BU_M' ,Mt!&B4IQXg905DQYiYM0 0LV!!6"X"m"w"""""*""#* ###-#2#D#N#U#Z#k#p##_##M~$$$$%%*%z%%&& &&&"&)&*0&;&B&I&N&U&\&a&h&o&t&{&&&&&&&&&&&&&&&&'''' '''/'9'>'E'J'Q'Y'c'h'o't'{''''''''|'''t'''''l'''d''(\( ((T((&(L+(2(9(D>(E(L(,Q(X(_($d(k(r(<w(~((4((((((((( (((((dmqmmmUm0Jm nV (VLVTqVqqq @Hlt    ,4`h     ( L T` ``#`# #%@#H%l%t(%( g h i j &7G^l0U  $(,048<@DHL PTX\` d$h(l,p0t4x8|<DhLPTX\`dhlptx|@  $(,048<@DHL PTX\` dh(l,p0t4x8|<@HLPTX`dhlpt8x!%)-159=AEIMQUY]a iquy }$(,048<@@DHLPTX\`dhlptx| !%)-159=AEIMU]ae imquy $$(,048<@DDHLPTX\\`dhlptxx|tx|(TA   $( ,$0(4,80<4@8D<H@LDPHTLXP\T`Xd\hldphtlxp|x| 8X p $( ,$048<@DH LPTX\`"d&h*l.p2t6x:|>TFNRVZ^bfjnr@z  $(,048<@DH LPTX\`"d&h*lp2t6x:|>BFJNRV$^bfjnrvD~\x %%%%%%% %%%%% %$%(%,%0%4%8%<l%@&Dd$H&L &P&T&X&\&` &d$&h(&l,&p$t4&x%|<&@&D&H&L&P&T&X&\&`&d&h&l&p&t&x&|&&&&&&&&&%&&&&%&&&&& &&&&& &$&(H%,&0&4&8&<&@'D'H'L 'P'Tt%X'\'` 'd$'h('l,'p0't%x8'|<'@'D'H'%P'T'X'\'`'d'%'` a f  f f_ d% e* fD8 f ? fM faY f` fl f f f f f f f ft f f. f  f f&& fT3 f @ fM f5Z fYg f* t ff fi f  fh fw f fE fY f  f! f f} fA f(* f7 f E fQ fY fI f f f  f' f f f  f1 f fu$ f7 f< fO fY\ fh f t f fI f f f f  fC f` f f$  f; f  f  f` fl  f f  fI+ f1 f7 f= f C fI fO fU f@ [ fJa fg f6m fs fy f  f fd  f f  f f f f fV  f f  f-  f f4  fj f f f  f f f;  f f fq! f( f). f5 f< fC fJ fS f\ f e fn f'w f5 f f f f f f f f f f  f f f; f_ fC  f f0 fI7 f J f}P fW f0e fx fd fI f f fR f  fR  f\  f6 f E fb f u f3 { f f f  f f f f f f_ f f fZ$ f2 fM @ fN f\ fC u f f_ f  f f f f f{ f f( f5 fB fO f\ fRi f0v f0 f f f f f f@ f f f f f fO fy, f 9 faF fS f` fm fu z fc f$ fq f f f f f f f f f f # fg0 fz = fJ f-W f,d fq f#~ fh f@ fr f f  f  f! f f ff f f f' f#4 fIA f N f[ fGh f u f_ fz f+ fr f f. f f^ f f f fV f fv+ f= fC f I f O fU f\ fi fav f f& fd f f f) f f f f f  f1 f S f` fm ffz f f fm f0 fN f| fq f  f f% f2 f ? fL fY f f fs f f@ f f f f f0 fC  f* fP f f f f, f> f< K fX f e fr f fF f fX f5 f  f f fn f f6 f7- fR ; f; G fS f` fI m f&  fa fm f# f)  fK  f f$ f f f  f f@* f; f/H_f f sM| f  f f~ f f f f|  f" fY/8 fEN f[d fqz fx f fp fh f` fX fP  fYH  f-@6 fC8L f.Y0b fo(x f  f fT f f f  f2 f 5 fK fa f w f f f$ f# f` f f" f f %7&L'V&k'u&&'?&'R&'e&'x&& &  *&;'H X&i'v#&'&' ''$'' 3'B'$N'9'C]'Pl']x'r'|'' ''''''0' '4','@6'K'JU (j'Zt('g/('sB('U('h('{($'.(C'M(b'l('('(' f f  f fD  f( b, b1 f= b?A b=R bhV bb[ dd f p bt by _  d b b d bB b>   b| bz b b b  b& = fGI`o f{ b b f b b f b bx b b*@ f6Hk b o bt f b\ bV fM# f  b b b b. b62 b0B bF bK f[ fg bk bp fx x$  % f $ 8% f $ f f c$ f!$! f.!$7! f@! fI! fc!e%l! f }!v%! f! f3 !$! bV! bP!$$!#"_ "$"$5"'^L"%Y"%j"'u" f" d@" fM" b" b" f" b7" b-" f " b" b" f& " f" bT " bL " f" b # b # d# bj # bB $# d0-# ft9# b =# b B# f S# f_# bf c# b^ h# # d# f## b # b #;### dp# ft $ b $ b $ f %$ f1$ bm 5$ bg :$ Z$ dc$ fto$ b s$ b x$ f $ f$ b$ b $N $ d$ ft$ bc$ b]$ f $ f$ b$ b% "% d+% ft7% b;% b@% f Q% f]% bYa% bSf% %% ft% b% b% f % f% b% b%% d0% ft& b& b & f & f&& b1*& b-/& O& d`X& ftd& bmh& bim& f ~& f& b& b&O & d& ft& b& b& f & f& b!& b& ' d ' ft,' b]0' bY5' f F' fR' bV' b[' {'0' f' b' b';' ' '/ 'm ' ( ( ,(0 9(o F( S(cq({(((((()#) dp,) ft8) b<) bA) f N) fZ) b^) bc)I)@ ) dp) bP) bB) b) b) b) b) bj) b\) b) b) b) b) b) b) bj) b\* b * b* bG* b+* dp'* b+* bh4* b8* bA* bE* bN* bRR* b:[* bf_* bXq* b8u* b~* b* b* bT* bB* b!* b*w* d* b* b* dp* b * b~ *+mK+(#+mp4+(0?+z T+(x^+Sk+m+(h+m*+(+ d+ b!+ b !+++ bH!+ bF!+ bn!+ bl!, b! , b!,#, b!', b!,,M,Z,k,(xv,E , ,,,, , d - b"- b " - d - by"- bq"- 4- A- N- f-( - - - -* -_- -8 .).''3.m. fr. f. f& . f.. f. b". b". b#. b#. b%. b%/ f/ b&/ b&$/ bv'(/ bX'-/ f9/ b(=/ b(B/ fJ/Se/ f m/Sv/ f//p/ f/ f// f// f/0 f0?0 f $090 fE0 b(I0 b(N0 faZ0 b(^0 b(c0x0 ft0 b4)0 b2)0 f 0 f0 bZ)0 bX)0;0001(1 F1P1 da1 b)e1 b~)n1 bH*r1 b6*{1 b6+1 b&+1 d1 b+1 b+1 b-1 b,1 b/1 b.1 b01 b 01 d1 b01 b01i1 b12 b12v2)2 d:2 b 2>2 b1G2 b2K2 b2P2 dY2 b3]2 b3d2 |22 2(@2!2\23#3:3(S32j3w3%393u33 f3 f3 f3 fa3 ft3 f 4 f4 ft 4 f -4 f<4 fN4 f[4 fh4 f4 f4 f4 f 4 f5 f5 f-5 f:5 fU5 f ^5 fp5 f5 f5 f5 f5 f5 f 5 f5 f3 6 f 6 f 6 f6 f$6 f-6 f66 f&?6 fH6 f$Q6 fZ6 f!c6 fl6 fu6 f~6 f6 ft6 f 6 f6 f6 f7 f7 fF7 f O7 fX7 fs7 f7 f7 f3 7 f7 f7 f7 f7 f 7 f 8 ft 8 f 8 f)8 ft68 f C8 fT8 ft8 f 8 f8 f8 f8 f 8 f9 f9 f&9 f?9 fGK9`b9 fn9 b4r9 bc49 b69 b69 b<9 b<9 f9 b2>9 b&>9 f9 b?9 b>9 bCC9 b7C9 f99 f::(:1: f9:PB: f&J:[:pd: f;l:u: f}:`: f: : fH::^: f : f :: f:: f:F: f:; f ;; f;l#; f=5; fP; fb; fk; ft; f; ; f ;#; f; f3 ; f; f;g; f; f ;~; f< f<< f$< f"<+< f4< f<<E< fN< f!V<_< fh< fq< fwy</< f < f< f << f< fM<< f< f<< f< f< f<z< f== f == f= f(= f 0=:9= f%B= fJ=QS= f1\={= bC= bC== d= bC= bC=x= d`= bDD= b0D= b&E= bE= bF> bE > bSG> b?G> d`> b1H"> b!H0>i!=> O>^k> x>> >> d > bH> bH>> dp? bI? bI?? d+? b J/? bJ8? bYJ Q feQ fQ fQ f`QQ buhR bkhR fR bhR bhR f$R bgi(R bci-ReJR^R fiwR flR f\R f7R fR fMR fR fR f R f S fS f|&S f?3S f% MS fW eS fcrS fp S fWS fyS f S fS fS fKS fqS f8S f T f&T f4T fJT f:jT fT flT fT fKU f >U f^U fc ~U fnU fU fU fU fV f6V fDHV fUV fbV f3}V fbV fOV f[V f W fW fi"W f~/W fe@W fLW fvXW f~iWW biW biW biW biWWW bjW bjWWW'XX_'X9XTX bGjXX b=jaX bjeX bjnX bEkrX b;kwX dX bkX bkX bkX bkX% XP XP X b4lX b2lX bYlX bWlY b~lY b|lYP )Y bl-Y bl>Y blBY blGYZ dYu Y Y Y ftY ftY fY fY frY frY fY fY fY fY fGY fGY fe Z fe Z f Z fZ fX Z fX #Z f'Z f0Z f4Z f{$I$Q%\%d%###x$$$%%%%##$%"%-%5%V$$^1$i1$q3$~$$         j(j0w;wCQYdlVzV22`mm%m%m4m4m7 ? J R ] e s {`mm ) ) r r       `) m1 mT  \  g  o  |   % * * D D w      ` m' mj  r  }      D D M M             (  0  ;  C E N E V N a N i  t  |          * >      & *. V: B f  n  z   * V   > V      #  / 7 C *K /m  u    * / D    * I  %-9*Ac k w*  *  #/*7Y a mu*  *1 9 E*MmE u *J  *  *l! ) 5*=l] e q*yS  *S;J=j=&jP@ X d lxj*V`mm@   `Z % `Z*j2= `ZBJ*U `ZZVbm `Zrz` `Zmm `Z@   j*V$,`8m@mj@ r ~ j*V`mm *G@ Oz Zz bz n vjw  *V   `m%m%0m%8m*Em*Mmz z z            -5AIV^jr%~%%%%%*%%%2@   $ 0 8DLXj`wlt%%*V`mm@ z z  & . : B N Vbjvj~w%%*V%%22 `mmR@ Z g o |jw*V  22` mmp mp(m5m=mfz n |   `"  `"%%* `"%28@ @ L T `jhwt|*V22`mm4m4mJmJmY%mY-mo8mo@mMmUm`mhmum}m@   j*V `m&mTQ \u gu o z   *VYY`mm!Q )z 5 = I Q \jdwowV25``mm:Rm m m m!, m!4 m%M m%U m)     m4 m= m= mY mY m[ m[ m!!#!+!*H!P!n!v!!!!!!!!!" " (" 0"+ ;" C" N"V"y" " " " " " """""# ####+#6#>#L#T#_#g#u#}### ##1#1#$ $$!$V,$V4$Z?$ZG$ R$ Z$e$m$9x$9$9$9$9$9$>$>$^$^$$$$$C$C%n%n%|#%|+%6%>%I%Q%\%d%o%w%%%%% %%1%1%S%S&G&G&#&+&z9&zA&M&U&c&k&nw&n&&&&&[&[&&& &'1 '1''''5'=' v'-~':':'S'S'''G'G' ' '''9(9 (>(>(^)(^1(z<(zD(nP(nX(|c(|k(w(((( ((G( (:()))G4)<)GZ)1b)G))!)<) )>)^)))|)))[*m*t** H*P*[*c*!s*<{* *>*^***|***[*m*t*+ 6+>+!I+<Q+ \+>d+^o+w++|+++[+m+t++ ++,,,",-,5,!@,<H, S,>[,^f,n,y,|,,,[,m,t,, - -- -+-3->-F-Q-Y-f-n-'y-'-<-<--------W-[-[-d.d .l..).1.=.E.R.>Z.^g.o.|.|...[.m.t.... ///'"/<*/W5/'>/WF/[Q/lY/ d/'m/>u/^/'///'/|//'//[/'// /'0!0-0<50WA0lI0 U0>]0^i0q0}0|000[00 0'0,0,0111"1-151@1>H1^S1[1f1|n1y1111?1?1C111v1 222%202D82^C2K2V2|^2i2q2|22?2?2C22222D2Y3Y 3^3 3.363A3|I3W3_3j3r3-3-3[33333D3^334| 444+434V?4VG4[4`44444$4$4P4P4z4z45555&5.595A5L5T5_5g5br5bz5l5l5W"5W"5\"5\"5R#5R#5]#5]#5h#5h#5s#5s#6#6`666666667&7N7N%7N-7N:7NB7PM7PU7P`7Ph7Tu7T}7g7g7k7k7~7~77777778888(808=8E8P8X8e8m8x88888888/8/83838J8J8f9f 99 9-959@9H9U9]9h9p9}999999 9 9 9 9$9$9:9:9>:>:Q:Q#:U0:U8:}C:}K:V:^:i:q:#|:#:':':C:C:\:\:\:\:d:d:z:z:~;~ ;; ;-;5;@;H;S;[; f; n;7y;H;4;U;r;;;o;;; ;;!;i!;!;?";G"<O"<"<"$<"#,<*#7<R#?<h#L<h#T<s#_<s#g<#<`<<<<<<<f<f< == =(=n6=n>=!I=!Q=!_=!g=W"r=W"z=\"=\"=1#=1#=R#=R#=Y#=Y#=h#=h#=o#=o#=#># >#2>`:>E>M>X>`>n>v>W">W">\">\">#?`$?/?7?E?M?X?`?r?z???????????7?7?H@H @b@b@.@6@5C@5K@Y@a@Us@U{@\@\@b@b@l@l@r@r@@@n@n@AAA!A/A7ABAJA| WA| _A mA uA;!A;!Ai!Ai!A!A!A!A!A?"A?"AG"AG"AO"AO"BW"BW"B"&B".B"9B"AB"OB"WB"iB"qB"#B"#B*#B*#B5#B5#BR#BR#B]#B]#Bh#Bh#Bs#Bs#C#C#C#CC~KC~YC~aCmCuCNCNCPCPCW"C\"C#CCCeCi DbDoDDrLDWD_DbjDrD}D D;!Di!D!D?"DG"DO"DW"D\"D"D"D"#D#D#&Er.E9EAEbLETE_E gE;!rEi!zE!E?"EG"EO"EW"E\"E"E"E"#E#E#FrF%F-F8F@FKFSF^FfFqF yF F F F F F F F F;!Fi!Fm!Fm!F!F?"FG" GO"GW"G#$G#SGr[GfGnGbyGGG G;!Gi!G!G?"GG"GO"GW"G\"G"G"G"#G#H#1Hr9HrGHrOHncHoH| wH!H?"HW"H\"H"H"H"#H#H#HH II I(I"5I"=IbMI\"UI"eI"mI"zI"I"I"I"I"I"#III"I" JJJ"%J"0J"8J"YJaJlJ"tJ"JJJ"J"J"J"J"J"K"K"U!FU!cU-kUTU-U9UUUUV~VVV&V.V9VAVNVHVV^aV^iVvV~VUVUV\V\VbV"V"V"#V*#V~V WHWW%Wb0W"8W"CW"#KW*#pW~xWWHWWWbW"W"W"#W*#X~ XXH X+X3X>XFX5QX5YX9dX9lXZwXZX^X^XkXkXXXXXXXXXXYYY4$Y4,Y-7YU?YbJY"RY"]Y"#eY*#Y~YYHYYYbY"Y"Y"#Y*# Z~ZZH'Zg5Zg=ZIZQZ]ZeZUqZUyZbZ"Z"Z"#Z*#Z~ZZZZHZb[U [\3[;[/F[/N[Y[a[4l[4t[<[<[B[[["["[[0[[0\7"\H.\6\C\K\v\~\\\7\\5\5\\b\r\n\\\| ]!]?"1]9]D]L]7W]_]j]br]r}]n]]]| ]!]?"]]]]7]^f^f^%^b-^r8^n@^K^S^| ^^!f^?"^^^^^_ __._.'_22_2:_7E_M_X_`_m_u__b_r_n_N_N_R_R____%_)_)_R`R `V`V `s+`s3`w>`wF`Q`Y`d`l`y``3`3`7`7`A`A`E`E`Y`Y`]`]`mam aaa*a2a =a Ea Ra Za9 ea9 ma= za= aG aG aK aK ah ah al al a| a!a5"bb'b/b:bBbMbUb7`bhbsbb{blblbrbnbbb| b!b?"bb cc"c*c76c>cJcRc^cbfcrtcn|ccc| c!c!c!c?"ccccd ddbdl;d*CdT`dhd*dd*dfdjdjdd0d e?e2ef:eXef`e~eseeeee e e2e5"e9" f9"f?".f6f!Tf\f!zf9fNffff+f+f0f0f?f?gDgD"gPMgUgagig$tg$|g'g'g8g8g;g;gLgLhOHhOPhPuh}h%h%hshshthth{h{hhh%i% iqiqit-it5i@iHigi%oi8{i8ioiiiiiiiijjGjOj Zj bj/ mj/ ujL jL j j j jj j j/ j/ jL kL k k k EkMk Xk `k/ kk/ skL kL k k k k k/ kL k k l/ lL l 4lL #@#H#`xhpxb @!i!!?"G"O"W"\""""""### (b0\"8"@"H"P"X"#px"""""" " " "( "0 "8 "@ "H "P "X "p x      b \" " " "#   " ( " "# ( ]0 a8 e@ oH P  X  ` @!h i!p !x ! ! ! G" O"  ] a e ! ! ! !    $0 8 @ $H -P !X !` !h ! ! ! ! ! ! ! ! ! ! ! ! ! - B G K O( T@ pH P +X .` Ph p x x  h " " "# *# t x ~  + . P   x  h "( "0 "#8 *#P X 5` 8h H    0    + . P   h xp !(?"@HPX`h+p.x@@Phxp !?"!! 0(U0c8fPUXc`fhk %5"?"!(mmu`,#m 4`Hp$`  m``#%.symtab.strtab.shstrtab.rela.text.data.bss.rela.gnu.build.attributes.text.hot.rela.gnu.build.attributes.hot.rela.gnu.build.attributes.unlikely.text.startup.rela.gnu.build.attributes.startup.text.exit.rela.gnu.build.attributes.exit.rodata.str1.1.rodata.str1.8.rela.text.unlikely.rela.rodata.rela.data.rel.ro.local.rela.debug_info.debug_abbrev.rela.debug_loc.rela.debug_aranges.rela.debug_ranges.rela.debug_line.debug_str.comment.text.hot.zzz.text.unlikely.zzz.text.startup.zzz.text.exit.zzz.note.GNU-stack.note.gnu.property.rela.eh_frame.group;@9;T9;l9;9;9 (@ 9&),)6)1@9 L`0[`0V@09 02z02u@0944@0955@@09272p9~;@p89,@< '@499FH 4@`9QHF\L@9 ]ܢplk@(`9#@{@hH9% @hp&9''#@@`9)0JH 0j.jjLj jjjjj jk 1 k,@P97m:  BPK!/mkmf.lognu[have_func: checking for rb_enc_raise() in ruby.h... -------------------- yes "gcc -o conftest -I../../../.ext/include/x86_64-linux -I../../.././include -I../../.././ext/json/parser -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC conftest.c -L. -L../../.. -L. -Wl,-rpath=/opt/cpanel/ea-ruby27/root/usr/lib64 -fstack-protector-strong -rdynamic -Wl,-export-dynamic -m64 -lruby-static -lpthread -lrt -lrt -ldl -lcrypt -lm -lm -lc" checked program was: /* begin */ 1: #include "ruby.h" 2: 3: int main(int argc, char **argv) 4: { 5: return !!argv[argc]; 6: } /* end */ "gcc -o conftest -I../../../.ext/include/x86_64-linux -I../../.././include -I../../.././ext/json/parser -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC conftest.c -L. -L../../.. -L. -Wl,-rpath=/opt/cpanel/ea-ruby27/root/usr/lib64 -fstack-protector-strong -rdynamic -Wl,-export-dynamic -m64 -lruby-static -lpthread -lrt -lrt -ldl -lcrypt -lm -lm -lc" conftest.c: In function 't': conftest.c:16:57: error: 'rb_enc_raise' undeclared (first use in this function); did you mean 'rb_exc_raise'? int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_enc_raise; return !p; } ^~~~~~~~~~~~ rb_exc_raise conftest.c:16:57: note: each undeclared identifier is reported only once for each function it appears in checked program was: /* begin */ 1: #include "ruby.h" 2: 3: #include 4: 5: /*top*/ 6: extern int t(void); 7: int main(int argc, char **argv) 8: { 9: if (argc > 1000000) { 10: int (* volatile tp)(void)=(int (*)(void))&t; 11: printf("%d", (*tp)()); 12: } 13: 14: return !!argv[argc]; 15: } 16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_enc_raise; return !p; } /* end */ "gcc -o conftest -I../../../.ext/include/x86_64-linux -I../../.././include -I../../.././ext/json/parser -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC conftest.c -L. -L../../.. -L. -Wl,-rpath=/opt/cpanel/ea-ruby27/root/usr/lib64 -fstack-protector-strong -rdynamic -Wl,-export-dynamic -m64 -lruby-static -lpthread -lrt -lrt -ldl -lcrypt -lm -lm -lc" checked program was: /* begin */ 1: #include "ruby.h" 2: 3: #include 4: 5: /*top*/ 6: extern int t(void); 7: int main(int argc, char **argv) 8: { 9: if (argc > 1000000) { 10: int (* volatile tp)(void)=(int (*)(void))&t; 11: printf("%d", (*tp)()); 12: } 13: 14: return !!argv[argc]; 15: } 16: extern void rb_enc_raise(); 17: int t(void) { rb_enc_raise(); return 0; } /* end */ -------------------- extconf.h is: /* begin */ 1: #ifndef EXTCONF_H 2: #define EXTCONF_H 3: #define HAVE_RB_ENC_RAISE 1 4: #endif /* end */ PK!:ܶrd.rbnu[PK!T͋͋c.rbnu[PK!733text.rbnu[PK! />>Vripper_state_lex.rbnu[PK!U jsimple.rbnu[PK!4QՆ Nruby_tools.rbnu[PK!_A markdown.rbnu[PK!5nn&ruby.rbnu[PK!h\;!;! changelog.rbnu[PK!hBlexer.rbnu[PK!׫z tbuilder.rbnu[PK!R&(&( }compiler.rbnu[PK!Sʘʘ prism_ruby.rbnu[PK!jCC% ?__pycache__/isoparser.cpython-311.pycnu[PK!v:gg g $T__pycache__/__init__.cpython-311.pycnu[PK!<#__pycache__/_parser.cpython-311.pycnu[PK!`R۬ $_parser.pynu[PK!oÿ33  isoparser.pynu[PK!o __init__.pynu[PK! !s(s(&Makefilenu[PK!(_vv extconf.rbnu[PK!(dependnu[PK!@GG extconf.hnu[PK!./%%  prereq.mknu[PK!gparser.cnu[PK! hh 6parser.rlnu[PK!CV ) parser.hnu[PK!p88parser.onu[PK!/ mkmf.lognu[PK