#include"command.hpp" #include<fstream> #include<filesystem> #include<regex> #include<sstream> #include<iomanip> #include<cstdlib> #include<random> #include<algorithm> #include<cctype> #include"game_state.hpp" #include"parser.hpp" #include"html.hpp" #include"command_reg.hpp" #if !defined(__EMSCRIPTEN__) && !defined(_WIN32) #include<unistd.h> #include<sys/wait.h> #include<signal.h> #include<fcntl.h> #include<sys/select.h> #include<sys/ioctl.h> #ifdef __APPLE__ #include<util.h> #else #include<pty.h> #endif #include<sys/stat.h> #endif #if !defined(__EMSCRIPTEN__) && defined(_WIN32) #include <windows.h> #endif #include<thread> #include<mutex> #include<atomic> namespace state { GameState *getGameState() { static GameState gameState; return &gameState; } } namespace cmd { namespace { class PathPatternMatcher { public: static std::vector<std::string> expand(const std::string& token, bool matchFiles, bool matchDirectories, bool keepUnmatched = true) { if (!containsWildcard(token)) { return {token}; } std::filesystem::path raw(token); const std::filesystem::path cwd = std::filesystem::current_path(); std::vector<std::filesystem::path> candidates; if (raw.is_absolute()) { candidates.push_back(raw.root_path()); } else { candidates.push_back(cwd); } std::filesystem::path relative = raw.is_absolute() ? raw.relative_path() : raw; std::vector<std::string> segments; for (const auto& part : relative) { std::string segment = part.string(); if (!segment.empty()) { segments.push_back(segment); } } for (const auto& segment : segments) { std::vector<std::filesystem::path> nextCandidates; if (segment == ".") { nextCandidates = candidates; } else if (segment == "..") { for (const auto& base : candidates) { nextCandidates.push_back(base.parent_path()); } } else if (!containsWildcard(segment)) { for (const auto& base : candidates) { std::filesystem::path candidate = base / segment; if (std::filesystem::exists(candidate)) { nextCandidates.push_back(candidate); } } } else { std::regex matcher = wildcardToRegex(segment); for (const auto& base : candidates) { if (!std::filesystem::exists(base) || !std::filesystem::is_directory(base)) { continue; } try { for (const auto& entry : std::filesystem::directory_iterator(base)) { if (std::regex_match(entry.path().filename().string(), matcher)) { nextCandidates.push_back(entry.path()); } } } catch (const std::filesystem::filesystem_error&) { } } } candidates = dedupe(nextCandidates); if (candidates.empty()) { break; } } std::vector<std::string> expanded; for (const auto& path : candidates) { if (!std::filesystem::exists(path)) { continue; } if (std::filesystem::is_directory(path) && !matchDirectories) { continue; } if (std::filesystem::is_regular_file(path) && !matchFiles) { continue; } if (!std::filesystem::is_directory(path) && !std::filesystem::is_regular_file(path)) { continue; } if (raw.is_absolute()) { expanded.push_back(path.lexically_normal().string()); } else { std::filesystem::path relativePath = path.lexically_relative(cwd); if (relativePath.empty()) { expanded.push_back(path.lexically_normal().string()); } else { expanded.push_back(relativePath.lexically_normal().string()); } } } std::sort(expanded.begin(), expanded.end()); expanded.erase(std::unique(expanded.begin(), expanded.end()), expanded.end()); if (expanded.empty() && keepUnmatched) { return {token}; } return expanded; } private: static bool containsWildcard(const std::string& value) { return value.find('*') != std::string::npos; } static std::regex wildcardToRegex(const std::string& wildcardSegment) { std::string pattern; pattern.reserve(wildcardSegment.size() * 2); pattern += '^'; for (char c : wildcardSegment) { if (c == '*') { pattern += ".*"; } else { if (c == '.' || c == '^' || c == '$' || c == '|' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '+' || c == '?' || c == '\\') { pattern += '\\'; } pattern += c; } } pattern += '$'; return std::regex(pattern); } static std::vector<std::filesystem::path> dedupe(const std::vector<std::filesystem::path>& values) { std::vector<std::filesystem::path> result = values; std::sort(result.begin(), result.end(), [](const std::filesystem::path& left, const std::filesystem::path& right) { return left.lexically_normal().string() < right.lexically_normal().string(); }); result.erase(std::unique(result.begin(), result.end(), [](const std::filesystem::path& left, const std::filesystem::path& right) { return left.lexically_normal().string() == right.lexically_normal().string(); }), result.end()); return result; } }; class CommandArgumentValidator { public: static bool ensureQuotedStringOrVariable(const Argument& arg, const std::string& commandName, std::ostream& output, const std::string& usageExample, bool allowOptionLiteral = false) { if (arg.type == ARG_STRING_LITERAL || arg.type == ARG_COMMAND_SUBST) { return true; } if (arg.type == ARG_VARIABLE) { try { (void)getVar(arg); return true; } catch (const std::runtime_error&) { output << commandName << ": variable '" << arg.value << "' was not found." << std::endl; output << "If this is a literal path/string, wrap it in quotes." << std::endl; output << "Example: " << usageExample << std::endl; return false; } } if (arg.type == ARG_LITERAL) { if (isIntegerLiteral(arg.value)) { return true; } if (allowOptionLiteral && !arg.value.empty() && arg.value[0] == '-') { return true; } } output << commandName << ": unquoted string argument '" << arg.value << "'" << std::endl; output << "MXCMD reminder: non-variable string arguments must be wrapped in quotes." << std::endl; output << "Example: " << usageExample << std::endl; output << "Allowed without quotes: variables (ex: $path) and integers." << std::endl; return false; } static bool ensureAllQuotedStringOrVariable(const std::vector<Argument>& args, const std::string& commandName, std::ostream& output, const std::string& usageExample, bool allowOptionLiteral = false) { for (const auto& arg : args) { if (!ensureQuotedStringOrVariable(arg, commandName, output, usageExample, allowOptionLiteral)) { return false; } } return true; } private: static bool isIntegerLiteral(const std::string& value) { if (value.empty()) { return false; } size_t index = (value[0] == '+' || value[0] == '-') ? 1 : 0; if (index >= value.size()) { return false; } for (; index < value.size(); ++index) { if (!std::isdigit(static_cast<unsigned char>(value[index]))) { return false; } } return true; } }; const std::vector<std::pair<std::string, std::string>>& builtinHelpEntries() { static const std::vector<std::pair<std::string, std::string>> entries = { {"help", "help [\"command\"]"}, {"echo", "echo \"text\""}, {"print", "print [--i|--f] value"}, {"cat", "cat \"file\""}, {"grep", "grep [--r|--e] \"pattern\" \"file\""}, {"cd", "cd \"path\""}, {"ls", "ls \"path\""}, {"dir", "dir \"path\""}, {"find", "find \"path\" \"pattern\""}, {"sort", "sort \"file\""}, {"pwd", "pwd"}, {"mkdir", "mkdir [--p] \"dir\""}, {"cp", "cp [--r] \"src\" \"dest\""}, {"mv", "mv \"src\" \"dest\""}, {"touch", "touch \"file\""}, {"head", "head [--n N] \"file\""}, {"tail", "tail [--n N] \"file\""}, {"wc", "wc [--l] [--w] [--c] \"file\""}, {"sed", "sed [--n] [--i] \"s/old/new/g\" \"file\""}, {"printf", "printf \"format\" \"arg\" ..."}, {"stringf", "stringf \"format\" \"arg\" ..."}, {"test", "test value --eq value"}, {"cmd", "cmd \"script.mx\" [args...]"}, {"visual", "visual \"script.mx\" \"output.html\""}, {"at", "at \"list\" index"}, {"len", "len \"list\""}, {"index", "index \"string\" start length"}, {"strlen", "strlen \"string\""}, {"strfind", "strfind start \"string\" \"search\""}, {"strfindr", "strfindr start \"string\" \"search\""}, {"strtok", "strtok \"string\" \"separator\""}, {"exec", "exec \"shell command\""}, {"cmdlist", "cmdlist"}, {"debug_cmd", "debug_cmd"}, {"argv", "argv \"length\" | argv index"}, {"extern", "extern \"library\" \"function\" \"command\""}, {"extern_cleanup", "extern_cleanup [--all|\"command\"]"}, {"debug_set", "debug_set \"name\" value"}, {"debug_get", "debug_get \"name\""}, {"debug_list", "debug_list"}, {"debug_clear", "debug_clear \"name\""}, {"debug_clear_all", "debug_clear_all"}, {"debug_search", "debug_search \"pattern\""}, {"debug_dump", "debug_dump"}, {"list_new", "list_new \"name\""}, {"list_add", "list_add \"name\" \"value\""}, {"list_remove", "list_remove \"name\" index"}, {"list_get", "list_get \"name\" index"}, {"list_set", "list_set \"name\" index \"value\""}, {"list_clear", "list_clear \"name\""}, {"list_erase", "list_erase \"name\""}, {"list_exists", "list_exists \"name\""}, {"list_init", "list_init \"name\" size \"value\""}, {"list_len", "list_len \"name\""}, {"list_tokens", "list_tokens \"name\""}, {"list_sort", "list_sort \"name\""}, {"list_reverse", "list_reverse \"name\""}, {"list_shuffle", "list_shuffle \"name\""}, {"list_copy", "list_copy \"from\" \"to\""}, {"list_pop", "list_pop \"name\""}, {"list_concat", "list_concat \"name1\" \"name2\""}, {"rand", "rand min max"}, {"expr", "expr \"expression\""}, {"getline", "getline var"}, {"regex_match", "regex_match \"pattern\" \"string\""}, {"regex_replace", "regex_replace \"pattern\" \"replacement\" \"string\""}, {"regex_search", "regex_search \"pattern\" \"string\""}, {"regex_split", "regex_split \"pattern\" \"string\""}, {"tokenize", "tokenize \"file\""}, {"exit", "exit [code]"}, {"quit", "quit [code]"} }; return entries; } } std::vector<std::string> argv; std::string app_name; std::string cmd_type = "cmd.exe /c "; int exitCommand(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { if(args.empty()) { throw Exit_Exception(0); return 0; } else if(args.size() == 1) { int exitCode = std::stoi(args[0]); throw Exit_Exception(exitCode); } throw Exit_Exception(0); return 0; } int helpCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.size() > 1) { output << "Usage: help [\"command\"]" << std::endl; return 1; } const auto& entries = builtinHelpEntries(); std::vector<std::pair<std::string, std::string>> sortedEntries(entries.begin(), entries.end()); std::sort(sortedEntries.begin(), sortedEntries.end(), [](const auto& left, const auto& right) { return left.first < right.first; }); if (args.size() == 1) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "help", output, "help \"cd\"")) { return 1; } std::string requested = getVar(args[0]); bool found = false; for (const auto& entry : sortedEntries) { if (entry.first == requested) { output << entry.first << " - " << entry.second << std::endl; found = true; } } if (!found) { output << "help: no built-in command named '" << requested << "'" << std::endl; output << "Run help with no arguments to list all commands." << std::endl; return 1; } return 0; } output << "Built-in commands:" << std::endl; for (const auto& entry : sortedEntries) { output << " " << std::left << std::setw(16) << entry.first << " " << entry.second << std::endl; } return 0; } int echoCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& stream) { std::ostringstream output;; for (size_t i = 0; i < args.size(); i++) { try { output << getVar(args[i]); } catch (const std::runtime_error &e) { output << args[i].value; } if (i < args.size() - 1) { output << " "; } } output << std::endl; if(&stream != &std::cout) { stream << output.str(); } else { printf("%s", output.str().c_str()); fflush(stdout); } return 0; } int catCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { std::string line; while (std::getline(input, line)) { output << line << std::endl; } return 0; } else { bool success = true; for (const auto& filename : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(filename, "cat", output, "cat \"file.txt\"")) { return 1; } std::string fileToken = getVar(filename); auto expanded = PathPatternMatcher::expand(fileToken, true, false, true); for (const auto& filen_ : expanded) { std::ifstream file(filen_); if (!file) { output << "cat: " << filen_ << ": No such file" << std::endl; success = false; continue; } std::string line; while (std::getline(file, line)) { output << line << std::endl; } } } fflush(stdout); output.flush(); return success ? 0 : 1; } } int grepCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "grep: missing pattern" << std::endl; return 1; } for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "grep", output, "grep \"pattern\" \"file.txt\"", true)) { return 1; } } bool useRegex = false; int patternIndex = 0; bool success = true; if (!args.empty() && (args[0].value == "--r" || args[0].value == "--e")) { useRegex = true; patternIndex = 1; if (static_cast<int>(args.size()) <= patternIndex) { output << "grep: missing pattern after " << args[0].value << std::endl; return 1; } } std::string patternStr = getVar(args[patternIndex]); std::vector<std::string> fileNames; for (size_t i = patternIndex + 1; i < args.size(); i++) { std::string file_n = getVar(args[i]); auto expanded = PathPatternMatcher::expand(file_n, true, false, true); fileNames.insert(fileNames.end(), expanded.begin(), expanded.end()); } if (useRegex) { try { std::regex re(patternStr, std::regex::extended); std::string line; if (!fileNames.empty()) { for (const auto& fileName : fileNames) { std::ifstream file(fileName); if (!file) { output << "grep: " << fileName << ": No such file" << std::endl; success = false; continue; } while (std::getline(file, line)) { if (std::regex_search(line, re)) { output << line << std::endl; } } } } else { while (std::getline(input, line)) { if (std::regex_search(line, re)) { output << line << std::endl; } } } } catch (const std::regex_error& e) { output << "grep: invalid regex pattern: " << patternStr << " - " << e.what() << std::endl; return 1; } } else { std::string line; if (!fileNames.empty()) { for (const auto& fileName : fileNames) { std::ifstream file(fileName); if (!file) { output << "grep: " << fileName << ": No such file" << std::endl; success = false; continue; } while (std::getline(file, line)) { if (line.find(patternStr) != std::string::npos) { output << line << std::endl; } } } } else { while (std::getline(input, line)) { if (line.find(patternStr) != std::string::npos) { output << line << std::endl; } } } } return success ? 0 : 1; } int printCommand(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "print: missing variable name" << std::endl; return 1; } bool output_integer = false; bool success = true; for (const auto& arg : args) { if (arg == "--i") { output_integer = true; continue; } else if(arg == "--f") { output_integer = false; continue; } else { if(output_integer == true) { try { output << std::stoi(arg) << std::endl; output_integer = false; } catch (const std::exception& e) { output << "print: can't convert '" << arg << "' to integer" << std::endl; success = false; } } else { output << arg << std::endl; } } } return success ? 0 : 1; } int cdCommand(const std::vector<Argument> &args, std::istream& input, std::ostream& output) { if (args.size() != 1) { output << "cd: expected exactly one argument" << std::endl; output << "cd: this command requires a single quoted string path." << std::endl; return 1; } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "cd", output, "cd \"sdl\"")) { return 1; } std::string path = getVar(args[0]); auto expanded = PathPatternMatcher::expand(path, false, true, false); if (expanded.empty()) { output << "cd: " << path << ": No matches found" << std::endl; return 1; } if (expanded.size() > 1) { output << "cd: " << path << ": matches multiple directories" << std::endl; for (const auto& match : expanded) { output << " " << match << std::endl; } return 1; } path = expanded.front(); try { std::filesystem::current_path(path); return 0; } catch (const std::filesystem::filesystem_error& e) { output << "cd: " << path << ": " << e.what() << std::endl; return 1; } } int listCommand(const std::vector<Argument> &args, std::istream& input, std::ostream& output) { std::vector<std::string> targets; if (args.empty()) { targets.push_back("."); } else { for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "ls", output, "ls \"path\"")) { return 1; } std::string token = getVar(arg); auto expanded = PathPatternMatcher::expand(token, true, true, true); targets.insert(targets.end(), expanded.begin(), expanded.end()); } } bool success = true; try { for (const auto& path : targets) { if (std::filesystem::is_directory(path)) { for (const auto& entry : std::filesystem::directory_iterator(path)) { output << entry.path().filename().string() << std::endl; } } else if (std::filesystem::exists(path)) { output << std::filesystem::path(path).filename().string() << std::endl; } else { output << "ls: cannot access '" << path << "': No such file or directory" << std::endl; success = false; } } return success ? 0 : 1; } catch (const std::filesystem::filesystem_error& e) { output << "ls: " << e.what() << std::endl; return 1; } } int sortCommand(const std::vector<Argument> &args, std::istream& input, std::ostream& output) { std::vector<std::string> lines; std::string line; if (args.empty()) { while (std::getline(input, line)) { lines.push_back(line); } } else { for (const auto& filename : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(filename, "sort", output, "sort \"file.txt\"")) { return 1; } std::string token = getVar(filename); auto expanded = PathPatternMatcher::expand(token, true, false, true); for (const auto& file_n : expanded) { std::ifstream file(file_n); if (!file) { output << "sort: " << file_n << ": No such file" << std::endl; return 1; } while (std::getline(file, line)) { lines.push_back(line); } } } } std::sort(lines.begin(), lines.end()); for (const auto& sortedLine : lines) { output << sortedLine << std::endl; } return 0; } int findCommand(const std::vector<Argument> &args, std::istream& input, std::ostream& output) { if (args.size() < 2) { output << "find: expected at least two arguments" << std::endl; return 1; } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "find", output, "find \"path\" \"pattern\"")) { return 1; } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[1], "find", output, "find \"path\" \"pattern\"")) { return 1; } std::string pathToken = getVar(args[0]); std::string pattern = getVar(args[1]); auto roots = PathPatternMatcher::expand(pathToken, false, true, false); if (roots.empty()) { output << "find: " << pathToken << ": No matches found" << std::endl; return 1; } bool success = true; try { for (const auto& root : roots) { for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) { if (entry.path().filename().string().find(pattern) != std::string::npos) { output << entry.path().string() << std::endl; } } } return success ? 0 : 1; } catch (const std::filesystem::filesystem_error& e) { output << "find: " << e.what() << std::endl; success = false; } return success ? 0 : 1; } int pwdCommand(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { try { output << std::filesystem::current_path().string() << std::endl; return 0; } catch (const std::filesystem::filesystem_error& e) { output << "pwd: " << e.what() << std::endl; return 1; } } int mkdirCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "mkdir: missing operand" << std::endl; return 1; } bool parents = false; std::vector<std::string> dirs; bool success = true; for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "mkdir", output, "mkdir \"new_dir\"", true)) { return 1; } std::string a = getVar(arg); if (a == "--p") { parents = true; } else { dirs.push_back(a); } } for (const auto& dir : dirs) { try { if (parents) { std::filesystem::create_directories(dir); } else { std::filesystem::create_directory(dir); } } catch (const std::filesystem::filesystem_error& e) { output << "mkdir: cannot create directory '" << dir << "': " << e.what() << std::endl; success = false; } } return success ? 0 : 1; } int cpCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.size() < 2) { output << "cp: missing destination file operand after '" << (args.empty() ? "cp" : args[0].value) << "'" << std::endl; return 1; } for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "cp", output, "cp \"src.txt\" \"dest.txt\"", true)) { return 1; } } bool recursive = false; std::vector<std::string> files; for (const auto& arg : args) { std::string a = getVar(arg); if (a == "--r" || a == "--R") { recursive = true; } else { files.push_back(a); } } if (files.size() < 2) { output << "cp: missing destination file operand" << std::endl; return 1; } std::string dest = files.back(); files.pop_back(); std::vector<std::string> expandedSources; for (const auto& src : files) { auto expanded = PathPatternMatcher::expand(src, true, true, true); expandedSources.insert(expandedSources.end(), expanded.begin(), expanded.end()); } if (expandedSources.empty()) { output << "cp: missing source file operand" << std::endl; return 1; } bool success = true; for (const auto& src : expandedSources) { try { if (recursive && std::filesystem::is_directory(src)) { std::filesystem::copy(src, dest, std::filesystem::copy_options::recursive); } else { std::filesystem::copy(src, dest); } } catch (const std::filesystem::filesystem_error& e) { output << "cp: cannot copy '" << src << "' to '" << dest << "': " << e.what() << std::endl; success = false; } } return success ? 0 : 1; } int mvCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.size() < 2) { output << "mv: missing destination file operand after '" << (args.empty() ? "mv" : args[0].value) << "'" << std::endl; return 1; } for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "mv", output, "mv \"src.txt\" \"dest.txt\"")) { return 1; } } std::string dest = getVar(args.back()); std::vector<std::string> sources; for (size_t i = 0; i < args.size() - 1; ++i) { std::string token = getVar(args[i]); auto expanded = PathPatternMatcher::expand(token, true, true, true); sources.insert(sources.end(), expanded.begin(), expanded.end()); } if (sources.empty()) { output << "mv: missing source file operand" << std::endl; return 1; } bool success = true; for (const auto& src : sources) { try { std::filesystem::rename(src, dest); } catch (const std::filesystem::filesystem_error& e) { output << "mv: cannot move '" << src << "' to '" << dest << "': " << e.what() << std::endl; success = false; } } return success ? 0 : 1; } int touchCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "touch: missing file operand" << std::endl; return 1; } bool success = true; for (const auto& file : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(file, "touch", output, "touch \"file.txt\"")) { return 1; } try { std::string token = getVar(file); auto expanded = PathPatternMatcher::expand(token, true, true, true); for (const auto& f : expanded) { if (!std::filesystem::exists(f)) { std::ofstream(f).close(); } else { auto now = std::filesystem::file_time_type::clock::now(); std::filesystem::last_write_time(f, now); } } } catch (const std::filesystem::filesystem_error& e) { output << "touch: cannot touch '" << file.value << "': " << e.what() << std::endl; success = false; } catch(const state::StateException &e) { } } return success ? 0 : 1; } int headCommand(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { int numLines = 10; std::vector<std::string> files; for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--n" && i + 1 < args.size()) { try { numLines = std::stoi(args[++i]); } catch (const std::exception& e) { output << "head: invalid number of lines: '" << args[i] << "'" << std::endl; return 1; } } else { auto expanded = PathPatternMatcher::expand(args[i], true, false, true); files.insert(files.end(), expanded.begin(), expanded.end()); } } bool success = true; if (files.empty()) { std::string line; int lineCount = 0; while (std::getline(input, line) && lineCount < numLines) { output << line << std::endl; lineCount++; } } else { for (const auto& file : files) { if (files.size() > 1) { output << "==> " << file << " <==" << std::endl; } std::ifstream in(file); if (!in) { output << "head: cannot open '" << file << "' for reading" << std::endl; success = false; continue; } std::string line; int lineCount = 0; while (std::getline(in, line) && lineCount < numLines) { output << line << std::endl; lineCount++; } } } return success ? 0 : 1; } int tailCommand(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { int numLines = 10; std::vector<std::string> files; for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--n" && i + 1 < args.size()) { try { numLines = std::stoi(args[++i]); } catch (const std::exception& e) { output << "tail: invalid number of lines: '" << args[i] << "'" << std::endl; return 1; } } else { auto expanded = PathPatternMatcher::expand(args[i], true, false, true); files.insert(files.end(), expanded.begin(), expanded.end()); } } bool success = true; if (files.empty()) { std::vector<std::string> lines; std::string line; while (std::getline(input, line)) { lines.push_back(line); } size_t start = lines.size() > static_cast<size_t>(numLines) ? lines.size() - static_cast<size_t>(numLines) : 0; for (size_t i = start; i < lines.size(); ++i) { output << lines[i] << std::endl; } } else { for (const auto& file : files) { if (files.size() > 1) { output << "==> " << file << " <==" << std::endl; } std::ifstream in(file); if (!in) { output << "tail: cannot open '" << file << "' for reading" << std::endl; success = false; continue; } std::vector<std::string> lines; std::string line; while (std::getline(in, line)) { lines.push_back(line); } size_t start = lines.size() > static_cast<size_t>(numLines) ? lines.size() -static_cast<size_t>(numLines) : 0; for (size_t i = start; i < lines.size(); ++i) { output << lines[i] << std::endl; } } } return success ? 0 : 1; } int wcCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { bool countLines = false; bool countWords = false; bool countChars = false; std::vector<std::string> files; for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "wc", output, "wc --l \"file.txt\"", true)) { return 1; } std::string a = getVar(arg); if (a == "--l") { countLines = true; } else if (a == "--w") { countWords = true; } else if (a == "--c") { countChars = true; } else { auto expanded = PathPatternMatcher::expand(a, true, false, true); files.insert(files.end(), expanded.begin(), expanded.end()); } } if (!countLines && !countWords && !countChars) { countLines = countWords = countChars = true; } int totalLines = 0; int totalWords = 0; int totalChars = 0; auto processStream = [&](std::istream& in, const std::string& name) { int lineCount = 0; int wordCount = 0; int charCount = 0; std::string line; while (std::getline(in, line)) { lineCount++; charCount += line.length() + 1; std::istringstream iss(line); std::string word; while (iss >> word) { wordCount++; } } if (countLines) output << std::setw(8) << lineCount << " "; if (countWords) output << std::setw(8) << wordCount << " "; if (countChars) output << std::setw(8) << charCount << " "; output << name << std::endl; totalLines += lineCount; totalWords += wordCount; totalChars += charCount; }; if (files.empty()) { processStream(input, ""); } else { for (const auto& file : files) { std::ifstream in(file); if (!in) { output << "wc: " << file << ": No such file or directory" << std::endl; continue; } processStream(in, file); } if (files.size() > 1) { if (countLines) output << std::setw(8) << totalLines << " "; if (countWords) output << std::setw(8) << totalWords << " "; if (countChars) output << std::setw(8) << totalChars << " "; output << "total" << std::endl; } } return 0; } int sedCommand(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "Usage: sed [options] 's/pattern/replacement/[g]' [file]" << std::endl; output << "Options:" << std::endl; output << " -n Suppress automatic printing of pattern space" << std::endl; output << " -i Edit files in place" << std::endl; return 1; } bool suppressOutput = false; bool editInPlace = false; std::string expression; std::string filename; for (const auto& arg : args) { if (!CommandArgumentValidator::ensureQuotedStringOrVariable(arg, "sed", output, "sed \"s/old/new/g\" \"file.txt\"", true)) { return 1; } } for (const auto& arg : args) { std::string a = getVar(arg); if (a.size()>2 && a[0] == '-' && a[1] == '-') { if (a.find('n') != std::string::npos) { suppressOutput = true; } if (a.find('i') != std::string::npos) { editInPlace = true; } } else if (a[0] == 's' && a.find('/') == 1) { expression = a; } else { filename = a; } } if (!filename.empty()) { auto expanded = PathPatternMatcher::expand(filename, true, false, false); if (expanded.empty()) { output << "Error: Cannot open file '" << filename << "'" << std::endl; return 1; } if (expanded.size() > 1) { output << "Error: sed filename pattern matches multiple files:" << std::endl; for (const auto& match : expanded) { output << " " << match << std::endl; } return 1; } filename = expanded.front(); } if (expression.empty() || expression[0] != 's') { output << "Error: Expected substitution expression (s/pattern/replacement/[g])" << std::endl; return 1; } size_t firstSlash = expression.find('/'); if (firstSlash == std::string::npos) { output << "Error: Invalid substitution format" << std::endl; return 1; } size_t secondSlash = expression.find('/', firstSlash + 1); if (secondSlash == std::string::npos) { output << "Error: Invalid substitution format" << std::endl; return 1; } size_t thirdSlash = expression.find('/', secondSlash + 1); if (thirdSlash == std::string::npos) { output << "Error: Invalid substitution format" << std::endl; return 1; } std::string pattern = expression.substr(firstSlash + 1, secondSlash - firstSlash - 1); std::string replacement = expression.substr(secondSlash + 1, thirdSlash - secondSlash - 1); bool globalReplace = thirdSlash < expression.length() - 1 && expression[thirdSlash + 1] == 'g'; try { std::regex regexPattern(pattern); std::ifstream inFile; std::stringstream outputBuffer; std::istream* inputStream = &input; if (!filename.empty()) { inFile.open(filename); if (!inFile.is_open()) { output << "Error: Cannot open file '" << filename << "'" << std::endl; return 1; } inputStream = &inFile; } std::string line; while (std::getline(*inputStream, line)) { std::string result; if (globalReplace) { result = std::regex_replace(line, regexPattern, replacement); } else { result = std::regex_replace(line, regexPattern, replacement, std::regex_constants::format_first_only); } if (!suppressOutput) { outputBuffer << result << std::endl; } } if (editInPlace && !filename.empty()) { std::ofstream outFile(filename); if (!outFile.is_open()) { output << "Error: Cannot write to file '" << filename << "'" << std::endl; return 1; } outFile << outputBuffer.str(); outFile.close(); } else { output << outputBuffer.str(); } if (inFile.is_open()) { inFile.close(); } } catch (const std::regex_error& e) { output << "Error in regular expression: " << e.what() << std::endl; return 1; } return 0; } int printfCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& stream_output) { std::ostringstream output; if (args.empty()) { output << "Usage: printf FORMAT [ARGUMENTS...]" << std::endl; output << "Print ARGUMENTS according to FORMAT" << std::endl; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "printf", stream_output, "printf \"%s\" \"text\"")) { return 1; } std::vector<std::string> expandedArgs; for (const auto& arg : args) { expandedArgs.push_back(getVar(arg)); } std::string formatStr = parseEscapeSequences(expandedArgs[0]); size_t argIndex = 1; size_t pos = 0; while (pos < formatStr.length()) { size_t percentPos = formatStr.find('%', pos); if (percentPos == std::string::npos) { output << formatStr.substr(pos); break; } output << formatStr.substr(pos, percentPos - pos); if (percentPos + 1 >= formatStr.length()) { output << '%'; break; } char specifier = formatStr[percentPos + 1]; std::string widthPrecision; size_t specPos = percentPos + 1; while (specPos < formatStr.length() && (std::isdigit(formatStr[specPos]) || formatStr[specPos] == '.' || formatStr[specPos] == '-' || formatStr[specPos] == '+')) { widthPrecision += formatStr[specPos]; specPos++; } if (specPos > percentPos + 1) { specifier = formatStr[specPos]; } switch (specifier) { case '%': output << '%'; break; case 'd': case 'i': if (argIndex < expandedArgs.size()) { try { int val = std::stoi(expandedArgs[argIndex++]); output << val; } catch (const std::exception&) { output << "0"; } } break; case 'f': if (argIndex < expandedArgs.size()) { try { double val = std::stod(expandedArgs[argIndex++]); size_t precision = 6; size_t dotPos = widthPrecision.find('.'); if (dotPos != std::string::npos && dotPos + 1 < widthPrecision.length()) { try { precision = std::stoul(widthPrecision.substr(dotPos + 1)); } catch (...) {} } output << std::fixed << std::setprecision(precision) << val; } catch (const std::exception&) { output << "0.000000"; } } break; case 's': if (argIndex < expandedArgs.size()) { output << expandedArgs[argIndex++]; } break; case 'x': if (argIndex < expandedArgs.size()) { try { unsigned int val = std::stoul(expandedArgs[argIndex++]); output << std::hex << val << std::dec; } catch (const std::exception&) { output << "0"; } } break; case 'X': if (argIndex < expandedArgs.size()) { try { unsigned int val = std::stoul(expandedArgs[argIndex++]); output << std::hex << std::uppercase << val << std::nouppercase << std::dec; } catch (const std::exception&) { output << "0"; } } break; case 'o': if (argIndex < expandedArgs.size()) { try { unsigned int val = std::stoul(expandedArgs[argIndex++]); output << std::oct << val << std::dec; } catch (const std::exception&) { output << "0"; } } break; case 'c': if (argIndex < expandedArgs.size()) { if (!expandedArgs[argIndex].empty()) { output << expandedArgs[argIndex][0]; } argIndex++; } break; default: output << '%' << specifier; } pos = specPos + 1; } if(&stream_output != &std::cout) { stream_output << output.str(); stream_output.flush(); } else { printf("%s", output.str().c_str()); fflush(stdout); } return 0; } int debugSet(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.size() != 2) { output << "Usage: debug set <variable> <value>" << std::endl; return 1; } if(args[0].type != ArgType::ARG_VARIABLE) { output << "Usage: debug_set <variable> <value>\n"; return 1; } std::string variable = args[0].value; std::string value = args[1].value; try { state::GameState *gameState = state::getGameState(); if(args[1].type == ArgType::ARG_VARIABLE) { value = gameState->getVariable(args[1].value); } gameState->setVariable(variable, value); } catch(const state::StateException &e) {} output << "Set Debug " << variable << " to " << value << std::endl; return 0; } int debugGet(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if (args.size() != 1) { output << "Usage: debug_get <variable>" << std::endl; return 1; } if(args[0].type != ArgType::ARG_VARIABLE) { output << "Useage debug_get <varaible>\n"; return 1; } std::string variable = args[0].value; state::GameState *gameState = state::getGameState(); try { std::string value = gameState->getVariable(variable); } catch (const state::StateException &e) { output << "Error: " << e.what() << std::endl; return 1; } return 0; } int debugList(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { state::GameState *gameState = state::getGameState(); gameState->listVariables(output); return 0; } int debugClear(const std::vector<Argument>& args, std::istream& input, std::ostream& output) { if(args.empty()) { output << "debugClear: Requires argument to clear.\n"; return 1; } if(args[0].type != ArgType::ARG_VARIABLE) { output << "debugClear: Variable name required\n"; return 1; } state::GameState *gameState = state::getGameState(); gameState->clearVariable(args[0].value); output << "Cleared all Debug variables" << std::endl; return 0; } int debugClearAll(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { state::GameState *gameState = state::getGameState(); gameState->clearVariables(); output << "Cleared all Debug variables" << std::endl; return 0; } int debugSearch(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { int patternIndex = 0; std::string pattern = ""; if (!args.empty() && (args[0] == "-r" || args[0] == "-e")) { patternIndex = 1; if (static_cast<int>(args.size()) <= patternIndex) { output << "grep: missing pattern after " << args[0] << std::endl; return 1; } } else if (!args.empty()) { pattern = args[0]; } state::GameState *state = state::getGameState(); state->searchVariables(output, pattern); return 0; } int dumpVariables(const std::vector<std::string>& args, std::istream& input, std::ostream& output) { if(!args.empty() && args.size() == 1) { state::GameState *state = state::getGameState(); if(state->dumpVariables(args[0])) { output << "var table dumpped to: " << args[0] << "\n"; } else { output << "var table dump failed\n"; return 1; } } return 0; } int testCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "test", output, "test \"left\" --eq \"right\"", true)) { return 1; } if (args.size() == 2) { const std::string& op = args[0].value; const std::string value = getVar(args[1]); if (op == "--z") { return value.empty() ? 0 : 1; } else if (op == "--n") { return !value.empty() ? 0 : 1; } else if (op == "--e") { return std::filesystem::exists(value) ? 0 : 1; } else if (op == "--f") { return (std::filesystem::exists(value) && std::filesystem::is_regular_file(value)) ? 0 : 1; } else if (op == "--d") { return (std::filesystem::exists(value) && std::filesystem::is_directory(value)) ? 0 : 1; } } if (args.size() >= 3) { const std::string leftValue = getVar(args[0]); const std::string& op = args[1].value; const std::string rightValue = getVar(args[2]); if (op == "=") { return leftValue == rightValue ? 0 : 1; } else if (op == "!=") { return leftValue != rightValue ? 0 : 1; } else if (op == "--eq") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left == right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else if (op == "--ne") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left != right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else if (op == "--gt") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left > right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else if (op == "--ge") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left >= right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else if (op == "--lt") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left < right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else if (op == "--le") { try { double left = std::stod(leftValue); double right = std::stod(rightValue); return left <= right ? 0 : 1; } catch (const std::exception&) { output << "test: integer expression expected instead i found: " << leftValue << " " << rightValue << std::endl; return 2; } } else { output << "test: unknown operator: " << op << std::endl; return 2; } } if (args.size() == 1) { return !getVar(args[0]).empty() ? 0 : 1; } return 1; } int cmdCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& output) { if (args.empty()) { output << "cmd: missing file operand" << std::endl; throw AstFailure("Exception: Script: execution failed missing operand.\n"); } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "cmd", output, "cmd \"script.mx\"")) { throw AstFailure("Exception: Script: execution failed invalid argument format.\n"); } std::string filename = getVar(args[0]); if (filename.size() >= 2 && ((filename.front() == '"' && filename.back() == '"') || (filename.front() == '\'' && filename.back() == '\''))) { filename = filename.substr(1, filename.size() - 2); } auto expandedScript = PathPatternMatcher::expand(filename, true, false, false); if (expandedScript.empty()) { output << "cmd: cannot open file '" << filename << "': No such file or directory" << std::endl; throw AstFailure("Exception: Script: " + filename + " execution failed. File not found\n"); } if (expandedScript.size() > 1) { output << "cmd: script pattern matches multiple files:" << std::endl; for (const auto& match : expandedScript) { output << " " << match << std::endl; } throw AstFailure("Exception: Script pattern matched multiple files.\n"); } filename = expandedScript.front(); cmd::AstExecutor &scriptExecutor = cmd::AstExecutor::getExecutor(); std::filesystem::path resolvedPath(filename); if (!resolvedPath.is_absolute()) { std::string basePath = scriptExecutor.getPath(); if (basePath.empty()) { basePath = std::filesystem::current_path().string(); } resolvedPath = std::filesystem::path(basePath) / resolvedPath; } resolvedPath = resolvedPath.lexically_normal(); cmd::app_name = resolvedPath.string(); std::ifstream file(resolvedPath); if (!file.is_open()) { output << "cmd: cannot open file '" << resolvedPath.string() << "': No such file or directory" << std::endl; throw AstFailure("Exception: Script: " + resolvedPath.string() + " execution failed. File not found\n"); } std::stringstream buffer; buffer << file.rdbuf(); std::string script = buffer.str(); if(!script.empty()) { script.erase( std::remove(script.begin(), script.end(), '\r'), script.end() ); } try { cmd::argv.clear(); for(size_t i = 1; i < args.size(); ++i) { cmd::argv.push_back(getVar(args[i])); } } catch(state::StateException &e) { throw AstFailure("Exception: Script: State Exception execution failed.\n"); return 1; } std::string previousPath = scriptExecutor.getPath(); std::filesystem::path parent = resolvedPath.parent_path(); if (parent.empty()) { scriptExecutor.setPath(std::filesystem::current_path().string()); } else { scriptExecutor.setPath(parent.string()); } struct CmdModeEnvGuard { std::string previous; bool hadPrevious = false; CmdModeEnvGuard() { const char* value = std::getenv("MXCMD_CMD_MODE"); if (value != nullptr) { hadPrevious = true; previous = value; } #ifdef _WIN32 _putenv_s("MXCMD_CMD_MODE", "1"); #else setenv("MXCMD_CMD_MODE", "1", 1); #endif } ~CmdModeEnvGuard() { if (hadPrevious) { #ifdef _WIN32 _putenv_s("MXCMD_CMD_MODE", previous.c_str()); #else setenv("MXCMD_CMD_MODE", previous.c_str(), 1); #endif } else { #ifdef _WIN32 _putenv_s("MXCMD_CMD_MODE", ""); #else unsetenv("MXCMD_CMD_MODE"); #endif } } } cmdModeGuard; try { scan::TString string_buffer(script); scan::Scanner scanner(string_buffer); cmd::Parser parser(scanner); auto ast = parser.parse(); #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 1; #endif scriptExecutor.execute(input, output, ast); #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 0; #endif scriptExecutor.setPath(previousPath); return 0; } catch (const scan::ScanExcept &e) { scriptExecutor.setPath(previousPath); output << "cmd: Scan error in " << resolvedPath.string() << ": " << e.why() << std::endl; return 1; } catch (const std::exception &e) { scriptExecutor.setPath(previousPath); output << "cmd: Error in " << resolvedPath.string() << ": " << e.what() << std::endl; return 1; } catch(const AstFailure &e) { scriptExecutor.setPath(previousPath); throw AstFailure("Exception: Script: " + resolvedPath.string() + " execution failed: " + std::string(e.what()) + "\n"); } catch (...) { scriptExecutor.setPath(previousPath); output << "cmd: Unknown error occurred while executing " << resolvedPath.string() << std::endl; return 1; } return 0; } int visualCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& output) { if(args.empty() || args.size() != 2) { output << "Usage: visual script output_filel.\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "visual", output, "visual \"script.mx\" \"out.html\"")) { return 1; } std::string op1 = getVar(args[0]), op2 = getVar(args[1]); std::ifstream infile(op1); if(!infile.is_open()) { output << "Could not open file: " << op1 << "\n"; return 1; } std::ostringstream stream; stream << infile.rdbuf(); std::ofstream ofile(op2); if(!ofile.is_open()) { output << "Could not create output file:" << op2 << "\n"; return 1; } try { scan::TString string_buffer(stream.str()); scan::Scanner scanner(string_buffer); cmd::Parser parser(scanner); auto ast = parser.parse(); html::gen_html(ofile, ast); return 0; } catch (const scan::ScanExcept &e) { output << "cmd: Scan error in " << op1 << ": " << e.why() << std::endl; return 1; } catch (const std::exception &e) { output << "cmd: Error in " << op1 << ": " << e.what() << std::endl; return 1; } catch (...) { output << "cmd: Unknown error occurred while executing " << op1 << std::endl; return 1; } return 0; } int atCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 2) { output << "Usage: at <list> index\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "at", output, "at \"item1 item2\" 0")) { return 1; } std::string op1= getVar(args[0]); std::string op2= getVar(args[1]); std::istringstream stream(op1); std::vector<std::string> values; std::string line; while(std::getline(stream, line)) { values.push_back(line); } int index = std::stoi(op2); if(index >= 0 && index < static_cast<int>(values.size())) output << values.at(index); return 0; } int lenCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 1) { output << "Usage: len <list>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "len", output, "len \"a b c\"")) { return 1; } std::string op1 = getVar(args[0]); if (op1.find('\n') != std::string::npos) { std::istringstream stream(op1); std::vector<std::string> values; std::string line; while(std::getline(stream, line)) { if (!line.empty()) { values.push_back(line); } } output << values.size(); } else { std::istringstream stream(op1); std::vector<std::string> values; std::string word; while(stream >> word) { values.push_back(word); } output << values.size(); } return 0; } int indexCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 3) { output << "Usage: index <string> start leenn\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "index", output, "index \"hello\" 1 3")) { return 1; } std::string operands[3]; operands[0] = getVar(args[0]); operands[1] = getVar(args[1]); operands[2] = getVar(args[2]); int val[2]; val[0] = std::stoi(operands[1]); val[1] = std::stoi(operands[2]); if(val[0] >= 0 && val[0] < static_cast<int>(operands[0].length())) { std::string temp = operands[0].substr(val[0], val[1]); output << temp; return 0; } return 1; } int strlenCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 1) { output << "Usage: strlen <string>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "strlen", output, "strlen \"hello\"")) { return 1; } std::string v = getVar(args[0]); output << v.length(); return 0; } int strfindCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() && args.size() != 3) { output << "Usage: <start> <string> <search>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "strfind", output, "strfind 0 \"hello world\" \"world\"")) { return 1; } std::string op1 = getVar(args[0]); std::string op2 = getVar(args[1]); std::string op3 = getVar(args[2]); int index = std::stoi(op1); auto pos = op2.find(op3, index); if(pos == std::string::npos) output << "-1"; else output << pos; return 0; } int strfindrCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() && args.size() != 3) { output << "Usage: <start> <string> <search>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "strfindr", output, "strfindr 0 \"hello world\" \"world\"")) { return 1; } std::string op1 = getVar(args[0]); std::string op2 = getVar(args[1]); std::string op3 = getVar(args[2]); int index = std::stoi(op1); auto pos = op2.rfind(op3, index); if(pos == std::string::npos) output << "-1"; else output << pos; return 0; } int strtokCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 2) { output << "Usage: strtok string seperator\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "strtok", output, "strtok \"a,b,c\" \",\"")) { return 1; } std::string op1 = getVar(args[0]); std::string sep = getVar(args[1]); if(sep.empty()) { for(char c : op1) { output << c << std::endl; } return 0; } size_t pos = 0; size_t found; while((found = op1.find(sep, pos)) != std::string::npos) { output << op1.substr(pos, found - pos) << std::endl; pos = found + sep.length(); } if(pos < op1.length()) { output << op1.substr(pos) << std::endl; } return 0; } int execCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { std::ostringstream all_args; for(auto &arg : args) { try { all_args << getVar(arg) << " "; } catch(const std::runtime_error &) { all_args << arg.value << " "; } } std::string command_str = all_args.str(); #if !defined(__EMSCRIPTEN__) && !defined(_WIN32) && defined(__linux__) || defined(__APPLE__) int master_fd, slave_fd; pid_t pid; if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) { output << "exec: failed to create pseudo-terminal" << std::endl; return 1; } pid = fork(); if (pid < 0) { output << "exec: failed to fork process" << std::endl; close(master_fd); close(slave_fd); return 1; } if (pid == 0) { close(master_fd); setsid(); ioctl(slave_fd, TIOCSCTTY, 0); dup2(slave_fd, STDIN_FILENO); dup2(slave_fd, STDOUT_FILENO); dup2(slave_fd, STDERR_FILENO); close(slave_fd); execl("/bin/sh", "sh", "-c", command_str.c_str(), NULL); exit(127); } else { close(slave_fd); int flags = fcntl(master_fd, F_GETFL, 0); fcntl(master_fd, F_SETFL, flags | O_NONBLOCK); output << std::unitbuf; bool still_running = true; std::string line_buffer; while (still_running) { if (cmd::AstExecutor::getExecutor().checkInterrupt()) { std::cout << "exec: interrupting process" << std::endl; if(killpg(pid, SIGINT) < 0) { output << "exec: failed to kill process" << std::endl; } usleep(100000); if (waitpid(pid, NULL, WNOHANG) == 0) { killpg(pid, SIGKILL); } close(master_fd); still_running = false; AstExecutor::getExecutor().setInterruptValue(false); break; } fd_set read_fds; FD_ZERO(&read_fds); FD_SET(master_fd, &read_fds); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10000; int ret = select(master_fd + 1, &read_fds, NULL, NULL, &tv); if (ret > 0) { char buffer[4096]; ssize_t bytes_read; while ((bytes_read = read(master_fd, buffer, sizeof(buffer) - 1)) > 0) { buffer[bytes_read] = '\0'; line_buffer += buffer; size_t pos = 0; while ((pos = line_buffer.find('\n')) != std::string::npos) { std::string line = line_buffer.substr(0, pos + 1); if(&output == &std::cout) { output << line; output.flush(); } else { AstExecutor::getExecutor().execUpdateCallback(line); } line_buffer.erase(0, pos + 1); } if (line_buffer.length() > 40) { if(&output == &std::cout) { output << line_buffer; output.flush(); } else { AstExecutor::getExecutor().execUpdateCallback(line_buffer); } line_buffer.clear(); } } if (bytes_read == 0) { if (!line_buffer.empty()) { if(&output == &std::cout) { output << line_buffer; output.flush(); } else { AstExecutor::getExecutor().execUpdateCallback(line_buffer); } } still_running = false; } if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { still_running = false; } } else if (ret == 0) { int status; pid_t result = waitpid(pid, &status, WNOHANG); if (result != 0) { if (!line_buffer.empty()) { if(&output == &std::cout) { output << line_buffer; output.flush(); } else { AstExecutor::getExecutor().execUpdateCallback(line_buffer); } } still_running = false; } } else { if (errno != EINTR) { break; } } } } int status; waitpid(pid, &status, 0); close(master_fd); output << std::nounitbuf; return WEXITSTATUS(status); #elif defined(_WIN32) && !defined(__EMSCRIPTEN__) std::istringstream iss(command_str); std::string first_word; iss >> first_word; if (first_word.ends_with(".exe") || first_word.ends_with(".EXE")) { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; HANDLE hStdOutRead, hStdOutWrite; if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0)) { output << "exec: failed to create output pipe" << std::endl; return 1; } SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0); HANDLE hStdInRead, hStdInWrite; if (!CreatePipe(&hStdInRead, &hStdInWrite, &sa, 0)) { CloseHandle(hStdOutRead); CloseHandle(hStdOutWrite); output << "exec: failed to create input pipe" << std::endl; return 1; } SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0); STARTUPINFO si; ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.hStdInput = hStdInRead; si.hStdOutput = hStdOutWrite; si.hStdError = hStdOutWrite; si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcess(NULL, const_cast<LPSTR>(command_str.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { CloseHandle(hStdOutRead); CloseHandle(hStdOutWrite); CloseHandle(hStdInRead); CloseHandle(hStdInWrite); output << "exec: failed to create process: " << command_str << std::endl; return 1; } CloseHandle(hStdOutWrite); CloseHandle(hStdInRead); CloseHandle(hStdInWrite); bool isStdOut = (&output == &std::cout); bool still_running = true; while (still_running) { DWORD bytesAvailable = 0; if (!PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &bytesAvailable, NULL)) { still_running = false; break; } if (bytesAvailable > 0) { char buffer[256]; DWORD bytesRead = 0; if (ReadFile(hStdOutRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) { buffer[bytesRead] = '\0'; if (isStdOut) { printf("%s", buffer); fflush(stdout); } else { output << buffer; output.flush(); } } else { still_running = false; break; } } else { DWORD currentChildExitCode; if (GetExitCodeProcess(pi.hProcess, ¤tChildExitCode)) { if (currentChildExitCode != STILL_ACTIVE) { still_running = false; } } else { still_running = false; break; } if (still_running) { Sleep(10); } } } DWORD final_child_exit_code = 0; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &final_child_exit_code); CloseHandle(hStdOutRead); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return static_cast<int>(final_child_exit_code); } else { std::string fullCommand = cmd::cmd_type + " " + command_str; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; HANDLE hStdOutRead, hStdOutWrite; if (!CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0)) { output << "exec: failed to create output pipe" << std::endl; return 1; } SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0); HANDLE hStdInRead, hStdInWrite; if (!CreatePipe(&hStdInRead, &hStdInWrite, &sa, 0)) { CloseHandle(hStdOutRead); CloseHandle(hStdOutWrite); output << "exec: failed to create input pipe" << std::endl; return 1; } SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0); STARTUPINFO si; ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.hStdInput = hStdInRead; si.hStdOutput = hStdOutWrite; si.hStdError = hStdOutWrite; si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcess(NULL, const_cast<LPSTR>(fullCommand.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { DWORD error = GetLastError(); printf("exec: failed to create process: %s Error: %ld\n ",fullCommand.c_str(), error); fflush(stdout); CloseHandle(hStdOutRead); CloseHandle(hStdOutWrite); CloseHandle(hStdInRead); CloseHandle(hStdInWrite); output << "exec: failed to create process: " << fullCommand << std::endl; return 1; } CloseHandle(hStdOutWrite); CloseHandle(hStdInRead); CloseHandle(hStdInWrite); bool isStdOut = (&output == &std::cout); bool still_running = true; while (still_running) { DWORD bytesAvailable = 0; if (!PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &bytesAvailable, NULL)) { still_running = false; break; } if (bytesAvailable > 0) { char buffer[256]; DWORD bytesRead = 0; if (ReadFile(hStdOutRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) { buffer[bytesRead] = '\0'; if (isStdOut) { printf("%s", buffer); fflush(stdout); } else { output << buffer; output.flush(); } } else { still_running = false; break; } } else { DWORD currentChildExitCode; if (GetExitCodeProcess(pi.hProcess, ¤tChildExitCode)) { if (currentChildExitCode != STILL_ACTIVE) { still_running = false; } } else { still_running = false; break; } if (still_running) { Sleep(10); } } } DWORD final_child_exit_code = 0; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &final_child_exit_code); CloseHandle(hStdOutRead); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return static_cast<int>(final_child_exit_code); } #endif } int commandListCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { AstExecutor::printCommandInfo(output); return 0; } int argvCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { output << "Usage: argv <index>\n"; return 1; } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "argv", output, "argv \"length\"")) { return 1; } if(args[0].type == ArgType::ARG_STRING_LITERAL && args[0].value == "length") { output << argv.size(); return 0; } else if(args[0].type == ArgType::ARG_STRING_LITERAL && args[0].value == "all") { for (size_t i = 0; i < argv.size(); ++i) { output << argv.at(i) << "\n"; } return 0; } else if(args[0].type == ArgType::ARG_STRING_LITERAL && args[0].value == "app") { output << app_name; return 0; } else if(args[0].type == ArgType::ARG_STRING_LITERAL) { output << "argv: invalid argument: " << args[0].value << "\n"; return 1; } int index = std::stoi(getVar(args[0])); if (index >= 0 && index < static_cast<int>(argv.size())) { output << argv.at(index); } else { output << "argv: index out of range\n"; return 1; } return 0; } int externCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty() || args.size() != 3) { output << "Usage: extern <library> <function> <command_name>\n"; output << "You have: "; for(auto &arg : args) { output << arg.value << " "; } output << "\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "extern", output, "extern \"libname.so\" \"func\" \"cmdname\"")) { return 1; } std::string libPath = getVar(args[0]); std::string funcName = getVar(args[1]); std::string cmdName = getVar(args[2]); std::string libLookup = libPath; std::vector<std::string> lookupCandidates; auto addCandidate = [&lookupCandidates](const std::filesystem::path& value) { std::string normalized = value.lexically_normal().string(); for (const auto& existing : lookupCandidates) { if (existing == normalized) { return; } } lookupCandidates.push_back(normalized); }; try { std::filesystem::path requested(libLookup); if (requested.is_absolute()) { addCandidate(requested); } else { addCandidate(std::filesystem::absolute(requested)); } std::filesystem::path appDir = std::filesystem::path(app_name).parent_path(); if (!appDir.empty()) { addCandidate(std::filesystem::absolute(appDir / requested)); addCandidate(std::filesystem::absolute(appDir / requested.filename())); } #if defined(__linux__) addCandidate(std::filesystem::path("/usr/local/lib") / requested); addCandidate(std::filesystem::path("/usr/local/lib") / requested.filename()); if (!requested.has_extension()) { std::string baseName = requested.filename().string(); if (baseName.rfind("lib", 0) == 0) { addCandidate(std::filesystem::path("/usr/local/lib") / (baseName + ".so")); } else { addCandidate(std::filesystem::path("/usr/local/lib") / ("lib" + baseName + ".so")); } } #elif defined(__APPLE__) addCandidate(std::filesystem::path("/usr/local/lib") / requested); addCandidate(std::filesystem::path("/usr/local/lib") / requested.filename()); if (!requested.has_extension()) { std::string baseName = requested.filename().string(); if (baseName.rfind("lib", 0) == 0) { addCandidate(std::filesystem::path("/usr/local/lib") / (baseName + ".dylib")); } else { addCandidate(std::filesystem::path("/usr/local/lib") / ("lib" + baseName + ".dylib")); } } #endif #ifdef _WIN32 char modulePath[MAX_PATH] = {0}; DWORD modulePathLen = GetModuleFileNameA(nullptr, modulePath, MAX_PATH); if (modulePathLen > 0 && modulePathLen < MAX_PATH) { std::filesystem::path exeDir = std::filesystem::path(modulePath).parent_path(); addCandidate(std::filesystem::absolute(exeDir / requested)); addCandidate(std::filesystem::absolute(exeDir / requested.filename())); } #endif } catch (...) { lookupCandidates.push_back(libLookup); } std::vector<std::string> orderedCandidates; #ifdef _WIN32 for (const auto& candidate : lookupCandidates) { try { std::filesystem::path dllPath = std::filesystem::path(candidate + ".dll"); if (std::filesystem::exists(dllPath)) { orderedCandidates.push_back(candidate); } } catch (...) { } } for (const auto& candidate : lookupCandidates) { if (std::find(orderedCandidates.begin(), orderedCandidates.end(), candidate) == orderedCandidates.end()) { orderedCandidates.push_back(candidate); } } #else orderedCandidates = lookupCandidates; #endif auto ® = AstExecutor::getRegistry(); std::shared_ptr<Library> lib; std::string loadedLibraryPath = libPath; for (const auto& candidate : orderedCandidates) { std::shared_ptr<Library> &candidateLib = reg.setLibrary(candidate); if (candidateLib) { lib = candidateLib; loadedLibraryPath = candidate; break; } } if(!lib) { std::cerr << "extern: failed to load library " << libPath << "\n"; #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 0; #else //exit(0); #endif return 1; } if(!lib->hasSymbol(funcName)) { std::cerr << "extern: function " << funcName << " not found in library " << libPath << "\n"; #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 0; #else //exit(0); #endif return 1; } ExternCommandInfo info; info.library = lib; info.func = lib->getFunction<plugin_func_t>(funcName); info.functionName = funcName; info.libraryPath = loadedLibraryPath; if(info.func) { reg.registerExternCommand(cmdName, info); return 0; } else { std::cerr << "extern: failed to get function pointer for " << funcName << "\n"; #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) program_running = 0; #else //exit(0); #endif return 1; } } int externCleanupCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { auto& reg = AstExecutor::getRegistry(); if (args.empty()) { std::size_t removedCommands = reg.clearExternCommands(); std::size_t removedLibraries = reg.pruneLibraries(); output << "extern_cleanup: removed " << removedCommands << " command(s), released " << removedLibraries << " librar(y/ies)" << std::endl; return 0; } if (args.size() != 1) { output << "Usage: extern_cleanup [--all|<command_name>]" << std::endl; return 1; } if (!CommandArgumentValidator::ensureQuotedStringOrVariable(args[0], "extern_cleanup", output, "extern_cleanup \"command_name\"", true)) { return 1; } std::string arg = getVar(args[0]); if (arg == "--all" || arg == "all") { std::size_t removedCommands = reg.clearExternCommands(); std::size_t removedLibraries = reg.pruneLibraries(); output << "extern_cleanup: removed " << removedCommands << " command(s), released " << removedLibraries << " librar(y/ies)" << std::endl; return 0; } bool removed = reg.unregisterExternCommand(arg); std::size_t removedLibraries = reg.pruneLibraries(); if (!removed) { output << "extern_cleanup: command not found: " << arg << std::endl; return 1; } output << "extern_cleanup: removed command '" << arg << "', released " << removedLibraries << " librar(y/ies)" << std::endl; return 0; } int newListCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { output << "Usage: list_new <name>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_new", output, "list_new \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (gameState->hasList(name)) { output << "Error: List '" << name << "' already exists." << std::endl; return 1; } gameState->createList(name); if(args.size() > 2) { std::string value = getVar(args[2]); std::string value2 = getVar(args[1]); gameState->initList(name, value, std::stoul(value2)); } return 0; } int newListAddCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.size() < 2) { output << "Usage: list_add <name> <value>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_add", output, "list_add \"my_list\" \"value\"")) { return 1; } std::string name = getVar(args[0]); std::string value = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { output << "Error: List '" << name << "' already exists." << std::endl; return 1; } gameState->addToList(name, value); return 0; } int newListRemoveCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.size() < 2) { output << "Usage: list_remove <name> <value>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_remove", output, "list_remove \"my_list\" 0")) { return 1; } std::string name = getVar(args[0]); std::string value = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { output << "Error: List '" << name << "' already exists." << std::endl; return 1; } size_t val = std::stoul(value); gameState->removeFromList(name, val); return 0; } int newListGetCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.size() < 2) { throw cmd::AstFailure("Usage: list_get <name> <index>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_get", output, "list_get \"my_list\" 0")) { return 1; } std::string name = getVar(args[0]); std::string indexStr = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } int index = std::stoi(indexStr); std::string value = gameState->getFromList(name, index); if (value.empty()) { throw cmd::AstFailure( "Error: Index out of range."); return 1; } output << value; return 0; } int newListSetCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.size() < 3) { output << "Usage: list_set <name> <index> <value>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_set", output, "list_set \"my_list\" 0 \"value\"")) { return 1; } std::string name = getVar(args[0]); std::string indexStr = getVar(args[1]); std::string value = getVar(args[2]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { output << "Error: List '" << name << "' does not exist." << std::endl; return 1; } size_t index = std::stoul(indexStr); gameState->setList(name, index, value); return 0; } int newListClearCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { output << "Usage: list_clear <name>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_clear", output, "list_clear \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { output << "Error: List '" << name << "' does not exist." << std::endl; return 1; } gameState->clearList(name); return 0; } int newListClearAllCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { state::GameState *gameState = state::getGameState(); gameState->clearAllLists(); return 0; } int newListExistsCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_exists <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_exists", output, "list_exists \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (gameState->hasList(name)) { output << "0"; } else { output << "1"; } return 0; } int newListInitCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty()) { output << "Usage: list_init <name> <size> <value>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_init", output, "list_init \"my_list\" 10 \"value\"")) { return 1; } std::string name = getVar(args[0]); std::string value = getVar(args[2]); std::string sizeStr = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { output << "Error: List '" << name << "' does not exist." << std::endl; return 1; } size_t size = std::stoul(sizeStr); gameState->initList(name, value, size); return 0; } int newListLenCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_len <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_len", output, "list_len \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } size_t len = gameState->getListLength(name); output << len; return 0; } int newListTokens(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty()) { throw cmd::AstFailure("Usage: list_tokens <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_tokens", output, "list_tokens \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } for(size_t i = 0; i < gameState->getListLength(name); ++i) { std::string value = gameState->getFromList(name, i); output << value << "\n"; } return 0; } int newListSortCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_sort <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_sort", output, "list_sort \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } gameState->sortList(name); return 0; } int newListReverseCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_reverse <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_reverse", output, "list_reverse \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } gameState->reverseList(name); return 0; } int newListShuffleCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_shuffle <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_shuffle", output, "list_shuffle \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } gameState->shuffleList(name); return 0; } int newListCopyCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_copy <name>"); return 1; } if(args.size() < 2) { throw cmd::AstFailure("Usage: list_copy <name> <name2>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_copy", output, "list_copy \"list1\" \"list2\"")) { return 1; } std::string name1 = getVar(args[0]); std::string name2 = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name1)) { throw cmd::AstFailure("Error: List '" + name1 + "' does not exist."); return 1; } gameState->copyList(name1, name2); return 0; } int newListPopCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_pop <name>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_pop", output, "list_pop \"my_list\"")) { return 1; } std::string name = getVar(args[0]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name)) { throw cmd::AstFailure("Error: List '" + name + "' does not exist."); return 1; } gameState->popList(name); return 0; } int concatListCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw cmd::AstFailure("Usage: list_concat <name1> <name2>"); return 1; } if(args.size() < 2) { throw cmd::AstFailure("Usage: list_concat <name1> <name2> <name3>"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "list_concat", output, "list_concat \"list1\" \"list2\"")) { return 1; } std::string name1 = getVar(args[0]); std::string name2 = getVar(args[1]); state::GameState *gameState = state::getGameState(); if (!gameState->hasList(name1)) { throw cmd::AstFailure("Error: List '" + name1 + "' does not exist."); return 1; } if(!gameState->hasList(name2)) { throw cmd::AstFailure("Error: List '" + name2 + "' does not exist."); return 1; } gameState->concatList(name1, name2); return 0; } int newRandCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if(args.empty()) { throw cmd::AstFailure("Usage: rand <min> <max>"); return 1; } std::string minStr = getVar(args[0]); std::string maxStr = getVar(args[1]); int min = std::stoi(minStr); int max = std::stoi(maxStr); if(min > max) { throw cmd::AstFailure("Usage: rand <min> <max>"); return 1; } std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(min, max); int randomNum = dis(gen); output << randomNum; return 0; } namespace expr_parser { enum class ExprTokenType { END, NUMBER, PLUS, MINUS, MUL, DIV, MOD, LPAREN, RPAREN, PLUSEQ, MINUSEQ, XOR, OR, AND, LOGICAL_AND, LOGICAL_OR, NOT, EQ, NEQ, GT, GTE, LT, LTE }; struct ExprToken { ExprTokenType type; int value; }; class ExprLexer { public: ExprLexer(const std::string& input) : scanner(input), pos(0) { scanner.scan(); next(); } void next() { if (pos >= scanner.size()) { current = {ExprTokenType::END, 0}; return; } auto t = scanner[pos++]; auto val = t.getTokenValue(); if (t.getTokenType() == types::TokenType::TT_NUM) { current = {ExprTokenType::NUMBER, std::stoi(val)}; } else if (val == "+") { current = {ExprTokenType::PLUS, 0}; } else if (val == "-") { current = {ExprTokenType::MINUS, 0}; } else if (val == "*") { current = {ExprTokenType::MUL, 0}; } else if (val == "/") { current = {ExprTokenType::DIV, 0}; } else if (val == "%") { current = {ExprTokenType::MOD, 0}; } else if (val == "(") { current = {ExprTokenType::LPAREN, 0}; } else if (val == ")") { current = {ExprTokenType::RPAREN, 0}; } else if (val == "+=") { current = {ExprTokenType::PLUSEQ, 0}; } else if (val == "-=") { current = {ExprTokenType::MINUSEQ, 0}; } else if (val == "^") { current = {ExprTokenType::XOR, 0}; } else if (val == "|") { current = {ExprTokenType::OR, 0}; } else if (val == "&") { current = {ExprTokenType::AND, 0}; } else if (val == "&&") { current = {ExprTokenType::LOGICAL_AND, 0}; } else if (val == "||") { current = {ExprTokenType::LOGICAL_OR, 0}; } else if (val == "!") { current = {ExprTokenType::NOT, 0}; } else if (val == "=") { current = {ExprTokenType::EQ, 0}; } else if (val == "!=") { current = {ExprTokenType::NEQ, 0}; } else if (val == ">") { current = {ExprTokenType::GT, 0}; } else if (val == ">=") { current = {ExprTokenType::GTE, 0}; } else if (val == "<") { current = {ExprTokenType::LT, 0}; } else if (val == "<=") { current = {ExprTokenType::LTE, 0}; } else { current = {ExprTokenType::END, 0}; } } ExprToken peek() const { return current; } void consume() { next(); } private: scan::Scanner scanner; size_t pos; ExprToken current; }; class ExprParser { public: ExprParser(ExprLexer& lexer) : lexer(lexer) {} int parse() { return parseLogical(); } private: ExprLexer& lexer; int parseAdd() { int val = parseTerm(); while (lexer.peek().type == ExprTokenType::PLUS || lexer.peek().type == ExprTokenType::MINUS) { if (lexer.peek().type == ExprTokenType::PLUS) { lexer.consume(); val += parseTerm(); } else { lexer.consume(); val -= parseTerm(); } } return val; } int parseBitwise() { int val = parseAdd(); while (lexer.peek().type == ExprTokenType::XOR || lexer.peek().type == ExprTokenType::OR || lexer.peek().type == ExprTokenType::AND) { if (lexer.peek().type == ExprTokenType::XOR) { lexer.consume(); val ^= parseAdd(); } else if (lexer.peek().type == ExprTokenType::OR) { lexer.consume(); val |= parseAdd(); } else { lexer.consume(); val &= parseAdd(); } } return val; } int parseComparison() { int val = parseBitwise(); while (lexer.peek().type == ExprTokenType::EQ || lexer.peek().type == ExprTokenType::NEQ || lexer.peek().type == ExprTokenType::GT || lexer.peek().type == ExprTokenType::GTE || lexer.peek().type == ExprTokenType::LT || lexer.peek().type == ExprTokenType::LTE) { if (lexer.peek().type == ExprTokenType::EQ) { lexer.consume(); val = (val == parseBitwise()); } else if (lexer.peek().type == ExprTokenType::NEQ) { lexer.consume(); val = (val != parseBitwise()); } else if (lexer.peek().type == ExprTokenType::GT) { lexer.consume(); val = (val > parseBitwise()); } else if (lexer.peek().type == ExprTokenType::GTE) { lexer.consume(); val = (val >= parseBitwise()); } else if (lexer.peek().type == ExprTokenType::LT) { lexer.consume(); val = (val < parseBitwise()); } else { lexer.consume(); val = (val <= parseBitwise()); } } return val; } int parseLogical() { int val = parseComparison(); while (lexer.peek().type == ExprTokenType::LOGICAL_OR || lexer.peek().type == ExprTokenType::LOGICAL_AND) { if (lexer.peek().type == ExprTokenType::LOGICAL_OR) { lexer.consume(); val = val || parseComparison(); } else { lexer.consume(); val = val && parseComparison(); } } return val; } int parseTerm() { int val = parseFactor(); while (lexer.peek().type == ExprTokenType::MUL || lexer.peek().type == ExprTokenType::DIV || lexer.peek().type == ExprTokenType::MOD) { if (lexer.peek().type == ExprTokenType::MUL) { lexer.consume(); val *= parseFactor(); } else if (lexer.peek().type == ExprTokenType::DIV) { lexer.consume(); int d = parseFactor(); if (d == 0) throw std::runtime_error("Division by zero"); val /= d; } else { lexer.consume(); int d = parseFactor(); if (d == 0) throw std::runtime_error("Modulo by zero"); val %= d; } } return val; } int parseFactor() { if (lexer.peek().type == ExprTokenType::NOT) { lexer.consume(); return !parseFactor(); } else if (lexer.peek().type == ExprTokenType::MINUS) { lexer.consume(); return -parseFactor(); } else if (lexer.peek().type == ExprTokenType::NUMBER) { int v = lexer.peek().value; lexer.consume(); return v; } else if (lexer.peek().type == ExprTokenType::LPAREN) { lexer.consume(); int v = parseLogical(); if (lexer.peek().type != ExprTokenType::RPAREN) throw std::runtime_error("Expected ')'"); lexer.consume(); return v; } throw std::runtime_error("Unexpected token"); } }; } int exprCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { throw AstFailure("Usage: expr <expression>\n"); return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "expr", output, "expr \"1 + 2 * 3\"")) { return 1; } std::string expression = getVar(args[0]); try { expr_parser::ExprLexer lexer(expression); expr_parser::ExprParser parser(lexer); int result = parser.parse(); output << result; return 0; } catch (const std::exception& e) { throw AstFailure("expr: error: " + std::string(e.what())); return 1; } } int getLineCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty()) { output << "Usage: getline var\n"; return 1; } std::string line; std::getline(input, line); if(line.empty()) { return 0; } state::GameState *gameState = state::getGameState(); std::string var = args[0].value; gameState->setVariable(var, line); return 0; } int regexMatchCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty() || args.size() != 2) { output << "Usage: regex_match <pattern> <string>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "regex_match", output, "regex_match \"pattern\" \"text\"")) { return 1; } std::string pattern = getVar(args[0]); std::string str = getVar(args[1]); std::regex re(pattern); if (std::regex_match(str, re)) { output << "1"; } else { output << "0"; } return 0; } int regexReplaceCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty() || args.size() != 3) { output << "Usage: regex_replace <pattern> <replacement> <string>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "regex_replace", output, "regex_replace \"pattern\" \"replacement\" \"text\"")) { return 1; } std::string pattern = getVar(args[0]); std::string replacement = getVar(args[1]); std::string str = getVar(args[2]); std::regex re(pattern); std::string result = std::regex_replace(str, re, replacement); output << result; return 0; } int regexSearchCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty() || args.size() != 2) { output << "Usage: regex_search <pattern> <string>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "regex_search", output, "regex_search \"pattern\" \"text\"")) { return 1; } std::string pattern = getVar(args[0]); std::string str = getVar(args[1]); std::regex re(pattern); if (std::regex_search(str, re)) { output << "1"; } else { output << "0"; } return 0; } int regexSplitCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream &output) { if (args.empty() || args.size() != 2) { output << "Usage: regex_split <pattern> <string>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "regex_split", output, "regex_split \"pattern\" \"text\"")) { return 1; } std::string pattern = getVar(args[0]); std::string str = getVar(args[1]); std::regex re(pattern); std::sregex_token_iterator it(str.begin(), str.end(), re, -1); std::sregex_token_iterator end; for (; it != end; ++it) { output << *it << "\n"; } return 0; } int tokenizeCommand(const std::vector<cmd::Argument>& args, std::istream& input, std::ostream& output) { if(args.empty()) { output << "Usage: tokenize <filename>\n"; return 1; } if (!CommandArgumentValidator::ensureAllQuotedStringOrVariable(args, "tokenize", output, "tokenize \"script.mx\"")) { return 1; } if(args.size() > 0) { std::string filename = getVar(args[0]); std::ostringstream stream; std::ifstream file(filename); if(!file.is_open()) { output << "tokenize: Failure to load file: " << filename << "\n"; return 1; } stream << file.rdbuf(); std::string fileContent = stream.str(); if(!fileContent.empty()) { fileContent.erase( std::remove(fileContent.begin(), fileContent.end(), '\r'), fileContent.end() ); } try { scan::Scanner scanner(fileContent); scanner.scan(); output << "Idx | Type | Value\n"; output << "----+----------------+--------------------------\n"; for (size_t i = 0; i < scanner.size(); ++i) { std::ostringstream data; types::print_type_TokenType(data,scanner[i].getTokenType()); output << std::setw(3) << i << " | " << std::setw(14) << data.str() << " | " << scanner[i].getTokenValue() << "\n"; } } catch(scan::ScanExcept &e) { output << e.why() << "\n"; return 1; } return 0; } return 1; } }