// Requires c++20 // Copyleft (c) 高憲成 B4236497 #include #include #include #include #include #include #include #include #include #include #include #include #include #include using u8 = uint8_t; using u16 = uint16_t; using u32 = uint32_t; namespace rv = std::views; enum class LineType { Instruction, Label }; struct ProgLine { u32 sourceLine; u32 realIndex; LineType type; u16 inst; std::string label; std::string comment; }; std::array registers; std::array registersLast; std::array memory; std::array memoryLast; u32 programCounter; std::vector program; std::map labelMap; bool halted = false; std::string haltMsg; std::string executionMsg; bool showProgram = true; std::string cursorPos(u32 c, u32 r) { return std::format("\033[{};{}H", r, c); } std::string cursorHome() { return std::format("\033[H"); } std::string cursorHide() { return std::format("\033[?25l"); } std::string cursorShow() { return std::format("\033[?25h"); } std::string screenClear() { return std::format("\033[2J"); } std::string lineClear() { return std::format("\033[2K"); } std::string ansiColor(u32 type, u32 f, u32 b) { return std::format("\033[{};{};{}m", type, f, b); } std::string ansiColorSingle(u32 c) { return std::format("\033[{}m", c); } std::string getInstExplanation(ProgLine& line); // type // 0 reset // 1 bold // fg // 37 white // bg // 40 black // 41 red struct BoxLayout { u32 innerX; u32 innerY; u32 afterTitleX; u32 innerW; u32 innerH; }; BoxLayout drawTitledBox(std::string title, u32 x, u32 y, u32 w, u32 h) { // static const std::string tr = "\u2510"; //"┐"; // static const std::string bl = "\u2514"; // "└"; // static const std::string br = "\u2518"; // "┘"; // static const std::string hor = "\u2500"; // "─"; // static const std::string vert = "\u2502"; // "│"; static const std::string tr = "="; //"┐"; static const std::string bl = "="; // "└"; static const std::string br = "="; // "┘"; static const std::string hor = "="; // "─"; static const std::string vert = "|"; // "│"; std::cout << cursorPos(x, y) << title; for (int i : rv::iota(0, (int)w - (int)title.size() - 1)) std::cout << hor; std::cout << tr; for (int i : rv::iota(0, (int)h - 2)) std::cout << cursorPos(x, y + 1 + i) << vert << cursorPos(x + w - 1, y + 1 + i) << vert; std::cout << cursorPos(x, y + h - 1) << bl; for (int i : rv::iota(0, (int)w - 2)) std::cout << hor; std::cout << br; return {x + 1, y + 1, x + (u32)title.size() + 1, w - 2, h - 2}; } void printRegisters() { auto box = drawTitledBox("Registers", 1, 1, 2+4*16, 2+2); std::cout << cursorPos(box.innerX, box.innerY); for (int i : rv::iota(0, (int)registers.size())) { std::cout << std::format(" {:X}", i); } std::cout << cursorPos(box.innerX, box.innerY + 1); for (int i : rv::iota(0, (int)registers.size())) { auto r = registers[i]; auto rl = registersLast[i]; std::cout << " "; if (r != rl) std::cout << ansiColor(1, 37, 41); std::cout << std::format("{:02X}", (u8)r); if (r != rl) std::cout << ansiColorSingle(0); } registersLast = registers; } void printMemory() { auto box = drawTitledBox("Memory", 1, 1+4, 2+4*16, 2+16); for (int i : rv::iota(0, (int)memory.size())) { if (i % 16 == 0) { std::cout << cursorPos(box.innerX, box.innerY + i / 16); std::cout << std::format("{:X} ", i / 16); } auto m = memory[i]; auto ml = memoryLast[i]; if (m != ml) std::cout << ansiColor(1, 37, 41); std::cout << std::format("{:02X}", (u8)m); if (m != ml) std::cout << ansiColorSingle(0); if (i % 16 != 15) std::cout << " "; } memoryLast = memory; } // This is actually the current source line void printProgramCounter() { auto box = drawTitledBox("Cur. Line", 1+66+1, 1, 9+2, 2+1); std::cout << cursorPos(box.innerX + box.innerW - 4, box.innerY) << std::format("{:4}", program[programCounter].sourceLine); } void printInst() { auto box = drawTitledBox("Inst.", 1+66+1, 1+3+1, 9+2, 2+1); auto& line = program[programCounter]; std::cout << cursorPos(box.innerX + box.innerW - 6, box.innerY) << std::format("0x{:04X}", line.inst); } void printProgram() { const int count = 19; // auto box = drawTitledBox("Program", 1+66+1+11+1, 1, 2+30, 2+count); auto box = drawTitledBox("Program", 1+66+1, 1, 2+35, 2+count); for (int i : rv::iota(0, count)) { int index = programCounter - count / 2 + i; std::cout << cursorPos(box.innerX, box.innerY + i); std::cout << std::string{" ", box.innerW}; if (index >= 0 && index < program.size()) { auto& line = program[index]; std::cout << cursorPos(box.innerX, box.innerY + i); std::cout << ((index == programCounter) ? '>' : ' '); if (line.type == LineType::Label) std::cout << std::format("{:3} {}:", line.sourceLine, line.label); else std::cout << std::format("{:3} 0x{:04X} {}", line.sourceLine, line.inst, getInstExplanation(line)); } } } void cursorGoAway() { std::cout << cursorPos(1, 24); } void printHaltMsg() { std::cout << cursorPos(1, 23) << "Halted"; std::cout << cursorPos(1, 24) << haltMsg; } void printExecutionMsg() { std::cout << cursorPos(1, 23) << executionMsg; } void clearMsg() { std::cout << cursorPos(1, 23) << lineClear(); std::cout << cursorPos(1, 24) << lineClear(); std::cout << cursorPos(1, 25) << lineClear(); } std::string trimStr(std::string s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); return s; } void readProgram(std::filesystem::path path) { std::string line; std::ifstream file{path}; u32 sourceLine = 0; u32 realIndex = 0; while (std::getline(file, line)) { sourceLine++; auto semicolon = line.find(';'); std::string comment; if (semicolon != std::string::npos) { comment = trimStr(line.substr(semicolon + 1)); line.erase(semicolon); } line = trimStr(line); if (line.empty()) continue; auto colon = line.find(':'); if (colon != std::string::npos) { auto label = trimStr(line.substr(0, colon)); program.emplace_back( sourceLine, realIndex, LineType::Label, 0, label, std::move(comment)); labelMap[label] = program.size() - 1; } else { auto inst = (u16)std::stoi(line, nullptr, 16); program.emplace_back( sourceLine, realIndex, LineType::Instruction, inst, "", std::move(comment)); realIndex++; } } } void dumpProgram() { for (auto& line : program) { std::cout << std::format("{:3}: {} {}\n", line.sourceLine, line.type == LineType::Label ? line.label + ":" : std::format("0x{:04X}", line.inst), line.comment.empty() ? "" : "; " + line.comment ); } } std::string parseJumpTarget(ProgLine& line) { std::string prefix = "JUMP to "; std::string jumpTarget = line.comment.substr( line.comment.find(prefix) + prefix.size()); jumpTarget = jumpTarget.substr( 0, jumpTarget.find(' ')); return jumpTarget; } // Compiles to big endian program binary void compileToPrg(std::filesystem::path path) { std::ofstream file{path, std::ios_base::out | std::ios_base::binary}; u32 bin[256] = {}; u32 i = 0; for (auto& line : program) { if (line.type == LineType::Instruction) { u16 inst = line.inst; if ((inst & 0xF000) >> 12 == 0xB) { std::string jumpTarget = parseJumpTarget(line); u32 jumpIndex = program[labelMap[jumpTarget] + 1].realIndex; u32 jumpAddr = jumpIndex * sizeof(ProgLine::inst); inst = (inst & 0xFF00) | (jumpAddr & 0x00FF); } inst = inst << 8 | inst >> 8; bin[i] = (u32)(inst & 0xFF); bin[i + 1] = (u32)((inst >> 8) & 0xFF); i += 2; } } file.write((char*)&bin, 256 * sizeof(u32)); } void executeCurrentLine() { auto& line = program[programCounter]; std::byte opCode = (std::byte)((line.inst & 0xF000) >> 12); std::byte operands[] = { (std::byte)((line.inst & 0x0F00) >> 8), (std::byte)((line.inst & 0x00F0) >> 4), (std::byte)(line.inst & 0x000F) }; u8 xy = ((u8)operands[1] << 4) | (u8)operands[2]; std::byte& r = registers[(u8)operands[0]]; std::byte& r1 = registers[(u8)operands[1]]; std::byte& r2 = registers[(u8)operands[2]]; executionMsg = std::format("Last executed: 0x{:04X} ", line.inst); switch ((int)opCode) { case 0x1: r = memory[xy]; executionMsg += getInstExplanation(line); break; case 0x2: r = (std::byte)xy; executionMsg += getInstExplanation(line); break; case 0x3: memory[xy] = r; executionMsg += getInstExplanation(line); break; case 0x4: registers[(u8)operands[2]] = registers[(u8)operands[1]]; executionMsg += getInstExplanation(line); break; case 0x5: r = (std::byte)((u8)r1 + (u8)r2); executionMsg += getInstExplanation(line); break; case 0x6: // TODO: floating point add r = (std::byte)((u8)r1 + (u8)r2); executionMsg += getInstExplanation(line); break; case 0x7: r = (std::byte)(r1 | r2); executionMsg += getInstExplanation(line); break; case 0x8: r = (std::byte)(r1 & r2); executionMsg += getInstExplanation(line); break; case 0x9: r = (std::byte)(r1 ^ r2); executionMsg += getInstExplanation(line); break; case 0xA: r = (std::byte)(std::rotr((u8)r, (u8)operands[2])); executionMsg += getInstExplanation(line); break; case 0xB: { std::string jumpTarget = parseJumpTarget(line); if (labelMap.find(jumpTarget) == labelMap.end()) { halted = true; haltMsg = std::format("Cannot find jump target {}", jumpTarget); break; } if (registers[0] == r) { programCounter = labelMap[jumpTarget]; // Lands on label executionMsg += getInstExplanation(line); } else { executionMsg += getInstExplanation(line) + " failed"; } break; } case 0xC: halted = true; haltMsg = std::format("HALT requested"); return; } programCounter++; } std::string getInstExplanation(ProgLine& line) { u8 opCode = (u8)((line.inst & 0xF000) >> 12); u8 operands[] = { (u8)((line.inst & 0x0F00) >> 8), (u8)((line.inst & 0x00F0) >> 4), (u8)(line.inst & 0x000F) }; u8 xy = (u8)((operands[1] << 4) | operands[2]); switch (opCode) { case 0x1: return std::format("LOAD R{:X} <- [0x{:02X}]", operands[0], xy); case 0x2: return std::format("LOAD R{:X} <- 0x{:02X}", operands[0], xy); case 0x3: return std::format("LOAD [0x{:02X}] <- R{:X}", xy, operands[0]); case 0x4: return std::format("MOVE R{:X} <- R{:X}", operands[2], operands[1]); case 0x5: return std::format("ADD R{:X} <- R{:X} R{:X}", operands[0], operands[1], operands[2]); case 0x6: return std::format("ADDF R{:X} <- R{:X} R{:X}", operands[0], operands[1], operands[2]); case 0x7: return std::format("OR R{:X} <- R{:X} R{:X}", operands[0], operands[1], operands[2]); case 0x8: return std::format("AND R{:X} <- R{:X} R{:X}", operands[0], operands[1], operands[2]); case 0x9: return std::format("XOR R{:X} <- R{:X} R{:X}", operands[0], operands[1], operands[2]); case 0xA: return std::format("ROTATE R{:X} by {}", operands[0], operands[2]); case 0xB: { std::string jumpTarget = parseJumpTarget(line); if (jumpTarget.empty()) return std::format("JUMP to ?"); else return std::format("JUMP to {}", jumpTarget); } case 0xC: return std::format("HALT"); } return std::format("urmom"); } int main(int argc, char *argv[]) { using namespace std::chrono_literals; if (argc < 2) { std::cout << std::format("Welcome to the {}Vole Interpreter{}!\n", ansiColor(1, 37, 41), ansiColorSingle(0)); std::cout << std::format("\n"); std::cout << std::format("I will display the register and memory contents,\n"); std::cout << std::format("and the instructions while it executes.\n"); std::cout << std::format("\n"); std::cout << std::format("Provide a {}Vole{} program file to run it.\n", ansiColor(1, 37, 41), ansiColorSingle(0)); std::cout << std::format("Commands:\n"); std::cout << std::format(" Q - Quit\n"); std::cout << std::format(" N - Step\n"); std::cout << std::format(" E [ms=50] [step=10] - Run to end, step applies when ms = 0\n"); std::cout << std::format(" P - Print parsed program\n"); std::cout << std::format(" PRG - Compile to .prg binary for SimpSim\n"); std::cout << std::format("\n"); std::cout << std::format("The file should be structured as below:\n"); std::cout << std::format("; Comments are allowed, empty lines are ignored\n"); std::cout << std::format("; Badly written program gives undefined behavior\n"); std::cout << std::format("\n"); std::cout << std::format("2855 ; LOAD R8 <- 0b01010101\n"); std::cout << std::format("LOOP: ; Label ends with a colon\n"); std::cout << std::format("B000 ; JUMP to LOOP if R0 == R0\n"); std::cout << std::format(" ; For JUMP, prefix comment with \"JUMP to LABEL\"\n"); std::cout << std::format("C000 ; HALT is needed!\n"); std::cout << std::format("\n"); std::cout << std::format("Copy{}left{} (c) 2025 B4236497\n", ansiColor(3, 37, 45), ansiColorSingle(0)); return 0; } const std::filesystem::path programPath = argv[1]; if (!std::filesystem::exists(programPath)) { std::cout << std::format("File doesn't exist: {}\n", programPath.string()); return 0; } readProgram(programPath); if (program.empty()) { std::cout << std::format("Empty program: {}\n", programPath.string()); return 0; } std::cout << screenClear(); std::cout << cursorHide(); bool runToEnd = false; auto runToEndDelay = std::chrono::duration_cast(20ms); u32 runToEndBatchSize = 10; while (!halted) { if (runToEnd) { // std::cout << std::flush; u32 batch = (runToEndDelay == 0ms) ? runToEndBatchSize : 1; for (int i : rv::iota(0u, batch ? batch : UINT_MAX)) { auto *line = &program[programCounter]; while (line->type == LineType::Label) line = &program[++programCounter]; executeCurrentLine(); if (halted) break; } if (runToEndDelay > 0ms) std::this_thread::sleep_for(runToEndDelay); } printRegisters(); printMemory(); // printProgramCounter(); // printInst(); if (showProgram) printProgram(); clearMsg(); printExecutionMsg(); while (!runToEnd) { cursorGoAway(); std::cout << "Q - Quit, N - Step, E [ms=50] [step=10] - Run to end: \n" << std::flush; std::cout << cursorShow(); std::istringstream ss; { std::string line; std::getline(std::cin, line); ss.str(line); } std::string command; ss >> command; clearMsg(); if (command.empty()) continue; if (command == "q") { halted = true; haltMsg = "HALT requested by command"; break; } else if (command == "n") { executeCurrentLine(); break; } else if (command == "s") { showProgram = !showProgram; printProgram(); } else if (command == "e") { int duration = -1; ss >> duration; if (duration >= 0) runToEndDelay = std::chrono::milliseconds{duration}; int batch = -1; ss >> batch; if (batch >= 0) runToEndBatchSize = batch; runToEnd = true; break; } else if (command == "p") { std::cout << cursorHome(); std::cout << screenClear(); dumpProgram(); return 0; } else if (command == "prg") { std::filesystem::path outPath = programPath; outPath.replace_filename("bin_" + outPath.filename().string()); outPath.replace_extension(".prg"); compileToPrg(outPath); std::cout << screenClear(); std::cout << cursorHome(); std::cout << std::format("Compiled \"{}\" to \"{}\"\n", programPath.filename().string(), outPath.filename().string()); return 0; } } std::cout << cursorHide(); } cursorGoAway(); clearMsg(); printHaltMsg(); return 0; }