From 850b2c23e2396b592021c891065e9b2749c5806a Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 7 Feb 2026 15:09:24 -0800 Subject: [PATCH] Modernize parser and shell code with Ruby 4 features --- ruby/shell/builtins.rb | 7 ++++--- ruby/shell/repl.rb | 35 ++++++++++++++++++----------------- ruby/shell/string_parser.rb | 27 ++++++++++++--------------- ruby/shell/word_expander.rb | 6 +++--- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/ruby/shell/builtins.rb b/ruby/shell/builtins.rb index edf9e7e..3479399 100644 --- a/ruby/shell/builtins.rb +++ b/ruby/shell/builtins.rb @@ -60,9 +60,10 @@ module Shell def builtin_cd(args) dir = args.first oldpwd = Dir.pwd - target = if dir.nil? + target = case dir + in nil Dir.home - elsif dir == "-" + in "-" ENV["OLDPWD"] || oldpwd else dir @@ -85,7 +86,7 @@ module Shell logger.warn "#{red("[ERROR]")} Invalid export command" return -1 else - ENV[name] = value_parts.join("=").gsub(EXPORT_VARIABLE_PATTERN) { |m| ENV[m[1..]] || "" } + ENV[name] = value_parts.join("=").gsub(EXPORT_VARIABLE_PATTERN) { ENV[it[1..]] || "" } end 0 end diff --git a/ruby/shell/repl.rb b/ruby/shell/repl.rb index e236df3..0263d84 100644 --- a/ruby/shell/repl.rb +++ b/ruby/shell/repl.rb @@ -54,19 +54,24 @@ module Shell commands = parse_line(line) result = 0 commands.each do |entry| - command = entry[:command] - next if command.strip.empty? - next if entry[:op] == :and && result != 0 + case entry + in StringParser::Command[text:, op:] + command = text + next if command.strip.empty? + next if op == :and && result != 0 - args = word_expander.expand(command) - program = args.shift - logger.verbose "Parsed command: #{program} #{args.inspect}" - if builtins.builtin?(program) - logger.verbose "Executing builtin #{program}" - result = builtins.exec(program, args) + args = word_expander.expand(command) + program = args.shift + logger.verbose "Parsed command: #{program} #{args.inspect}" + if builtins.builtin?(program) + logger.verbose "Executing builtin #{program}" + result = builtins.exec(program, args) + else + logger.verbose "Shelling out for #{program}" + result = job_control.exec_command(program, args) + end else - logger.verbose "Shelling out for #{program}" - result = job_control.exec_command(program, args) + raise ArgumentError, "Unknown parsed command node: #{entry.inspect}" end end result @@ -76,12 +81,8 @@ module Shell end # Looks like this: /path/to/somewhere% - def prompt(pwd) - "#{blue(pwd)}#{white("%")} #{CLEAR}" - end + def prompt(pwd) = "#{blue(pwd)}#{white("%")} #{CLEAR}" - def parse_line(line) - StringParser.split_commands(line) - end + def parse_line(line) = StringParser.split_commands(line) end end diff --git a/ruby/shell/string_parser.rb b/ruby/shell/string_parser.rb index be68a1f..6030445 100644 --- a/ruby/shell/string_parser.rb +++ b/ruby/shell/string_parser.rb @@ -1,5 +1,6 @@ module Shell class StringParser + Command = Data.define(:text, :op) Token = Data.define(:type, :value) class Scanner @@ -336,22 +337,20 @@ module Shell tokens = Scanner.new(line).tokenize_command_list tokens.each do |token| - case token.type - when :text - commands << {command: token.value, op: next_op} - if next_op == :and && token.value.strip.empty? + case token + in Token[type: :text, value:] + if next_op == :and && value.strip.empty? raise ArgumentError, "syntax error: expected command after `&&`" end + commands << Command.new(text: value, op: next_op) next_op = :always - when :separator - if token.value == :and - if commands.empty? || commands.last[:command].strip.empty? - raise ArgumentError, "syntax error near unexpected token `&&`" - end - next_op = :and - else - next_op = :always + in Token[type: :separator, value: :and] + if commands.empty? || commands.last.text.strip.empty? + raise ArgumentError, "syntax error near unexpected token `&&`" end + next_op = :and + in Token[type: :separator, value: :always] + next_op = :always else raise ArgumentError, "Unknown token type: #{token.type}" end @@ -360,9 +359,7 @@ module Shell commands end - def read_dollar_paren(line, start_index) - Scanner.new(line, index: start_index).read_dollar_paren_body - end + def read_dollar_paren(line, start_index) = Scanner.new(line, index: start_index).read_dollar_paren_body end end end diff --git a/ruby/shell/word_expander.rb b/ruby/shell/word_expander.rb index d3ed403..2eef5a8 100644 --- a/ruby/shell/word_expander.rb +++ b/ruby/shell/word_expander.rb @@ -39,7 +39,7 @@ module Shell expanded = expand_variables(word.text) .tr(ESCAPED_DOLLAR, "$") .tr(ESCAPED_BACKTICK, "`") - expand_braces(expanded).map { |part| SplitWord.new(text: part, globbed: word.globbed) } + expand_braces(expanded).map { SplitWord.new(text: it, globbed: word.globbed) } end .flat_map do |word| if word.globbed @@ -116,7 +116,7 @@ module Shell if glob_words.empty? words << SplitWord.new(text: field, globbed: false) else - glob_words.each { |glob_word| words << SplitWord.new(text: glob_word, globbed: true) } + glob_words.each { words << SplitWord.new(text: it, globbed: true) } end else words << SplitWord.new(text: field, globbed: false) @@ -535,7 +535,7 @@ module Shell return [word] unless body.include?(",") parts = body.split(",", -1) - parts.flat_map { |part| expand_braces(prefix + part + suffix) } + parts.flat_map { expand_braces(prefix + it + suffix) } end def escaped_replacement(char)