New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Always use local variables in current context to parse code #397
Conversation
…l and assignment_expression check
…ight define new localvars and change result of assignment_expression?
lib/irb/ruby-lex.rb
Outdated
@@ -269,18 +275,15 @@ def each_top_level_statement | |||
end | |||
end | |||
|
|||
def lex | |||
def lex(context: nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this and the below context passing necessary? It looks like lex
is never called with context
argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This context is mainly used to test RubyLex's nesting_level and code_block_open calculation in test_ruby_lex.rb
I fixed to call lex with context in L248.
I also changed the optional keyword parameter context:
to normal required parameter.
- Not to forget calling method without context
- Method in this library that receives only context is using normal parameter, not keyword parameter. (ex:
set_auto_indent(context)
,suspend_context(context)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary?
Yes.
Since RubyLex#lex just returns @input.call in mulitline mode, this code is needed only when USE_MULTILINE is false.
Example when nil is passed to context here:
% irb --nomultiline
irb(main):001:0> a = 8
=> 8
irb(main):002:0> a /2 #=> should be 4
irb(main):003:0/
irb(main):004:0/
@indent = process_nesting_level | ||
@ltype = process_literal_type | ||
@tokens = self.class.ripper_lex_without_warning(code, context: context) | ||
@ltype, @indent, @continue, @code_block_open = check_state(code, @tokens, context: context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch on using check_state
instead 👍
lib/irb/ruby-lex.rb
Outdated
@@ -136,16 +136,20 @@ def set_prompt(p = nil, &block) | |||
:on_param_error | |||
] | |||
|
|||
def self.local_variables_assign_code(context: nil, local_variables: []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call it generate_local_variables_assign_code
?
test/irb/test_color.rb
Outdated
result_without_lvars = "a #{RED}#{BOLD}/#{CLEAR}#{RED}(b +1)#{CLEAR}#{RED}#{BOLD}/i#{CLEAR}" | ||
result_with_lvar = "a /(b #{BLUE}#{BOLD}+1#{CLEAR})/i" | ||
result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i" | ||
if colorize_code_supported? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessary because irb requires Ruby 2.5+, which always satisfy this condition. I added #413 to remove this obsolete check.
code_block_open is not working on truffleruby
About the failing test Result of
|
signal_status(:IN_EVAL) do | ||
begin | ||
line.untaint if RUBY_VERSION < '2.7' | ||
if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? | ||
IRB.set_measure_callback | ||
end | ||
is_assignment = assignment_expression?(line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why moving this line here though? It doesn't look like to change the behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually changes the behavior in a rare case.
irb = IRB::Irb.new
context = irb.instance_variable_get('@context')
code = "a /'/i if false; a=1; x=1000.times.to_a#'.size"
irb.assignment_expression?(code) #=> true
context.evaluate(code, 1) #=> [0,1,2,3,...] assignment to local variable x
irb.assignment_expression?(code) #=> false
context.evaluate(code, 1) #=> 0 not an assignment expression
assignment_expression?
check should be done before @context.evaluate
(in L519 and L530)
I added test case for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow thanks for spotting this. I think it also worth a comment then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the example 👍
@@ -827,7 +828,9 @@ def assignment_expression?(line) | |||
# array of parsed expressions. The first element of each expression is the | |||
# expression's type. | |||
verbose, $VERBOSE = $VERBOSE, nil | |||
result = ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0)) | |||
code = "#{RubyLex.generate_local_variables_assign_code(context: @context) || 'nil;'}\n#{line}" | |||
node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not super easy to understand. Would you mind adding a comment for it?
result = ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0)) | ||
code = "#{RubyLex.generate_local_variables_assign_code(context: @context) || 'nil;'}\n#{line}" | ||
node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) | ||
result = ASSIGNMENT_NODE_TYPES.include?(node_type) | ||
$VERBOSE = verbose |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this should be in an ensure
block.
lib/irb/ruby-lex.rb
Outdated
@@ -136,16 +136,20 @@ def set_prompt(p = nil, &block) | |||
:on_param_error | |||
] | |||
|
|||
def self.generate_local_variables_assign_code(context: nil, local_variables: []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it weird to let generate_local_variables_assign_code
take both context
and local_variables
while:
- They're never used at the same time.
- Local variables are all retrieved from the same context, just at different timings.
Can we make it self.generate_local_variables_assign_code(local_variables)
instead?
And we can add something like Context#local_variables
to encapsulate &.workspace&.binding&.local_variables
So that:
- We can have less methods taking
context
while what they actually need are just the local variables - But it's also easier to generate local variables from a context
generate_local_variables_assign_code
can have a simpler implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.generate_local_variables_assign_code(local_variables)
Context#local_variables
It looks good, I fixed it
signal_status(:IN_EVAL) do | ||
begin | ||
line.untaint if RUBY_VERSION < '2.7' | ||
if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? | ||
IRB.set_measure_callback | ||
end | ||
is_assignment = assignment_expression?(line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow thanks for spotting this. I think it also worth a comment then?
signal_status(:IN_EVAL) do | ||
begin | ||
line.untaint if RUBY_VERSION < '2.7' | ||
if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? | ||
IRB.set_measure_callback | ||
end | ||
is_assignment = assignment_expression?(line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the example 👍
Co-authored-by: Stan Lo <stan001212@gmail.com>
@tompng I think the |
@k0kubun would you mind giving it a look as well? thx |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Thank you!
ruby/irb#397) * Use local_variables for colorize, code_block_open check, nesting_level and assignment_expression check * Check if expression is an assignment BEFORE evaluating it. evaluate might define new localvars and change result of assignment_expression? * Add local_variables dependent code test * pend local variable dependent test on truffleruby code_block_open is not working on truffleruby * Always pass context to RubyLex#lex * Rename local_variable_assign_code generator method name * Add assignment expression truncate test * Add Context#local_variables and make generate_local_variables_assign_code more simple * Update lib/irb/input-method.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Add a comment why assignment expression check should be done before evaluate ruby/irb@c8b3877281 Co-authored-by: Stan Lo <stan001212@gmail.com> Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
🎉 |
* Use local_variables for colorize, code_block_open check, nesting_level and assignment_expression check * Check if expression is an assignment BEFORE evaluating it. evaluate might define new localvars and change result of assignment_expression? * Add local_variables dependent code test * pend local variable dependent test on truffleruby code_block_open is not working on truffleruby * Always pass context to RubyLex#lex * Rename local_variable_assign_code generator method name * Add assignment expression truncate test * Add Context#local_variables and make generate_local_variables_assign_code more simple * Update lib/irb/input-method.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Add a comment why assignment expression check should be done before evaluate Co-authored-by: Stan Lo <stan001212@gmail.com> Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
ruby/irb#397) * Use local_variables for colorize, code_block_open check, nesting_level and assignment_expression check * Check if expression is an assignment BEFORE evaluating it. evaluate might define new localvars and change result of assignment_expression? * Add local_variables dependent code test * pend local variable dependent test on truffleruby code_block_open is not working on truffleruby * Always pass context to RubyLex#lex * Rename local_variable_assign_code generator method name * Add assignment expression truncate test * Add Context#local_variables and make generate_local_variables_assign_code more simple * Update lib/irb/input-method.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Add a comment why assignment expression check should be done before evaluate ruby/irb@c8b3877281 Co-authored-by: Stan Lo <stan001212@gmail.com> Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Ruby will parse differently depending on defined local_variables.
Therefore coloring, indent,
code_block_open
andassignment_expression?
needs local_variables in current context.assignment expression check
The code below is an assignment expression and the evaluated result (large array) should be truncated.
But once it is evaluated, it turns into a division expression(
a / "string".size
).Assignment expression check should be done before evaluation.
semicolon and newline character
There are
;\n
in the code to be parsed."a=b=nil;\n#{original_code}"
Without
\n
, syntax valid code=begin\n=end
will be syntax error.Without
;
,.to_s
will wrongly be syntax ok.