← index
ats/system/emtest.cpp
Source: ats/system/emtest.cpp
#include <emscripten/bind.h>
#include <sstream>
#include <iomanip>
#include "function.hpp"
#include "translate.hpp"
#include "tee_streambuf.hpp"
#include"code.hpp"

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 += "&amp;";  break;
                case '<':  out += "&lt;";   break;
                case '>':  out += "&gt;";   break;
                case '"': out += "&quot;"; break;
                case '\'': out += "&#39;";  break;
                default:   out.push_back(c); break;
            }
        }
        return out;
    }
    std::string json_escape(const std::string &s) {
        std::string out;
        for (char c : s) {
            switch (c) {
                case '\\': out += "\\\\"; break;
                case '"':  out += "\\\""; break;
                case '\n': out += "\\n"; break;
                case '\r': out += "\\r"; break;
                case '\t': out += "\\t"; break;
                default:    out.push_back(c); break;
            }
        }
        return out;
    }
}

// Function that returns HTML as a string
std::string generateHTML() {
    std::ostringstream html;
    
    // Check if we have valid instructions
    if(code.instruct.size() == 0) {
        html << R"(<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ATS - Debug Report</title>
    <style>
        body {
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            background: linear-gradient(135deg, #1a1a1a, #2d1b1b);
            color: #f0f0f0;
            text-align: center;
            padding: 50px;
        }
        .error-box {
            background: rgba(204, 0, 0, 0.1);
            border: 2px solid #cc0000;
            border-radius: 10px;
            padding: 30px;
            margin: 20px auto;
            max-width: 600px;
        }
    </style>
</head>
<body>
    <div class="error-box">
        <h1 style="color: #cc0000;">⚠️ No Instructions Found</h1>
        <p>You need to load and build code first before generating a report.</p>
        <p>Steps:</p>
        <ol style="text-align: left; max-width: 400px; margin: 20px auto;">
            <li>Enter your ATS assembly code</li>
            <li>Click "Build Code"</li>
            <li>Then click "Generate Report"</li>
        </ol>
    </div>
</body>
</html>)";
        return html.str();
    }
    
    html << R"(<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ATS - Debug Report</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            background: linear-gradient(135deg, #1a1a1a, #2d1b1b);
            color: #f0f0f0;
            line-height: 1.6;
            min-height: 100vh;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .header {
            text-align: center;
            margin-bottom: 30px;
            padding: 20px;
            background: linear-gradient(45deg, #cc0000, #800000);
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(204, 0, 0, 0.3);
        }
        
        .header h1 {
            color: white;
            font-size: 2.5em;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
            margin-bottom: 10px;
        }
        
        .header p {
            color: #ffcccc;
            font-size: 1.1em;
        }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin-bottom: 30px;
        }
        
        .stat-card {
            background: rgba(204, 0, 0, 0.1);
            border: 2px solid #cc0000;
            border-radius: 8px;
            padding: 15px;
            text-align: center;
        }
        
        .stat-card h3 {
            color: #cc0000;
            margin-bottom: 5px;
        }
        
        .stat-card .value {
            color: white;
            font-size: 1.5em;
            font-weight: bold;
        }
        
        .table-container {
            background: rgba(0, 0, 0, 0.7);
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
            margin-bottom: 20px;
        }
        
        .table-title {
            background: linear-gradient(45deg, #cc0000, #990000);
            color: white;
            padding: 15px;
            font-size: 1.3em;
            font-weight: bold;
            text-align: center;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
        }
        
        table {
            width: 100%;
            border-collapse: collapse;
            font-size: 0.9em;
        }
        
        th {
            background: linear-gradient(180deg, #800000, #600000);
            color: white;
            padding: 12px 8px;
            text-align: center;
            font-weight: bold;
            border-bottom: 2px solid #cc0000;
        }
        
        td {
            padding: 10px 8px;
            text-align: center;
            border-bottom: 1px solid #333;
            background: rgba(255, 255, 255, 0.02);
        }
        
        tr:nth-child(even) td {
            background: rgba(204, 0, 0, 0.05);
        }
        
        tr:hover td {
            background: rgba(204, 0, 0, 0.15);
            color: white;
        }
        
        .line-num { color: #ff6666; font-weight: bold; }
        .instruction { color: #ffcccc; font-family: 'Consolas', monospace; text-align: left; padding-left: 15px; }
        .machine-code { color: #66ff66; font-family: 'Consolas', monospace; font-weight: bold; }
        .address-mode { color: #ffff66; }
        .label { color: #ff9999; font-weight: bold; }
        .opcode { color: #99ccff; font-weight: bold; }
        .operand { color: #ccffcc; }
        
        .footer {
            text-align: center;
            margin-top: 30px;
            padding: 20px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 8px;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🔧 ATS Debug Report</h1>
            <p> Script Assembly Analysis & Machine Code Generation</p>
        </div>
        
        <div class="stats-grid">
            <div class="stat-card">
                <h3>Total Instructions</h3>
                <div class="value">)" << code.instruct.size() << R"(</div>
            </div>
            <div class="stat-card">
                <h3>Code Size</h3>
                <div class="value">)" << code.instruct.size() * 2 << R"( bytes</div>
            </div>
            <div class="stat-card">
                <h3>Labels Found</h3>
                <div class="value">)";
    
    // Count labels safely
    int label_count = 0;
    try {
        for(unsigned int i = 0; i < code.instruct.size(); ++i) {
            if(code.instruct[i].label) label_count++;
        }
    } catch(...) {
        label_count = 0;
    }
    html << label_count;
    
    html << R"(</div>
            </div>
            <div class="stat-card">
                <h3>Memory Range</h3>
                <div class="value">$0000-$)" << std::hex << std::uppercase << (code.instruct.size() * 2) << std::dec << R"(</div>
            </div>
        </div>
        
        <div class="table-container">
            <div class="table-title">📋 Instruction Analysis</div>
            <table>
                <thead>
                    <tr>
                        <th>Index</th>
                        <th>Line #</th>
                        <th>Label</th>
                        <th>Instruction</th>
                        <th>Opcode</th>
                        <th>Address Mode</th>
                        <th>Operand</th>
                        <th>Machine Code</th>
                    </tr>
                </thead>
                <tbody>)";
    
    try {
        for(unsigned int i = 0; i < code.instruct.size(); ++i) {
            auto& inst = code.instruct[i];  
            std::string hex_code;
            try {
                hex_code = icode::instructionToHex(inst);
            } catch(...) {
                hex_code = "ERROR";
            }
            
            html << "                    <tr>\n";
            html << "                        <td class=\"line-num\">" << i << "</td>\n";
            html << "                        <td class=\"line-num\">" << inst.line_num << "</td>\n";
            
            // Label column
            if(inst.label) {
                html << "                        <td class=\"label\">" << html_escape(inst.label_text) << "</td>\n";
            } else {
                html << "                        <td>-</td>\n";
            }
            
            // Instruction text
            html << "                        <td class=\"instruction\">" << html_escape(inst.text) << "</td>\n";
            
            // Opcode (safely)
            try {
                html << "                        <td class=\"opcode\">" << html_escape(icode::op_array[static_cast<unsigned int>(inst.opcode)]) << "</td>\n";
            } catch(...) {
                html << "                        <td class=\"opcode\">ERROR</td>\n";
            }
            
            // Address mode (safely)
            try {
                html << "                        <td class=\"address-mode\">" << html_escape(interp::add_mode[inst.mode]) << "</td>\n";
            } catch(...) {
                html << "                        <td class=\"address-mode\">ERROR</td>\n";
            }
            
            // Operand info
            html << "                        <td class=\"operand\">";
            try {
                if(inst.op1.op_t == icode::op_type::OP_LABELTEXT) {
                    html << "Label: " << html_escape(inst.op1.label_text);
                } else if(inst.op1.op_t == icode::op_type::OP_DECIMAL) {
                    html << "$" << std::hex << std::uppercase << (int)inst.op1.op << std::dec;
                } else if(inst.op1.op_t == icode::op_type::OP_MEMORY) {
                    html << "$" << std::hex << std::uppercase << inst.op1.op << std::dec;
                } else if(inst.op1.op_t == icode::op_type::OP_LABEL) {
                    html << "Line: " << inst.op1.op;
                } else {
                    html << "-";
                }
            } catch(...) {
                html << "ERROR";
            }
            html << "</td>\n";
            
            // Machine code
            html << "                        <td class=\"machine-code\">" << html_escape(hex_code) << "</td>\n";
            
            html << "                    </tr>\n";
        }
    } catch(...) {
        html << "                    <tr><td colspan=\"8\" style=\"color: #ff6666; text-align: center;\">Error processing instructions</td></tr>\n";
    }
    
    html << R"(                </tbody>
            </table>
        </div>
        
        <div class="footer">
            <p>Generated by ATS (Assembly Script) Debugger</p>
        </div>
    </div>
</body>
</html>)";
    
    return html.str();
}

// Add a global initialization function
bool initializeSystem() {
    static bool initialized = false;
    interp::mem_write = true; // Enable memory write tracking
    if (!initialized) {
        interp::setup_tee_for_interp_stream();
        code.reset();
        initialized = true;
        return true;
    }
    return true;
}

// Wrapper class for Emscripten
class ATSDebugger {
private:
    std::string last_error;
    bool code_loaded;
    bool code_built;
    bool execution_started;
    int current_instruction_index;
    
public:
    ATSDebugger() : code_loaded(false), code_built(false), execution_started(false), current_instruction_index(0) {
        initializeSystem(); // Ensure system is initialized
    }
    
    std::string getDebugHTML() {
        if (!code_built) {
            return R"(<!DOCTYPE html>
<html><body style="font-family: monospace; background: #1a1a1a; color: #ff6666; text-align: center; padding: 50px;">
<h1>⚠️ Code Not Built</h1>
<p>Please load and build your code first before generating a report.</p>
</body></html>)";
        }
        return generateHTML();
    }
    
    bool loadCode(const std::string& ats_code) {
        try {
            if (!interp::openLineString(ats_code)) {
                last_error = "Failed to load code from string: " + interp::comp_err.str();
                code_loaded = false;
                code_built = false;
                return false;
            }
            last_error.clear();
            code_loaded = true;
            code_built = false;
            execution_started = false;
            current_instruction_index = 0;
            return true;
        } catch (const std::exception& e) {
            last_error = std::string("Load error: ") + e.what();
            code_loaded = false;
            code_built = false;
            return false;
        }
    }
    
    bool buildCode() {
        if (!code_loaded) {
            last_error = "No code loaded - call loadCode first";
            return false;
        }
        try {
            code.reset();
            bool result = translate::build_code();
            if (!result) {
                last_error = interp::comp_err.str();
                code_built = false;
                return false;
            }
            last_error.clear();
            code_built = true;
            execution_started = false;
            current_instruction_index = 0;
            return true;
        } catch (const std::exception& e) {
            last_error = std::string("Build error: ") +  interp::comp_err.str();
            code_built = false;
            return false;
        }
    }
    
    bool startExecution() {
        if (!code_built) {
            last_error = "Code not built - cannot start execution";
            return false;
        }
        try {
            execution_started = true;
            current_instruction_index = 0;
            last_error.clear();
            return true;
        } catch (const std::exception& e) {
            last_error = std::string("Start execution error: ") + e.what();
            return false;
        }
    }
    
    bool stepInstruction() {
        if (!code_built) {
            last_error = "Code not built - cannot step";
            return false;
        }
        
        if (!execution_started) {
            execution_started = true;
            current_instruction_index = 0;
            code.proc.ip = 0;
        }
         
        try {
            int ip_before = code.proc.getIp();
            code.step();
            current_instruction_index = code.proc.getIp();
            if (current_instruction_index < 0 || current_instruction_index >= static_cast<int>(code.instruct.size())) {
                if (current_instruction_index >= static_cast<int>(code.instruct.size())) {
                    last_error = "End of program reached";
                    return false;
                }
            }
            
            last_error.clear();
            return true;
        } catch (const std::exception& e) {
            last_error = std::string("Step error: ") + e.what();
            return false;
        }
    }
    
    std::string getProcessorState() {
        std::ostringstream state;
        state << "{";
        state << "\"pc\":" << code.proc.getIp() << ",";
        state << "\"ac\":" << (int)code.proc.reg_a << ",";
        state << "\"x\":" << (int)code.proc.reg_x << ",";
        state << "\"y\":" << (int)code.proc.reg_y << ",";
        state << "\"sr\":" << (int)code.proc.valFlags() << ",";
        state << "\"sp\":" << (int)code.proc.sp << ",";
        
        state << "\"carry\":" << (code.proc.getFlag(icode::FLAG_CARRY) ? "true" : "false") << ",";
        state << "\"zero\":" << (code.proc.getFlag(icode::FLAG_ZERO) ? "true" : "false") << ",";
        state << "\"interrupt\":" << (code.proc.getFlag(icode::FLAG_INTERRUPT) ? "true" : "false") << ",";
        state << "\"decimal\":" << (code.proc.getFlag(icode::FLAG_DECIMAL) ? "true" : "false") << ",";
        state << "\"break\":" << (code.proc.getFlag(icode::FLAG_BREAK) ? "true" : "false") << ",";
        state << "\"overflow\":" << (code.proc.getFlag(icode::FLAG_OVERFLOW) ? "true" : "false") << ",";
        state << "\"negative\":" << (code.proc.getFlag(icode::FLAG_NEGATIVE) ? "true" : "false") << ",";
        
        state << "\"currentInstruction\":" << code.proc.getIp() << ",";
        state << "\"totalInstructions\":" << code.instruct.size();
        state << "}";
        return state.str();
    }

    std::string getCurrentInstructionInfo() {
        int nextIndex = execution_started ? code.proc.getIp() : 0;
        
        if (nextIndex < 0 || nextIndex >= static_cast<int>(code.instruct.size())) {
            return "{\"valid\":false}";
        }
        
        auto& inst = code.instruct[nextIndex];
        std::ostringstream info;
        info << "{";
        info << "\"valid\":true,";
        info << "\"index\":" << nextIndex << ",";
        info << "\"lineNum\":" << inst.line_num << ",";
        info << "\"text\":\"" << json_escape(inst.text) << "\",";
        info << "\"opcode\":\"" << json_escape(icode::op_array[static_cast<unsigned int>(inst.opcode)]) << "\",";
        info << "\"addressMode\":\"" << json_escape(interp::add_mode[inst.mode]) << "\",";
        
        if (inst.opcode == icode::opc::BNE || inst.opcode == icode::opc::BEQ || 
            inst.opcode == icode::opc::BCC || inst.opcode == icode::opc::BCS ||
            inst.opcode == icode::opc::BPL || inst.opcode == icode::opc::BMI ||
            inst.opcode == icode::opc::BVC || inst.opcode == icode::opc::BVS) {
            info << "\"isBranch\":true,";
            info << "\"branchTarget\":" << inst.op1.label_index << ",";
            
            bool zeroFlag = code.proc.getFlag(icode::FLAG_ZERO);
            info << "\"zeroFlag\":" << (zeroFlag ? "true" : "false") << ",";
            
            bool willBranch = false;
            switch (inst.opcode) {
                case icode::opc::BNE: willBranch = !zeroFlag; break;
                case icode::opc::BEQ: willBranch = zeroFlag; break;
            }
            info << "\"willBranch\":" << (willBranch ? "true" : "false") << ",";
        } else {
            info << "\"isBranch\":false,";
        }
        
        info << "\"actualIP\":" << code.proc.getIp();
        info << "}";
        return info.str();
    }
    
    void resetExecution() {
        execution_started = false;
        current_instruction_index = 0;
        if (code_built) {
            code.reset();
        }
    }
    
    bool isExecutionStarted() {
        return execution_started;
    }
    
    bool isExecutionComplete() {
        return execution_started && current_instruction_index >= static_cast<int>(code.instruct.size());
    }
    
    int getInstructionCount() {
        return static_cast<int>(code.instruct.size());
    }
    
    std::string getLastError() {
        return last_error;
    }
    
    bool isCodeLoaded() {
        return code_loaded;
    }
    
    bool isCodeBuilt() {
        return code_built;
    }
    
    std::string getMemoryState() {
        std::ostringstream memory;
        memory << "{";
        memory << "\"size\":65536,";
        memory << "\"data\":[";
        
        // Output memory as array of bytes
        for (int i = 0; i < 65536; i++) {
            if (i > 0) memory << ",";
            memory << (int)code.peek(i);
        }
        
        memory << "],";
        memory << "\"modified\":[";
        
        // Track which addresses were modified (you'd need to implement this tracking)
        // For now, return empty array
        memory << "]";
        memory << "}";
        return memory.str();
    }


    std::string getModifiedMemory() {
        std::ostringstream result;
        result << "[";
        for (size_t i = 0; i < interp::modifiedMemoryAddresses.size(); i++) {
            if (i > 0) result << ",";
            result << "{\"address\":" << interp::modifiedMemoryAddresses[i].first 
                << ",\"value\":" << (int)interp::modifiedMemoryAddresses[i].second << "}";
        }
        result << "]";
        return result.str();
    }

};

// Emscripten bindings
EMSCRIPTEN_BINDINGS(ats_module) {
    emscripten::class_<ATSDebugger>("ATSDebugger")
        .constructor<>()
        .function("loadCode", &ATSDebugger::loadCode)
        .function("buildCode", &ATSDebugger::buildCode)
        .function("getDebugHTML", &ATSDebugger::getDebugHTML)
        .function("getInstructionCount", &ATSDebugger::getInstructionCount)
        .function("getLastError", &ATSDebugger::getLastError)
        .function("startExecution", &ATSDebugger::startExecution)
        .function("stepInstruction", &ATSDebugger::stepInstruction)
        .function("resetExecution", &ATSDebugger::resetExecution)
        .function("getProcessorState", &ATSDebugger::getProcessorState)
        .function("getCurrentInstructionInfo", &ATSDebugger::getCurrentInstructionInfo)
        .function("getMemoryState", &ATSDebugger::getMemoryState)
        .function("getModifiedMemory", &ATSDebugger::getModifiedMemory);
    
    // Standalone function
    emscripten::function("generateHTML", &generateHTML);
}