Skip to content

Commit 172453c

Browse files
tompngst0012
andauthored
Simplify each_top_level_statement (#576)
* Simplify each_top_level_statement, reduce instance vars * Update lib/irb/ruby-lex.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Remove unused ltype from TestRubyLex#check_state response * Remove unnecessary const path of TerminateLineInput * Combine duplicated code state check into method --------- Co-authored-by: Stan Lo <stan001212@gmail.com>
1 parent e10fcee commit 172453c

File tree

2 files changed

+56
-75
lines changed

2 files changed

+56
-75
lines changed

lib/irb/ruby-lex.rb

Lines changed: 49 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ def initialize
1818

1919
def initialize(context)
2020
@context = context
21-
@exp_line_no = @line_no = 1
22-
@indent = 0
23-
@continue = false
24-
@line = ""
21+
@line_no = 1
2522
@prompt = nil
2623
end
2724

@@ -42,6 +39,11 @@ def self.compile_with_errors_suppressed(code, line_no: 1)
4239
result
4340
end
4441

42+
def single_line_command?(code)
43+
command = code.split(/\s/, 2).first
44+
@context.symbol_alias?(command) || @context.transform_args?(command)
45+
end
46+
4547
# io functions
4648
def set_input(&block)
4749
@input = block
@@ -65,14 +67,9 @@ def configure_io(io)
6567
end
6668
else
6769
# Accept any single-line input for symbol aliases or commands that transform args
68-
command = code.split(/\s/, 2).first
69-
if @context.symbol_alias?(command) || @context.transform_args?(command)
70-
next true
71-
end
70+
next true if single_line_command?(code)
7271

73-
code.gsub!(/\s*\z/, '').concat("\n")
74-
tokens = self.class.ripper_lex_without_warning(code, context: @context)
75-
ltype, indent, continue, code_block_open = check_state(code, tokens)
72+
ltype, indent, continue, code_block_open = check_code_state(code)
7673
if ltype or indent > 0 or continue or code_block_open
7774
false
7875
else
@@ -210,67 +207,56 @@ def check_state(code, tokens)
210207
[ltype, indent, continue, code_block_open]
211208
end
212209

213-
def prompt
214-
if @prompt
215-
@prompt.call(@ltype, @indent, @continue, @line_no)
216-
end
210+
def check_code_state(code)
211+
check_target_code = code.gsub(/\s*\z/, '').concat("\n")
212+
tokens = self.class.ripper_lex_without_warning(check_target_code, context: @context)
213+
check_state(check_target_code, tokens)
217214
end
218215

219-
def initialize_input
220-
@ltype = nil
221-
@indent = 0
222-
@continue = false
223-
@line = ""
224-
@exp_line_no = @line_no
225-
@code_block_open = false
216+
def save_prompt_to_context_io(ltype, indent, continue, line_num_offset)
217+
# Implicitly saves prompt string to `@context.io.prompt`. This will be used in the next `@input.call`.
218+
@prompt.call(ltype, indent, continue, @line_no + line_num_offset)
226219
end
227220

228-
def each_top_level_statement
229-
initialize_input
230-
catch(:TERM_INPUT) do
231-
loop do
232-
begin
233-
prompt
234-
unless l = lex
235-
throw :TERM_INPUT if @line == ''
236-
else
237-
@line_no += l.count("\n")
238-
if l == "\n"
239-
@exp_line_no += 1
240-
next
241-
end
242-
@line.concat l
243-
if @code_block_open or @ltype or @continue or @indent > 0
244-
next
245-
end
246-
end
247-
if @line != "\n"
248-
@line.force_encoding(@io.encoding)
249-
yield @line, @exp_line_no
250-
end
251-
raise TerminateLineInput if @io.eof?
252-
@line = ''
253-
@exp_line_no = @line_no
254-
255-
@indent = 0
256-
rescue TerminateLineInput
257-
initialize_input
258-
prompt
259-
end
221+
def readmultiline
222+
save_prompt_to_context_io(nil, 0, false, 0)
223+
224+
# multiline
225+
return @input.call if @io.respond_to?(:check_termination)
226+
227+
# nomultiline
228+
code = ''
229+
line_offset = 0
230+
loop do
231+
line = @input.call
232+
unless line
233+
return code.empty? ? nil : code
260234
end
235+
236+
code << line
237+
# Accept any single-line input for symbol aliases or commands that transform args
238+
return code if single_line_command?(code)
239+
240+
ltype, indent, continue, code_block_open = check_code_state(code)
241+
return code unless ltype or indent > 0 or continue or code_block_open
242+
243+
line_offset += 1
244+
save_prompt_to_context_io(ltype, indent, continue, line_offset)
261245
end
262246
end
263247

264-
def lex
265-
line = @input.call
266-
if @io.respond_to?(:check_termination)
267-
return line # multiline
248+
def each_top_level_statement
249+
loop do
250+
code = readmultiline
251+
break unless code
252+
253+
if code != "\n"
254+
code.force_encoding(@io.encoding)
255+
yield code, @line_no
256+
end
257+
@line_no += code.count("\n")
258+
rescue TerminateLineInput
268259
end
269-
code = @line + (line.nil? ? '' : line)
270-
code.gsub!(/\s*\z/, '').concat("\n")
271-
@tokens = self.class.ripper_lex_without_warning(code, context: @context)
272-
@ltype, @indent, @continue, @code_block_open = check_state(code, @tokens)
273-
line
274260
end
275261

276262
def process_continue(tokens)

test/irb/test_ruby_lex.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,27 +83,22 @@ def assert_row_indenting(lines, row)
8383
end
8484

8585
def assert_nesting_level(lines, expected, local_variables: [])
86-
ruby_lex = ruby_lex_for_lines(lines, local_variables: local_variables)
86+
indent, _code_block_open = check_state(lines, local_variables: local_variables)
8787
error_message = "Calculated the wrong number of nesting level for:\n #{lines.join("\n")}"
88-
assert_equal(expected, ruby_lex.instance_variable_get(:@indent), error_message)
88+
assert_equal(expected, indent, error_message)
8989
end
9090

9191
def assert_code_block_open(lines, expected, local_variables: [])
92-
ruby_lex = ruby_lex_for_lines(lines, local_variables: local_variables)
92+
_indent, code_block_open = check_state(lines, local_variables: local_variables)
9393
error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}"
94-
assert_equal(expected, ruby_lex.instance_variable_get(:@code_block_open), error_message)
94+
assert_equal(expected, code_block_open, error_message)
9595
end
9696

97-
def ruby_lex_for_lines(lines, local_variables: [])
97+
def check_state(lines, local_variables: [])
9898
context = build_context(local_variables)
9999
ruby_lex = RubyLex.new(context)
100-
101-
io = proc{ lines.join("\n") }
102-
ruby_lex.set_input do
103-
lines.join("\n")
104-
end
105-
ruby_lex.lex
106-
ruby_lex
100+
_ltype, indent, _continue, code_block_open = ruby_lex.check_code_state(lines.join("\n"))
101+
[indent, code_block_open]
107102
end
108103

109104
def test_auto_indent

0 commit comments

Comments
 (0)