← index
MasterX_System/cmd/src/cmd.cpp
Source: MasterX_System/cmd/src/cmd.cpp
#include"ast.hpp"

#include"parser.hpp"

#include"scanner.hpp"

#include"types.hpp"

#include"string_buffer.hpp"

#include"html.hpp"

#include"command.hpp"

#include<iostream>

#include<string>

#include<fstream>

#include<optional>

#include<vector>

#include<memory>

#include<functional>

#include<unordered_map>

#include<sstream>

#include <iomanip>

#include<readline/readline.h>

#include<readline/history.h>

#include"version_info.hpp"

#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

#include <signal.h>

#include <unistd.h>

#endif

#ifdef _WIN32

#include<windows.h>

#endif

#include<cstdlib>

#include"cmd_argz.hpp"


struct Args {
    bool debug_output = false;
    bool debug_syntax_highlight = false;
    bool command_proc = false;
    bool stdin_input = false;
    bool debug_tokens = false;
    std::string command_text;
    std::string filename;
    std::vector<std::string> arguments;
};

Args proc_custom_args(int &argc, char **argv) {
	Args args;
	Argz<std::string> parser(argc, argv);
    parser.addOptionSingle('h', "Display help message")
        .addOptionDouble('H', "help", "Display help message")
        .addOptionSingle('v', "Display version")
        .addOptionDouble('V', "version", "Display version")     
        .addOptionSingle('i', "stdin input")
        .addOptionDouble('I', "stdin", "stdin input")
        .addOptionSingle('d', "debug output")
        .addOptionDouble('D', "debug", "debug output")  
        .addOptionSingle('s', "debug syntax highlight")
        .addOptionDouble('S', "syntax", "debug syntax highlight")
        .addOptionSingle('t', "debug tokens")
        .addOptionDouble('T', "tokens", "debug tokens")
        .addOptionSingleValue('c', "command")
        .addOptionDoubleValue('C', "command", "command")
        ;
    Argument<std::string> arg;
    int value = 0;
    try {
        while((value = parser.proc(arg)) != -1) {
            switch(value) {
                case 'h':
                case 'H':
                case 'v':
                case 'V':
                    std::cout << "MXCMD " << version_string << "\n(C) 1999-2025 LostSideDead Software\n\n";
                    parser.help(std::cout);
                    exit(EXIT_SUCCESS);
                    break;
                case 'i':
                case 'I':
                    args.stdin_input = true;;
                    break;
                case 'd':
                case 'D':
                    args.debug_output = true;
                    break;
                case 's':
                case 'S':
                    args.debug_syntax_highlight = true;
                    break;
                case 'c':
                case 'C':
                    args.command_proc = true;
                    args.command_text = arg.arg_value;
                    break;
                case 't':
                case 'T':
                    args.debug_tokens = true;
                    break;
                case '-':
                default:
                    args.arguments.push_back(arg.arg_value);
                    break;

                }
        }
    } catch (const ArgException<std::string>& e) {
        std::cerr << "mxcmd: Argument Exception" << e.text() << std::endl;
		return args;
    }
    cmd::argv.clear();
    for(size_t i = 1; i < args.arguments.size(); ++i) {
        cmd::argv.push_back(args.arguments.at(i));
    }
    return args;
}

#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

void sigint_handler(int sig) {
    if (program_running) {
        program_running = 0;
    } else {
          std::cout << "\nMXCMD: Interrupt signal received. Exiting...\n";
          exit(130);
    }
}
#endif


void dumpTokens(scan::Scanner &scan, std::ostream& out = std::cout) {
    out << "Idx | Type           | Value\n";
    out << "----+----------------+--------------------------\n";
    for (size_t i = 0; i < scan.size(); ++i) {
        std::ostringstream data;
        types::print_type_TokenType(data,scan[i].getTokenType());
        out << std::setw(3) << i << " | "
            << std::setw(14) << data.str() << " | "
            << scan[i].getTokenValue() << "\n";
    }
}

