From d3e08abd3057f4cc0c6064531eb42861b4ba1af1 Mon Sep 17 00:00:00 2001 From: zanostro Date: Sun, 14 Dec 2025 13:09:14 +0100 Subject: [PATCH] assembling first version --- simulator_SIC_XE/gui/qt/mainwindow.cpp | 100 +++++ simulator_SIC_XE/gui/qt/mainwindow.h | 1 + simulator_SIC_XE/gui/qt/mainwindow.ui | 6 + simulator_SIC_XE/include/code.h | 36 ++ simulator_SIC_XE/res/rec.asm | 223 +++++++++++ simulator_SIC_XE/src/code.cpp | 513 +++++++++++++++++++++++++ simulator_SIC_XE/src/parser.cpp | 18 +- 7 files changed, 896 insertions(+), 1 deletion(-) create mode 100644 simulator_SIC_XE/res/rec.asm diff --git a/simulator_SIC_XE/gui/qt/mainwindow.cpp b/simulator_SIC_XE/gui/qt/mainwindow.cpp index b3d7525..da6fb69 100644 --- a/simulator_SIC_XE/gui/qt/mainwindow.cpp +++ b/simulator_SIC_XE/gui/qt/mainwindow.cpp @@ -6,6 +6,8 @@ #include "../../include/opcode.h" #include "../../include/constants.h" #include "../../include/loader.h" +#include "../../include/parser.h" +#include "../../include/code.h" #include #include @@ -20,6 +22,9 @@ #include #include #include +#include +#include +#include class Loader; @@ -102,6 +107,7 @@ MainWindow::MainWindow(QWidget *parent) : // Connect menu actions connect(ui->actionLoad_Object_File, &QAction::triggered, this, &MainWindow::loadObjectFile); + connect(ui->actionLoad_Asm_file, &QAction::triggered, this, &MainWindow::loadAsmFile); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog); connect(ui->actionFrequency, &QAction::triggered, this, &MainWindow::showFrequencyDialog); @@ -981,6 +987,100 @@ void MainWindow::loadObjectFile() } } +void MainWindow::loadAsmFile() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Load Assembly File"), + QString(), + tr("Assembly Files (*.asm);;All Files (*)")); + + if (fileName.isEmpty()) { + return; + } + + try { + // Stop execution if running + m_controller->stop(); + + // Reset machine state + m_machine->reset(); + + // Read assembly file + std::ifstream file(fileName.toStdString()); + if (!file.is_open()) { + throw std::runtime_error("Could not open file: " + fileName.toStdString()); + } + + std::string source((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + + // Parse and assemble + Parser parser; + Code code = parser.parse(source); + code.assemble(); + + // Generate object code + std::string objCode = code.emitText(); + + // Create resources directory if it doesn't exist + QDir dir; + if (!dir.exists("resources")) { + dir.mkpath("resources"); + } + + // Save object file to resources directory + QFileInfo fileInfo(fileName); + QString objFileName = "resources/" + fileInfo.completeBaseName() + ".obj"; + + std::ofstream objFile(objFileName.toStdString()); + if (!objFile.is_open()) { + throw std::runtime_error("Could not create object file: " + objFileName.toStdString()); + } + objFile << objCode; + objFile.close(); + + // Generate and save log file + QString logFileName = "resources/" + fileInfo.completeBaseName() + ".log"; + std::ofstream logFile(logFileName.toStdString()); + if (!logFile.is_open()) { + throw std::runtime_error("Could not create log file: " + logFileName.toStdString()); + } + + logFile << "=== SIC/XE Assembler Log ===\n\n"; + logFile << "Source file: " << fileName.toStdString() << "\n"; + logFile << "Object file: " << objFileName.toStdString() << "\n\n"; + + logFile << "=== Symbols ===\n"; + logFile << code.dumpSymbols() << "\n\n"; + + logFile << "=== Code ===\n"; + logFile << code.dumpCode() << "\n\n"; + + logFile << "=== Object Code ===\n"; + logFile << objCode << "\n"; + + logFile.close(); + + // Load the generated object file + Loader loader(m_machine, objFileName.toStdString()); + loader.load(); + + // Update displays + updateRegisterDisplays(); + updateMemoryDisplay(); + updateDisassemblyDisplay(); + + QMessageBox::information(this, tr("Success"), + tr("Assembly successful!\nObject file: %1\nLog file: %2") + .arg(objFileName).arg(logFileName)); + + } catch (const std::exception &e) { + QMessageBox::critical(this, tr("Error"), + tr("Failed to assemble file: %1").arg(e.what())); + } +} + void MainWindow::showAboutDialog() { QMessageBox::about(this, tr("About SIC/XE Simulator"), diff --git a/simulator_SIC_XE/gui/qt/mainwindow.h b/simulator_SIC_XE/gui/qt/mainwindow.h index 2dba671..cc8f042 100644 --- a/simulator_SIC_XE/gui/qt/mainwindow.h +++ b/simulator_SIC_XE/gui/qt/mainwindow.h @@ -51,6 +51,7 @@ private slots: void onDisassemblyGoToStart(); void onDisassemblyGoToEnd(); void loadObjectFile(); + void loadAsmFile(); void showAboutDialog(); void showFrequencyDialog(); diff --git a/simulator_SIC_XE/gui/qt/mainwindow.ui b/simulator_SIC_XE/gui/qt/mainwindow.ui index e9084bd..21085fa 100644 --- a/simulator_SIC_XE/gui/qt/mainwindow.ui +++ b/simulator_SIC_XE/gui/qt/mainwindow.ui @@ -892,6 +892,7 @@ File + @@ -924,6 +925,11 @@ About + + + Load Asm file + + diff --git a/simulator_SIC_XE/include/code.h b/simulator_SIC_XE/include/code.h index 6a0c9b7..8c65df0 100644 --- a/simulator_SIC_XE/include/code.h +++ b/simulator_SIC_XE/include/code.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include "node.h" @@ -17,8 +19,42 @@ public: const string toString() const; + // Two-pass assembler methods + void assemble(); + std::vector emitCode(); + std::string emitText(); + std::string dumpSymbols() const; + std::string dumpCode() const; + private: std::vector> _lines; + + // Assembler state + std::unordered_map _symbolTable; + std::vector _locationCounters; // Location counter per line + int _startAddress = 0; + int _programLength = 0; + std::string _programName; + int _baseRegister = -1; // -1 means not set + + // Pass 1: build symbol table and assign addresses + void firstPass(); + + // Pass 2: generate code + void secondPass(); + + // Helper methods + int getInstructionLength(const std::shared_ptr& node, int locationCounter) const; + std::vector generateInstruction(const InstructionNode* inst, int address); + std::vector generateData(const DataNode* data); + + // Addressing mode selection + struct AddressingResult { + int nixbpe; // ni, x, b, p, e bits + int displacement; // 12-bit or 20-bit + bool success; + }; + AddressingResult selectAddressingMode(int targetAddress, int pc, bool indexed, bool immediate, bool indirect, bool extended) const; }; diff --git a/simulator_SIC_XE/res/rec.asm b/simulator_SIC_XE/res/rec.asm new file mode 100644 index 0000000..212bf23 --- /dev/null +++ b/simulator_SIC_XE/res/rec.asm @@ -0,0 +1,223 @@ +prog START 0 + +.------------------------------------------- +. MAIN LOOP +. +. Psevdo: +. sp = 0 +. while true: +. n = readFA() +. if n == 0: halt +. acc = 1 +. fact() ; rekurzivno: acc = n! +. printStdout(acc) +.------------------------------------------- + CLEAR A + STA sp + +loop JSUB readFA + COMP #0 + JEQ halt + + STA n + LDA #1 + STA acc + + JSUB fact + LDA acc + JSUB printStdout + + J loop + +halt J halt + +.------------------------------------------- +. readFA +. +. Psevdo: +. B = 0 +. while true: +. ch = RD(FA) +. if ch == CR or ch == LF: break +. digit = ch - '0' +. B = B * 10 + digit +. return B +.------------------------------------------- +readFA CLEAR B + LDS #10 + +rd_loopFA RD #0xFA + COMP #0x0D . CR? + JEQ rd_doneCR_FA + COMP #0x0A . LF? + JEQ rd_doneFA + + SUB #0x30 + MULR S,B . B = B * 10 + ADDR A,B . B = B + digit + J rd_loopFA + +rd_doneCR_FA RD #0xFA . pogoltni LF po CR +rd_doneFA CLEAR A + RMO B,A + RSUB + +.------------------------------------------- +. fact +. +. Psevdo (globalni n, acc, sklad L): +. fact(): +. push(L) +. if n <= 1: +. pop(L); return +. acc = acc * n +. n = n - 1 +. fact() +. pop(L); return +.------------------------------------------- +fact . push L + LDA sp + ADD #3 + STA sp + LDX sp + STL stackL,X + + LDA n + COMP #1 + JGT fact_rec + + . base case: n <= 1 + LDX sp + LDL stackL,X + LDA sp + SUB #3 + STA sp + RSUB + +fact_rec . recursive case: acc *= n; n--; fact() + + LDB acc + LDS n + MULR S,B + STB acc + + LDA n + SUB #1 + STA n + + JSUB fact + + . pop L in return to caller + LDX sp + LDL stackL,X + LDA sp + SUB #3 + STA sp + RSUB + +.------------------------------------------- +. printStdout +. +. Psevdo: +. if A == 0: +. print "0\n" +. return +. ps_val = A +. ps_len = 0 +. while ps_val > 0: +. q = ps_val / 10 +. r = ps_val % 10 +. buf[ps_len] = '0' + r +. ps_len++ +. ps_val = q +. for i = ps_len-1 .. 0: +. print buf[i] +. print "\r\n" +.------------------------------------------- +printStdout COMP #0 + JEQ ps_zero + + STA ps_val + LDA #0 + STA ps_len + LDS #10 + LDT #0x30 . '0' + +ps_div LDA ps_val + COMP #0 + JEQ ps_divdone + + RMO A,B + DIVR S,B . kvocient v B + RMO B,X . X = kvocient + + MULR S,B + SUBR B,A . A = ostanek + ADDR T,A . A = '0' + ostanek + STA psdigit + + LDA ps_len + STA ps_idx + LDA #psbuf + ADD ps_idx + STA ps_ptr + LDA psdigit + STCH @ps_ptr + + LDA ps_len + ADD #1 + STA ps_len + + RMO X,A + STA ps_val + J ps_div + +ps_divdone LDA ps_len + SUB #1 + STA ps_idx + +ps_print LDA ps_idx + COMP #0 + JLT ps_end + + LDA #psbuf + ADD ps_idx + STA ps_ptr + LDCH @ps_ptr + WD #1 + + LDA ps_idx + SUB #1 + STA ps_idx + J ps_print + +ps_end LDA #0x0D . CR + WD #1 + LDA #0x0A . LF + WD #1 + RSUB + +ps_zero LDA #0x30 . "0" + WD #1 + LDA #0x0D + WD #1 + LDA #0x0A + WD #1 + RSUB + +.data +. rekurzija faktoriala +sp WORD 0 . stack pointer +n WORD 0 +acc WORD 0 . akumulator za faktorial +stackL RESB 60 + +. printStdout +ps_val WORD 0 +ps_len WORD 0 +ps_idx WORD 0 +psdigit WORD 0 +ps_ptr WORD 0 +psbuf RESB 12 + + END prog diff --git a/simulator_SIC_XE/src/code.cpp b/simulator_SIC_XE/src/code.cpp index 0db062a..9b2850f 100644 --- a/simulator_SIC_XE/src/code.cpp +++ b/simulator_SIC_XE/src/code.cpp @@ -1,4 +1,10 @@ #include "code.h" +#include "opcode.h" +#include "constants.h" +#include +#include +#include +#include void Code::addLine(const std::shared_ptr &line) { @@ -18,3 +24,510 @@ const string Code::toString() const } return result; } + +// ============================================================ +// TWO-PASS ASSEMBLER IMPLEMENTATION +// ============================================================ + +void Code::assemble() { + firstPass(); + secondPass(); +} + +void Code::firstPass() { + _symbolTable.clear(); + _locationCounters.clear(); + _locationCounters.resize(_lines.size(), 0); + + int locationCounter = 0; + bool startFound = false; + + for (size_t i = 0; i < _lines.size(); ++i) { + auto& line = _lines[i]; + _locationCounters[i] = locationCounter; + + // Handle label + std::string label = line->getLabel(); + if (!label.empty()) { + if (_symbolTable.find(label) != _symbolTable.end()) { + throw std::runtime_error("Duplicate symbol: " + label); + } + _symbolTable[label] = locationCounter; + } + + // Check for directives + if (auto* directive = dynamic_cast(line.get())) { + switch (directive->kind()) { + case DirectiveKind::START: { + if (std::holds_alternative(directive->arg())) { + _startAddress = std::get(directive->arg()); + locationCounter = _startAddress; + _locationCounters[i] = locationCounter; + if (!label.empty()) { + _symbolTable[label] = locationCounter; + _programName = label; + } + startFound = true; + } + break; + } + case DirectiveKind::END: + _programLength = locationCounter - _startAddress; + break; + + case DirectiveKind::BASE: { + // BASE sets base register for addressing + if (std::holds_alternative(directive->arg())) { + // Will resolve in second pass + } + break; + } + case DirectiveKind::NOBASE: + _baseRegister = -1; + break; + + case DirectiveKind::EQU: { + // EQU defines symbol value + if (!label.empty() && std::holds_alternative(directive->arg())) { + _symbolTable[label] = std::get(directive->arg()); + } + break; + } + case DirectiveKind::ORG: { + // ORG changes location counter + if (std::holds_alternative(directive->arg())) { + locationCounter = std::get(directive->arg()); + } + break; + } + default: + break; + } + continue; + } + + // Handle data directives + if (auto* data = dynamic_cast(line.get())) { + int length = 0; + switch (data->kind()) { + case DataKind::WORD: + length = 3; // 24-bit word + break; + case DataKind::BYTE: { + if (std::holds_alternative>(data->value())) { + length = std::get>(data->value()).size(); + } + break; + } + case DataKind::RESW: { + if (std::holds_alternative(data->value())) { + length = std::get(data->value()) * 3; + } + break; + } + case DataKind::RESB: { + if (std::holds_alternative(data->value())) { + length = std::get(data->value()); + } + break; + } + } + locationCounter += length; + continue; + } + + // Handle instructions + if (auto* inst = dynamic_cast(line.get())) { + int length = getInstructionLength(line, locationCounter); + locationCounter += length; + } + } + + if (!startFound) { + _startAddress = 0; + } + _programLength = locationCounter - _startAddress; +} + +int Code::getInstructionLength(const std::shared_ptr& node, int locationCounter) const { + auto* inst = dynamic_cast(node.get()); + if (!inst || !inst->getMnemonic()) { + return 0; + } + + auto mnemonic = inst->getMnemonic(); + InstructionType type = mnemonic->type(); + + switch (type) { + case InstructionType::TYPE1: + return 1; + case InstructionType::TYPE2: + return 2; + case InstructionType::TYPE3_4: + return mnemonic->extended() ? 4 : 3; + default: + return 0; + } +} + +void Code::secondPass() { + // Generate code for all instructions and data + // This will be used by emitCode() and emitText() +} + +std::vector Code::emitCode() { + std::vector code; + code.resize(_programLength, 0); + + for (size_t i = 0; i < _lines.size(); ++i) { + auto& line = _lines[i]; + int address = _locationCounters[i]; + int offset = address - _startAddress; + + if (offset < 0 || offset >= _programLength) { + continue; + } + + // Generate instruction + if (auto* inst = dynamic_cast(line.get())) { + auto bytes = generateInstruction(inst, address); + for (size_t j = 0; j < bytes.size() && (offset + j) < code.size(); ++j) { + code[offset + j] = bytes[j]; + } + } + + // Generate data + if (auto* data = dynamic_cast(line.get())) { + auto bytes = generateData(data); + for (size_t j = 0; j < bytes.size() && (offset + j) < code.size(); ++j) { + code[offset + j] = bytes[j]; + } + } + } + + return code; +} + +std::vector Code::generateInstruction(const InstructionNode* inst, int address) { + std::vector bytes; + + if (!inst || !inst->getMnemonic()) { + return bytes; + } + + auto mnemonic = inst->getMnemonic(); + uint8_t opcode = mnemonic->opcode(); + InstructionType type = mnemonic->type(); + bool extended = mnemonic->extended(); + const auto& operands = mnemonic->operands(); + + switch (type) { + case InstructionType::TYPE1: { + bytes.push_back(opcode); + break; + } + + case InstructionType::TYPE2: { + bytes.push_back(opcode); + uint8_t r1 = 0, r2 = 0; + if (operands.size() >= 1 && std::holds_alternative(operands[0])) { + r1 = std::get(operands[0]).num & 0xF; + } + if (operands.size() >= 2 && std::holds_alternative(operands[1])) { + r2 = std::get(operands[1]).num & 0xF; + } + bytes.push_back((r1 << 4) | r2); + break; + } + + case InstructionType::TYPE3_4: { + // Format 3 or 4 instruction + int ni = 0, x = 0, b = 0, p = 0, e = 0; + int targetAddress = 0; + bool immediate = false, indirect = false, indexed = false; + + // Parse operand + if (!operands.empty()) { + if (std::holds_alternative(operands[0])) { + immediate = true; + targetAddress = std::get(operands[0]).value; + ni = 0x01; // n=0, i=1 + } else if (std::holds_alternative(operands[0])) { + auto& sym = std::get(operands[0]); + immediate = sym.immediate; + indirect = sym.indirect; + indexed = sym.indexed; + + // Look up symbol + auto it = _symbolTable.find(sym.name); + if (it != _symbolTable.end()) { + targetAddress = it->second; + } + + // Set ni bits + if (immediate) { + ni = 0x01; // n=0, i=1 + } else if (indirect) { + ni = 0x02; // n=1, i=0 + } else { + ni = 0x03; // n=1, i=1 (simple/direct) + } + } + } else { + // No operand (like RSUB) + ni = 0x03; + } + + if (indexed) { + x = 1; + } + + if (extended) { + e = 1; + } + + // Calculate PC for addressing + int pc = address + (extended ? 4 : 3); + + // Select addressing mode + auto result = selectAddressingMode(targetAddress, pc, indexed, immediate, indirect, extended); + + if (result.success) { + b = (result.nixbpe >> 2) & 1; + p = (result.nixbpe >> 1) & 1; + e = result.nixbpe & 1; + } + + int displacement = result.displacement; + + // Build instruction bytes + uint8_t byte1 = (opcode & 0xFC) | ni; + uint8_t byte2 = (x << 7) | (b << 6) | (p << 5) | (e << 4); + + bytes.push_back(byte1); + + if (extended) { + // Format 4: 20-bit address + byte2 |= (displacement >> 16) & 0x0F; + bytes.push_back(byte2); + bytes.push_back((displacement >> 8) & 0xFF); + bytes.push_back(displacement & 0xFF); + } else { + // Format 3: 12-bit displacement + byte2 |= (displacement >> 8) & 0x0F; + bytes.push_back(byte2); + bytes.push_back(displacement & 0xFF); + } + break; + } + + default: + break; + } + + return bytes; +} + +Code::AddressingResult Code::selectAddressingMode(int targetAddress, int pc, bool indexed, bool immediate, bool indirect, bool extended) const { + AddressingResult result; + result.success = false; + result.nixbpe = 0; + result.displacement = 0; + + // Immediate mode - use target address directly + if (immediate) { + if (extended) { + result.nixbpe = 0x01; // e=1, b=0, p=0 + result.displacement = targetAddress & 0xFFFFF; // 20 bits + } else { + result.nixbpe = 0x00; // e=0, b=0, p=0 + result.displacement = targetAddress & 0xFFF; // 12 bits + } + result.success = true; + return result; + } + + // Extended format - use absolute address + if (extended) { + result.nixbpe = 0x01; // e=1, b=0, p=0 + result.displacement = targetAddress & 0xFFFFF; + result.success = true; + return result; + } + + // Try PC-relative (-2048 to +2047) + int pcDisp = targetAddress - pc; + if (pcDisp >= -2048 && pcDisp <= 2047) { + result.nixbpe = 0x02; // p=1, b=0, e=0 + result.displacement = pcDisp & 0xFFF; + result.success = true; + return result; + } + + // Try base-relative (0 to 4095) + if (_baseRegister >= 0) { + int baseDisp = targetAddress - _baseRegister; + if (baseDisp >= 0 && baseDisp <= 4095) { + result.nixbpe = 0x04; // b=1, p=0, e=0 + result.displacement = baseDisp & 0xFFF; + result.success = true; + return result; + } + } + + // Try direct (0 to 4095) + if (targetAddress >= 0 && targetAddress <= 4095) { + result.nixbpe = 0x00; // b=0, p=0, e=0 + result.displacement = targetAddress & 0xFFF; + result.success = true; + return result; + } + + // Try SIC format (0 to 32767, 15 bits) + if (targetAddress >= 0 && targetAddress <= 32767) { + result.nixbpe = 0x00; + result.displacement = targetAddress & 0x7FFF; + result.success = true; + return result; + } + + // Could not find suitable addressing mode + result.success = false; + return result; +} + +std::vector Code::generateData(const DataNode* data) { + std::vector bytes; + + if (!data) { + return bytes; + } + + switch (data->kind()) { + case DataKind::WORD: { + if (std::holds_alternative(data->value())) { + int value = std::get(data->value()) & 0xFFFFFF; + // SIC/XE stores words in big-endian (MSB first) + bytes.push_back((value >> 16) & 0xFF); + bytes.push_back((value >> 8) & 0xFF); + bytes.push_back(value & 0xFF); + } + break; + } + + case DataKind::BYTE: { + if (std::holds_alternative>(data->value())) { + bytes = std::get>(data->value()); + } + break; + } + + case DataKind::RESW: + case DataKind::RESB: + // Reserved space - emit zeros (handled by initialized array) + break; + } + + return bytes; +} + +std::string Code::emitText() { + std::ostringstream oss; + + // H record: program name, start address, length + oss << "H "; + std::string name = _programName.empty() ? "PROG" : _programName; + name.resize(6, ' '); + oss << name << " "; + oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress << " "; + oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _programLength; + oss << "\n"; + + // T records: text (code/data) + std::vector code = emitCode(); + int textStart = 0; + + while (textStart < code.size()) { + int textLength = std::min(30, (int)code.size() - textStart); + + // Skip all-zero sections for RESW/RESB + bool allZeros = true; + for (int i = 0; i < textLength; ++i) { + if (code[textStart + i] != 0) { + allZeros = false; + break; + } + } + + if (!allZeros) { + oss << "T "; + oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << (_startAddress + textStart) << " "; + oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase << textLength << " "; + + for (int i = 0; i < textLength; ++i) { + oss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase << (int)code[textStart + i]; + } + oss << "\n"; + } + + textStart += textLength; + } + + // E record: execution start address + oss << "E "; + oss << std::setfill('0') << std::setw(6) << std::hex << std::uppercase << _startAddress; + oss << "\n"; + + return oss.str(); +} + +std::string Code::dumpSymbols() const { + std::ostringstream oss; + oss << "=== Symbol Table ===\n"; + oss << std::left << std::setw(20) << "Symbol" << "Address\n"; + oss << std::string(30, '-') << "\n"; + + for (const auto& [symbol, address] : _symbolTable) { + oss << std::left << std::setw(20) << symbol; + oss << std::hex << std::uppercase << std::setw(6) << std::setfill('0') << address << "\n"; + } + + return oss.str(); +} + +std::string Code::dumpCode() const { + std::ostringstream oss; + oss << "=== Code Listing ===\n"; + oss << std::hex << std::uppercase << std::setfill('0'); + + std::vector code = const_cast(this)->emitCode(); + + for (size_t i = 0; i < _lines.size(); ++i) { + auto& line = _lines[i]; + int address = _locationCounters[i]; + int offset = address - _startAddress; + + // Print address + oss << std::setw(6) << address << " "; + + // Print generated bytes + int length = getInstructionLength(line, address); + if (auto* data = dynamic_cast(line.get())) { + auto bytes = const_cast(this)->generateData(data); + length = bytes.size(); + } + + for (int j = 0; j < length && (offset + j) < code.size(); ++j) { + oss << std::setw(2) << (int)code[offset + j]; + } + + // Pad for alignment + for (int j = length; j < 12; ++j) { + oss << " "; + } + + oss << " " << line->toString() << "\n"; + } + + return oss.str(); +} diff --git a/simulator_SIC_XE/src/parser.cpp b/simulator_SIC_XE/src/parser.cpp index c5bf939..3decfe9 100644 --- a/simulator_SIC_XE/src/parser.cpp +++ b/simulator_SIC_XE/src/parser.cpp @@ -390,6 +390,18 @@ std::shared_ptr Parser::parseInstruction() { lexer_.skipWhitespace(); + // Check for comment after label - create a label-only instruction node + if (lexer_.peek() == '.') { + std::string comment = std::string(lexer_.readTo('\n')); + // Return an instruction node with just the label (null mnemonic) + auto node = std::make_shared( + std::move(label), + nullptr, + std::move(comment) + ); + return node; + } + // Check for extended format prefix bool isExtended = lexer_.peek() == '+'; if (isExtended) { @@ -399,7 +411,11 @@ std::shared_ptr Parser::parseInstruction() { std::string name = std::string(lexer_.readAlphanumeric()); if (name.empty()) { - throw SyntaxError("Mnemonic or directive expected", lexer_.row, lexer_.col); + throw SyntaxError( + "Mnemonic or directive expected (label='" + label + "')", + lexer_.row, + lexer_.col + ); } // Check if it's a directive or data directive