#include "mxvm/parser.hpp" #include "mxvm/ast.hpp" #include "mxvm/icode.hpp" #include "scanner/exception.hpp" #include "scanner/scanner.hpp" #include <filesystem> #include <fstream> #include <iomanip> #include <iostream> #include <set> #include <unordered_map> namespace { std::string html_escape(const std::string &s) { std::string out; out.reserve(s.size() * 12 / 10 + 8); for (char c : s) { switch (c) { case '&': out += "&"; break; case '<': out += "<"; break; case '>': out += ">"; break; case '"': out += """; break; case '\'': out += "'"; break; default: out.push_back(c); break; } } return out; } std::string js_escape(const std::string &s) { std::string out; for (char c : s) { switch (c) { case '\\': out += "\\\\"; break; case '\'': out += "\\'"; break; case '"': out += "\\\""; break; default: out.push_back(c); break; } } return html_escape(out); } } // namespace namespace mxvm { bool debug_mode = false; bool instruct_mode = false; bool html_mode = false; ModuleParser::ModuleParser(const Mode &mode, const std::string &m, const std::string &source) : mod_name(m), scanner(source), parser_mode(mode) {} uint64_t ModuleParser::scan() { scanner.scan(); return scanner.size(); } bool ModuleParser::parse() { next(); require("module"); next(); require(types::TokenType::TT_ID); std::string mod_name = token->getTokenValue(); next(); require("{"); while (!match("}")) { if (match(types::TokenType::TT_ID) && token->getTokenValue() == "extern") { next(); require(types::TokenType::TT_ID); std::string func_name = token->getTokenValue(); functions.push_back({func_name, mod_name}); next(); continue; } next(); } return true; } scan::TToken ModuleParser::operator[](size_t pos) { return at(pos); } scan::TToken ModuleParser::at(size_t pos) { if (pos < scanner.size()) return scanner[pos]; throw scan::ScanExcept("Out of bounds"); return scanner[0]; } bool ModuleParser::generateProgramCode(const Mode &m, const std::string &mod_id, const std::string &mod_name1, std::unique_ptr<Program> &program) { for (auto &f : functions) { if (m == Mode::MODE_INTERPRET) program->add_runtime_extern(mod_name, mod_name1, "mxvm_" + mod_id + "_" + f.name, f.name); else program->add_extern(mod_name, f.name, true); } return true; } bool ModuleParser::next() { while (index < scanner.size() && scanner[index].getTokenType() != types::TokenType::TT_STR && scanner[index].getTokenValue() == "\n") { index++; } if (index < scanner.size()) { token = &scanner[index++]; return true; } return false; } bool ModuleParser::match(const std::string &s) { return (token->getTokenValue() == s); } bool ModuleParser::match(const types::TokenType &t) { return (token->getTokenType() == t); } void ModuleParser::require(const std::string &s) { if (!match(s)) { throw mx::Exception("Syntax Error: Looking for: " + s + " on line " + std::to_string(token->getLine())); } } void ModuleParser::require(const types::TokenType &t) { if (!match(t)) { throw mx::Exception("Syntax Error: Looking for: " + std::to_string(static_cast<unsigned int>(t)) + " on line " + std::to_string(token->getLine())); } } Parser::Parser(const std::string &source) : source_file(source), scanner(source), validator(source) { } uint64_t Parser::scan() { try { scanner.scan(); } catch (scan::ScanExcept &e) { std::cerr << "Scanner error: " << e.why() << "\n"; } return scanner.size(); } auto Parser::operator[](size_t pos) { if (pos < scanner.size()) { return scanner[pos]; } throw mx::Exception("Error: scanner index out of bounds.\n"); return scanner[0]; } std::unique_ptr<ProgramNode> Parser::parseAST() { std::unique_ptr<ProgramNode> mainProgram = nullptr; std::vector<std::unique_ptr<ProgramNode>> allDeclarations; std::string name; for (uint64_t i = 0; i < scanner.size(); ++i) { auto token = this->operator[](i); std::string tokenValue = token.getTokenValue(); if (i + 1 < scanner.size()) name = this->operator[](i + 1).getTokenValue(); if (tokenValue == "program" || tokenValue == "object") { auto declaration = parseProgramOrObject(i); if (declaration) { if (tokenValue == "program") { declaration->object = false; declaration->name = name; mainProgram = std::move(declaration); object_name = name; } else { declaration->object = true; declaration->name = name; object_name = name; allDeclarations.push_back(std::move(declaration)); } } } } if (!mainProgram) { mainProgram = std::make_unique<ProgramNode>(); mainProgram->name = name; mainProgram->object = false; object_name = name; } for (auto &obj : allDeclarations) { mainProgram->addInlineObject(std::move(obj)); } return mainProgram; } std::unique_ptr<ObjectNode> Parser::parseObject(uint64_t &index) { if (index >= scanner.size()) return nullptr; std::string name = this->operator[](index).getTokenValue(); index++; if (index < scanner.size()) { if (this->operator[](index).getTokenValue() == ",") index++; } return std::make_unique<ObjectNode>(name); } void Parser::processObjectSection(SectionNode *sectionNode, std::unique_ptr<Program> &program) { for (const auto &statement : sectionNode->statements) { auto objNode = dynamic_cast<ObjectNode *>(statement.get()); if (objNode) { std::string &name = objNode->name; processObjectFile(name, program); } } } void Parser::processObjectFile(const std::string &src, std::unique_ptr<Program> &program) { static std::set<std::string> processing_files; if (processing_files.count(src)) { return; } processing_files.insert(src); std::fstream file; std::string path; if (object_path.ends_with("/")) path = object_path; else path = object_path + "/"; file.open(path + src + ".mxvm", std::ios::in); if (!file.is_open()) { return; } std::ostringstream stream; stream << file.rdbuf(); file.close(); std::string full_path = path + src + ".mxvm"; if (!std::filesystem::exists(full_path)) { throw mx::Exception("Object file not found: " + full_path); } mxvm::Validator file_validator(stream.str()); try { if (!file_validator.validate(full_path)) { throw mx::Exception("Validation failed for: " + full_path); } } catch (mx::Exception &e) { throw; } std::unique_ptr<Parser> parser(new Parser(stream.str())); parser->module_path = module_path; parser->object_path = object_path; parser->object_mode = true; parser->parser_mode = parser_mode; parser->platform = platform; parser->scan(); auto ast = parser->parseAST(); if (!ast) return; for (const auto &inlineObj : ast->inlineObjects) { auto objProgram = std::make_unique<Program>(); objProgram->name = inlineObj->name; objProgram->object = true; objProgram->object_external = true; objProgram->filename = path + src + ".mxvm"; objProgram->platform = platform; for (const auto §ion : inlineObj->sections) { auto sectionNode = dynamic_cast<SectionNode *>(section.get()); if (!sectionNode) continue; if (sectionNode->type == SectionNode::DATA) { parser->processDataSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::CODE) { parser->processCodeSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::MODULE) { parser->processModuleSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::OBJECT) { parser->processObjectSection(sectionNode, objProgram); } } if (Program::base != nullptr && objProgram->object) Program::base->add_object(objProgram->name, objProgram.get()); registerObjectExterns(program, objProgram); program->objects.push_back(std::move(objProgram)); } processing_files.erase(src); } std::unique_ptr<SectionNode> Parser::parseSection(uint64_t &index) { index++; if (index >= scanner.size()) return nullptr; auto sectionNameToken = this->operator[](index); std::string sectionName = sectionNameToken.getTokenValue(); SectionNode::SectionType sectionType; if (sectionName == "data") { sectionType = SectionNode::DATA; } else if (sectionName == "code") { sectionType = SectionNode::CODE; } else if (sectionName == "module") { sectionType = SectionNode::MODULE; } else if (sectionName == "object") { sectionType = SectionNode::OBJECT; } else { return nullptr; } auto section = std::make_unique<SectionNode>(sectionType); index++; if (index < scanner.size() && this->operator[](index).getTokenValue() == "{") { index++; while (index < scanner.size()) { auto token = this->operator[](index); std::string value = token.getTokenValue(); if (value == "}") { index++; break; } if (value == "\n" || value == " " || value == "\t") { index++; continue; } if (value == "//" || value.starts_with("//") || value == "#" || value.starts_with("#")) { auto comment = parseComment(index); if (comment) { section->addStatement(std::move(comment)); } continue; } if (sectionType == SectionNode::DATA) { auto variable = parseDataVariable(index); if (variable) { section->addStatement(std::move(variable)); } else { index++; } } else if (sectionType == SectionNode::MODULE) { auto module = parseModule(index); if (module) { section->addStatement(std::move(module)); } else { index++; } } else if (sectionType == SectionNode::OBJECT) { auto obj = parseObject(index); if (obj) { section->addStatement(std::move(obj)); } else { index++; } } else if (sectionType == SectionNode::CODE) { if (token.getTokenType() == types::TokenType::TT_ID && index + 1 < scanner.size() && this->operator[](index + 1).getTokenValue() == ":") { auto label = parseLabel(index); if (label) { section->addStatement(std::move(label)); } } else if (token.getTokenType() == types::TokenType::TT_ID && token.getTokenValue() == "function" && index + 2 < scanner.size() && this->operator[](index + 1).getTokenType() == types::TokenType::TT_ID && this->operator[](index + 2).getTokenValue() == ":") { index++; auto label = parseLabel(index); if (label) { label->function = true; section->addStatement(std::move(label)); continue; } } else { auto instruction = parseCodeInstruction(index); if (instruction) { section->addStatement(std::move(instruction)); } else { index++; } } } } } return section; } std::unique_ptr<ModuleNode> Parser::parseModule(uint64_t &index) { if (index >= scanner.size()) return nullptr; std::string name = this->operator[](index).getTokenValue(); index++; if (index < scanner.size()) { if (this->operator[](index).getTokenValue() == ",") index++; } return std::make_unique<ModuleNode>(name); } std::unique_ptr<VariableNode> Parser::parseDataVariable(uint64_t &index) { if (index >= scanner.size()) return nullptr; bool is_global = false; auto token = this->operator[](index); if (token.getTokenValue() == "export") { is_global = true; index++; if (index >= scanner.size()) return nullptr; token = this->operator[](index); } std::string typeStr = token.getTokenValue(); VarType varType; if (typeStr == "int") varType = VarType::VAR_INTEGER; else if (typeStr == "float") varType = VarType::VAR_FLOAT; else if (typeStr == "string") varType = VarType::VAR_STRING; else if (typeStr == "ptr") varType = VarType::VAR_POINTER; else if (typeStr == "array") varType = VarType::VAR_ARRAY; else if (typeStr == "byte") varType = VarType::VAR_BYTE; else return nullptr; index++; if (index >= scanner.size()) return nullptr; auto nameToken = this->operator[](index); if (nameToken.getTokenType() != types::TokenType::TT_ID) { return nullptr; } std::string varName = nameToken.getTokenValue(); index++; if (index < scanner.size() && this->operator[](index).getTokenValue() == "=") { index++; std::string add_op; if (index < scanner.size() && this->operator[](index).getTokenValue() == "-") { add_op = "-"; index++; } if (index < scanner.size()) { auto valueToken = this->operator[](index); std::string value = valueToken.getTokenValue(); types::TokenType tokenType = valueToken.getTokenType(); switch (tokenType) { case types::TokenType::TT_STR: if (value.starts_with("\"") && value.ends_with("\"")) { value = value.substr(1, value.length() - 2); } index++; { auto node = std::make_unique<VariableNode>(varType, varName, value); node->is_global = is_global; return node; } case types::TokenType::TT_NUM: index++; { auto node = std::make_unique<VariableNode>(varType, varName, add_op + value); node->is_global = is_global; return node; } case types::TokenType::TT_HEX: index++; { auto node = std::make_unique<VariableNode>(varType, varName, value); node->is_global = is_global; return node; } case types::TokenType::TT_ID: index++; { auto node = std::make_unique<VariableNode>(varType, varName, value); node->is_global = is_global; return node; } default: index++; { auto node = std::make_unique<VariableNode>(varType, varName, value); node->is_global = is_global; return node; } } } } else if (index < scanner.size() && this->operator[](index).getTokenValue() == "," && varType == VarType::VAR_STRING) { index++; size_t buf_size = 0; if (index < scanner.size()) { buf_size = std::stoll(this->operator[](index).getTokenValue(), nullptr, 0); } if (buf_size == 0) { throw mx::Exception("string buffer: " + varName + " requires valid size"); } auto node = std::make_unique<VariableNode>(varType, varName, buf_size); node->is_global = is_global; return node; } auto node = std::make_unique<VariableNode>(varType, varName); node->is_global = is_global; return node; } std::unique_ptr<InstructionNode> Parser::parseCodeInstruction(uint64_t &index) { static std::unordered_map<std::string, Inc> instructionMap = { {"mov", MOV}, {"load", LOAD}, {"store", STORE}, {"add", ADD}, {"sub", SUB}, {"mul", MUL}, {"div", DIV}, {"or", OR}, {"and", AND}, {"xor", XOR}, {"not", NOT}, {"mod", MOD}, {"cmp", CMP}, {"fcmp", FCMP}, {"jmp", JMP}, {"je", JE}, {"jne", JNE}, {"jl", JL}, {"jle", JLE}, {"jg", JG}, {"jge", JGE}, {"jz", JZ}, {"jnz", JNZ}, {"ja", JA}, {"jb", JB}, {"jae", JAE}, {"jbe", JBE}, {"jc", JC}, {"jnc", JNC}, {"jp", JP}, {"jnp", JNP}, {"jo", JO}, {"jno", JNO}, {"js", JS}, {"jns", JNS}, {"print", PRINT}, {"exit", EXIT}, {"alloc", ALLOC}, {"free", FREE}, {"getline", GETLINE}, {"push", PUSH}, {"pop", POP}, {"stack_load", STACK_LOAD}, {"stack_store", STACK_STORE}, {"stack_sub", STACK_SUB}, {"call", CALL}, {"ret", RET}, {"string_print", STRING_PRINT}, {"done", DONE}, {"to_int", TO_INT}, {"to_float", TO_FLOAT}, {"invoke", INVOKE}, {"return", RETURN}, {"neg", NEG}}; if (index >= scanner.size()) return nullptr; auto token = this->operator[](index); std::string tokenValue = token.getTokenValue(); if (token.getTokenType() == types::TokenType::TT_ID) { if (index + 1 < scanner.size() && this->operator[](index + 1).getTokenValue() == ":") { return nullptr; } } auto instIt = instructionMap.find(tokenValue); if (instIt == instructionMap.end()) { throw mx::Exception("Error invalid instruction: " + tokenValue); return nullptr; } Inc instruction = instIt->second; index++; std::vector<Operand> operands; while (index < scanner.size()) { std::string add_value; if (index < scanner.size() && this->operator[](index).getTokenValue() == "-") { add_value = "-"; index++; } auto operandToken = this->operator[](index); std::string value = operandToken.getTokenValue(); types::TokenType tokenType = operandToken.getTokenType(); if (value == "\n" || value == "//" || value.starts_with("//") || value == "#" || value.starts_with("#") || value == "}") { break; } if (value == "," || value == " " || value == "\t") { index++; continue; } Operand operand; switch (tokenType) { case types::TokenType::TT_NUM: operand.op = add_value + value; operand.op_value = std::stoll(add_value + value); operand.type = OperandType::OP_CONSTANT; break; case types::TokenType::TT_HEX: operand.op = value; if (value.starts_with("0x") || value.starts_with("0X")) { operand.op_value = std::stoll(value, nullptr, 16); } else { operand.op_value = std::stoll(value, nullptr, 16); } operand.type = OperandType::OP_CONSTANT; break; case types::TokenType::TT_ID: if (index + 1 < scanner.size() && this->operator[](index + 1).getTokenValue() == ".") { operand.object = value; index += 2; if (index < scanner.size()) value = this->operator[](index).getTokenValue(); } if (operand.object.empty()) operand.op = value; else operand.op = operand.object + "." + value; operand.label = value; operand.type = OperandType::OP_VARIABLE; break; case types::TokenType::TT_STR: operand.op = value; operand.type = OperandType::OP_CONSTANT; break; case types::TokenType::TT_SYM: if (value != ",") { operand.op = value; } break; default: operand.op = value; break; } if (!operand.op.empty()) { operands.push_back(operand); } index++; } return std::make_unique<InstructionNode>(instruction, operands); } std::unique_ptr<CommentNode> Parser::parseComment(uint64_t &index) { std::string commentText; auto token = this->operator[](index); if (token.getTokenValue() == "//" || token.getTokenValue() == "#" || token.getTokenValue().starts_with("//") || token.getTokenValue().starts_with("#")) { index++; } while (index < scanner.size()) { auto token = this->operator[](index); if (token.getTokenValue() == "\n") { break; } commentText += token.getTokenValue() + " "; index++; } return std::make_unique<CommentNode>(commentText); } std::unique_ptr<LabelNode> Parser::parseLabel(uint64_t &index) { if (index >= scanner.size()) return nullptr; auto token = this->operator[](index); if (token.getTokenType() != types::TokenType::TT_ID) { return nullptr; } std::string labelName = token.getTokenValue(); index++; if (index < scanner.size() && this->operator[](index).getTokenValue() == ":") { index++; return std::make_unique<LabelNode>(labelName); } return nullptr; } void Parser::parse() { auto ast = parseAST(); if (ast) { std::cout << ast->toString() << std::endl; } } bool Parser::generateProgramCode(const Mode &mode, std::unique_ptr<Program> &program) { parser_mode = mode; try { if (!validator.validate(program->filename)) { return false; } } catch (mx::Exception &e) { std::cerr << e.what() << "\n"; return false; } auto ast = parseAST(); if (ast) { program->name = ast->name; if (!ast->root_name.empty()) { program->root_name = ast->root_name; program->name = ast->root_name; program->object = false; } else { program->object = true; } for (const auto §ion : ast->sections) { auto sectionNode = dynamic_cast<SectionNode *>(section.get()); if (!sectionNode) continue; if (sectionNode->type == SectionNode::DATA) { processDataSection(sectionNode, program); } else if (sectionNode->type == SectionNode::CODE) { processCodeSection(sectionNode, program); } else if (sectionNode->type == SectionNode::MODULE) { processModuleSection(sectionNode, program); } else if (sectionNode->type == SectionNode::OBJECT) { processObjectSection(sectionNode, program); } } for (const auto &inlineObj : ast->inlineObjects) { auto objProgram = std::make_unique<Program>(); objProgram->name = inlineObj->name; objProgram->object = true; objProgram->object_external = false; objProgram->filename = program->filename; objProgram->platform = platform; object_name = inlineObj->name; for (const auto §ion : inlineObj->sections) { auto sectionNode = dynamic_cast<SectionNode *>(section.get()); if (!sectionNode) continue; if (sectionNode->type == SectionNode::DATA) { processDataSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::CODE) { processCodeSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::MODULE) { processModuleSection(sectionNode, objProgram); } else if (sectionNode->type == SectionNode::OBJECT) { processObjectSection(sectionNode, objProgram); } } registerObjectExterns(program, objProgram); if (Program::base != nullptr && objProgram->object) Program::base->add_object(objProgram->name, objProgram.get()); program->objects.push_back(std::move(objProgram)); } if (mode == Mode::MODE_COMPILE) { std::set<std::string> generated_objects; std::function<void(std::unique_ptr<Program> &)> compileAllObjects; compileAllObjects = [&](std::unique_ptr<Program> &p) { if (!p) return; for (auto &obj : p->objects) { if (obj && generated_objects.find(obj->name) == generated_objects.end()) { generateObjectAssemblyFile(obj); generated_objects.insert(obj->name); if (html_mode) { std::ofstream htmlFile(obj->name + ".html"); if (htmlFile.is_open()) { generateDebugHTML(htmlFile, obj); std::cout << Col("MXVM: Generated ", mx::Color::BRIGHT_GREEN) << "Debug HTML for: " << obj->name << "\n"; } } } compileAllObjects(obj); } }; compileAllObjects(program); } if (program->object == false) { if (!program->validateNames(validator)) { throw mx::Exception("Could not validate variables/functions\n"); } } } return true; } void Parser::collectObjectNames(std::vector<std::pair<std::string, std::string>> &names, const std::unique_ptr<Program> &program) { for (const auto &objPtr : program->objects) { if (!objPtr) continue; for (const auto &ext : objPtr->external) { if (std::find(names.begin(), names.end(), std::make_pair(ext.mod, ext.name)) == names.end()) names.push_back(std::make_pair(ext.mod, ext.name)); } collectObjectNames(names, objPtr); } } void Parser::printObjectHTML(std::ostream &out, const std::unique_ptr<Program> &objPtr) { out << R"(<div class="object-section"> <div class="object-title">Object: <span style="color: lime;"><strong>)" << html_escape(objPtr->name) << R"(</span></strong></div>)"; out << R"( <div class="stats" style="margin-bottom:30px;"> <div class="stat-card"> <span class="number">)" << objPtr->vars.size() << R"(</span> <span class="label">Variables</span> </div> <div class="stat-card"> <span class="number">)" << objPtr->inc.size() << R"(</span> <span class="label">Instructions</span> </div> <div class="stat-card"> <span class="number">)" << objPtr->labels.size() << R"(</span> <span class="label">Labels</span> </div> <div class="stat-card"> <span class="number">)" << objPtr->objects.size() << R"(</span> <span class="label">Objects</span> </div> </div> )"; out << R"( <div class="section"> <div class="section-header"><span class="icon">🧮</span> Variables</div> <div class="section-content">)"; if (objPtr->vars.empty()) { out << R"(<div class="no-data">No variables defined</div>)"; } else { out << R"(<div class="variables-grid">)"; for (const auto &var : objPtr->vars) { out << R"(<div class="variable-card"> <div class="variable-name">)" << html_escape(var.first) << R"(</div> <div class="variable-type">)"; switch (var.second.type) { case VarType::VAR_INTEGER: out << "Integer"; break; case VarType::VAR_FLOAT: out << "Float"; break; case VarType::VAR_STRING: out << "String"; break; case VarType::VAR_POINTER: out << "Pointer"; break; case VarType::VAR_LABEL: out << "Label"; break; case VarType::VAR_EXTERN: out << "External"; break; case VarType::VAR_ARRAY: out << "Array"; break; case VarType::VAR_BYTE: out << "Byte"; break; default: out << "Unknown"; break; } out << R"(</div> <div class="variable-value">)"; switch (var.second.type) { case VarType::VAR_INTEGER: case VarType::VAR_BYTE: out << var.second.var_value.int_value; break; case VarType::VAR_FLOAT: out << std::fixed << std::setprecision(6) << var.second.var_value.float_value; break; case VarType::VAR_STRING: out << "\"" << html_escape(Program::escapeNewLines(var.second.var_value.str_value)) << "\""; break; case VarType::VAR_POINTER: case VarType::VAR_EXTERN: if (var.second.var_value.ptr_value == nullptr) out << "null"; else out << var.second.var_value.ptr_value; break; case VarType::VAR_LABEL: out << var.second.var_value.label_value; break; default: out << var.second.var_value.int_value; break; } out << R"(</div> </div>)"; } out << R"(</div>)"; } out << R"(</div> </div>)"; out << R"(<div class="section"> <div class="section-header"><span class="icon">🔖</span> Labels</div> <div class="section-content">)"; if (objPtr->labels.empty()) { out << R"(<div class="no-data">No labels defined</div>)"; } else { out << R"(<table class="instructions-table"> <thead> <tr> <th>Name</th> <th>Address</th> <th>Function</th> </tr> </thead> <tbody>)"; for (const auto &label : objPtr->labels) { out << R"(<tr> <td>)" << html_escape(label.first) << R"(</td> <td>)" << "0x" << std::hex << label.second.first << std::dec << R"(</td> <td>)" << (label.second.second ? "true" : "false") << R"(</td> </tr>)"; } out << R"(</tbody> </table>)"; } out << R"(</div> </div>)"; out << R"( <div class="section"> <div class="section-header"><span class="icon">⚙</span> Instructions</div> <div class="section-content">)"; if (objPtr->inc.empty()) { out << R"(<div class="no-data">No instructions defined</div>)"; } else { out << R"(<table class="instructions-table"> <thead> <tr> <th>#</th> <th>Opcode</th> <th>Instruction</th> <th>Operand 1</th> <th>Operand 2</th> <th>Operand 3</th> <th>Extra Operands</th> </tr> </thead> <tbody>)"; for (size_t i = 0; i < objPtr->inc.size(); ++i) { const auto &instr = objPtr->inc[i]; out << "<tr>" << "<td>0x" << std::hex << std::uppercase << i << std::dec << "</td>" << "<td class=\"opcode\">0x" << std::hex << std::uppercase << static_cast<int>(instr.instruction) << std::dec << "</td>" << "<td class=\"opcode-name\">" << html_escape(IncType[static_cast<int>(instr.instruction)]) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op1.op) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op2.op) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op3.op) << "</td>" << "<td class=\"operand\">"; for (size_t j = 0; j < instr.vop.size(); ++j) { if (j > 0) out << ", "; out << html_escape(instr.vop[j].op); } out << "</td></tr>"; } out << R"(</tbody> </table>)"; } out << R"(</div> </div>)"; if (!objPtr->objects.empty()) { out << R"(<div class="section"> <div class="section-header"><span class="icon">🧩</span> Nested Objects</div> <div class="section-content">)"; for (const auto &nestedObj : objPtr->objects) { if (nestedObj) { printObjectHTML(out, nestedObj); } } out << R"(</div> </div>)"; } out << R"(<div class="section"> <div class="section-header"><span class="icon">📜</span> Compiled Assembly</div> <div class="section-content"> <textarea id="asm-)" << html_escape(objPtr->name) << R"(" readonly style="width:100%;height:300px;background:#222;color:#e0e0e0;border-radius:8px;padding:16px;font-family:'Fira Mono', 'Consolas', 'Courier New', monospace;font-size:1rem;">)"; out << html_escape(objPtr->assembly_code); out << R"(</textarea> <div style="margin-top:10px;">)"; out << "<button class=\"btn\" onclick=\"copyAsm('asm-" << js_escape(objPtr->name) << "')\">Copy</button>"; out << "<button class=\"btn\" onclick=\"downloadAsm('asm-" << js_escape(objPtr->name) << "', '" << js_escape(objPtr->name) << ".s')\">Download</button>"; out << R"(</div> </div> </div> )"; } bool Parser::generateDebugHTML(std::ostream &out, std::unique_ptr<Program> &program) { out << R"(<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MXVM Debug Report - )" << html_escape(program->name) << R"(</title> </head> <body> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #000; color: #e0e0e0; line-height: 1.6; min-height: 100vh; } .container { width: 100%; max-width: 100vw; margin: 0 auto; padding: 20px; background: transparent; } .header { background: #222; border-radius: 15px; padding: 30px; margin-bottom: 30px; box-shadow: 0 8px 32px rgba(183, 28, 28, 0.2); border: 1px solid #b71c1c; } .header h1 { color: #ff5252; font-size: 2.5rem; margin-bottom: 10px; font-weight: 700; text-shadow: 1px 1px 2px #000; } .header .subtitle { color: #e57373; font-size: 1.2rem; margin-bottom: 20px; } .stats { display: flex; gap: 20px; flex-wrap: wrap; } .stat-card { background: #b71c1c; color: #fff; padding: 15px 25px; border-radius: 10px; text-align: center; min-width: 150px; box-shadow: 0 4px 15px rgba(183, 28, 28, 0.3); } .stat-card .number { font-size: 2rem; font-weight: bold; display: block; } .stat-card .label { font-size: 0.9rem; opacity: 0.9; } .section { background: #181818; border-radius: 15px; margin-bottom: 30px; box-shadow: 0 8px 32px rgba(183, 28, 28, 0.1); border: 1px solid #b71c1c; overflow: hidden; } .section-header { background: #b71c1c; color: #fff; padding: 20px 30px; font-size: 1.4rem; font-weight: 600; display: flex; align-items: center; gap: 10px; } .section-content { padding: 30px; } .variables-grid { display: grid; gap: 15px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } .variable-card { background: #222; border: 1px solid #b71c1c; border-radius: 10px; padding: 20px; color: #fff; transition: transform 0.2s, box-shadow 0.2s; } .variable-card:hover { transform: translateY(-2px); box-shadow: 0 4px 20px rgba(183, 28, 28, 0.2); } .variable-name { font-weight: 600; font-size: 1.1rem; color: #ff5252; margin-bottom: 8px; } .variable-type { display: inline-block; background: #b71c1c; color: #fff; padding: 4px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 500; margin-bottom: 10px; } .variable-value { font-family: 'Courier New', monospace; background: #181818; border: 1px solid #b71c1c; border-radius: 5px; padding: 10px; color: #e0e0e0; word-break: break-all; } .instructions-table { width: 100%; border-collapse: collapse; background: #222; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px rgba(183, 28, 28, 0.1); } .instructions-table th { background: #b71c1c; color: #fff; padding: 15px; text-align: left; font-weight: 600; } .instructions-table td { padding: 12px 15px; border-bottom: 1px solid #b71c1c; color: #e0e0e0; } .instructions-table tr:nth-child(even) { background: #181818; } .instructions-table tr:hover { background: #2d2d2d; } .opcode { font-weight: 600; color: #ff5252; } .operand { font-family: 'Courier New', monospace; color: #e0e0e0; } .object-section { margin-left: 20px; border-left: 3px solid #b71c1c; padding-left: 20px; } .object-title { font-size: 1.2rem; font-weight: 600; color: #ff5252; margin-bottom: 15px; padding: 10px 15px; background: #222; border-radius: 8px; border-left: 4px solid #b71c1c; } .btn { background: #b71c1c; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; margin-right: 10px; font-weight: 600; transition: background 0.2s; } .btn:hover { background: #ff5252; } .icon { width: 20px; height: 20px; display: inline-block; } .no-data { text-align: center; color: #e57373; font-style: italic; padding: 40px; } @media (max-width: 768px) { .container { padding: 10px; } .header h1 { font-size: 2rem; } .stats { justify-content: center; } .variables-grid { grid-template-columns: 1fr; } .instructions-table { font-size: 0.9rem; } } </style> )"; bool mainHasContent = !program->vars.empty() || !program->inc.empty() || !program->labels.empty(); bool hasObjects = !program->objects.empty(); out << R"( <div class="container"> <div class="header"> <h1>MXVM Debug Report</h1> <div class="subtitle">Program: <span style="color: blue;"><strong>)" << html_escape(program->name) << R"(</span></strong></div> <div class="stats"> <div class="stat-card"> <span class="number">)" << program->vars.size() << R"(</span> <span class="label">Variables</span> </div> <div class="stat-card"> <span class="number">)" << program->inc.size() << R"(</span> <span class="label">Instructions</span> </div> <div class="stat-card"> <span class="number">)" << program->objects.size() << R"(</span> <span class="label">Objects</span> </div> <div class="stat-card"> <span class="number">)" << program->labels.size() << R"(</span> <span class="label">Labels</span> </div> </div> </div>)"; if (mainHasContent) { out << R"(<div class="section"> <div class="section-header"> <span class="icon">🧮</span> Variables </div> <div class="section-content">)"; if (program->vars.empty()) { out << R"(<div class="no-data">No variables defined</div>)"; } else { out << R"(<div class="variables-grid">)"; for (const auto &var : program->vars) { out << R"(<div class="variable-card"> <div class="variable-name">)" << html_escape(var.first) << R"(</div> <div class="variable-type">)"; switch (var.second.type) { case VarType::VAR_INTEGER: out << "Integer"; break; case VarType::VAR_FLOAT: out << "Float"; break; case VarType::VAR_STRING: out << "String"; break; case VarType::VAR_POINTER: out << "Pointer"; break; case VarType::VAR_LABEL: out << "Label"; break; case VarType::VAR_EXTERN: out << "External"; break; case VarType::VAR_ARRAY: out << "Array"; break; case VarType::VAR_BYTE: out << "Byte"; break; default: out << "Unknown"; break; } out << R"(</div> <div class="variable-value">)"; switch (var.second.type) { case VarType::VAR_INTEGER: case VarType::VAR_BYTE: out << var.second.var_value.int_value; break; case VarType::VAR_FLOAT: out << std::fixed << std::setprecision(6) << var.second.var_value.float_value; break; case VarType::VAR_STRING: out << "\"" << html_escape(Program::escapeNewLines(var.second.var_value.str_value)) << "\""; break; case VarType::VAR_POINTER: case VarType::VAR_EXTERN: if (var.second.var_value.ptr_value == nullptr) out << "null"; else out << var.second.var_value.ptr_value; break; case VarType::VAR_LABEL: out << var.second.var_value.label_value; break; default: out << var.second.var_value.int_value; break; } out << R"(</div> </div>)"; } out << R"(</div>)"; } out << R"(</div> </div>)"; out << R"(<div class="section"> <div class="section-header"><span class="icon">🔖</span> Labels</div> <div class="section-content">)"; if (program->labels.empty()) { out << R"(<div class="no-data">No labels defined</div>)"; } else { out << R"(<table class="instructions-table"> <thead> <tr> <th>Name</th> <th>Function</th> </tr> </thead> <tbody>)"; for (const auto &label : program->labels) { out << "<tr><td>" << html_escape(label.first) << "</td><td>0x" << std::hex << label.second.first << std::dec << R"(</td> <td>)" << (label.second.second ? "true" : "false") << R"(</td> </tr>)"; } out << R"(</tbody> </table>)"; } out << R"(</div> </div>)"; out << R"(<div class="section"> <div class="section-header"> <span class="icon">⚙</span> Instructions </div> <div class="section-content">)"; if (program->inc.empty()) { out << R"(<div class="no-data">No instructions defined</div>)"; } else { out << R"(<table class="instructions-table"> <thead> <tr> <th>#</th> <th>Opcode</th> <th>Instruction</th> <th>Operand 1</th> <th>Operand 2</th> <th>Operand 3</th> <th>Extra Operands</th> </tr> </thead> <tbody>)"; for (size_t i = 0; i < program->inc.size(); ++i) { const auto &instr = program->inc[i]; out << "<tr>" << "<td>0x" << std::hex << std::uppercase << i << std::dec << "</td>" << "<td class=\"opcode\">0x" << std::hex << std::uppercase << static_cast<int>(instr.instruction) << std::dec << "</td>" << "<td class=\"opcode-name\">" << html_escape(IncType[static_cast<int>(instr.instruction)]) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op1.op) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op2.op) << "</td>" << "<td class=\"operand\">" << html_escape(instr.op3.op) << "</td>" << "<td class=\"operand\">"; for (size_t j = 0; j < instr.vop.size(); ++j) { if (j > 0) out << ", "; out << html_escape(instr.vop[j].op); } out << "</td></tr>"; } out << R"(</tbody> </table>)"; } out << R"(<div class="section"> <div class="section-header"><span class="icon">📜</span> Compiled Assembly</div> <div class="section-content"> <textarea id="asm-main" readonly style="width:100%;height:300px;background:#222;color:#e0e0e0;border-radius:8px;padding:16px;font-family:'Fira Mono', 'Consolas', 'Courier New', monospace;font-size:1rem;">)"; out << html_escape(program->assembly_code); out << R"(</textarea> <div style="margin-top:10px;">)"; out << "<button class=\"btn\" onclick=\"copyAsm('asm-main')\">Copy</button>"; out << "<button class=\"btn\" onclick=\"downloadAsm('asm-main', '" << js_escape(program->name) << ".s')\">Download</button>"; out << R"(</div> </div> </div> )"; } if (hasObjects) { out << R"(<div class="section"> <div class="section-header"> <span class="icon">📦</span> Objects </div> <div class="section-content">)"; for (const auto &obj : program->objects) { if (obj) { printObjectHTML(out, obj); } } out << R"(</div> </div>)"; } out << R"( </div>)"; out << R"( <script> function copyAsm(id) { var textarea = document.getElementById(id); textarea.select(); navigator.clipboard.writeText(textarea.value); } function downloadAsm(id, filename) { var textarea = document.getElementById(id); var blob = new Blob([textarea.value], {type: 'text/plain'}); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); } </script> </body> </html> )"; return true; } void Parser::processDataSection(SectionNode *sectionNode, std::unique_ptr<Program> &program) { for (const auto &statement : sectionNode->statements) { auto variableNode = dynamic_cast<VariableNode *>(statement.get()); if (variableNode) { Variable var; var.type = variableNode->type; var.var_name = program->name + "." + variableNode->name; if (variableNode->hasInitializer) { setVariableValue(var, variableNode->type, variableNode->initialValue); } else { setDefaultVariableValue(var, variableNode->type); } var.var_value.buffer_size = variableNode->buffer_size; var.is_global = variableNode->is_global; var.obj_name = program->name; program->add_variable(var.var_name, var); } } } void Parser::processModuleSection(SectionNode *sectionNode, std::unique_ptr<Program> &program) { for (const auto &statement : sectionNode->statements) { auto moduleNode = dynamic_cast<ModuleNode *>(statement.get()); if (moduleNode) { std::string &name = moduleNode->name; processModuleFile(name, program); } } } void Parser::processModuleFile(const std::string &src, std::unique_ptr<Program> &program) { std::string module_name = "libmxvm_" + src; if (!module_path.ends_with("/")) module_path += "/"; if (!include_path.ends_with("/")) include_path += "/"; std::string shared_ext = ".so"; #ifdef _WIN32 shared_ext = ".dll"; #elif defined(__APPLE__) shared_ext = ".dylib"; #endif std::string module_path_so = module_path + "modules/" + src + "/" + module_name + shared_ext; std::string msys2prefix; #ifdef _WIN32 msys2prefix = std::getenv("MSYSTEM_PREFIX"); size_t pos = msys2prefix.find("/msys64/"); if (pos != std::string::npos) { msys2prefix = msys2prefix.substr(0, pos + 8); } else { msys2prefix = ""; } #endif std::string module_src = msys2prefix + include_path + src + "/" + src + ".mxvm"; std::fstream file; file.open(module_src, std::ios::in); if (!file.is_open()) { throw mx::Exception("Error could not find module source file: " + module_src); } std::ostringstream data; data << file.rdbuf(); ModuleParser mod_parser(this->parser_mode, src, data.str()); if (mod_parser.scan() > 0) { if (mod_parser.parse() && mod_parser.generateProgramCode(this->parser_mode, src, module_path_so, program)) { return; } else { throw mx::Exception("Error parsing module file.\n"); } } } void Parser::processCodeSection(SectionNode *sectionNode, std::unique_ptr<Program> &program) { std::unordered_map<std::string, size_t> labelMap; size_t instructionIndex = 0; for (const auto &statement : sectionNode->statements) { if (auto labelNode = dynamic_cast<LabelNode *>(statement.get())) { labelMap[labelNode->name] = instructionIndex; program->add_label(labelNode->name, instructionIndex, labelNode->function); } else if (dynamic_cast<InstructionNode *>(statement.get())) { instructionIndex++; } } for (const auto &statement : sectionNode->statements) { auto instructionNode = dynamic_cast<InstructionNode *>(statement.get()); if (instructionNode) { Instruction instr; instr.instruction = instructionNode->instruction; if (instructionNode->operands.size() > 0) { instr.op1 = instructionNode->operands[0]; resolveLabelReference(instr.op1, labelMap); } if (instructionNode->operands.size() > 1) { instr.op2 = instructionNode->operands[1]; resolveLabelReference(instr.op2, labelMap); } if (instructionNode->operands.size() > 2) { instr.op3 = instructionNode->operands[2]; resolveLabelReference(instr.op3, labelMap); } if (instructionNode->operands.size() > 3) { for (size_t i = 3; i < instructionNode->operands.size(); ++i) { Operand extraOp = instructionNode->operands[i]; resolveLabelReference(extraOp, labelMap); instr.vop.push_back(extraOp); } } program->add_instruction(instr); } } } void Parser::setVariableValue(Variable &var, VarType type, const std::string &value, size_t buf_size) { switch (type) { case VarType::VAR_BYTE: case VarType::VAR_INTEGER: if (value.starts_with("0x") || value.starts_with("0X")) { var.var_value.int_value = std::stoull(value, nullptr, 16); } else { var.var_value.int_value = std::stoull(value); } var.var_value.type = type; break; case VarType::VAR_FLOAT: var.var_value.float_value = std::stod(value); var.var_value.type = VarType::VAR_FLOAT; break; case VarType::VAR_STRING: var.var_value.str_value = value; var.var_value.type = VarType::VAR_STRING; var.var_value.buffer_size = buf_size; break; case VarType::VAR_POINTER: var.var_value.ptr_value = nullptr; if (value == "null" || value == "0") { var.var_value.ptr_value = nullptr; var.var_value.str_value = "null"; } var.var_value.type = VarType::VAR_POINTER; break; case VarType::VAR_LABEL: var.var_value.label_value = value; var.var_value.type = VarType::VAR_LABEL; break; default: setDefaultVariableValue(var, type); break; } } void Parser::setDefaultVariableValue(Variable &var, VarType type) { switch (type) { case VarType::VAR_INTEGER: var.var_value.int_value = 0; var.var_value.type = VarType::VAR_INTEGER; break; case VarType::VAR_FLOAT: var.var_value.float_value = 0.0; var.var_value.type = VarType::VAR_FLOAT; break; case VarType::VAR_STRING: var.var_value.str_value = ""; var.var_value.type = VarType::VAR_STRING; break; case VarType::VAR_POINTER: var.var_value.ptr_value = nullptr; var.var_value.type = VarType::VAR_POINTER; break; case VarType::VAR_LABEL: var.var_value.label_value = ""; var.var_value.type = VarType::VAR_LABEL; break; default: var.var_value.type = VarType::VAR_NULL; break; } } void Parser::resolveLabelReference(Operand &operand, const std::unordered_map<std::string, size_t> &labelMap) { if (!operand.label.empty()) { auto it = labelMap.find(operand.label); if (it != labelMap.end()) { operand.op_value = static_cast<int>(it->second); } } } std::unique_ptr<ProgramNode> Parser::parseProgramOrObject(uint64_t &i) { auto program = std::make_unique<ProgramNode>(); std::string tokenValue = this->operator[](i).getTokenValue(); program->object = (tokenValue == "object"); i++; if (i < scanner.size()) { auto nameToken = this->operator[](i); program->name = nameToken.getTokenValue(); if (program->object == false) program->root_name = nameToken.getTokenValue(); i++; } if (i < scanner.size() && this->operator[](i).getTokenValue() == "{") { i++; while (i < scanner.size()) { auto innerToken = this->operator[](i); std::string innerValue = innerToken.getTokenValue(); if (innerValue == "}") { i++; break; } if (innerValue == "section") { auto section = parseSection(i); if (section) { program->addSection(std::move(section)); } continue; } i++; } } return program; } void Parser::generateObjectAssemblyFile(std::unique_ptr<Program> &objProgram) { std::string objectFileName = objProgram->name + ".s"; std::ofstream objectFile(objectFileName); if (!objectFile.is_open()) { throw mx::Exception("Could not create object assembly file: " + objectFileName); } std::ostringstream code_v; objProgram->generateCode(platform, objProgram->object, code_v); objProgram->assembly_code = code_v.str(); std::string opt_code = objProgram->gen_optimize(objProgram->assembly_code, platform); objProgram->assembly_code = opt_code; objectFile << opt_code; objectFile.close(); if (debug_mode) { std::cout << "Generated object assembly file: " << objectFileName << std::endl; } if (Program::base != nullptr) Program::base->add_filename(objectFileName); } void Parser::registerObjectExterns(std::unique_ptr<Program> &mainProgram, const std::unique_ptr<Program> &objProgram) { for (const auto &[labelName, labelInfo] : objProgram->labels) { if (labelInfo.second && Program::base != nullptr) Program::base->add_extern(objProgram->name, labelName, false); } for (const auto &ext : objProgram->external) { if (Program::base != nullptr) Program::base->add_extern(ext.mod, ext.name, true); } for (const auto &child : objProgram->objects) { if (child) registerObjectExterns(mainProgram, child); } } } // namespace mxvm