void execute_command(const std::string &text) {
    fflush(stdout);
    try {
        cmd::AstExecutor &executor = cmd::AstExecutor::getExecutor();
        scan::TString string_buffer(text);
        scan::Scanner scanner(string_buffer);
        cmd::Parser parser(scanner);
        auto ast = parser.parse();
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

        program_running = 1;
#endif

        executor.execute(std::cin, std::cout, ast);
        fflush(stdout);
        exit(0);
    } catch (const scan::ScanExcept &e) {
        std::cerr << "Scan error: " << e.why() << std::endl;
        exit(EXIT_FAILURE);
    } catch (const std::exception &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
        exit(EXIT_FAILURE);
    } catch(const cmd::AstFailure &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
        exit(EXIT_FAILURE);
    } catch (const state::StateException &e) {
        std::cerr << "State error: " << e.what() << std::endl;;
        exit(EXIT_FAILURE);
    } catch(const cmd::Exit_Exception  &e) {
        std::cout << "\nExit: " << e.getCode() << std::endl;
        exit(e.getCode());
    }
    catch (...) {
        std::cerr << "Unknown error occurred." << std::endl;
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char **argv) {
#ifdef WINDOWS_MODE

    cmd::AstExecutor::getExectur().windows_mode = true;
#endif

#ifndef _WIN32

    cmd::AstExecutor::getExecutor().setUpdateCallback(
        [](const std::string &chunk) {
            std::cout << chunk;
        }
    );
#endif


#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

    struct sigaction sa;
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, nullptr);
#endif

    Args args = proc_custom_args(argc, argv);
    if(args.command_proc) {
        if(args.command_text.empty()) {
            std::cerr << "mx: No command provided.\n";
            exit(EXIT_FAILURE);
        }
        execute_command(args.command_text);
        exit(EXIT_SUCCESS);
    }
    std::cout << "MXCMD " << version_string << "\n(C) 1999-2025 LostSideDead Software\n\n";
    cmd::app_name = argv[0];

#ifdef _WIN32

cmd::AstExecutor::getExecutor().getRegistry().registerTypedCommand("exec", 
    [](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();
        
        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));
        
        std::vector<std::string> envVars;
        
        
        LPCH env = GetEnvironmentStrings();
        if (env) {
            LPCH current = env;
            while (*current) {
                std::string envVar(current);
                
        
                if (envVar.substr(0, 18) != "PYTHONUNBUFFERED=" &&
                    envVar.substr(0, 5) != "TERM=" &&
                    envVar.substr(0, 8) != "COLUMNS=" &&
                    envVar.substr(0, 6) != "LINES=" &&
                    envVar.substr(0, 10) != "_STDBUF_O=" &&
                    envVar.substr(0, 10) != "_STDBUF_E=") {
                    envVars.push_back(envVar);
                }
                current += envVar.length() + 1;
            }
            FreeEnvironmentStrings(env);
        }
        
        
        envVars.push_back("PYTHONUNBUFFERED=1");
        envVars.push_back("TERM=dumb");           // Force dumb terminal

        envVars.push_back("COLUMNS=80");          // Set terminal width

        envVars.push_back("LINES=24");            // Set terminal height

        envVars.push_back("_STDBUF_O=L");         // Force line buffering for stdout

        envVars.push_back("_STDBUF_E=L");         // Force line buffering for stderr

        envVars.push_back("MSYS=enable_pcon");    // Enable pseudo console in MSYS2

        envVars.push_back("CYGWIN=disable_pcon"); // Disable pseudo console buffering in Cygwin

        
        
        std::string envBlock;
        for (const auto& var : envVars) {
            envBlock += var + '\0';
        }
        envBlock += '\0';
        
        
        std::string cmdLine;
        std::string bashPath;
        char *opt = getenv("MSYS2_BASH_PATH");
        
        if(cmd::cmd_type.find("wsl.exe") != std::string::npos) {
            cmdLine = "wsl.exe -e bash -c \"" + command_str + "\"";
        } else {
            if (opt != nullptr && std::filesystem::exists(opt)) {
                bashPath = opt;
            } else if (std::filesystem::exists("C:\\msys64\\usr\\bin\\bash.exe")) {
                bashPath = "C:\\msys64\\usr\\bin\\bash.exe";
            } else if (std::filesystem::exists("C:\\msys64\\mingw64\\bin\\bash.exe")) {
                bashPath = "C:\\msys64\\mingw64\\bin\\bash.exe";
            } else if (std::filesystem::exists("C:\\Program Files\\Git\\bin\\bash.exe")) {
                bashPath = "C:\\Program Files\\Git\\bin\\bash.exe";
            }
            
            if (!bashPath.empty() && std::filesystem::exists(bashPath)) {
                // Use stdbuf with environment variables for maximum unbuffering

                if (bashPath.find("msys64") != std::string::npos) {
                    cmdLine = "\"" + bashPath + "\" -c \"stdbuf -oL -eL " + command_str + "\"";
                } else {
                    cmdLine = "\"" + bashPath + "\" -c \"" + command_str + "\"";
                }
            } else {
                CloseHandle(hStdOutRead);
                CloseHandle(hStdOutWrite);
                CloseHandle(hStdInRead);
                CloseHandle(hStdInWrite);
                output << "exec: bash not found" << std::endl;
                return 1;
            }
        }
        
        if (!CreateProcess(NULL, const_cast<LPSTR>(cmdLine.c_str()), NULL, NULL, TRUE, 
                        CREATE_NO_WINDOW, 
                        const_cast<LPSTR>(envBlock.c_str()), // Use custom environment

                        NULL, &si, &pi)) {
            CloseHandle(hStdOutRead);
            CloseHandle(hStdOutWrite);
            CloseHandle(hStdInRead);
            CloseHandle(hStdInWrite);
            output << "exec: failed to create process" << std::endl;
            return 1;
        }
        
        CloseHandle(hStdOutWrite);
        CloseHandle(hStdInRead);
        CloseHandle(hStdInWrite);
        
        
        DWORD bytesRead;
        char buffer[64]; 
        DWORD bytesAvailable;
        
        while (true) {
            DWORD exitCode;
            if (GetExitCodeProcess(pi.hProcess, &exitCode) && exitCode != STILL_ACTIVE) {
                while (PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &bytesAvailable, NULL) && bytesAvailable > 0) {
                    DWORD toRead = std::min(bytesAvailable, static_cast<DWORD>(sizeof(buffer) - 1));
                    if (ReadFile(hStdOutRead, buffer, toRead, &bytesRead, NULL) && bytesRead > 0) {
                        buffer[bytesRead] = '\0';
                        printf("%s", buffer);
                        fflush(stdout);
                    }
                }
                break;
            }
            
            if (PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &bytesAvailable, NULL) && bytesAvailable > 0) {
                DWORD toRead = std::min(bytesAvailable, static_cast<DWORD>(sizeof(buffer) - 1));
                if (ReadFile(hStdOutRead, buffer, toRead, &bytesRead, NULL) && bytesRead > 0) {
                    buffer[bytesRead] = '\0';
                    printf("%s", buffer);
                    fflush(stdout);
                }
            } else {
                Sleep(1); 
            }
        }
        
        DWORD finalExitCode = 0;
        GetExitCodeProcess(pi.hProcess, &finalExitCode);
        CloseHandle(hStdOutRead);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        fflush(stdout);
        
        return static_cast<int>(finalExitCode);
    });
