-
Notifications
You must be signed in to change notification settings - Fork 119
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
Improve command input recognition #805
base: master
Are you sure you want to change the base?
Conversation
lib/irb.rb
Outdated
|
||
if command_match | ||
command_or_alias = command_match[:cmd_name] | ||
arg = [command_match[:cmd_arg], command_match[:cmd_flag]].compact.join(' ') |
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 will open a follow up PR to see if extracting cmd_flag
can simplify how commands implement options.
Update: #809
I think One idea is to exclude only these assignments info = a # but accept =~ == ===
info += a # and other operators
info &&= a
info ||= a Maybe we can check local variable existence just like show_source == -s # command
show_source =~ -s # command
show_source = /regexp/ # ruby
show_source =~ -s # ruby (because show_source is local variable)
show_source == -s # ruby
info + 1 # command
info ||= 1 # ruby
info + 1 # ruby (because info is local variable)
info 1 # (ruby? |
Yeah I agree. But IMO the solution doesn't need to perfectly cover all cases. It only has to be slightly better than the current behaviour. Also, sooner or later we need to do some kind of regexp match on input in order to make command options easier to support, instead of writing them in To be clear, I'm not saying this PR is THE solution. I'd like to see how many cases it can support as well, so more cases are welcome 🙏
I worry that this may make the behaviour harder to understand as the same input, like |
Capturing flags will regexp will enable refactoring like this: #809 |
I like #809 idea 👍 Although I have some concern about regexp including flag and args. It restricts not only command's implementation but also user's input. Input like |
I agree local_variable defined or not will make implementation complicated and harder to understand the behavior. I think we can use a simple regexp to handle it. I made a list of command-like ruby expressions. (maybe there are some missing pattern, but it should be a rare case) Possible command-like style local variable assignment:info = expr
info += expr # `*=` `&&=` `||=` and other
info ,other = expr Possible command-like style local variable usage:info
info # comment
info => pattern
info in pattern
info + expr # (+ - * ** / % ^ & | < <= <=> >= > << >> == =~ != !~ === && || and or)
info . method
info &. method
info ... range_end
info :: method
info :: Const
info [key]
info [key] = value
info [key] += value
info if expr
info ? expr : expr
info ; expr
info \ # (continue to next line) Most of them are weird code, not realistic. I think rejecting only these patterns as command is enough. info = expr
info += expr # *= &&= ||= etc.
info + expr # - * ** / % etc.
|
I personally expect us to inevitably come back to regexp for this, unless we're writing a custom parser for IRB commands, which is an overkill IMO.
I think to achieve good abstraction, command
Thanks for listing them. I've updated the tests are it looks like these are newly supported by this PR: info = expr
info += expr # `*=` `&&=` `||=` and other
info ,other = expr
info = expr
info += expr # *= &&= ||= etc.
info + expr # - * ** / % etc.
info if expr
info ? expr : expr
info ; expr
info # comment |
How about something like this? It can be implemented with regexp. if input.match?(/\A#{command_name_regexp}( |\z)/)
# Maybe command
if input.match?(/\A#{command_name_regexp} #{not_a_command_operators_regexp} /)
# Not a command. `info = `, `info + `, `info *= ` will match. ruby code
elsif input.match?(COMMAND_REGEXP)
# Valid command
else
# Command with invalid option. not a ruby code.
# I think warning message for this is important.
end
end |
If you don't mind, I want to take a pause here and first make sure we're trying to find a solution to the same problem. The currently implementation goes through these examination:
If I understand your concern correctly, it's about we only treat input as either command or Ruby code, which will miss the opportunity to warn users about incorrect forms of commands? |
Yes, that's what I'm concerned about. For example, when
This case, user can know that command exists and the usage is wrong.
But in this case, user does not know what is wrong. maybe usage, maybe command name, or something else. I think this is a kind of degradation. |
I've made a change so that when
The same warning is not printed when there's no exception raised to avoid noise. |
Currently, we simply split the input on whitespace and use the first word as the command name to check if it's a command. But this means that assigning a local variable which's name is the same as a command will also be recognized as a command. For example, in the following case, `info` is recognized as a command: ``` irb(main):001> info = 123 `debug` command is only available when IRB is started with binding.irb => nil ``` This commit improves the command input recognition by using more sophis- ticated regular expressions.
…command As we now proceed to stricter syntax checking for commands, incorrect command input would be processed as Ruby instead and likely causes errors. In that case, we should raise a warning to the user. For example, if an input is `show_source Foo bar`, the user would see a warning about the possible syntax error for using `show_source` command. But with this approach, we also need add a condition for the `measure` command as it's actually used as a method call with block.
@@ -66,6 +67,9 @@ def should_be_handled_by_debugger? | |||
end | |||
|
|||
def evaluable_code | |||
# Because measure command is used as a method, we need to treat it differently | |||
return @code if @command == "measure" |
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.
SIMPLE_COMMAND_REGEXP = /^#{COMMAND_NAME_REGEXP}\z/ | ||
COMMAND_WITH_ARGS_REGEXP = /^#{COMMAND_NAME_REGEXP} +#{COMMAND_ARG_REGEXP}\z/ | ||
COMMAND_WITH_FLAGS_REGEXP = /^#{COMMAND_NAME_REGEXP} +#{COMMAND_FLAG_REGEXP}\z/ | ||
COMMAND_WITH_ARGS_AND_FLAGS_REGEXP = /^#{COMMAND_NAME_REGEXP} +#{COMMAND_ARG_REGEXP} +#{COMMAND_FLAG_REGEXP}\z/ |
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 think this still matches to the second line. It should be /^
→ /\A
Command (Last line matches to COMMAND_REGEXP
)
show_source = 1..
2# Set#include?
Not a command (Both line does not match to COMMAND_REGEXP
)
show_source = 1..
2 # Set#include?
Currently, we simply split the input on whitespace and use the first word as the command name to check if it's a command. But this means that assigning a local variable which's name is the same as a command will also be recognized as a command.
For example, in the following case,
info
is recognized as a command:This commit improves the command input recognition by using more sophisticated regular expressions.
Closes #803