#include "mpw-shell.h" #include "fdset.h" #include "value.h" #include #include /* * I'm sick of fighting with lemon. Just generate it by hand. * */ template T pop(std::vector &v) { T t = std::move(v.back()); v.pop_back(); return t; } int open(const std::string &name, int flags) { // dup2 does not copy the O_CLOEXEC flag so it's safe to use. int fd = ::open(name.c_str(), flags | O_CLOEXEC, 0666); if (fd < 0) { std::string error = "### MPW Shell - Unable to open "; error.push_back('"'); error.append(name); error.push_back('"'); error.push_back('.'); throw std::runtime_error(error); } return fd; } void parse_tokens(std::vector &&tokens, process &p) { fdset fds; std::vector argv; std::reverse(tokens.begin(), tokens.end()); argv.reserve(tokens.size()); // first token is always treated as a string. token t = pop(tokens); argv.emplace_back(std::move(t.string)); while(!tokens.empty()) { t = pop(tokens); switch (t.type) { // >, >> -- redirect stdout. case '>': case '>>': { int flags; if (t.type == '>') flags = O_WRONLY | O_CREAT | O_TRUNC; else flags = O_WRONLY | O_CREAT | O_APPEND; if (tokens.empty()) { throw std::runtime_error("### MPW Shell - Missing file name."); } token name = pop(tokens); int fd = open(name.string, flags); fds.set(1, fd); } break; // < -- redirect stdin. case '<': { int flags = O_RDONLY; if (tokens.empty()) { throw std::runtime_error("### MPW Shell - Missing file name."); } token name = pop(tokens); int fd = open(name.string, flags); fds.set(0, fd); } break; default: argv.emplace_back(std::move(t.string)); break; } } p.arguments = std::move(argv); p.fds = std::move(fds); } class expression_parser { public: expression_parser(const std::string &n, std::vector &&t) : name(n), tokens(std::move(t)) {} expression_parser(const expression_parser &) = delete; expression_parser(expression_parser &&) = delete; expression_parser& operator=(const expression_parser &) = delete; expression_parser& operator=(expression_parser &&) = delete; // returns integer value of the expression. int32_t evaluate(); private: value terminal(); value unary(); value binary(); value eval(int op, value &lhs, value &rhs); [[noreturn]] void expect_binary_operator(); [[noreturn]] void end_of_expression(); [[noreturn]] void divide_by_zero(); int peek_type() const; token next(); static int precedence(int); void skip() { if (!tokens.empty()) tokens.pop_back(); } const std::string &name; std::vector tokens; }; int expression_parser::peek_type() const { if (tokens.empty()) return token::eof; return tokens.back().type; } token expression_parser::next() { if (tokens.empty()) return token("", token::eof); // error? return pop(tokens); } void expression_parser::expect_binary_operator() { token t = next(); std::string error; error = "### " + name; error += " - Expected a binary operator when \""; error += t.string; error += "\" was encountered."; throw std::runtime_error(error); } void expression_parser::end_of_expression() { std::string error; error = "### " + name + " - Unexpected end of expression."; throw std::runtime_error(error); } void expression_parser::divide_by_zero() { std::string error; error = "### " + name + " - Attempt to divide by zero."; throw std::runtime_error(error); } value expression_parser::binary() { std::vector output; std::vector> operators; value v = unary(); output.emplace_back(std::move(v)); for(;;) { // check for an operator. int type = peek_type(); if (type == token::eof) break; if (type == ')') break; int p = precedence(type); if (!p) expect_binary_operator(); skip(); while (!operators.empty() && operators.back().second <= p) { // reduce top ops. int op = operators.back().first; operators.pop_back(); value rhs = pop(output); value lhs = pop(output); output.emplace_back(eval(op, lhs, rhs)); } operators.push_back(std::make_pair(type, p)); v = unary(); output.emplace_back(std::move(v)); } // reduce... while (!operators.empty()) { int op = pop(operators).first; value rhs = pop(output); value lhs = pop(output); output.emplace_back(eval(op, lhs, rhs)); } if (output.size() != 1) throw std::runtime_error("binary stack error"); return pop(output); } int expression_parser::precedence(int op) { switch (op) { case '*': case '%': case '/': return 3; case '+': case '-': return 4; case '>>': case '<<': return 5; case '<': case '<=': case '>': case '>=': return 6; case '==': case '!=': case token::equivalent: case token::not_equivalent: return 7; case '&': return 8; case '^': return 9; case '|': return 10; case '&&': return 11; case '||': return 12; } return 0; //throw std::runtime_error("unimplemented op";); } value expression_parser::eval(int op, value &lhs, value &rhs) { switch (op) { case '*': return lhs.to_number() * rhs.to_number(); case '/': if (!rhs.to_number()) divide_by_zero(); return lhs.to_number() / rhs.to_number(); case '%': if (!rhs.to_number()) divide_by_zero(); return lhs.to_number() % rhs.to_number(); case '+': return lhs.to_number() + rhs.to_number(); case '-': return lhs.to_number() - rhs.to_number(); case '>': return lhs.to_number() > rhs.to_number(); case '<': return lhs.to_number() < rhs.to_number(); case '<=': return lhs.to_number() <= rhs.to_number(); case '>=': return lhs.to_number() >= rhs.to_number(); case '>>': return lhs.to_number() >> rhs.to_number(); case '<<': return lhs.to_number() >> rhs.to_number(); // logical || . NaN ok case '||': return lhs.to_number(1) || rhs.to_number(1); // logical && . NaN ok case '&&': return lhs.to_number(1) && rhs.to_number(1); case '|': return lhs.to_number() | rhs.to_number(); case '&': return lhs.to_number() & rhs.to_number(); case '^': return lhs.to_number() ^ rhs.to_number(); case '==': // string ==. 0x00==0 -> 0 // as a special case, 0=="". go figure. if (lhs.string == "" && rhs.string == "0") return 1; if (lhs.string == "0" && rhs.string == "") return 1; return lhs.string == rhs.string; case '!=': if (lhs.string == "" && rhs.string == "0") return 0; if (lhs.string == "0" && rhs.string == "") return 0; return lhs.string != rhs.string; } // todo... throw std::runtime_error("unimplemented op"); } value expression_parser::unary() { int type = peek_type(); switch (type) { case '-': case '+': case '!': case '~': next(); value v = unary(); // + is a nop.. doesn't even check if it's a number. if (type == '-') v = -v.to_number(); if (type == '~') v = ~v.to_number(); if (type == '!') v = !v.to_number(1); // logical !, NaN ok. return v; } return terminal(); } value expression_parser::terminal() { int type = peek_type(); if (type == token::text) { token t = next(); return value(std::move(t.string)); } if (type == '(') { next(); value v = binary(); type = peek_type(); if (type != ')') { end_of_expression(); } next(); return v; } // insert a fake token. return value(); } int32_t expression_parser::evaluate() { if (tokens.empty()) return 0; value v = binary(); if (!tokens.empty()) { if (tokens.back().type == ')') throw std::runtime_error("### MPW Shell - Extra ) command."); throw std::runtime_error("evaluation stack error."); // ?? should be caught above. } return v.to_number(1); } int32_t evaluate_expression(const std::string &name, std::vector &&tokens) { expression_parser p(name, std::move(tokens)); return p.evaluate(); }