#ifndef __AST_HPP_X_ #define __AST_HPP_X_ // #define DEBUG_MODE_ON #include "command_reg.hpp" #include "game_state.hpp" #include <atomic> #include <cmath> #include <cstdio> #include <filesystem> #include <fstream> #include <functional> #include <iostream> #include <memory> #include <optional> #include <set> #include <sstream> #include <string> #include <unordered_map> #include <vector> #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) #include <signal.h> #include <unistd.h> #endif #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) inline volatile sig_atomic_t program_running = 0; #endif namespace cmd { class Node; class Expression; class AstExecutor; class Command; class Pipeline; class Redirection; class Sequence; class StringLiteral; class NumberLiteral; class VariableReference; class VariableAssignment; class LogicalAnd; class LogicalOr; class LogicalNot; class BinaryExpression; class UnaryExpression; class CommandSubstitution; class IfStatement; class WhileStatement; class ForStatement; class Break; class Continue; extern bool windows_mode; class AstFailure { public: AstFailure(const std::string &message) : message(message) {} const char *what() const noexcept { return message.c_str(); } private: std::string message; }; class BreakException { public: const char *what() const noexcept { return "Break statement"; } }; class ContinueException { public: const char *what() const noexcept { return "Continue statement"; } }; class ReturnException {}; class Exit_Exception { public: explicit Exit_Exception(int code) : code(code) {} int getCode() const { return code; } private: int code; }; class Node { public: virtual ~Node() = default; virtual void print(std::ostream &out, int indent = 0) const = 0; virtual std::string toString() const { return "Node"; } virtual void execute(const AstExecutor &executor) const {} protected: std::string getIndent(int indent) const { return std::string(indent, ' '); } std::string escapeHtml(const std::string &text) const { std::string result = text; size_t pos = 0; while ((pos = result.find("<", pos)) != std::string::npos) { result.replace(pos, 1, "<"); pos += 4; } pos = 0; while ((pos = result.find(">", pos)) != std::string::npos) { result.replace(pos, 1, ">"); pos += 4; } pos = 0; while ((pos = result.find("&", pos)) != std::string::npos && result.substr(pos, 4) != "<" && result.substr(pos, 4) != ">") { result.replace(pos, 1, "&"); pos += 5; } pos = 0; while ((pos = result.find("\"", pos)) != std::string::npos) { result.replace(pos, 1, """); pos += 6; } return result; } }; class Break : public Node { public: void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node break'>\n"; out << spaces << " <h3>Break</h3>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"keyword\">break</span>"; } }; class Continue : public Node { public: void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node continue'>\n"; out << spaces << " <h3>Continue</h3>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"keyword\">continue</span>"; } }; class Expression : public Node { public: virtual ~Expression() = default; virtual std::string evaluate(const AstExecutor &executor) const = 0; virtual double evaluateNumber(const AstExecutor &executor) const = 0; virtual std::string toString() const override { return "Expression"; } }; class Return : public Node { public: explicit Return(std::shared_ptr<Expression> value) : value(std::move(value)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node return'>\n"; out << spaces << " <h3>Return</h3>\n"; out << spaces << " <div class='value'>\n"; value->print(out, indent + 4); out << spaces << " </div>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "return " + value->toString(); } std::shared_ptr<Expression> value; }; enum ArgType { ARG_LITERAL, ARG_VARIABLE, ARG_STRING_LITERAL, ARG_COMMAND_SUBST }; struct Argument { std::string value; ArgType type; std::shared_ptr<Node> cmdNode = nullptr; }; std::string getVar(const Argument &arg); std::string parseEscapeSequences(const std::string &input); std::string getArgTypeString(const Argument &arg); class Command : public Node { public: Command(std::string name, std::vector<Argument> args) : name(std::move(name)), args(std::move(args)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node command'>\n"; out << spaces << " <h3>Command: " << escapeHtml(name) << "</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th colspan='2'>Arguments</th></tr>\n"; for (const auto &arg : args) { out << spaces << " <tr><td class='" << (arg.type == ARG_VARIABLE ? "variable" : (arg.type == ARG_STRING_LITERAL ? "string-literal" : "literal")) << "'>" << escapeHtml(arg.value) << "</td>"; out << "<td>(" << (arg.type == ARG_VARIABLE ? "variable" : (arg.type == ARG_STRING_LITERAL ? "string literal" : "literal")) << ")</td></tr>\n"; } out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result = "<span class=\"command\">" + escapeHtml(name) + "</span>"; for (const auto &arg : args) { result += " "; if (arg.type == ARG_VARIABLE) { result += "<span class=\"variable\">" + escapeHtml(arg.value) + "</span>"; } else if (arg.type == ARG_STRING_LITERAL) { result += "<span class=\"string\">\"" + escapeHtml(arg.value) + "\"</span>"; } else { result += "<span class=\"argument\">" + escapeHtml(arg.value) + "</span>"; } } return result; } std::string name; std::vector<Argument> args; }; class CommandDefinition : public Node { public: CommandDefinition(std::string name, std::vector<std::string> parameters, std::shared_ptr<Node> body) : name(std::move(name)), parameters(std::move(parameters)), body(std::move(body)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node command-definition'>\n"; out << spaces << " <h3>CommandDefinition: " << escapeHtml(name) << "</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Parameters</th></tr>\n"; out << spaces << " <tr><td>\n"; for (const auto ¶m : parameters) { out << spaces << " <div class='parameter'>" << escapeHtml(param) << "</div>\n"; } out << spaces << " </td></tr>\n"; out << spaces << " <tr><th>Body</th></tr>\n"; out << spaces << " <tr><td>\n"; body->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result = "<span class=\"keyword\">function</span> <span class=\"function\">" + escapeHtml(name) + "</span>(<span class=\"variable\">"; for (size_t i = 0; i < parameters.size(); i++) { result += escapeHtml(parameters[i]); if (i < parameters.size() - 1) { result += "</span>, <span class=\"variable\">"; } } result += "</span>) {\n" + body->toString() + "\n}"; return result; } std::string name; std::vector<std::string> parameters; std::shared_ptr<Node> body; }; class LogicalAnd : public Node { public: LogicalAnd(std::shared_ptr<Node> left, std::shared_ptr<Node> right) : left(std::move(left)), right(std::move(right)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node logical-and'>\n"; out << spaces << " <h3>LogicalAnd</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th colspan='2'>Left Operand</th></tr>\n"; out << spaces << " <tr><td colspan='2'>\n"; left->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><td colspan='2' class='symbol'>&&</td></tr>\n"; out << spaces << " <tr><th colspan='2'>Right Operand</th></tr>\n"; out << spaces << " <tr><td colspan='2'>\n"; right->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return left->toString() + " <span class=\"operator\">&&</span> " + right->toString(); } std::shared_ptr<Node> left; std::shared_ptr<Node> right; }; class LogicalOr : public Node { public: LogicalOr(std::shared_ptr<Node> left, std::shared_ptr<Node> right) : left(std::move(left)), right(std::move(right)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node logical-or'>\n"; out << spaces << " <h3>LogicalOr</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th colspan='2'>Left Operand</th></tr>\n"; out << spaces << " <tr><td colspan='2'>\n"; left->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><td colspan='2' class='symbol'>||</td></tr>\n"; out << spaces << " <tr><th colspan='2'>Right Operand</th></tr>\n"; out << spaces << " <tr><td colspan='2'>\n"; right->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return left->toString() + " <span class=\"operator\">||</span> " + right->toString(); } std::shared_ptr<Node> left; std::shared_ptr<Node> right; }; class LogicalNot : public Node { public: LogicalNot(std::shared_ptr<Node> operand) : operand(std::move(operand)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node logical-not'>\n"; out << spaces << " <h3>LogicalNot</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Operand</th></tr>\n"; out << spaces << " <tr><td>\n"; operand->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"operator\">!</span>" + operand->toString(); } std::shared_ptr<Node> operand; }; class IfStatement : public Node { public: struct Branch { std::shared_ptr<Node> condition; std::shared_ptr<Node> action; }; IfStatement(std::vector<Branch> branches, std::shared_ptr<Node> elseAction = nullptr) : branches(std::move(branches)), elseAction(std::move(elseAction)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node if-statement'>\n"; out << spaces << " <h3>IfStatement</h3>\n"; out << spaces << " <table>\n"; for (size_t i = 0; i < branches.size(); ++i) { out << spaces << " <tr><th colspan='2'>" << (i == 0 ? "if" : "elif") << "</th></tr>\n"; out << spaces << " <tr><td>condition:</td><td>\n"; branches[i].condition->print(out, indent + 8); out << spaces << " </td></tr>\n"; out << spaces << " <tr><td>then:</td><td>\n"; branches[i].action->print(out, indent + 8); out << spaces << " </td></tr>\n"; } if (elseAction) { out << spaces << " <tr><th colspan='2'>else</th></tr>\n"; out << spaces << " <tr><td colspan='2'>\n"; elseAction->print(out, indent + 8); out << spaces << " </td></tr>\n"; } out << spaces << " <tr><td colspan='2' class='symbol'>fi</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result = "<span class=\"keyword\">if</span> "; for (const auto &branch : branches) { result += branch.condition->toString() + " <span class=\"keyword\">then</span> " + branch.action->toString() + " "; } if (elseAction) { result += " <span class=\"keyword\">else</span> " + elseAction->toString(); } result += " <span class=\"keyword\">fi</span>"; return result; } std::vector<Branch> branches; std::shared_ptr<Node> elseAction; }; class WhileStatement : public Node { public: WhileStatement(std::shared_ptr<Node> condition, std::shared_ptr<Node> body) : condition(std::move(condition)), body(std::move(body)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node while-statement'>\n"; out << spaces << " <h3>WhileStatement</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Condition</th></tr>\n"; out << spaces << " <tr><td>\n"; condition->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><th>Body</th></tr>\n"; out << spaces << " <tr><td>\n"; body->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><td class='symbol'>done</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"keyword\">while</span> " + condition->toString() + " <span class=\"keyword\">do</span> " + body->toString() + " <span class=\"keyword\">done</span>"; } std::shared_ptr<Node> condition; std::shared_ptr<Node> body; }; class ForStatement : public Node { public: ForStatement(std::string variable, std::vector<Argument> values, std::shared_ptr<Node> body) : variable(std::move(variable)), values(std::move(values)), body(std::move(body)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node for-statement'>\n"; out << spaces << " <h3>ForStatement</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Variable</th><td class='variable'>" << escapeHtml(variable) << "</td></tr>\n"; out << spaces << " <tr><th>Values</th><td>\n"; out << spaces << " <table>\n"; for (const auto &val : values) { out << spaces << " <tr><td class='" << (val.type == ARG_LITERAL ? "literal" : "variable") << "'>" << escapeHtml(val.value) << "</td><td>" << (val.type == ARG_LITERAL ? "literal" : "variable") << "</td></tr>\n"; } out << spaces << " </table>\n"; out << spaces << " </td></tr>\n"; out << spaces << " <tr><th>Body</th><td>\n"; body->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><td colspan='2' class='symbol'>done</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result = "<span class=\"keyword\">for</span> <span class=\"variable\">" + escapeHtml(variable) + "</span> <span class=\"keyword\">in</span> "; for (const auto &val : values) { result += val.value + " "; } result += "<span class=\"keyword\">do</span> " + body->toString() + " <span class=\"keyword\">done</span>"; return result; } std::string variable; std::vector<Argument> values; std::shared_ptr<Node> body; }; class Pipeline : public Node { public: Pipeline(std::vector<std::shared_ptr<Command>> commands) : commands(std::move(commands)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node pipeline'>\n"; out << spaces << " <h3>Pipeline</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Commands</th></tr>\n"; for (size_t i = 0; i < commands.size(); i++) { out << spaces << " <tr><td>\n"; commands[i]->print(out, indent + 6); if (i < commands.size() - 1) { out << spaces << " <div class='symbol'>|</div>\n"; } out << spaces << " </td></tr>\n"; } out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result; for (size_t i = 0; i < commands.size(); i++) { result += commands[i]->toString(); if (i < commands.size() - 1) { result += " <span class=\"operator\">|</span> "; } } return result; } std::vector<std::shared_ptr<Command>> commands; }; class Redirection : public Node { public: enum Type { INPUT, OUTPUT, APPEND }; Redirection(std::shared_ptr<Node> command, Type type, std::string file) : command(std::move(command)), type(type), file(std::move(file)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node redirection'>\n"; out << spaces << " <h3>Redirection</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Type</th><td class='symbol'>"; if (type == INPUT) { out << "< (Input)"; } else if (type == OUTPUT) { out << "> (Output)"; } else { out << ">> (Append)"; } out << "</td></tr>\n"; out << spaces << " <tr><th>File</th><td class='filename'>" << escapeHtml(file) << "</td></tr>\n"; out << spaces << " <tr><th>Command</th><td>\n"; command->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result; if (type == INPUT) { result += "< " + file + " "; } else if (type == OUTPUT) { result += "> " + file + " "; } else { result += ">> " + file + " "; } result += command->toString(); return result; } std::shared_ptr<Node> command; Type type; std::string file; }; class Sequence : public Node { public: Sequence(std::vector<std::shared_ptr<Node>> commands) : commands(std::move(commands)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node sequence'>\n"; out << spaces << " <h3>Sequence</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Commands</th></tr>\n"; for (size_t i = 0; i < commands.size(); i++) { out << spaces << " <tr><td>\n"; commands[i]->print(out, indent + 6); out << spaces << " </td></tr>\n"; } out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result; for (size_t i = 0; i < commands.size(); i++) { result += commands[i]->toString(); if (i < commands.size() - 1) { result += " <span class=\"operator\">;</span> "; } } return result; } std::vector<std::shared_ptr<Node>> commands; }; class StringLiteral : public Expression { public: StringLiteral(std::string value) : value(std::move(value)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node string-literal'>\n"; out << spaces << " <h3>StringLiteral</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Value</th></tr>\n"; out << spaces << " <tr><td class='string-literal'>" << escapeHtml(value) << "</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string evaluate(const AstExecutor &executor) const override { return value; } double evaluateNumber(const AstExecutor &executor) const override { try { return std::stod(value); } catch (const std::exception &) { return 0.0; } } std::string toString() const override { return "<span class=\"string\">\"" + escapeHtml(value) + "\"</span>"; } std::string value; }; class VariableAssignment : public Node { public: VariableAssignment(std::string name, std::shared_ptr<Node> value) : name(std::move(name)), value(std::move(value)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node variable-assignment'>\n"; out << spaces << " <h3>VariableAssignment</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Name</th><th>Value</th></tr>\n"; out << spaces << " <tr><td class='variable'>" << escapeHtml(name) << "</td><td>\n"; value->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"variable\">" + escapeHtml(name) + "</span> <span class=\"operator\">=</span> " + value->toString(); } std::string name; std::shared_ptr<Node> value; }; class NumberLiteral : public Expression { public: NumberLiteral(double value) : value(value) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node number-literal'>\n"; out << spaces << " <h3>NumberLiteral</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Value</th></tr>\n"; out << spaces << " <tr><td class='literal'>" << value << "</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"number\">" + std::to_string(value) + "</span>"; } std::string evaluate(const AstExecutor &executor) const override { return std::to_string(value); } double evaluateNumber(const AstExecutor &executor) const override { return value; } double value; }; class VariableReference : public Expression { public: VariableReference(const std::string &name) : name(name) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node variable-reference'>\n"; out << spaces << " <h3>VariableReference</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Name</th></tr>\n"; out << spaces << " <tr><td class='variable'>" << escapeHtml(name) << "</td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { return "<span class=\"variable\">" + escapeHtml(name) + "</span>"; } std::string evaluate(const AstExecutor &executor) const override; double evaluateNumber(const AstExecutor &executor) const override; std::string name; }; class UnaryExpression : public Expression { public: enum OpType { INCREMENT, DECREMENT, NEGATE }; enum Position { PREFIX, POSTFIX }; UnaryExpression(std::shared_ptr<Expression> operand, OpType op, Position pos = PREFIX) : operand(std::move(operand)), op(op), position(pos) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node unary-expression'>\n"; out << spaces << " <h3>UnaryExpression</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Operator</th><td class='operator'>"; switch (op) { case INCREMENT: out << "++"; break; case DECREMENT: out << "--"; break; case NEGATE: out << "-"; break; } out << "</td></tr>\n"; out << spaces << " <tr><th>Position</th><td class='position'>" << (position == PREFIX ? "prefix" : "postfix") << "</td></tr>\n"; out << spaces << " <tr><th>Operand</th><td>\n"; operand->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string result; if (position == PREFIX) { if (op == INCREMENT) result += "<span class=\"operator\">++</span>"; else if (op == DECREMENT) result += "<span class=\"operator\">--</span>"; else result += "<span class=\"operator\">-</span>"; } result += operand->toString(); if (position == POSTFIX) { if (op == INCREMENT) result += "<span class=\"operator\">++</span>"; else result += "<span class=\"operator\">--</span>"; } return result; } std::string evaluate(const AstExecutor &executor) const override { return std::to_string(evaluateNumber(executor)); } double evaluateNumber(const AstExecutor &executor) const override; std::shared_ptr<Expression> operand; OpType op; Position position; }; class AstExecutor { AstExecutor(); std::atomic<bool> *exec_interrupt = nullptr; public: bool windows_mode = false; static AstExecutor &getExecutor(); void setInterrupt(std::atomic<bool> *interrupt) { exec_interrupt = interrupt; } void setInterruptValue(bool b) { if (exec_interrupt) exec_interrupt->store(b); } bool checkInterrupt() { return exec_interrupt != nullptr && exec_interrupt->load(); } bool on_fail = true; void setUpdateCallback(std::function<void(const std::string &)> callback); void execUpdateCallback(const std::string &text); void setTerm(const bool &t) { on_fail = t; } std::string getPath() const { return std::filesystem::current_path().string(); } void setPath(const std::string &newPath) { if (std::filesystem::exists(newPath)) { path = newPath; std::filesystem::current_path(path); } else { throw std::runtime_error("Path does not exist: " + newPath + " if in local directory use ./ before file name"); } } void addCommand(const std::string &name, CommandFunction func) { registry.registerCommand(name, func); } void execute(std::istream &defaultInput, std::ostream &defaultOutputStream, const std::shared_ptr<cmd::Node> &node) { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 1; #endif returnSignal = false; try { #ifdef DEBUG_MODE_ON if (std::dynamic_pointer_cast<cmd::Command>(node)) { std::cout << "DEBUG: Node is a Command" << std::endl; } else if (std::dynamic_pointer_cast<cmd::Sequence>(node)) { std::cout << "DEBUG: Node is a Sequence" << std::endl; } else if (std::dynamic_pointer_cast<cmd::Pipeline>(node)) { std::cout << "DEBUG: Node is a Pipeline" << std::endl; } else if (std::dynamic_pointer_cast<cmd::Redirection>(node)) { std::cout << "DEBUG: Node is a Redirection" << std::endl; } else if (std::dynamic_pointer_cast<cmd::VariableAssignment>(node)) { std::cout << "DEBUG: Node is a VariableAssignment" << std::endl; } else { std::cout << "DEBUG: Node is of UNKNOWN type" << std::endl; } #endif #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { defaultOutputStream << "Command interrupted" << std::endl; defaultOutputStream.flush(); return; } #endif executeNode(node, defaultInput, defaultOutputStream); defaultOutputStream.flush(); if (&defaultOutputStream == &std::cout) { std::fflush(stdout); } } catch (const AstFailure &e) { defaultOutputStream.flush(); if (&defaultOutputStream == &std::cout) { std::fflush(stdout); } throw; } catch (const std::exception &e) { defaultOutputStream << "Exception: " << e.what() << std::endl; defaultOutputStream.flush(); if (&defaultOutputStream == &std::cout) { std::fflush(stdout); } } } void setVariable(const std::string &name, const std::string &value) { state::GameState *gameState = state::getGameState(); gameState->setVariable(name, value); } std::optional<std::string> getVariable(const std::string &name) const { state::GameState *gameState = state::getGameState(); try { return gameState->getVariable(name); } catch (const state::StateException &e) { return std::nullopt; } return std::nullopt; } std::string expandVariables(const std::string &input, std::set<std::string> expandingVars = {}) const { if (input.empty()) return input; std::string result = input; if (std::isalpha(input[0]) || input[0] == '_') { bool isValidVarName = true; for (char c : input) { if (!std::isalnum(c) && c != '_') { isValidVarName = false; break; } } if (isValidVarName) { auto value = getVariable(input); if (value) { return *value; } else { throw std::runtime_error("Invalid varaible name: " + input); } } } size_t pos = 0; while ((pos = result.find("${", pos)) != std::string::npos) { size_t end = result.find("}", pos); if (end == std::string::npos) break; std::string varName = result.substr(pos + 2, end - pos - 2); if (expandingVars.find(varName) != expandingVars.end()) { result.replace(pos, end - pos + 1, "[circular reference]"); pos += 20; throw std::runtime_error("Circular reference detected for variable: " + varName); continue; } auto value = getVariable(varName); if (value) { std::set<std::string> newExpandingVars = expandingVars; newExpandingVars.insert(varName); std::string expandedValue = expandVariables(*value, newExpandingVars); result.replace(pos, end - pos + 1, expandedValue); pos += expandedValue.length(); } else { result.replace(pos, end - pos + 1, ""); } } pos = 0; while ((pos = result.find("$", pos)) != std::string::npos) { if (pos + 1 < result.size() && result[pos + 1] == '{') { pos++; continue; } size_t end = pos + 1; while (end < result.size() && (std::isalnum(result[end]) || result[end] == '_')) { end++; } if (end > pos + 1) { std::string varName = result.substr(pos + 1, end - pos - 1); if (expandingVars.find(varName) != expandingVars.end()) { result.replace(pos, end - pos, "[circular reference]"); pos += 20; continue; } auto value = getVariable(varName); if (value) { std::set<std::string> newExpandingVars = expandingVars; newExpandingVars.insert(varName); std::string expandedValue = expandVariables(*value, newExpandingVars); result.replace(pos, end - pos, expandedValue); pos += expandedValue.length(); } else { result.replace(pos, end - pos, ""); } } else { pos++; } } return result; } int getLastExitStatus() const { return lastExitStatus; } void executeInternal(std::istream &input, std::ostream &output, std::shared_ptr<Node> node) const { std::istream *oldInput = this->input; std::ostream *oldOutput = this->output; this->input = &input; this->output = &output; node->execute(*this); this->input = oldInput; this->output = oldOutput; } void executeDirectly(const std::shared_ptr<Node> &node, std::istream &input, std::ostream &output) { executeNode(node, input, output); } static CommandRegistry &getRegistry() { return registry; } void executeCommandDefinition(const std::shared_ptr<cmd::CommandDefinition> &def, std::istream &input, std::ostream &output) { CommandRegistry::UserDefinedCommandInfo info{def->parameters, def->body}; registry.registerUserDefinedCommand(def->name, info); lastExitStatus = 0; } void executeReturn(const std::shared_ptr<cmd::Return> &returnStmt, std::istream &input, std::ostream &output) { double result = returnStmt->value->evaluateNumber(*this); lastExitStatus = static_cast<int>(result); returnSignal = true; } static void printCommandInfo(std::ostream &out) { registry.printInfo(out); } bool getReturnSignal() const { return returnSignal; } void setReturnSignal(bool signal) { returnSignal = signal; } private: static CommandRegistry registry; static AstExecutor instance; std::string path; int lastExitStatus = 0; mutable std::istream *input = nullptr; mutable std::ostream *output = nullptr; bool returnSignal = false; void executeNode(const std::shared_ptr<cmd::Node> &node, std::istream &input, std::ostream &output); void executeCommand(const std::shared_ptr<cmd::Command> &cmd, std::istream &input, std::ostream &output) { if (cmd->args.size() == 1 && (cmd->args[0].value == "++" || cmd->args[0].value == "--")) { auto varName = cmd->name; auto op = cmd->args[0].value; auto varValue = getVariable(varName); if (varValue) { auto varRef = std::make_shared<cmd::VariableReference>(varName); auto unary = std::make_shared<cmd::UnaryExpression>( varRef, op == "++" ? cmd::UnaryExpression::INCREMENT : cmd::UnaryExpression::DECREMENT, cmd::UnaryExpression::POSTFIX); auto assignment = std::make_shared<cmd::VariableAssignment>(varName, unary); executeVariableAssignment(assignment, input, output); return; } } lastExitStatus = registry.executeCommand(cmd->name, cmd->args, input, output); output.flush(); if (&output == &std::cout) { std::fflush(stdout); } if (lastExitStatus != 0 && cmd->name != "test") { // output << cmd->name << ": command failed with exit status " << lastExitStatus << std::endl; throw AstFailure(cmd->name + ": command failed with exit status " + std::to_string(lastExitStatus)); } } void executeSequence(const std::shared_ptr<cmd::Sequence> &seq, std::istream &input, std::ostream &output) { for (const auto &cmd : seq->commands) { executeNode(cmd, input, output); if (returnSignal) { break; } } } void executePipeline(const std::shared_ptr<cmd::Pipeline> &pipe, std::istream &input, std::ostream &output) { if (pipe->commands.size() >= 1 && pipe->commands[0]->name == "exec") { std::ostringstream pipelineCmd; for (const auto &arg : pipe->commands[0]->args) { pipelineCmd << getVar(arg) << " "; } for (size_t i = 1; i < pipe->commands.size(); i++) { pipelineCmd << " | " << pipe->commands[i]->name; for (const auto &arg : pipe->commands[i]->args) { pipelineCmd << " " << getVar(arg); } } std::vector<Argument> newArgs; Argument arg; arg.value = pipelineCmd.str(); arg.type = ARG_LITERAL; newArgs.push_back(arg); lastExitStatus = registry.executeCommand("exec", newArgs, input, output); return; } std::stringstream buffer; for (size_t i = 0; i < pipe->commands.size() - 1; i++) { std::stringstream nextBuffer; auto cmd = pipe->commands[i]; std::istream *currentInput = (i == 0) ? &input : &buffer; lastExitStatus = registry.executeCommand(cmd->name, cmd->args, *currentInput, nextBuffer); if (lastExitStatus != 0) { output << cmd->name << ": command failed with exit status " << lastExitStatus << std::endl; if (on_fail) { throw AstFailure(cmd->name + ": command failed"); } } buffer = std::move(nextBuffer); } auto lastCmd = pipe->commands.back(); lastExitStatus = registry.executeCommand(lastCmd->name, lastCmd->args, buffer, output); if (lastExitStatus != 0) { output << lastCmd->name << ": command failed with exit status " << lastExitStatus << std::endl; if (on_fail) { throw AstFailure(lastCmd->name + ": command failed."); } } } void executeRedirection(const std::shared_ptr<cmd::Redirection> &redir, std::istream &input, std::ostream &output) { if (redir->type == cmd::Redirection::INPUT) { std::ifstream fileInput(redir->file); if (!fileInput) { throw std::runtime_error("Failed to open input file: " + redir->file); } executeNode(redir->command, fileInput, output); } else if (redir->type == cmd::Redirection::OUTPUT) { std::ofstream fileOutput(redir->file); if (!fileOutput) { throw std::runtime_error("Failed to open output file: " + redir->file); } executeNode(redir->command, input, fileOutput); } else if (redir->type == cmd::Redirection::APPEND) { std::ofstream fileOutput(redir->file, std::ios::app); if (!fileOutput) { throw std::runtime_error("Failed to open output file for append: " + redir->file); } executeNode(redir->command, input, fileOutput); } } void executeVariableAssignment(const std::shared_ptr<cmd::VariableAssignment> &varAssign, std::istream &input, std::ostream &output) { try { if (auto unaryExpr = std::dynamic_pointer_cast<cmd::UnaryExpression>(varAssign->value)) { if ((unaryExpr->op == cmd::UnaryExpression::INCREMENT || unaryExpr->op == cmd::UnaryExpression::DECREMENT)) { unaryExpr->evaluateNumber(*this); if (unaryExpr->position == cmd::UnaryExpression::PREFIX) { auto varRef = std::dynamic_pointer_cast<cmd::VariableReference>(unaryExpr->operand); if (varRef && varRef->name == varAssign->name) { lastExitStatus = 0; return; } } else if (unaryExpr->position == cmd::UnaryExpression::POSTFIX) { lastExitStatus = 0; return; } } } if (auto strLit = std::dynamic_pointer_cast<cmd::StringLiteral>(varAssign->value)) { std::string value = strLit->value; if (value.size() >= 2 && ((value.front() == '"' && value.back() == '"') || (value.front() == '\'' && value.back() == '\''))) { value = value.substr(1, value.size() - 2); } setVariable(varAssign->name, value); } else if (auto expr = std::dynamic_pointer_cast<cmd::Expression>(varAssign->value)) { try { std::string value = expr->evaluate(*this); setVariable(varAssign->name, value); } catch (const std::exception &e) { output << "Assignment Error: " << e.what() << "\n"; lastExitStatus = 1; if (on_fail) { throw AstFailure("Assignment Failed: " + std::string(e.what())); } return; } lastExitStatus = 0; return; } else { throw std::runtime_error("Unsupported value type in assignment"); } lastExitStatus = 0; return; } catch (const std::exception &e) { output << "Assignment error: " << e.what() << std::endl; lastExitStatus = 1; if (on_fail) { throw AstFailure("Assignment Failed: " + std::string(e.what())); } } } void executeLogicalAnd(const std::shared_ptr<cmd::LogicalAnd> &logicalAnd, std::istream &input, std::ostream &output) { executeNode(logicalAnd->left, input, output); if (lastExitStatus == 0) { executeNode(logicalAnd->right, input, output); } } void executeLogicalOr(const std::shared_ptr<cmd::LogicalOr> &logicalOr, std::istream &input, std::ostream &output) { executeNode(logicalOr->left, input, output); if (lastExitStatus != 0) { executeNode(logicalOr->right, input, output); } } void executeLogicalNot(const std::shared_ptr<cmd::LogicalNot> &logicalNot, std::istream &input, std::ostream &output) { executeNode(logicalNot->operand, input, output); lastExitStatus = (lastExitStatus == 0) ? 1 : 0; } void executeIfStatement(const std::shared_ptr<cmd::IfStatement> &ifStmt, std::istream &input, std::ostream &output) { for (const auto &branch : ifStmt->branches) { executeNode(branch.condition, input, output); if (lastExitStatus == 0) { executeNode(branch.action, input, output); return; } } if (ifStmt->elseAction) { executeNode(ifStmt->elseAction, input, output); } } void executeWhileStatement(const std::shared_ptr<cmd::WhileStatement> &whileStmt, std::istream &input, std::ostream &outputStream) { try { while (true) { std::ostringstream output; #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { outputStream << "- [ Loop interrupted ]-" << std::endl; break; } #endif executeNode(whileStmt->condition, input, output); if (returnSignal) { break; } if (lastExitStatus != 0) { execUpdateCallback(output.str()); break; } try { executeNode(whileStmt->body, input, output); if (returnSignal) { break; } } catch (const ContinueException &) { continue; } static size_t iterationCount = 0; if (++iterationCount % 10 == 0) { execUpdateCallback(output.str()); iterationCount = 0; } outputStream << output.str(); outputStream.flush(); if (&outputStream == &std::cout) { std::fflush(stdout); } } } catch (const BreakException &) { } } void executeForStatement(const std::shared_ptr<cmd::ForStatement> &forStmt, std::istream &input, std::ostream &output) { std::optional<std::string> originalValue = getVariable(forStmt->variable); bool hadOriginalValue = originalValue.has_value(); try { if (forStmt->values.size() == 1 && forStmt->values[0].type == ARG_VARIABLE) { std::string listName = forStmt->values[0].value; state::GameState *gameState = state::getGameState(); if (gameState->hasList(listName)) { size_t listSize = gameState->getListLength(listName); for (size_t i = 0; i < listSize; i++) { std::string item = gameState->getFromList(listName, i); setVariable(forStmt->variable, item); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "- [ Loop interrupted ]-" << std::endl; break; } #endif executeNode(forStmt->body, input, output); } catch (const ContinueException &) { continue; } } if (hadOriginalValue) { setVariable(forStmt->variable, originalValue.value()); } else { gameState->setVariable(forStmt->variable, ""); } return; } } for (const auto &value : forStmt->values) { if (value.type == ARG_COMMAND_SUBST && value.cmdNode) { std::stringstream cmdInput, cmdOutput; executeDirectly(value.cmdNode, cmdInput, cmdOutput); std::string result = cmdOutput.str(); while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) { result.pop_back(); } if (result.find('\n') != std::string::npos) { std::istringstream lineStream(result); std::string line; while (std::getline(lineStream, line)) { if (!line.empty()) { setVariable(forStmt->variable, line); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "- [ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } } else { std::istringstream wordStream(result); std::string word; while (wordStream >> word) { setVariable(forStmt->variable, word); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "-[ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } } else if (value.type == ARG_VARIABLE) { std::optional<std::string> varValue = getVariable(value.value); if (varValue.has_value()) { std::string valueStr = varValue.value(); if (valueStr.find('\n') != std::string::npos) { std::istringstream lineStream(valueStr); std::string line; while (std::getline(lineStream, line)) { if (!line.empty()) { setVariable(forStmt->variable, line); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "- [ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } } else if (valueStr.find_first_of(" \t") != std::string::npos) { std::istringstream wordStream(valueStr); std::string word; while (wordStream >> word) { setVariable(forStmt->variable, word); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "-[ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } else { setVariable(forStmt->variable, valueStr); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "-[ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } } else { std::string literalValue = value.value; if (literalValue.size() >= 2 && ((literalValue.front() == '"' && literalValue.back() == '"') || (literalValue.front() == '\'' && literalValue.back() == '\''))) { literalValue = literalValue.substr(1, literalValue.size() - 2); if (literalValue.find('\n') != std::string::npos) { std::istringstream lineStream(literalValue); std::string line; while (std::getline(lineStream, line)) { if (!line.empty()) { setVariable(forStmt->variable, line); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "-[ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); execUpdateCallback(""); } catch (const ContinueException &) { continue; } } } continue; } } setVariable(forStmt->variable, literalValue); try { #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) if (program_running == 0) { output << "-[ Loop interrupted ]- " << std::endl; break; } #endif executeNode(forStmt->body, input, output); } catch (const ContinueException &) { continue; } } } } catch (const BreakException &) { } if (hadOriginalValue) { setVariable(forStmt->variable, originalValue.value()); } else { try { state::GameState *gameState = state::getGameState(); gameState->setVariable(forStmt->variable, ""); } catch (...) { setVariable(forStmt->variable, ""); } } execUpdateCallback(""); } }; class BinaryExpression : public Expression { public: enum OpType { ADD, SUBTRACT, MULTIPLY, DIVIDE, MODULO }; BinaryExpression(std::shared_ptr<Expression> left, OpType op, std::shared_ptr<Expression> right) : left(std::move(left)), op(op), right(std::move(right)) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node binary-expression'>\n"; out << spaces << " <h3>BinaryExpression</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Operator</th><td class='operator'>"; switch (op) { case ADD: out << "++"; break; case SUBTRACT: out << "--"; break; case MULTIPLY: out << "*"; break; case DIVIDE: out << "/"; break; case MODULO: out << "%"; break; } out << "</td></tr>\n"; out << spaces << " <tr><th>Left Operand</th><td>\n"; left->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " <tr><th>Right Operand</th><td>\n"; right->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string toString() const override { std::string opSymbol; switch (op) { case ADD: opSymbol = "+"; break; case SUBTRACT: opSymbol = "-"; break; case MULTIPLY: opSymbol = "*"; break; case DIVIDE: opSymbol = "/"; break; case MODULO: opSymbol = "%"; break; } return left->toString() + " <span class=\"operator\">" + opSymbol + "</span> " + right->toString(); } std::string evaluate(const AstExecutor &executor) const override { return std::to_string(evaluateNumber(executor)); } double evaluateNumber(const AstExecutor &executor) const override { double leftVal = left->evaluateNumber(executor); double rightVal = right->evaluateNumber(executor); switch (op) { case ADD: return leftVal + rightVal; case SUBTRACT: return leftVal - rightVal; case MULTIPLY: return leftVal * rightVal; case DIVIDE: if (rightVal == 0) throw std::runtime_error("Division by zero"); return leftVal / rightVal; case MODULO: if (rightVal == 0) throw std::runtime_error("Modulo by zero"); return std::fmod(leftVal, rightVal); default: throw std::runtime_error("Unknown binary operator"); } } std::shared_ptr<Expression> left; OpType op; std::shared_ptr<Expression> right; }; class CommandSubstitution : public Expression { public: CommandSubstitution(std::shared_ptr<Node> command, bool is_var = true) : command(command), isVar(is_var) {} void print(std::ostream &out, int indent = 0) const override { std::string spaces = getIndent(indent); out << spaces << "<div class='node command-substitution'>\n"; out << spaces << " <h3>CommandSubstitution</h3>\n"; out << spaces << " <table>\n"; out << spaces << " <tr><th>Command</th></tr>\n"; out << spaces << " <tr><td>\n"; command->print(out, indent + 6); out << spaces << " </td></tr>\n"; out << spaces << " </table>\n"; out << spaces << "</div>\n"; } std::string evaluate(const AstExecutor &executor) const override { std::stringstream input; std::stringstream output; if (auto cmd = std::dynamic_pointer_cast<cmd::Command>(command)) { std::vector<Argument> expandedArgs; for (const auto &arg : cmd->args) { Argument expandedArg; if (arg.type == ARG_VARIABLE) { expandedArg.value = executor.expandVariables(arg.value); expandedArg.type = ARG_STRING_LITERAL; } else if (arg.type == ARG_STRING_LITERAL) { expandedArg.value = arg.value; expandedArg.type = ARG_STRING_LITERAL; } else if (arg.type == ARG_COMMAND_SUBST && arg.cmdNode) { std::stringstream nestedOutput; const_cast<AstExecutor &>(executor).executeDirectly(arg.cmdNode, input, nestedOutput); expandedArg.value = nestedOutput.str(); while (!expandedArg.value.empty() && (expandedArg.value.back() == '\n' || expandedArg.value.back() == '\r')) { expandedArg.value.pop_back(); } expandedArg.type = ARG_STRING_LITERAL; } else { expandedArg.value = arg.value; expandedArg.type = arg.type; } expandedArgs.push_back(expandedArg); } int exitStatus = const_cast<AstExecutor &>(executor).getRegistry().executeCommand( cmd->name, expandedArgs, input, output); if (exitStatus != 0) { } } else if (auto seq = std::dynamic_pointer_cast<cmd::Sequence>(command)) { } else { const_cast<AstExecutor &>(executor).executeDirectly(command, input, output); } std::string result = output.str(); while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) { result.pop_back(); } return result; } double evaluateNumber(const AstExecutor &executor) const override { std::string result = evaluate(executor); try { return std::stod(result); } catch (...) { return 0.0; } } std::string toString() const override { return "<span class=\"operator\">$(</span> " + command->toString() + " <span class=\"operator\">)</span>"; } std::shared_ptr<Node> command; bool isVar; }; } // namespace cmd #endif