123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 |
- /*
- * Copyright (c) 2020, the SerenityOS developers.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- #include "Parser.h"
- #include <ctype.h>
- #include <stdio.h>
- #include <unistd.h>
- char Parser::peek()
- {
- if (m_offset == m_input.length())
- return 0;
- ASSERT(m_offset < m_input.length());
- return m_input[m_offset];
- }
- char Parser::consume()
- {
- auto ch = peek();
- ++m_offset;
- return ch;
- }
- void Parser::putback()
- {
- ASSERT(m_offset > 0);
- --m_offset;
- }
- bool Parser::expect(char ch)
- {
- return expect(StringView { &ch, 1 });
- }
- bool Parser::expect(const StringView& expected)
- {
- if (expected.length() + m_offset > m_input.length())
- return false;
- for (size_t i = 0; i < expected.length(); ++i) {
- if (peek() != expected[i])
- return false;
- consume();
- }
- return true;
- }
- template<typename A, typename... Args>
- RefPtr<A> Parser::create(Args... args)
- {
- return adopt(*new A(AST::Position { m_rule_start_offsets.last(), m_offset }, args...));
- }
- [[nodiscard]] OwnPtr<Parser::ScopedOffset> Parser::push_start()
- {
- return make<ScopedOffset>(m_rule_start_offsets, m_offset);
- }
- static constexpr bool is_whitespace(char c)
- {
- return c == ' ' || c == '\t';
- }
- static constexpr bool is_word_character(char c)
- {
- return (c <= '9' && c >= '0') || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '_';
- }
- static constexpr bool is_digit(char c)
- {
- return c <= '9' && c >= '0';
- }
- static constexpr auto is_not(char c)
- {
- return [c](char ch) { return ch != c; };
- }
- static constexpr auto is_any_of(StringView s)
- {
- return [s](char ch) { return s.contains(ch); };
- }
- static inline char to_byte(char a, char b)
- {
- char buf[3] { a, b, 0 };
- return strtol(buf, nullptr, 16);
- }
- RefPtr<AST::Node> Parser::parse()
- {
- m_offset = 0;
- auto toplevel = parse_toplevel();
- if (m_offset < m_input.length()) {
- // Parsing stopped midway, this is a syntax error.
- auto error_start = push_start();
- m_offset = m_input.length();
- auto syntax_error_node = create<AST::SyntaxError>("Unexpected tokens past the end");
- if (toplevel)
- return create<AST::Join>(move(toplevel), move(syntax_error_node));
- return syntax_error_node;
- }
- return toplevel;
- }
- RefPtr<AST::Node> Parser::parse_toplevel()
- {
- auto rule_start = push_start();
- if (auto sequence = parse_sequence())
- return create<AST::Execute>(sequence);
- return nullptr;
- }
- RefPtr<AST::Node> Parser::parse_sequence()
- {
- consume_while(is_any_of(" \t\n;")); // ignore whitespaces or terminators without effect.
- auto rule_start = push_start();
- auto var_decls = parse_variable_decls();
- switch (peek()) {
- case '}':
- return var_decls;
- case ';':
- case '\n': {
- if (!var_decls)
- break;
- consume_while(is_any_of("\n;"));
- auto rest = parse_sequence();
- if (rest)
- return create<AST::Sequence>(move(var_decls), move(rest));
- return var_decls;
- }
- default:
- break;
- }
- RefPtr<AST::Node> first = nullptr;
- if (auto control_structure = parse_control_structure())
- first = control_structure;
- if (!first)
- first = parse_or_logical_sequence();
- if (!first)
- return var_decls;
- if (var_decls)
- first = create<AST::Sequence>(move(var_decls), move(first));
- consume_while(is_whitespace);
- switch (peek()) {
- case ';':
- case '\n':
- consume_while(is_any_of("\n;"));
- if (auto expr = parse_sequence()) {
- return create<AST::Sequence>(move(first), move(expr)); // Sequence
- }
- return first;
- case '&': {
- auto execute_pipe_seq = first->would_execute() ? first : static_cast<RefPtr<AST::Node>>(create<AST::Execute>(first));
- consume();
- auto bg = create<AST::Background>(move(first)); // Execute Background
- if (auto rest = parse_sequence())
- return create<AST::Sequence>(move(bg), move(rest)); // Sequence Background Sequence
- return bg;
- }
- default:
- return first;
- }
- }
- RefPtr<AST::Node> Parser::parse_variable_decls()
- {
- auto rule_start = push_start();
- consume_while(is_whitespace);
- auto offset_before_name = m_offset;
- auto var_name = consume_while(is_word_character);
- if (var_name.is_empty())
- return nullptr;
- if (!expect('=')) {
- m_offset = offset_before_name;
- return nullptr;
- }
- auto name_expr = create<AST::BarewordLiteral>(move(var_name));
- auto start = push_start();
- auto expression = parse_expression();
- if (!expression || expression->is_syntax_error()) {
- m_offset = start->offset;
- if (peek() == '(') {
- consume();
- auto command = parse_pipe_sequence();
- if (!command)
- m_offset = start->offset;
- else if (!expect(')'))
- command->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren"));
- expression = command;
- }
- }
- if (!expression) {
- if (is_whitespace(peek())) {
- auto string_start = push_start();
- expression = create<AST::StringLiteral>("");
- } else {
- m_offset = offset_before_name;
- return nullptr;
- }
- }
- Vector<AST::VariableDeclarations::Variable> variables;
- variables.append({ move(name_expr), move(expression) });
- if (consume_while(is_whitespace).is_empty())
- return create<AST::VariableDeclarations>(move(variables));
- auto rest = parse_variable_decls();
- if (!rest)
- return create<AST::VariableDeclarations>(move(variables));
- ASSERT(rest->is_variable_decls());
- auto* rest_decl = static_cast<AST::VariableDeclarations*>(rest.ptr());
- variables.append(rest_decl->variables());
- return create<AST::VariableDeclarations>(move(variables));
- }
- RefPtr<AST::Node> Parser::parse_or_logical_sequence()
- {
- consume_while(is_whitespace);
- auto rule_start = push_start();
- auto and_sequence = parse_and_logical_sequence();
- if (!and_sequence)
- return nullptr;
- consume_while(is_whitespace);
- auto saved_offset = m_offset;
- if (!expect("||")) {
- m_offset = saved_offset;
- return and_sequence;
- }
- auto right_and_sequence = parse_and_logical_sequence();
- if (!right_and_sequence)
- right_and_sequence = create<AST::SyntaxError>("Expected an expression after '||'");
- return create<AST::Or>(create<AST::Execute>(move(and_sequence)), create<AST::Execute>(move(right_and_sequence)));
- }
- RefPtr<AST::Node> Parser::parse_and_logical_sequence()
- {
- consume_while(is_whitespace);
- auto rule_start = push_start();
- auto pipe_sequence = parse_pipe_sequence();
- if (!pipe_sequence)
- return nullptr;
- consume_while(is_whitespace);
- auto saved_offset = m_offset;
- if (!expect("&&")) {
- m_offset = saved_offset;
- return pipe_sequence;
- }
- auto right_and_sequence = parse_and_logical_sequence();
- if (!right_and_sequence)
- right_and_sequence = create<AST::SyntaxError>("Expected an expression after '&&'");
- return create<AST::And>(create<AST::Execute>(move(pipe_sequence)), create<AST::Execute>(move(right_and_sequence)));
- }
- RefPtr<AST::Node> Parser::parse_pipe_sequence()
- {
- auto rule_start = push_start();
- auto command = parse_command();
- if (!command)
- return nullptr;
- consume_while(is_whitespace);
- if (peek() != '|')
- return command;
- consume();
- if (auto pipe_seq = parse_pipe_sequence()) {
- return create<AST::Pipe>(move(command), move(pipe_seq)); // Pipe
- }
- putback();
- return command;
- }
- RefPtr<AST::Node> Parser::parse_command()
- {
- auto rule_start = push_start();
- consume_while(is_whitespace);
- auto redir = parse_redirection();
- if (!redir) {
- auto list_expr = parse_list_expression();
- if (!list_expr)
- return nullptr;
- auto cast = create<AST::CastToCommand>(move(list_expr)); // Cast List Command
- auto next_command = parse_command();
- if (!next_command)
- return cast;
- return create<AST::Join>(move(cast), move(next_command)); // Join List Command
- }
- auto command = parse_command();
- if (!command)
- return redir;
- return create<AST::Join>(move(redir), command); // Join Command Command
- }
- RefPtr<AST::Node> Parser::parse_control_structure()
- {
- auto rule_start = push_start();
- consume_while(is_whitespace);
- if (auto for_loop = parse_for_loop())
- return for_loop;
- return nullptr;
- }
- RefPtr<AST::Node> Parser::parse_for_loop()
- {
- auto rule_start = push_start();
- if (!expect("for")) {
- m_offset = rule_start->offset;
- return nullptr;
- }
- if (consume_while(is_any_of(" \t\n")).is_empty()) {
- m_offset = rule_start->offset;
- return nullptr;
- }
- auto variable_name = consume_while(is_word_character);
- Optional<size_t> in_start_position;
- if (variable_name.is_empty()) {
- variable_name = "it";
- } else {
- consume_while(is_whitespace);
- auto in_error_start = push_start();
- in_start_position = in_error_start->offset;
- if (!expect("in")) {
- auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop");
- return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block
- }
- }
- consume_while(is_whitespace);
- RefPtr<AST::Node> iterated_expression;
- {
- auto iter_error_start = push_start();
- iterated_expression = parse_expression();
- if (!iterated_expression) {
- auto syntax_error = create<AST::SyntaxError>("Expected an expression in 'for' loop");
- return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr, move(in_start_position)); // ForLoop Var Iterated Block
- }
- }
- consume_while(is_any_of(" \t\n"));
- {
- auto obrace_error_start = push_start();
- if (!expect('{')) {
- auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'for' loop body");
- return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(syntax_error), move(in_start_position)); // ForLoop Var Iterated Block
- }
- }
- auto body = parse_toplevel();
- {
- auto cbrace_error_start = push_start();
- if (!expect('}')) {
- auto error_start = push_start();
- RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'for' loop body");
- if (body)
- body->set_is_syntax_error(*syntax_error);
- else
- body = syntax_error;
- }
- }
- return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block
- }
- RefPtr<AST::Node> Parser::parse_redirection()
- {
- auto rule_start = push_start();
- auto pipe_fd = 0;
- auto number = consume_while(is_digit);
- if (number.is_empty()) {
- pipe_fd = -1;
- } else {
- auto fd = number.to_int();
- ASSERT(fd.has_value());
- pipe_fd = fd.value();
- }
- switch (peek()) {
- case '>': {
- consume();
- if (peek() == '>') {
- consume();
- consume_while(is_whitespace);
- pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
- auto path = parse_expression();
- if (!path) {
- if (!at_end()) {
- // Eat a character and hope the problem goes away
- consume();
- }
- return create<AST::SyntaxError>("Expected a path");
- }
- return create<AST::WriteAppendRedirection>(pipe_fd, move(path)); // Redirection WriteAppend
- }
- if (peek() == '&') {
- consume();
- // FIXME: 'fd>&-' Syntax not the best. needs discussion.
- if (peek() == '-') {
- consume();
- pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
- return create<AST::CloseFdRedirection>(pipe_fd); // Redirection CloseFd
- }
- int dest_pipe_fd = 0;
- auto number = consume_while(is_digit);
- pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
- if (number.is_empty()) {
- dest_pipe_fd = -1;
- } else {
- auto fd = number.to_int();
- ASSERT(fd.has_value());
- dest_pipe_fd = fd.value();
- }
- auto redir = create<AST::Fd2FdRedirection>(pipe_fd, dest_pipe_fd); // Redirection Fd2Fd
- if (dest_pipe_fd == -1)
- redir->set_is_syntax_error(*create<AST::SyntaxError>("Expected a file descriptor"));
- return redir;
- }
- consume_while(is_whitespace);
- pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO;
- auto path = parse_expression();
- if (!path) {
- if (!at_end()) {
- // Eat a character and hope the problem goes away
- consume();
- }
- return create<AST::SyntaxError>("Expected a path");
- }
- return create<AST::WriteRedirection>(pipe_fd, move(path)); // Redirection Write
- }
- case '<': {
- consume();
- enum {
- Read,
- ReadWrite,
- } mode { Read };
- if (peek() == '>') {
- mode = ReadWrite;
- consume();
- }
- consume_while(is_whitespace);
- pipe_fd = pipe_fd >= 0 ? pipe_fd : STDIN_FILENO;
- auto path = parse_expression();
- if (!path) {
- if (!at_end()) {
- // Eat a character and hope the problem goes away
- consume();
- }
- return create<AST::SyntaxError>("Expected a path");
- }
- if (mode == Read)
- return create<AST::ReadRedirection>(pipe_fd, move(path)); // Redirection Read
- return create<AST::ReadWriteRedirection>(pipe_fd, move(path)); // Redirection ReadWrite
- }
- default:
- return nullptr;
- }
- }
- RefPtr<AST::Node> Parser::parse_list_expression()
- {
- consume_while(is_whitespace);
- auto rule_start = push_start();
- Vector<RefPtr<AST::Node>> nodes;
- do {
- auto expr = parse_expression();
- if (!expr)
- break;
- nodes.append(move(expr));
- } while (!consume_while(is_whitespace).is_empty());
- if (nodes.is_empty())
- return nullptr;
- return create<AST::ListConcatenate>(move(nodes)); // Concatenate List
- }
- RefPtr<AST::Node> Parser::parse_expression()
- {
- auto rule_start = push_start();
- auto starting_char = peek();
- auto read_concat = [&](auto expr) -> RefPtr<AST::Node> {
- if (is_whitespace(peek()))
- return expr;
- if (auto next_expr = parse_expression())
- return create<AST::Juxtaposition>(move(expr), move(next_expr));
- return expr;
- };
- if (strchr("&|){} ;<>\n", starting_char) != nullptr)
- return nullptr;
- if (isdigit(starting_char)) {
- ScopedValueRollback offset_rollback { m_offset };
- auto redir = parse_redirection();
- if (redir)
- return nullptr;
- }
- if (starting_char == '$') {
- if (auto variable = parse_variable())
- return read_concat(variable);
- if (auto inline_exec = parse_evaluate())
- return read_concat(inline_exec);
- }
- if (starting_char == '#')
- return parse_comment();
- if (starting_char == '(') {
- consume();
- auto list = parse_list_expression();
- if (!expect(')')) {
- m_offset = rule_start->offset;
- return nullptr;
- }
- return read_concat(create<AST::CastToList>(move(list))); // Cast To List
- }
- return read_concat(parse_string_composite());
- }
- RefPtr<AST::Node> Parser::parse_string_composite()
- {
- auto rule_start = push_start();
- if (auto string = parse_string()) {
- if (auto next_part = parse_string_composite())
- return create<AST::Juxtaposition>(move(string), move(next_part)); // Concatenate String StringComposite
- return string;
- }
- if (auto variable = parse_variable()) {
- if (auto next_part = parse_string_composite())
- return create<AST::Juxtaposition>(move(variable), move(next_part)); // Concatenate Variable StringComposite
- return variable;
- }
- if (auto glob = parse_glob()) {
- if (auto next_part = parse_string_composite())
- return create<AST::Juxtaposition>(move(glob), move(next_part)); // Concatenate Glob StringComposite
- return glob;
- }
- if (auto bareword = parse_bareword()) {
- if (auto next_part = parse_string_composite())
- return create<AST::Juxtaposition>(move(bareword), move(next_part)); // Concatenate Bareword StringComposite
- return bareword;
- }
- if (auto inline_command = parse_evaluate()) {
- if (auto next_part = parse_string_composite())
- return create<AST::Juxtaposition>(move(inline_command), move(next_part)); // Concatenate Execute StringComposite
- return inline_command;
- }
- return nullptr;
- }
- RefPtr<AST::Node> Parser::parse_string()
- {
- auto rule_start = push_start();
- if (at_end())
- return nullptr;
- if (peek() == '"') {
- consume();
- auto inner = parse_doublequoted_string_inner();
- if (!inner)
- inner = create<AST::SyntaxError>("Unexpected EOF in string");
- if (!expect('"')) {
- inner = create<AST::DoubleQuotedString>(move(inner));
- inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating double quote"));
- return inner;
- }
- return create<AST::DoubleQuotedString>(move(inner)); // Double Quoted String
- }
- if (peek() == '\'') {
- consume();
- auto text = consume_while(is_not('\''));
- bool is_error = false;
- if (!expect('\''))
- is_error = true;
- auto result = create<AST::StringLiteral>(move(text)); // String Literal
- if (is_error)
- result->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating single quote"));
- return move(result);
- }
- return nullptr;
- }
- RefPtr<AST::Node> Parser::parse_doublequoted_string_inner()
- {
- auto rule_start = push_start();
- if (at_end())
- return nullptr;
- StringBuilder builder;
- while (!at_end() && peek() != '"') {
- if (peek() == '\\') {
- consume();
- if (at_end()) {
- break;
- }
- auto ch = consume();
- switch (ch) {
- case '\\':
- default:
- builder.append(ch);
- break;
- case 'x': {
- if (m_input.length() <= m_offset + 2)
- break;
- auto first_nibble = tolower(consume());
- auto second_nibble = tolower(consume());
- if (!isxdigit(first_nibble) || !isxdigit(second_nibble)) {
- builder.append(first_nibble);
- builder.append(second_nibble);
- break;
- }
- builder.append(to_byte(first_nibble, second_nibble));
- break;
- }
- case 'a':
- builder.append('\a');
- break;
- case 'b':
- builder.append('\b');
- break;
- case 'e':
- builder.append('\x1b');
- break;
- case 'f':
- builder.append('\f');
- break;
- case 'r':
- builder.append('\r');
- break;
- case 'n':
- builder.append('\n');
- break;
- }
- continue;
- }
- if (peek() == '$') {
- auto string_literal = create<AST::StringLiteral>(builder.to_string()); // String Literal
- if (auto variable = parse_variable()) {
- auto inner = create<AST::StringPartCompose>(
- move(string_literal),
- move(variable)); // Compose String Variable
- if (auto string = parse_doublequoted_string_inner()) {
- return create<AST::StringPartCompose>(move(inner), move(string)); // Compose Composition Composition
- }
- return inner;
- }
- if (auto evaluate = parse_evaluate()) {
- auto composition = create<AST::StringPartCompose>(
- move(string_literal),
- move(evaluate)); // Compose String Sequence
- if (auto string = parse_doublequoted_string_inner()) {
- return create<AST::StringPartCompose>(move(composition), move(string)); // Compose Composition Composition
- }
- return composition;
- }
- }
- builder.append(consume());
- }
- return create<AST::StringLiteral>(builder.to_string()); // String Literal
- }
- RefPtr<AST::Node> Parser::parse_variable()
- {
- auto rule_start = push_start();
- if (at_end())
- return nullptr;
- if (peek() != '$')
- return nullptr;
- consume();
- switch (peek()) {
- case '$':
- case '?':
- case '*':
- case '#':
- return create<AST::SpecialVariable>(consume()); // Variable Special
- default:
- break;
- }
- auto name = consume_while(is_word_character);
- if (name.length() == 0) {
- putback();
- return nullptr;
- }
- return create<AST::SimpleVariable>(move(name)); // Variable Simple
- }
- RefPtr<AST::Node> Parser::parse_evaluate()
- {
- auto rule_start = push_start();
- if (at_end())
- return nullptr;
- if (peek() != '$')
- return nullptr;
- consume();
- if (peek() == '(') {
- consume();
- auto inner = parse_pipe_sequence();
- if (!inner)
- inner = create<AST::SyntaxError>("Unexpected EOF in list");
- if (!expect(')'))
- inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren"));
- return create<AST::Execute>(move(inner), true);
- }
- auto inner = parse_expression();
- if (!inner) {
- inner = create<AST::SyntaxError>("Expected a command");
- } else {
- if (inner->is_list()) {
- auto execute_inner = create<AST::Execute>(move(inner), true);
- inner = execute_inner;
- } else {
- auto dyn_inner = create<AST::DynamicEvaluate>(move(inner));
- inner = dyn_inner;
- }
- }
- return inner;
- }
- RefPtr<AST::Node> Parser::parse_comment()
- {
- if (at_end())
- return nullptr;
- if (peek() != '#')
- return nullptr;
- consume();
- auto text = consume_while(is_not('\n'));
- return create<AST::Comment>(move(text)); // Comment
- }
- RefPtr<AST::Node> Parser::parse_bareword()
- {
- auto rule_start = push_start();
- StringBuilder builder;
- auto is_acceptable_bareword_character = [](char c) {
- return strchr("\\\"'*$&#|(){} ?;<>\n", c) == nullptr;
- };
- while (!at_end()) {
- char ch = peek();
- if (ch == '\\') {
- consume();
- if (!at_end()) {
- ch = consume();
- if (is_acceptable_bareword_character(ch))
- builder.append('\\');
- }
- builder.append(ch);
- continue;
- }
- if (is_acceptable_bareword_character(ch)) {
- builder.append(consume());
- continue;
- }
- break;
- }
- if (builder.is_empty())
- return nullptr;
- auto current_end = m_offset;
- auto string = builder.to_string();
- if (string.starts_with('~')) {
- String username;
- RefPtr<AST::Node> tilde, text;
- auto first_slash_index = string.index_of("/");
- if (first_slash_index.has_value()) {
- username = string.substring_view(1, first_slash_index.value() - 1);
- string = string.substring_view(first_slash_index.value(), string.length() - first_slash_index.value());
- } else {
- username = string.substring_view(1, string.length() - 1);
- string = "";
- }
- // Synthesize a Tilde Node with the correct positioning information.
- {
- m_offset -= string.length();
- tilde = create<AST::Tilde>(move(username));
- }
- if (string.is_empty())
- return tilde;
- // Synthesize a BarewordLiteral Node with the correct positioning information.
- {
- m_offset = tilde->position().end_offset;
- auto text_start = push_start();
- m_offset = current_end;
- text = create<AST::BarewordLiteral>(move(string));
- }
- return create<AST::Juxtaposition>(move(tilde), move(text)); // Juxtaposition Varible Bareword
- }
- if (string.starts_with("\\~")) {
- // Un-escape the tilde, but only at the start (where it would be an expansion)
- string = string.substring(1, string.length() - 1);
- }
- return create<AST::BarewordLiteral>(move(string)); // Bareword Literal
- }
- RefPtr<AST::Node> Parser::parse_glob()
- {
- auto rule_start = push_start();
- auto bareword_part = parse_bareword();
- if (at_end())
- return bareword_part;
- char ch = peek();
- if (ch == '*' || ch == '?') {
- consume();
- StringBuilder textbuilder;
- if (bareword_part) {
- StringView text;
- if (bareword_part->is_bareword()) {
- auto bareword = static_cast<AST::BarewordLiteral*>(bareword_part.ptr());
- text = bareword->text();
- } else {
- // FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*'
- putback();
- bareword_part->set_is_syntax_error(*create<AST::SyntaxError>(String::format("Unexpected %s inside a glob", bareword_part->class_name().characters())));
- return bareword_part;
- }
- textbuilder.append(text);
- }
- textbuilder.append(ch);
- auto glob_after = parse_glob();
- if (glob_after) {
- if (glob_after->is_glob()) {
- auto glob = static_cast<AST::BarewordLiteral*>(glob_after.ptr());
- textbuilder.append(glob->text());
- } else if (glob_after->is_bareword()) {
- auto bareword = static_cast<AST::BarewordLiteral*>(glob_after.ptr());
- textbuilder.append(bareword->text());
- } else {
- ASSERT_NOT_REACHED();
- }
- }
- return create<AST::Glob>(textbuilder.to_string()); // Glob
- }
- return bareword_part;
- }
- StringView Parser::consume_while(Function<bool(char)> condition)
- {
- auto start_offset = m_offset;
- while (!at_end() && condition(peek()))
- consume();
- return m_input.substring_view(start_offset, m_offset - start_offset);
- }
|