diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index 9fcc1a2683..39e53ec175 100644 --- a/Shell/Parser.cpp +++ b/Shell/Parser.cpp @@ -25,15 +25,15 @@ */ #include "Parser.h" +#include #include #include -#include void Parser::commit_token(AllowEmptyToken allow_empty) { if (allow_empty == AllowEmptyToken::No && m_token.is_empty()) return; - if (m_state == InRedirectionPath) { + if (state() == InRedirectionPath) { m_redirections.last().path = String::copy(m_token); m_token.clear_with_capacity(); return; @@ -72,11 +72,19 @@ void Parser::begin_redirect_write(int fd) m_redirections.append({ Redirection::FileWrite, fd }); } +bool Parser::in_state(State state) const +{ + for (auto s : m_state_stack) + if (s == state) + return true; + return false; +} + Vector Parser::parse() { for (size_t i = 0; i < m_input.length(); ++i) { char ch = m_input.characters()[i]; - switch (m_state) { + switch (state()) { case State::Free: if (ch == ' ') { commit_token(); @@ -102,13 +110,13 @@ Vector Parser::parse() begin_redirect_write(STDOUT_FILENO); // Search for another > for append. - m_state = State::InWriteAppendOrRedirectionPath; + push_state(State::InWriteAppendOrRedirectionPath); break; } if (ch == '<') { commit_token(); begin_redirect_read(STDIN_FILENO); - m_state = State::InRedirectionPath; + push_state(State::InRedirectionPath); break; } if (ch == '\\') { @@ -122,14 +130,14 @@ Vector Parser::parse() break; } if (ch == '\'') { - m_state = State::InSingleQuotes; + push_state(State::InSingleQuotes); break; } if (ch == '\"') { - m_state = State::InDoubleQuotes; + push_state(State::InDoubleQuotes); break; } - + // redirection from zsh-style multi-digit fd, such as {10}>file if (ch == '{') { bool is_multi_fd_redirection = false; @@ -162,11 +170,11 @@ Vector Parser::parse() if (m_input.characters()[redir_end] == '>') { begin_redirect_write(fd); // Search for another > for append. - m_state = State::InWriteAppendOrRedirectionPath; + push_state(State::InWriteAppendOrRedirectionPath); } if (m_input.characters()[redir_end] == '<') { begin_redirect_read(fd); - m_state = State::InRedirectionPath; + push_state(State::InRedirectionPath); } i = redir_end; @@ -183,7 +191,7 @@ Vector Parser::parse() ++i; // Search for another > for append. - m_state = State::InWriteAppendOrRedirectionPath; + push_state(State::InWriteAppendOrRedirectionPath); break; } if (next_ch == '<') { @@ -191,7 +199,7 @@ Vector Parser::parse() begin_redirect_read(ch - '0'); ++i; - m_state = State::InRedirectionPath; + push_state(State::InRedirectionPath); break; } } @@ -201,26 +209,30 @@ Vector Parser::parse() case State::InWriteAppendOrRedirectionPath: if (ch == '>') { commit_token(); - m_state = State::InRedirectionPath; + pop_state(); + push_state(State::InRedirectionPath); ASSERT(m_redirections.size()); m_redirections[m_redirections.size() - 1].type = Redirection::FileWriteAppend; break; } // Not another > means that it's probably a path. - m_state = InRedirectionPath; + pop_state(); + push_state(InRedirectionPath); [[fallthrough]]; case State::InRedirectionPath: if (ch == '<') { commit_token(); begin_redirect_read(STDIN_FILENO); - m_state = State::InRedirectionPath; + pop_state(); + push_state(State::InRedirectionPath); break; } if (ch == '>') { commit_token(); begin_redirect_read(STDOUT_FILENO); - m_state = State::InRedirectionPath; + pop_state(); + push_state(State::InRedirectionPath); break; } if (ch == '|') { @@ -230,7 +242,15 @@ Vector Parser::parse() return {}; } do_pipe(); - m_state = State::Free; + pop_state(); + break; + } + if (ch == '"') { + push_state(State::InDoubleQuotes); + break; + } + if (ch == '\'') { + push_state(State::InSingleQuotes); break; } if (ch == ' ') @@ -239,16 +259,18 @@ Vector Parser::parse() break; case State::InSingleQuotes: if (ch == '\'') { - commit_token(AllowEmptyToken::Yes); - m_state = State::Free; + if (!in_state(State::InRedirectionPath)) + commit_token(AllowEmptyToken::Yes); + pop_state(); break; } m_token.append(ch); break; case State::InDoubleQuotes: if (ch == '\"') { - commit_token(AllowEmptyToken::Yes); - m_state = State::Free; + if (!in_state(State::InRedirectionPath)) + commit_token(AllowEmptyToken::Yes); + pop_state(); break; } if (ch == '\\') { @@ -270,6 +292,16 @@ Vector Parser::parse() break; }; } + + while (m_state_stack.size() > 1) { + auto allow_empty = AllowEmptyToken::No; + if (state() == State::InDoubleQuotes || state() == State::InSingleQuotes) + allow_empty = AllowEmptyToken::Yes; + commit_token(allow_empty); + pop_state(); + } + ASSERT(state() == State::Free); + commit_token(); commit_subcommand(); commit_command(); diff --git a/Shell/Parser.h b/Shell/Parser.h index 06950e02ff..aaac780862 100644 --- a/Shell/Parser.h +++ b/Shell/Parser.h @@ -67,7 +67,10 @@ public: Vector parse(); private: - enum class AllowEmptyToken { No, Yes }; + enum class AllowEmptyToken { + No, + Yes, + }; void commit_token(AllowEmptyToken = AllowEmptyToken::No); void commit_subcommand(); void commit_command(); @@ -82,7 +85,22 @@ private: InWriteAppendOrRedirectionPath, InRedirectionPath, }; - State m_state { Free }; + + State state() const { return m_state_stack.last(); } + + void pop_state() + { + m_state_stack.take_last(); + } + + void push_state(State state) + { + m_state_stack.append(state); + } + + bool in_state(State) const; + + Vector m_state_stack { Free }; String m_input; Vector m_commands;