csc360-a1-shell/ruby/shell/string_parser.rb

139 lines
3.2 KiB
Ruby

module Shell
class StringParser
class << self
def split_commands(line)
commands = []
command = +""
state = :unquoted
next_op = :always
i = 0
while i < line.length
c = line[i]
case state
when :unquoted
case c
when ";"
commands << {command: command, op: next_op}
command = +""
next_op = :always
i += 1
next
when "&"
if line[i + 1] == "&"
if command.strip.empty?
raise ArgumentError, "syntax error near unexpected token `&&`"
end
commands << {command: command, op: next_op}
command = +""
next_op = :and
i += 2
next
end
when "'"
state = :single_quoted
when "\""
state = :double_quoted
when "\\"
state = :escaped
end
command << c
when :single_quoted
command << c
state = :unquoted if c == "'"
when :double_quoted
command << c
if c == "\\"
state = :double_quoted_escape
elsif c == "\""
state = :unquoted
end
when :double_quoted_escape
command << c
state = :double_quoted
when :escaped
command << c
state = :unquoted
else
raise "Unknown state #{state}"
end
i += 1
end
if next_op == :and && command.strip.empty?
raise ArgumentError, "syntax error: expected command after `&&`"
end
commands << {command: command, op: next_op}
commands
end
def read_dollar_paren(line, start_index)
output = +""
i = start_index
depth = 1
state = :unquoted
while i < line.length
c = line[i]
case state
when :unquoted
case c
when "("
depth += 1
output << c
when ")"
depth -= 1
return [output, i + 1] if depth.zero?
output << c
when "'"
output << c
state = :single_quoted
when "\""
output << c
state = :double_quoted
when "\\"
output << c
if i + 1 < line.length
output << line[i + 1]
i += 1
end
else
output << c
end
when :single_quoted
output << c
state = :unquoted if c == "'"
when :double_quoted
if c == "\\"
output << c
if i + 1 < line.length
output << line[i + 1]
i += 1
end
else
output << c
state = :unquoted if c == "\""
end
else
raise "Unknown state #{state}"
end
i += 1
end
raise ArgumentError, "Unmatched $(...)"
end
end
end
end