123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- #include "Parser.h"
- #include <stdio.h>
- #include <unistd.h>
- #include <ctype.h>
- void Parser::commit_token()
- {
- if (m_token.is_empty())
- return;
- if (m_state == InRedirectionPath) {
- m_redirections.last().path = String::copy(m_token);
- m_token.clear_with_capacity();
- return;
- }
- m_tokens.append(String::copy(m_token));
- m_token.clear_with_capacity();
- };
- void Parser::commit_subcommand()
- {
- if (m_tokens.is_empty())
- return;
- m_subcommands.append({ move(m_tokens), move(m_redirections), {} });
- }
- void Parser::commit_command()
- {
- if (m_subcommands.is_empty())
- return;
- m_commands.append({ move(m_subcommands) });
- }
- void Parser::do_pipe()
- {
- m_redirections.append({ Redirection::Pipe, STDOUT_FILENO });
- commit_subcommand();
- }
- void Parser::begin_redirect_read(int fd)
- {
- m_redirections.append({ Redirection::FileRead, fd });
- }
- void Parser::begin_redirect_write(int fd)
- {
- m_redirections.append({ Redirection::FileWrite, fd });
- }
- Vector<Command> Parser::parse()
- {
- for (int i = 0; i < m_input.length(); ++i) {
- char ch = m_input.characters()[i];
- switch (m_state) {
- case State::Free:
- if (ch == ' ') {
- commit_token();
- break;
- }
- if (ch == ';') {
- commit_token();
- commit_subcommand();
- commit_command();
- break;
- }
- if (ch == '|') {
- commit_token();
- if (m_tokens.is_empty()) {
- fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
- return {};
- }
- do_pipe();
- break;
- }
- if (ch == '>') {
- commit_token();
- begin_redirect_write(STDOUT_FILENO);
- // Search for another > for append.
- m_state = State::InWriteAppendOrRedirectionPath;
- break;
- }
- if (ch == '<') {
- commit_token();
- begin_redirect_read(STDIN_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '\\') {
- if (i == m_input.length() - 1) {
- fprintf(stderr, "Syntax error: Nothing to escape (\\)\n");
- return {};
- }
- char next_ch = m_input.characters()[i + 1];
- m_token.append(next_ch);
- ++i;
- break;
- }
- if (ch == '\'') {
- m_state = State::InSingleQuotes;
- break;
- }
- if (ch == '\"') {
- m_state = State::InDoubleQuotes;
- break;
- }
-
- // redirection from zsh-style multi-digit fd, such as {10}>file
- if (ch == '{') {
- bool is_multi_fd_redirection = false;
- int redir_end = i + 1;
- while (redir_end < m_input.length()) {
- char lookahead_ch = m_input.characters()[redir_end];
- if (isdigit(lookahead_ch)) {
- ++redir_end;
- continue;
- }
- if (lookahead_ch == '}' && redir_end + 1 != m_input.length()) {
- // Disallow {}> and {}<
- if (redir_end == i + 1)
- break;
- ++redir_end;
- if (m_input.characters()[redir_end] == '>' || m_input.characters()[redir_end] == '<')
- is_multi_fd_redirection = true;
- break;
- }
- break;
- }
- if (is_multi_fd_redirection) {
- commit_token();
- int fd = atoi(&m_input.characters()[i + 1]);
- if (m_input.characters()[redir_end] == '>') {
- begin_redirect_write(fd);
- // Search for another > for append.
- m_state = State::InWriteAppendOrRedirectionPath;
- }
- if (m_input.characters()[redir_end] == '<') {
- begin_redirect_read(fd);
- m_state = State::InRedirectionPath;
- }
- i = redir_end;
- break;
- }
- }
- if (isdigit(ch)) {
- if (i != m_input.length() - 1) {
- char next_ch = m_input.characters()[i + 1];
- if (next_ch == '>') {
- commit_token();
- begin_redirect_write(ch - '0');
- ++i;
- // Search for another > for append.
- m_state = State::InWriteAppendOrRedirectionPath;
- break;
- }
- if (next_ch == '<') {
- commit_token();
- begin_redirect_read(ch - '0');
- ++i;
- m_state = State::InRedirectionPath;
- break;
- }
- }
- }
- m_token.append(ch);
- break;
- case State::InWriteAppendOrRedirectionPath:
- if (ch == '>') {
- commit_token();
- m_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;
- [[fallthrough]];
- case State::InRedirectionPath:
- if (ch == '<') {
- commit_token();
- begin_redirect_read(STDIN_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '>') {
- commit_token();
- begin_redirect_read(STDOUT_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '|') {
- commit_token();
- if (m_tokens.is_empty()) {
- fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
- return {};
- }
- do_pipe();
- m_state = State::Free;
- break;
- }
- if (ch == ' ')
- break;
- m_token.append(ch);
- break;
- case State::InSingleQuotes:
- if (ch == '\'') {
- commit_token();
- m_state = State::Free;
- break;
- }
- m_token.append(ch);
- break;
- case State::InDoubleQuotes:
- if (ch == '\"') {
- commit_token();
- m_state = State::Free;
- break;
- }
- if (ch == '\\') {
- if (i == m_input.length() - 1) {
- fprintf(stderr, "Syntax error: Nothing to escape (\\)\n");
- return {};
- }
- char next_ch = m_input.characters()[i + 1];
- if (next_ch == '$' || next_ch == '`'
- || next_ch == '"' || next_ch == '\\') {
- m_token.append(next_ch);
- ++i;
- continue;
- }
- m_token.append('\\');
- break;
- }
- m_token.append(ch);
- break;
- };
- }
- commit_token();
- commit_subcommand();
- commit_command();
- if (!m_subcommands.is_empty()) {
- for (auto& redirection : m_subcommands.last().redirections) {
- if (redirection.type == Redirection::Pipe) {
- fprintf(stderr, "Syntax error: Nothing after last pipe (|)\n");
- return {};
- }
- }
- }
- return move(m_commands);
- }
|