Parser.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #include "Parser.h"
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <ctype.h>
  5. void Parser::commit_token()
  6. {
  7. if (m_token.is_empty())
  8. return;
  9. if (m_state == InRedirectionPath) {
  10. m_redirections.last().path = String::copy(m_token);
  11. m_token.clear_with_capacity();
  12. return;
  13. }
  14. m_tokens.append(String::copy(m_token));
  15. m_token.clear_with_capacity();
  16. };
  17. void Parser::commit_subcommand()
  18. {
  19. if (m_tokens.is_empty())
  20. return;
  21. m_subcommands.append({ move(m_tokens), move(m_redirections), {} });
  22. }
  23. void Parser::commit_command()
  24. {
  25. if (m_subcommands.is_empty())
  26. return;
  27. m_commands.append({ move(m_subcommands) });
  28. }
  29. void Parser::do_pipe()
  30. {
  31. m_redirections.append({ Redirection::Pipe, STDOUT_FILENO });
  32. commit_subcommand();
  33. }
  34. void Parser::begin_redirect_read(int fd)
  35. {
  36. m_redirections.append({ Redirection::FileRead, fd });
  37. }
  38. void Parser::begin_redirect_write(int fd)
  39. {
  40. m_redirections.append({ Redirection::FileWrite, fd });
  41. }
  42. Vector<Command> Parser::parse()
  43. {
  44. for (int i = 0; i < m_input.length(); ++i) {
  45. char ch = m_input.characters()[i];
  46. switch (m_state) {
  47. case State::Free:
  48. if (ch == ' ') {
  49. commit_token();
  50. break;
  51. }
  52. if (ch == ';') {
  53. commit_token();
  54. commit_subcommand();
  55. commit_command();
  56. break;
  57. }
  58. if (ch == '|') {
  59. commit_token();
  60. if (m_tokens.is_empty()) {
  61. fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
  62. return {};
  63. }
  64. do_pipe();
  65. break;
  66. }
  67. if (ch == '>') {
  68. commit_token();
  69. begin_redirect_write(STDOUT_FILENO);
  70. // Search for another > for append.
  71. m_state = State::InWriteAppendOrRedirectionPath;
  72. break;
  73. }
  74. if (ch == '<') {
  75. commit_token();
  76. begin_redirect_read(STDIN_FILENO);
  77. m_state = State::InRedirectionPath;
  78. break;
  79. }
  80. if (ch == '\\') {
  81. if (i == m_input.length() - 1) {
  82. fprintf(stderr, "Syntax error: Nothing to escape (\\)\n");
  83. return {};
  84. }
  85. char next_ch = m_input.characters()[i + 1];
  86. m_token.append(next_ch);
  87. ++i;
  88. break;
  89. }
  90. if (ch == '\'') {
  91. m_state = State::InSingleQuotes;
  92. break;
  93. }
  94. if (ch == '\"') {
  95. m_state = State::InDoubleQuotes;
  96. break;
  97. }
  98. // redirection from zsh-style multi-digit fd, such as {10}>file
  99. if (ch == '{') {
  100. bool is_multi_fd_redirection = false;
  101. int redir_end = i + 1;
  102. while (redir_end < m_input.length()) {
  103. char lookahead_ch = m_input.characters()[redir_end];
  104. if (isdigit(lookahead_ch)) {
  105. ++redir_end;
  106. continue;
  107. }
  108. if (lookahead_ch == '}' && redir_end + 1 != m_input.length()) {
  109. // Disallow {}> and {}<
  110. if (redir_end == i + 1)
  111. break;
  112. ++redir_end;
  113. if (m_input.characters()[redir_end] == '>' || m_input.characters()[redir_end] == '<')
  114. is_multi_fd_redirection = true;
  115. break;
  116. }
  117. break;
  118. }
  119. if (is_multi_fd_redirection) {
  120. commit_token();
  121. int fd = atoi(&m_input.characters()[i + 1]);
  122. if (m_input.characters()[redir_end] == '>') {
  123. begin_redirect_write(fd);
  124. // Search for another > for append.
  125. m_state = State::InWriteAppendOrRedirectionPath;
  126. }
  127. if (m_input.characters()[redir_end] == '<') {
  128. begin_redirect_read(fd);
  129. m_state = State::InRedirectionPath;
  130. }
  131. i = redir_end;
  132. break;
  133. }
  134. }
  135. if (isdigit(ch)) {
  136. if (i != m_input.length() - 1) {
  137. char next_ch = m_input.characters()[i + 1];
  138. if (next_ch == '>') {
  139. commit_token();
  140. begin_redirect_write(ch - '0');
  141. ++i;
  142. // Search for another > for append.
  143. m_state = State::InWriteAppendOrRedirectionPath;
  144. break;
  145. }
  146. if (next_ch == '<') {
  147. commit_token();
  148. begin_redirect_read(ch - '0');
  149. ++i;
  150. m_state = State::InRedirectionPath;
  151. break;
  152. }
  153. }
  154. }
  155. m_token.append(ch);
  156. break;
  157. case State::InWriteAppendOrRedirectionPath:
  158. if (ch == '>') {
  159. commit_token();
  160. m_state = State::InRedirectionPath;
  161. ASSERT(m_redirections.size());
  162. m_redirections[m_redirections.size() - 1].type = Redirection::FileWriteAppend;
  163. break;
  164. }
  165. // Not another > means that it's probably a path.
  166. m_state = InRedirectionPath;
  167. [[fallthrough]];
  168. case State::InRedirectionPath:
  169. if (ch == '<') {
  170. commit_token();
  171. begin_redirect_read(STDIN_FILENO);
  172. m_state = State::InRedirectionPath;
  173. break;
  174. }
  175. if (ch == '>') {
  176. commit_token();
  177. begin_redirect_read(STDOUT_FILENO);
  178. m_state = State::InRedirectionPath;
  179. break;
  180. }
  181. if (ch == '|') {
  182. commit_token();
  183. if (m_tokens.is_empty()) {
  184. fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
  185. return {};
  186. }
  187. do_pipe();
  188. m_state = State::Free;
  189. break;
  190. }
  191. if (ch == ' ')
  192. break;
  193. m_token.append(ch);
  194. break;
  195. case State::InSingleQuotes:
  196. if (ch == '\'') {
  197. commit_token();
  198. m_state = State::Free;
  199. break;
  200. }
  201. m_token.append(ch);
  202. break;
  203. case State::InDoubleQuotes:
  204. if (ch == '\"') {
  205. commit_token();
  206. m_state = State::Free;
  207. break;
  208. }
  209. if (ch == '\\') {
  210. if (i == m_input.length() - 1) {
  211. fprintf(stderr, "Syntax error: Nothing to escape (\\)\n");
  212. return {};
  213. }
  214. char next_ch = m_input.characters()[i + 1];
  215. if (next_ch == '$' || next_ch == '`'
  216. || next_ch == '"' || next_ch == '\\') {
  217. m_token.append(next_ch);
  218. ++i;
  219. continue;
  220. }
  221. m_token.append('\\');
  222. break;
  223. }
  224. m_token.append(ch);
  225. break;
  226. };
  227. }
  228. commit_token();
  229. commit_subcommand();
  230. commit_command();
  231. if (!m_subcommands.is_empty()) {
  232. for (auto& redirection : m_subcommands.last().redirections) {
  233. if (redirection.type == Redirection::Pipe) {
  234. fprintf(stderr, "Syntax error: Nothing after last pipe (|)\n");
  235. return {};
  236. }
  237. }
  238. }
  239. return move(m_commands);
  240. }