mirror of
https://github.com/ruby/ruby.git
synced 2025-09-18 01:54:00 +02:00

* [ruby/irb] Stub a screen size for tests6663057083
* [ruby/irb] Support GitHub Actions8e9e6c4037
* [ruby/irb] Stub a screen size for test_context http://ci.rvm.jp/logfiles/brlog.trunk-random1.20210119-074232ea87592d4a
* [ruby/irb] Use a real screen size for pp by default9b9300dec2
* [ruby/irb] Rescue Errno::EINVAL on IRB pp20210119
T070008Z.log.html.gz is caused by: /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `winsize': Invalid argument - <STDIN> (Errno::EINVAL) from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline.rb:168:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/forwardable.rb:238:in `get_screen_size' from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/irb/color_printer.rb:7:in `pp' from -e:1:in `<main>'1719514598
* [ruby/irb] Split test files for IRB::Color and IRB::ColorPrinterd95e8daab3
* [ruby/irb] Undefine unused constantseea9c16804
* [ruby/irb] Remove pp-specific stub from TestColor because it was for TestColorPrinter7569206fd4
* [ruby/irb] Delete a doodle-level memo comment...fc3e1d9e0c
* [ruby/irb] Indent correctly with keyword "for" and "in"47c83ea724
* [ruby/irb] Indent correctly with method calling with receivere7c68e74a0
* [ruby/irb] add `IRB::FileInputMethod.open` to ensure closing associated File * tweak some methods not to raise exception after `#close` * use it in `IRB::IrbLoader#{source_file,load_file}ec2947acbd
* [ruby/irb] use `RubyLex::TerminateLineInput` appropriately [Bug #17564] * using the appropriciate exception instead of `break` so that the session can be continue after the `irb_source` and `irb_load` commands * suppress extra new line due to one more `#prompt` callbdefaa7cfd
* [ruby/irb] specify the `VERBOSE` to `false` and fix tests to fit502c590925
* In test, need to pass a context to IRB::WorkSpace.new explicitly * Fix absolute path predicate on Windows A path starts with '/' is not an absolute path on Windows, because of drive letter or UNC. * [ruby/irb] follow up the actual line number7aed8fe3b1
* [ruby/irb] Add info.rb to gemspecadbba19adf
* [ruby/irb] Allow "measure" command to take block20f1ca23e9
* [ruby/irb] Enable to reassign a new block with "measure" commandb444573aa2
* [ruby/reline] Cache pasting state in processing a key Because it's too slow. The rendering time in IRB has been reduced as follows: start = Time.now def each_top_level_statement initialize_input catch(:TERM_INPUT) do loop do begin prompt unless l = lex throw :TERM_INPUT if @line == '' else @line_no += l.count("\n") next if l == "\n" @line.concat l if @code_block_open or @ltype or @continue or @indent > 0 next end end if @line != "\n" @line.force_encoding(@io.encoding) yield @line, @exp_line_no end break if @io.eof? @line = '' @exp_line_no = @line_no @indent = 0 rescue TerminateLineInput initialize_input prompt end end end end puts "Duration: #{Time.now - start} seconds" 0.22sec -> 0.14secb8b3dd52c0
* [ruby/reline] Initialize uninitialized variables in tests25af4bb64b
* [ruby/reline] Remove an unused variable123ea51166
* [ruby/reline] Scroll down when ^C is pressed6877a7e3f5
* [ruby/reline] Show all lines higher than the screen when finished On Unix-like OSes, logs prior to the screen are not editable. When the code is higher than the screen, the code is only shown on the screen until input is finished, but when it is finished, all lines are outputted.8cd9132a39
* [ruby/reline] Handle past logs correctly when the code is higher than the screenf197139b4a
* [ruby/reline] Update cursor info by inserting newline even if not in pasting92d314f514
* [ruby/reline] Move cursor just after the last line when finishedba06e4c480
* [ruby/reline] The vi_histedit supports multiline This closes ruby/reline#253.f131f86d71
* [ruby/reline] Autowrap correctly when inserting chars in the middle of a lineebaf37255f
* [ruby/reline] Terminate correctly in the middle of lines higher than the screene1d9240ada
* [ruby/irb] Version 1.3.34c87035b7c
* [ruby/reline] Version 0.2.3b26c7d60c8
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com> Co-authored-by: Nobuhiro IMAI <nov@yo.rim.or.jp> Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org> Co-authored-by: ima1zumi <mariimaizumi5@gmail.com>
742 lines
21 KiB
Ruby
742 lines
21 KiB
Ruby
# frozen_string_literal: false
|
|
#
|
|
# irb/ruby-lex.rb - ruby lexcal analyzer
|
|
# $Release Version: 0.9.6$
|
|
# $Revision$
|
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
|
#
|
|
# --
|
|
#
|
|
#
|
|
#
|
|
|
|
require "ripper"
|
|
require "jruby" if RUBY_ENGINE == "jruby"
|
|
|
|
# :stopdoc:
|
|
class RubyLex
|
|
|
|
class TerminateLineInput < StandardError
|
|
def initialize
|
|
super("Terminate Line Input")
|
|
end
|
|
end
|
|
|
|
def initialize
|
|
@exp_line_no = @line_no = 1
|
|
@indent = 0
|
|
@continue = false
|
|
@line = ""
|
|
@prompt = nil
|
|
end
|
|
|
|
def self.compile_with_errors_suppressed(code)
|
|
line_no = 1
|
|
begin
|
|
result = yield code, line_no
|
|
rescue ArgumentError
|
|
code = ";\n#{code}"
|
|
line_no = 0
|
|
result = yield code, line_no
|
|
end
|
|
result
|
|
end
|
|
|
|
# io functions
|
|
def set_input(io, p = nil, &block)
|
|
@io = io
|
|
if @io.respond_to?(:check_termination)
|
|
@io.check_termination do |code|
|
|
code.gsub!(/\s*\z/, '').concat("\n")
|
|
ltype, indent, continue, code_block_open = check_state(code)
|
|
if ltype or indent > 0 or continue or code_block_open
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
end
|
|
if @io.respond_to?(:dynamic_prompt)
|
|
@io.dynamic_prompt do |lines|
|
|
lines << '' if lines.empty?
|
|
result = []
|
|
tokens = ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join)
|
|
code = String.new
|
|
partial_tokens = []
|
|
unprocessed_tokens = []
|
|
line_num_offset = 0
|
|
tokens.each do |t|
|
|
partial_tokens << t
|
|
unprocessed_tokens << t
|
|
if t[2].include?("\n")
|
|
t_str = t[2]
|
|
t_str.each_line("\n") do |s|
|
|
code << s << "\n"
|
|
ltype, indent, continue, code_block_open = check_state(code, partial_tokens)
|
|
result << @prompt.call(ltype, indent, continue || code_block_open, @line_no + line_num_offset)
|
|
line_num_offset += 1
|
|
end
|
|
unprocessed_tokens = []
|
|
else
|
|
code << t[2]
|
|
end
|
|
end
|
|
unless unprocessed_tokens.empty?
|
|
ltype, indent, continue, code_block_open = check_state(code, unprocessed_tokens)
|
|
result << @prompt.call(ltype, indent, continue || code_block_open, @line_no + line_num_offset)
|
|
end
|
|
result
|
|
end
|
|
end
|
|
if p.respond_to?(:call)
|
|
@input = p
|
|
elsif block_given?
|
|
@input = block
|
|
else
|
|
@input = Proc.new{@io.gets}
|
|
end
|
|
end
|
|
|
|
def set_prompt(p = nil, &block)
|
|
p = block if block_given?
|
|
if p.respond_to?(:call)
|
|
@prompt = p
|
|
else
|
|
@prompt = Proc.new{print p}
|
|
end
|
|
end
|
|
|
|
ERROR_TOKENS = [
|
|
:on_parse_error,
|
|
:compile_error,
|
|
:on_assign_error,
|
|
:on_alias_error,
|
|
:on_class_name_error,
|
|
:on_param_error
|
|
]
|
|
|
|
def ripper_lex_without_warning(code)
|
|
verbose, $VERBOSE = $VERBOSE, nil
|
|
tokens = nil
|
|
self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
|
|
lexer = Ripper::Lexer.new(inner_code, '-', line_no)
|
|
if lexer.respond_to?(:scan) # Ruby 2.7+
|
|
tokens = []
|
|
pos_to_index = {}
|
|
lexer.scan.each do |t|
|
|
if pos_to_index.has_key?(t[0])
|
|
index = pos_to_index[t[0]]
|
|
found_tk = tokens[index]
|
|
if ERROR_TOKENS.include?(found_tk[1]) && !ERROR_TOKENS.include?(t[1])
|
|
tokens[index] = t
|
|
end
|
|
else
|
|
pos_to_index[t[0]] = tokens.size
|
|
tokens << t
|
|
end
|
|
end
|
|
else
|
|
tokens = lexer.parse
|
|
end
|
|
end
|
|
tokens
|
|
ensure
|
|
$VERBOSE = verbose
|
|
end
|
|
|
|
def find_prev_spaces(line_index)
|
|
return 0 if @tokens.size == 0
|
|
md = @tokens[0][2].match(/(\A +)/)
|
|
prev_spaces = md.nil? ? 0 : md[1].count(' ')
|
|
line_count = 0
|
|
@tokens.each_with_index do |t, i|
|
|
if t[2].include?("\n")
|
|
line_count += t[2].count("\n")
|
|
if line_count >= line_index
|
|
return prev_spaces
|
|
end
|
|
if (@tokens.size - 1) > i
|
|
md = @tokens[i + 1][2].match(/(\A +)/)
|
|
prev_spaces = md.nil? ? 0 : md[1].count(' ')
|
|
end
|
|
end
|
|
end
|
|
prev_spaces
|
|
end
|
|
|
|
def set_auto_indent(context)
|
|
if @io.respond_to?(:auto_indent) and context.auto_indent_mode
|
|
@io.auto_indent do |lines, line_index, byte_pointer, is_newline|
|
|
if is_newline
|
|
@tokens = ripper_lex_without_warning(lines[0..line_index].join("\n"))
|
|
prev_spaces = find_prev_spaces(line_index)
|
|
depth_difference = check_newline_depth_difference
|
|
depth_difference = 0 if depth_difference < 0
|
|
prev_spaces + depth_difference * 2
|
|
else
|
|
code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join
|
|
last_line = lines[line_index]&.byteslice(0, byte_pointer)
|
|
code += last_line if last_line
|
|
@tokens = ripper_lex_without_warning(code)
|
|
corresponding_token_depth = check_corresponding_token_depth
|
|
if corresponding_token_depth
|
|
corresponding_token_depth
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def check_state(code, tokens = nil)
|
|
tokens = ripper_lex_without_warning(code) unless tokens
|
|
ltype = process_literal_type(tokens)
|
|
indent = process_nesting_level(tokens)
|
|
continue = process_continue(tokens)
|
|
code_block_open = check_code_block(code, tokens)
|
|
[ltype, indent, continue, code_block_open]
|
|
end
|
|
|
|
def prompt
|
|
if @prompt
|
|
@prompt.call(@ltype, @indent, @continue, @line_no)
|
|
end
|
|
end
|
|
|
|
def initialize_input
|
|
@ltype = nil
|
|
@indent = 0
|
|
@continue = false
|
|
@line = ""
|
|
@exp_line_no = @line_no
|
|
@code_block_open = false
|
|
end
|
|
|
|
def each_top_level_statement
|
|
initialize_input
|
|
catch(:TERM_INPUT) do
|
|
loop do
|
|
begin
|
|
prompt
|
|
unless l = lex
|
|
throw :TERM_INPUT if @line == ''
|
|
else
|
|
@line_no += l.count("\n")
|
|
if l == "\n"
|
|
@exp_line_no += 1
|
|
next
|
|
end
|
|
@line.concat l
|
|
if @code_block_open or @ltype or @continue or @indent > 0
|
|
next
|
|
end
|
|
end
|
|
if @line != "\n"
|
|
@line.force_encoding(@io.encoding)
|
|
yield @line, @exp_line_no
|
|
end
|
|
raise TerminateLineInput if @io.eof?
|
|
@line = ''
|
|
@exp_line_no = @line_no
|
|
|
|
@indent = 0
|
|
rescue TerminateLineInput
|
|
initialize_input
|
|
prompt
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def lex
|
|
line = @input.call
|
|
if @io.respond_to?(:check_termination)
|
|
return line # multiline
|
|
end
|
|
code = @line + (line.nil? ? '' : line)
|
|
code.gsub!(/\s*\z/, '').concat("\n")
|
|
@tokens = ripper_lex_without_warning(code)
|
|
@continue = process_continue
|
|
@code_block_open = check_code_block(code)
|
|
@indent = process_nesting_level
|
|
@ltype = process_literal_type
|
|
line
|
|
end
|
|
|
|
def process_continue(tokens = @tokens)
|
|
# last token is always newline
|
|
if tokens.size >= 2 and tokens[-2][1] == :on_regexp_end
|
|
# end of regexp literal
|
|
return false
|
|
elsif tokens.size >= 2 and tokens[-2][1] == :on_semicolon
|
|
return false
|
|
elsif tokens.size >= 2 and tokens[-2][1] == :on_kw and ['begin', 'else', 'ensure'].include?(tokens[-2][2])
|
|
return false
|
|
elsif !tokens.empty? and tokens.last[2] == "\\\n"
|
|
return true
|
|
elsif tokens.size >= 1 and tokens[-1][1] == :on_heredoc_end # "EOH\n"
|
|
return false
|
|
elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME)
|
|
# end of literal except for regexp
|
|
return true
|
|
end
|
|
false
|
|
end
|
|
|
|
def check_code_block(code, tokens = @tokens)
|
|
return true if tokens.empty?
|
|
if tokens.last[1] == :on_heredoc_beg
|
|
return true
|
|
end
|
|
|
|
begin # check if parser error are available
|
|
verbose, $VERBOSE = $VERBOSE, nil
|
|
case RUBY_ENGINE
|
|
when 'ruby'
|
|
self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
|
|
RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no)
|
|
end
|
|
when 'jruby'
|
|
JRuby.compile_ir(code)
|
|
else
|
|
catch(:valid) do
|
|
eval("BEGIN { throw :valid, true }\n#{code}")
|
|
false
|
|
end
|
|
end
|
|
rescue EncodingError
|
|
# This is for a hash with invalid encoding symbol, {"\xAE": 1}
|
|
rescue SyntaxError => e
|
|
case e.message
|
|
when /unterminated (?:string|regexp) meets end of file/
|
|
# "unterminated regexp meets end of file"
|
|
#
|
|
# example:
|
|
# /
|
|
#
|
|
# "unterminated string meets end of file"
|
|
#
|
|
# example:
|
|
# '
|
|
return true
|
|
when /syntax error, unexpected end-of-input/
|
|
# "syntax error, unexpected end-of-input, expecting keyword_end"
|
|
#
|
|
# example:
|
|
# if ture
|
|
# hoge
|
|
# if false
|
|
# fuga
|
|
# end
|
|
return true
|
|
when /syntax error, unexpected keyword_end/
|
|
# "syntax error, unexpected keyword_end"
|
|
#
|
|
# example:
|
|
# if (
|
|
# end
|
|
#
|
|
# example:
|
|
# end
|
|
return false
|
|
when /syntax error, unexpected '\.'/
|
|
# "syntax error, unexpected '.'"
|
|
#
|
|
# example:
|
|
# .
|
|
return false
|
|
when /unexpected tREGEXP_BEG/
|
|
# "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('"
|
|
#
|
|
# example:
|
|
# method / f /
|
|
return false
|
|
end
|
|
ensure
|
|
$VERBOSE = verbose
|
|
end
|
|
|
|
if defined?(Ripper::EXPR_BEG)
|
|
last_lex_state = tokens.last[3]
|
|
if last_lex_state.allbits?(Ripper::EXPR_BEG)
|
|
return false
|
|
elsif last_lex_state.allbits?(Ripper::EXPR_DOT)
|
|
return true
|
|
elsif last_lex_state.allbits?(Ripper::EXPR_CLASS)
|
|
return true
|
|
elsif last_lex_state.allbits?(Ripper::EXPR_FNAME)
|
|
return true
|
|
elsif last_lex_state.allbits?(Ripper::EXPR_VALUE)
|
|
return true
|
|
elsif last_lex_state.allbits?(Ripper::EXPR_ARG)
|
|
return false
|
|
end
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def process_nesting_level(tokens = @tokens)
|
|
indent = 0
|
|
in_oneliner_def = nil
|
|
tokens.each_with_index { |t, index|
|
|
# detecting one-liner method definition
|
|
if in_oneliner_def.nil?
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
in_oneliner_def = :ENDFN
|
|
end
|
|
else
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
# continuing
|
|
elsif t[3].allbits?(Ripper::EXPR_BEG)
|
|
if t[2] == '='
|
|
in_oneliner_def = :BODY
|
|
end
|
|
else
|
|
if in_oneliner_def == :BODY
|
|
# one-liner method definition
|
|
indent -= 1
|
|
end
|
|
in_oneliner_def = nil
|
|
end
|
|
end
|
|
|
|
case t[1]
|
|
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
|
|
indent += 1
|
|
when :on_rbracket, :on_rbrace, :on_rparen
|
|
indent -= 1
|
|
when :on_kw
|
|
next if index > 0 and tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
|
|
case t[2]
|
|
when 'do'
|
|
syntax_of_do = take_corresponding_syntax_to_kw_do(tokens, index)
|
|
indent += 1 if syntax_of_do == :method_calling
|
|
when 'def', 'case', 'for', 'begin', 'class', 'module'
|
|
indent += 1
|
|
when 'if', 'unless', 'while', 'until'
|
|
# postfix if/unless/while/until must be Ripper::EXPR_LABEL
|
|
indent += 1 unless t[3].allbits?(Ripper::EXPR_LABEL)
|
|
when 'end'
|
|
indent -= 1
|
|
end
|
|
end
|
|
# percent literals are not indented
|
|
}
|
|
indent
|
|
end
|
|
|
|
def is_method_calling?(tokens, index)
|
|
tk = tokens[index]
|
|
if tk[3].anybits?(Ripper::EXPR_CMDARG) and tk[1] == :on_ident
|
|
# The target method call to pass the block with "do".
|
|
return true
|
|
elsif tk[3].anybits?(Ripper::EXPR_ARG) and tk[1] == :on_ident
|
|
non_sp_index = tokens[0..(index - 1)].rindex{ |t| t[1] != :on_sp }
|
|
if non_sp_index
|
|
prev_tk = tokens[non_sp_index]
|
|
if prev_tk[3].anybits?(Ripper::EXPR_DOT) and prev_tk[1] == :on_period
|
|
# The target method call with receiver to pass the block with "do".
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
false
|
|
end
|
|
|
|
def take_corresponding_syntax_to_kw_do(tokens, index)
|
|
syntax_of_do = nil
|
|
# Finding a syntax correnponding to "do".
|
|
index.downto(0) do |i|
|
|
tk = tokens[i]
|
|
# In "continue", the token isn't the corresponding syntax to "do".
|
|
non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
|
|
first_in_fomula = false
|
|
if non_sp_index.nil?
|
|
first_in_fomula = true
|
|
elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
|
|
first_in_fomula = true
|
|
end
|
|
if is_method_calling?(tokens, i)
|
|
syntax_of_do = :method_calling
|
|
break if first_in_fomula
|
|
elsif tk[1] == :on_kw && %w{while until for}.include?(tk[2])
|
|
# A loop syntax in front of "do" found.
|
|
#
|
|
# while cond do # also "until" or "for"
|
|
# end
|
|
#
|
|
# This "do" doesn't increment indent because the loop syntax already
|
|
# incremented.
|
|
syntax_of_do = :loop_syntax
|
|
break if first_in_fomula
|
|
end
|
|
end
|
|
syntax_of_do
|
|
end
|
|
|
|
def is_the_in_correspond_to_a_for(tokens, index)
|
|
syntax_of_in = nil
|
|
# Finding a syntax correnponding to "do".
|
|
index.downto(0) do |i|
|
|
tk = tokens[i]
|
|
# In "continue", the token isn't the corresponding syntax to "do".
|
|
non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
|
|
first_in_fomula = false
|
|
if non_sp_index.nil?
|
|
first_in_fomula = true
|
|
elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
|
|
first_in_fomula = true
|
|
end
|
|
if tk[1] == :on_kw && tk[2] == 'for'
|
|
# A loop syntax in front of "do" found.
|
|
#
|
|
# while cond do # also "until" or "for"
|
|
# end
|
|
#
|
|
# This "do" doesn't increment indent because the loop syntax already
|
|
# incremented.
|
|
syntax_of_in = :for
|
|
end
|
|
break if first_in_fomula
|
|
end
|
|
syntax_of_in
|
|
end
|
|
|
|
def check_newline_depth_difference
|
|
depth_difference = 0
|
|
open_brace_on_line = 0
|
|
in_oneliner_def = nil
|
|
@tokens.each_with_index do |t, index|
|
|
# detecting one-liner method definition
|
|
if in_oneliner_def.nil?
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
in_oneliner_def = :ENDFN
|
|
end
|
|
else
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
# continuing
|
|
elsif t[3].allbits?(Ripper::EXPR_BEG)
|
|
if t[2] == '='
|
|
in_oneliner_def = :BODY
|
|
end
|
|
else
|
|
if in_oneliner_def == :BODY
|
|
# one-liner method definition
|
|
depth_difference -= 1
|
|
end
|
|
in_oneliner_def = nil
|
|
end
|
|
end
|
|
|
|
case t[1]
|
|
when :on_ignored_nl, :on_nl, :on_comment
|
|
if index != (@tokens.size - 1) and in_oneliner_def != :BODY
|
|
depth_difference = 0
|
|
open_brace_on_line = 0
|
|
end
|
|
next
|
|
when :on_sp
|
|
next
|
|
end
|
|
case t[1]
|
|
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
|
|
depth_difference += 1
|
|
open_brace_on_line += 1
|
|
when :on_rbracket, :on_rbrace, :on_rparen
|
|
depth_difference -= 1 if open_brace_on_line > 0
|
|
when :on_kw
|
|
next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
|
|
case t[2]
|
|
when 'do'
|
|
syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
|
|
depth_difference += 1 if syntax_of_do == :method_calling
|
|
when 'def', 'case', 'for', 'begin', 'class', 'module'
|
|
depth_difference += 1
|
|
when 'if', 'unless', 'while', 'until', 'rescue'
|
|
# postfix if/unless/while/until/rescue must be Ripper::EXPR_LABEL
|
|
unless t[3].allbits?(Ripper::EXPR_LABEL)
|
|
depth_difference += 1
|
|
end
|
|
when 'else', 'elsif', 'ensure', 'when'
|
|
depth_difference += 1
|
|
when 'in'
|
|
unless is_the_in_correspond_to_a_for(@tokens, index)
|
|
depth_difference += 1
|
|
end
|
|
when 'end'
|
|
depth_difference -= 1
|
|
end
|
|
end
|
|
end
|
|
depth_difference
|
|
end
|
|
|
|
def check_corresponding_token_depth
|
|
corresponding_token_depth = nil
|
|
is_first_spaces_of_line = true
|
|
is_first_printable_of_line = true
|
|
spaces_of_nest = []
|
|
spaces_at_line_head = 0
|
|
open_brace_on_line = 0
|
|
in_oneliner_def = nil
|
|
@tokens.each_with_index do |t, index|
|
|
# detecting one-liner method definition
|
|
if in_oneliner_def.nil?
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
in_oneliner_def = :ENDFN
|
|
end
|
|
else
|
|
if t[3].allbits?(Ripper::EXPR_ENDFN)
|
|
# continuing
|
|
elsif t[3].allbits?(Ripper::EXPR_BEG)
|
|
if t[2] == '='
|
|
in_oneliner_def = :BODY
|
|
end
|
|
else
|
|
if in_oneliner_def == :BODY
|
|
# one-liner method definition
|
|
if is_first_printable_of_line
|
|
corresponding_token_depth = spaces_of_nest.pop
|
|
else
|
|
spaces_of_nest.pop
|
|
corresponding_token_depth = nil
|
|
end
|
|
end
|
|
in_oneliner_def = nil
|
|
end
|
|
end
|
|
|
|
case t[1]
|
|
when :on_ignored_nl, :on_nl, :on_comment
|
|
if in_oneliner_def != :BODY
|
|
corresponding_token_depth = nil
|
|
spaces_at_line_head = 0
|
|
is_first_spaces_of_line = true
|
|
is_first_printable_of_line = true
|
|
open_brace_on_line = 0
|
|
end
|
|
next
|
|
when :on_sp
|
|
spaces_at_line_head = t[2].count(' ') if is_first_spaces_of_line
|
|
is_first_spaces_of_line = false
|
|
next
|
|
end
|
|
case t[1]
|
|
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
|
|
spaces_of_nest.push(spaces_at_line_head + open_brace_on_line * 2)
|
|
open_brace_on_line += 1
|
|
when :on_rbracket, :on_rbrace, :on_rparen
|
|
if is_first_printable_of_line
|
|
corresponding_token_depth = spaces_of_nest.pop
|
|
else
|
|
spaces_of_nest.pop
|
|
corresponding_token_depth = nil
|
|
end
|
|
open_brace_on_line -= 1
|
|
when :on_kw
|
|
next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
|
|
case t[2]
|
|
when 'do'
|
|
syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
|
|
if syntax_of_do == :method_calling
|
|
spaces_of_nest.push(spaces_at_line_head)
|
|
end
|
|
when 'def', 'case', 'for', 'begin', 'class', 'module'
|
|
spaces_of_nest.push(spaces_at_line_head)
|
|
when 'rescue'
|
|
unless t[3].allbits?(Ripper::EXPR_LABEL)
|
|
corresponding_token_depth = spaces_of_nest.last
|
|
end
|
|
when 'if', 'unless', 'while', 'until'
|
|
# postfix if/unless/while/until must be Ripper::EXPR_LABEL
|
|
unless t[3].allbits?(Ripper::EXPR_LABEL)
|
|
spaces_of_nest.push(spaces_at_line_head)
|
|
end
|
|
when 'else', 'elsif', 'ensure', 'when', 'in'
|
|
corresponding_token_depth = spaces_of_nest.last
|
|
when 'end'
|
|
if is_first_printable_of_line
|
|
corresponding_token_depth = spaces_of_nest.pop
|
|
else
|
|
spaces_of_nest.pop
|
|
corresponding_token_depth = nil
|
|
end
|
|
end
|
|
end
|
|
is_first_spaces_of_line = false
|
|
is_first_printable_of_line = false
|
|
end
|
|
corresponding_token_depth
|
|
end
|
|
|
|
def check_string_literal(tokens)
|
|
i = 0
|
|
start_token = []
|
|
end_type = []
|
|
while i < tokens.size
|
|
t = tokens[i]
|
|
case t[1]
|
|
when :on_tstring_beg
|
|
start_token << t
|
|
end_type << [:on_tstring_end, :on_label_end]
|
|
when :on_regexp_beg
|
|
start_token << t
|
|
end_type << :on_regexp_end
|
|
when :on_symbeg
|
|
acceptable_single_tokens = %i{on_ident on_const on_op on_cvar on_ivar on_gvar on_kw}
|
|
if (i + 1) < tokens.size and acceptable_single_tokens.all?{ |st| tokens[i + 1][1] != st }
|
|
start_token << t
|
|
end_type << :on_tstring_end
|
|
end
|
|
when :on_backtick
|
|
start_token << t
|
|
end_type << :on_tstring_end
|
|
when :on_qwords_beg, :on_words_beg, :on_qsymbols_beg, :on_symbols_beg
|
|
start_token << t
|
|
end_type << :on_tstring_end
|
|
when :on_heredoc_beg
|
|
start_token << t
|
|
end_type << :on_heredoc_end
|
|
when *end_type.last
|
|
start_token.pop
|
|
end_type.pop
|
|
end
|
|
i += 1
|
|
end
|
|
start_token.last.nil? ? '' : start_token.last
|
|
end
|
|
|
|
def process_literal_type(tokens = @tokens)
|
|
start_token = check_string_literal(tokens)
|
|
case start_token[1]
|
|
when :on_tstring_beg
|
|
case start_token[2]
|
|
when ?" then ?"
|
|
when /^%.$/ then ?"
|
|
when /^%Q.$/ then ?"
|
|
when ?' then ?'
|
|
when /^%q.$/ then ?'
|
|
end
|
|
when :on_regexp_beg then ?/
|
|
when :on_symbeg then ?:
|
|
when :on_backtick then ?`
|
|
when :on_qwords_beg then ?]
|
|
when :on_words_beg then ?]
|
|
when :on_qsymbols_beg then ?]
|
|
when :on_symbols_beg then ?]
|
|
when :on_heredoc_beg
|
|
start_token[2] =~ /<<[-~]?(['"`])[_a-zA-Z0-9]+\1/
|
|
case $1
|
|
when ?" then ?"
|
|
when ?' then ?'
|
|
when ?` then ?`
|
|
else ?"
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
# :startdoc:
|