#endif

    if(argc == 1) {
        bool active = true;
        bool debug_cmd = false;
        try {
            cmd::AstExecutor &executor = cmd::AstExecutor::getExecutor();
            using_history();
            read_history(".cmd_history");
            std::ostringstream input_stream;
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

            signal(SIGINT, sigint_handler);
#endif

            while(active) {
                try {
                    std::string prompt = executor.getPath() + " $> ";
                    char* line = readline(prompt.c_str());
                    if (line == nullptr) {
                        std::cout << std::endl;  
                        write_history(".cmd_history");
                        break;
                    }

                    if (line[0] != '\0') {

                        if(line[0] == '{') {
                            std::string iline;
                            input_stream.str("");
                            while(true) {
                                char *in_line = readline("... ");
                                iline = in_line;
                                if(in_line != nullptr) {
                                    free(in_line);
                                    auto pos = iline.find("}");
                                    if(pos != std::string::npos) {
                                        std::string left;
                                        left = iline.substr(0, pos);
                                        input_stream << left << "\n";
                                        break;
                                    } else {
                                        add_history(iline.c_str());
                                        input_stream << iline << "\n";
                                    }
                                }
                            }
                            scan::TString string_buffer(input_stream.str());
                            scan::Scanner scanner(string_buffer);
                            cmd::Parser parser(scanner);
                            auto ast = parser.parse();
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

                            program_running = 1;
#endif

                            executor.execute(std::cin, std::cout, ast);
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

                            program_running = 0;
#endif

                            if(debug_cmd) {
                                ast->print(std::cout);
                            }
                            continue;
                        }
                        std::string command_data;
                        add_history(line);
                        command_data = line;
                        free(line);

                        if(command_data == "wsl_on") {

#ifdef _WIN32

                            if(!std::filesystem::exists("C:\\Windows\\System32\\wsl.exe")) {
                                std::cerr << "WSL executable not found. Please ensure WSL is installed." << std::endl;
                                continue;
                            } else {
                                std::cout << "WSL mode enabled. Use 'wsl_off' to disable." << std::endl;
                                cmd::cmd_type = "wsl.exe";
                                continue;
                            }
#else

                            std::cerr << "WSL mode is not available on this platform." << std::endl;
                            continue;
#endif


                        } else if(command_data == "wsl_off") {
#ifdef _WIN32

                            cmd::cmd_type = "cmd.exe /c ";
                            std::cout << "WSL mode disabled. Using default command shell." << std::endl;
                            continue;
#else

                            std::cerr << "WSL mode is not available on this platform." << std::endl;    
                            continue;
#endif


                        } else if(command_data == "clear" || command_data == "cls") {
                            std::cout << "\033[2J\033[1;1H"; 
                            continue;
                        } else if(command_data == "exit" || command_data == "quit" || command_data == "q") {
                            active = false;
                            continue;
                        } else if(command_data == "@debug_on") {
                            debug_cmd = true;
                            std::cout << "Debugging commands on." << std::endl;
                            continue;
                        } else if(command_data == "@debug_off") {
                            debug_cmd = false;
                            std::cout << "Debugging commands off." << std::endl;
                            continue;
                        }
                    
                        scan::TString string_buffer(command_data);
                        scan::Scanner scanner(string_buffer);
                        cmd::Parser parser(scanner);
                        auto ast = parser.parse();
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

                        program_running = 1;
#endif

                        executor.execute(std::cin, std::cout, ast);

#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

                        program_running = 1;
#endif

                        if(debug_cmd) {
                            ast->print(std::cout);
                        }
                    }
                } catch (const scan::ScanExcept &e) {
                    std::cerr << "Scan error: " << e.why() << std::endl;
                } catch (const std::exception &e) {
                    std::cerr << "Exception: " << e.what() << std::endl;
                } catch(const cmd::AstFailure &e) {
                    std::cerr << "Exception: " << e.what() << std::endl;
                } catch (const state::StateException &e) {
                    std::cerr << "State error: " << e.what() << std::endl;;
                } catch(const cmd::Exit_Exception  &e) {
                    std::cout << "\nExit: " << e.getCode() << std::endl;
                }
                 catch (...) {
                    std::cerr << "Unknown error occurred." << std::endl;
                }
            }
            write_history(".cmd_history");
        } catch (const scan::ScanExcept &e) {
            std::cerr << "Scan error: " << e.why() << std::endl;
        } catch (const std::exception &e) {
            std::cerr << "Exception: " << e.what() << std::endl;
        } catch (const cmd::AstFailure &e) {
            std::cerr << "Exception: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Unknown error occurred." << std::endl;
        }

    } else if(argc >= 2  && args.stdin_input == false) {

        cmd::AstExecutor &executor = cmd::AstExecutor::getExecutor();
        bool debug_cmd = false;
        bool debug_html = false;
        bool debug_tokens = false;
            std::ostringstream stream;
            std::fstream file;
            if(args.debug_output) {
                debug_cmd = true;
            }
            if(args.debug_syntax_highlight) {
                debug_html = true;
            }
            if(args.debug_tokens) {
                debug_tokens = true;
            }
            if(args.arguments.size() == 0) {
                std::cerr << "mx: No script file name provided.\n";
                return EXIT_FAILURE;
            }
            std::string app_name = args.arguments[0];
            file.open(app_name, std::ios::in);
            if(!file.is_open()) {
                std::cerr << "Error loading file: " << app_name << "\n";;
                return EXIT_FAILURE;
            }
            stream << file.rdbuf();
            std::string fileContent = stream.str();
            
            if(!fileContent.empty()) {
                fileContent.erase(
                    std::remove(fileContent.begin(), fileContent.end(), '\r'),
                        fileContent.end()
                );
            }

            if (fileContent.size() >= 2 && fileContent[0] == '#' && fileContent[1] == '!') {
                size_t newlinePos = fileContent.find('\n');
                if (newlinePos != std::string::npos) {
                    fileContent = fileContent.substr(newlinePos + 1);
                }
            }

            std::atomic<bool> exec_interrupt = false;
            executor.setInterrupt(&exec_interrupt);
            try {
                scan::TString string_buffer(fileContent); 
                scan::Scanner scanner(string_buffer);
                cmd::Parser parser(scanner);
                auto ast = parser.parse();

                if(debug_tokens) {
                    std::ofstream out_file("debug.tokens.txt");
                    std::cout << "Debug Tokens Information written to: debug.tokens.txt\n\n";
                    dumpTokens(scanner, out_file);
                }

    #if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
                program_running = 1;
    #endif
                executor.setPath(std::filesystem::path(app_name).parent_path().string());
                executor.execute(std::cin, std::cout, ast);

                std::cout.flush();
                std::cout << std::flush;
                if(debug_cmd)  {
                    std::ofstream html_file("debug.html");
                    html::gen_html(html_file, ast);
                    std::cout << "Debug Information written to: debug.html\n\n";
                }
                if(debug_html) {
                    std::ofstream html_file("debug.syntax.html");
                    html::gen_html_color(html_file, ast);
                    std::cout << "Debug Syntax Information written to: debug.syntax.html\n\n";
                }
                
            } catch(const scan::ScanExcept &e) {
                std::cerr << "Scan Error: " << e.why() << std::endl;
                return EXIT_FAILURE;
            } catch(const std::exception &e) {
                std::cerr << "Exception: " << e.what() << std::endl;
                return EXIT_FAILURE;
            } catch(const cmd::AstFailure &e) {
                std::cerr << "Failure: " << e.what() << std::endl;
                return EXIT_FAILURE;   
            } catch(const state::StateException &e) {
                std::cerr << "State Exception: " << e.what() << std::endl;
            } catch(const cmd::Exit_Exception &e) {
                std::cout << "\nExit: " << e.getCode() << std::endl;
                return e.getCode();
            }
            catch(...) {
                std::cerr << "Fatal Error has occoured.\n";
                throw;
                return EXIT_FAILURE;
            }
        } else if(argc == 2 && args.stdin_input) {
            try {
            cmd::AstExecutor &executor = cmd::AstExecutor::getExecutor();
            std::ostringstream stream;
            stream << std::cin.rdbuf();
            scan::TString string_buffer(stream.str());
            scan::Scanner scanner(string_buffer);
            cmd::Parser parser(scanner);
            auto ast = parser.parse();
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

            program_running = 1;
#endif

            executor.execute(std::cin, std::cout, ast);
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)

            program_running = 0;
#endif    

        } catch(const scan::ScanExcept &e) {
            std::cerr << "Scan Error: " << e.why() << std::endl;
            return EXIT_FAILURE;
        } catch(const std::exception &e) {
            std::cerr << "Exception: " << e.what() << std::endl;
            return EXIT_FAILURE;
        } catch(cmd::AstFailure &e) {
            std::cerr << "Failure: " << e.what() << std::endl;
            return EXIT_FAILURE;
        } catch(state::StateException &e) {
            std::cerr << "State Exception: " << e.what() << std::endl;
            return EXIT_FAILURE;
        } catch(cmd::Exit_Exception &e) {
            std::cout << "\nExit: " << e.getCode() << std::endl;
            return e.getCode();
        }
        catch(...) {
            std::cerr << "Unknown Error has Occoured..\n";
            return EXIT_FAILURE;
        }
    }
    return EXIT_SUCCESS;
}