diff --git a/cobor_vm_extension/.sconsign.dblite b/cobor_vm_extension/.sconsign.dblite index 465ca06..461bc0e 100644 Binary files a/cobor_vm_extension/.sconsign.dblite and b/cobor_vm_extension/.sconsign.dblite differ diff --git a/cobor_vm_extension/bin/linux/libcobor_vm_extension.linux.template_debug.x86_64.so b/cobor_vm_extension/bin/linux/libcobor_vm_extension.linux.template_debug.x86_64.so index d5398ff..e3da281 100755 Binary files a/cobor_vm_extension/bin/linux/libcobor_vm_extension.linux.template_debug.x86_64.so and b/cobor_vm_extension/bin/linux/libcobor_vm_extension.linux.template_debug.x86_64.so differ diff --git a/cobor_vm_extension/cobor_vm/include/cobor_virtual_machine.hpp b/cobor_vm_extension/cobor_vm/include/cobor_virtual_machine.hpp index d5d5c01..9d4179b 100644 --- a/cobor_vm_extension/cobor_vm/include/cobor_virtual_machine.hpp +++ b/cobor_vm_extension/cobor_vm/include/cobor_virtual_machine.hpp @@ -1,5 +1,6 @@ #pragma once +#include "constants.hpp" #include "godot_cpp/classes/ref_counted.hpp" namespace CoborVM @@ -11,10 +12,28 @@ namespace CoborVM protected: static void _bind_methods(); + private: + std::vector program; + std::vector register_memory; + public: CoborVirtualMachine() = default; ~CoborVirtualMachine() override = default; godot::PackedStringArray parse_source_code(godot::String source_code); + godot::String set_registers(int size, godot::PackedInt32Array preloaded_values); + godot::PackedInt32Array get_registers(); + int get_program_counter(); + godot::String run_step(); + godot::String run_all(); + + private: + // Execution + godot::String run_executable_step(ExecutableStep step); + // Parsing + bool string_to_instruction(godot::String command_string, Instruction &outInstruction); + bool instruction_to_executable_step(Instruction instruction, godot::PackedStringArray line, ExecutableStep &outExecutableStep); + bool validate_and_parse_pointer(const godot::String &arg, int &outValue); + bool validate_and_parse_literal(const godot::String &arg, int &outValue); }; } \ No newline at end of file diff --git a/cobor_vm_extension/cobor_vm/include/constants.hpp b/cobor_vm_extension/cobor_vm/include/constants.hpp new file mode 100644 index 0000000..8f1ecf5 --- /dev/null +++ b/cobor_vm_extension/cobor_vm/include/constants.hpp @@ -0,0 +1,96 @@ +#include +#include +#include + +namespace CoborVM +{ + enum Instruction + { + EMPTY, + + SET, + CLR, + CPY, + SWP, + + ADD, + SUB, + MUL, + DIV, + + SKIP, + JMP, + JMPTO, + + EQL, + BIGR, + SMLR, + ZERO, + }; + + struct ExecutableStep + { + Instruction instruction = Instruction::EMPTY; + int arg1; + int arg2; + int arg3; + + int lineNumber; + }; + + // Map from string to instruction + const std::unordered_map stringToInstructionMap = { + {"SET", SET}, + {"CLR", CLR}, + {"CPY", CPY}, + {"SWP", SWP}, + {"ADD", ADD}, + {"SUB", SUB}, + {"MUL", MUL}, + {"DIV", DIV}, + {"SKIP", SKIP}, + {"JMP", JMP}, + {"JMPTO", JMPTO}, + {"EQL", EQL}, + {"BIGR", BIGR}, + {"SMLR", SMLR}, + {"ZERO", ZERO}}; + + // Map from instruction to string + const std::unordered_map InstructionToStringMap = { + {SET, "SET"}, + {CLR, "CLR"}, + {CPY, "CPY"}, + {SWP, "SWP"}, + {ADD, "ADD"}, + {SUB, "SUB"}, + {MUL, "MUL"}, + {DIV, "DIV"}, + {SKIP, "SKIP"}, + {JMP, "JMP"}, + {JMPTO, "JMPTO"}, + {EQL, "EQL"}, + {BIGR, "BIGR"}, + {SMLR, "SMLR"}, + {ZERO, "ZERO"}}; + + // Define the expected argument types for each instruction (true = pointer, false = literal) + const std::unordered_map> instructionPatterns = { + {Instruction::SKIP, {}}, // No arguments + {Instruction::EMPTY, {}}, + {Instruction::JMP, {false}}, // One literal argument + {Instruction::JMPTO, {false}}, + {Instruction::CLR, {true}}, // One pointer argument + {Instruction::ZERO, {true}}, + {Instruction::SET, {false, true}}, // One literal and one pointer argument + {Instruction::CPY, {true, true}}, // Two pointer arguments + {Instruction::SWP, {true, true}}, + {Instruction::EQL, {true, true}}, + {Instruction::BIGR, {true, true}}, + {Instruction::SMLR, {true, true}}, + {Instruction::ADD, {true, true, true}}, // Three pointer arguments + {Instruction::SUB, {true, true, true}}, + {Instruction::MUL, {true, true, true}}, + {Instruction::DIV, {true, true, true}}, + }; +} \ No newline at end of file diff --git a/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.cpp b/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.cpp index debab8b..c0fca68 100644 --- a/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.cpp +++ b/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.cpp @@ -1,224 +1,358 @@ #include "cobor_virtual_machine.hpp" -enum Instruction -{ - ERR, - - SET, - CLR, - CPY, - SWP, - - ADD, - SUB, - MUL, - DIV, - - SKIP, - JMP, - JMPTO, - - EQL, - BIGR, - SMLR, - ZERO, -}; - -struct ExecutableStep -{ - Instruction instruction = Instruction::ERR; - int arg1; - int arg2; - int arg3; - - int lineNumber; -}; +using namespace CoborVM; void CoborVM::CoborVirtualMachine::_bind_methods() { godot::ClassDB::bind_method(godot::D_METHOD("parse_source_code", "source_code"), &CoborVirtualMachine::parse_source_code); + godot::ClassDB::bind_method(godot::D_METHOD("set_registers", "size", "preloaded_values"), &CoborVirtualMachine::set_registers); + godot::ClassDB::bind_method(godot::D_METHOD("get_registers"), &CoborVirtualMachine::get_registers); + godot::ClassDB::bind_method(godot::D_METHOD("get_program_counter"), &CoborVirtualMachine::get_program_counter); + godot::ClassDB::bind_method(godot::D_METHOD("run_step"), &CoborVirtualMachine::run_step); + godot::ClassDB::bind_method(godot::D_METHOD("run_all"), &CoborVirtualMachine::run_all); } -Instruction stringToInstruction(godot::String command_string); -ExecutableStep InstructionToExecutableStep(Instruction Instruction, godot::PackedStringArray line); - godot::PackedStringArray CoborVM::CoborVirtualMachine::parse_source_code(godot::String source_code) { godot::PackedStringArray parseFaults; + program.clear(); source_code = source_code.to_upper(); auto lines = source_code.split("\n"); int lineNumber = 0; for (auto &line : lines) { + lineNumber++; + ExecutableStep step; + step.instruction = EMPTY; + step.lineNumber = lineNumber; + line = line.strip_edges(); - auto words = line.split(" ", false); - Instruction command = stringToInstruction(words[0]); - if (command == ERR) + if (!line.is_empty()) { - parseFaults.append(godot::String("Error: Invalid command at line ") + - godot::String::num_int64(lineNumber) + - godot::String(": {") + line + godot::String("}")); - continue; + auto words = line.split(" ", false); + Instruction instruction; + if (!string_to_instruction(words[0], instruction)) + { + parseFaults.append(godot::String("Error: Invalid instruction at line ") + + godot::String::num_int64(lineNumber) + + godot::String(": {") + line + godot::String("}")); + continue; + } + if (!instruction_to_executable_step(instruction, words, step)) + { + parseFaults.append(godot::String("Error: Invalid arguments at line ") + + godot::String::num_int64(lineNumber) + + godot::String(": {") + line + godot::String("}")); + continue; + } } - lineNumber++; + program.emplace_back(step); } return parseFaults; } -Instruction stringToInstruction(godot::String command_string) +godot::String CoborVM::CoborVirtualMachine::set_registers(int size, godot::PackedInt32Array preloaded_values) { - std::string cmd = command_string.utf8().get_data(); - if (cmd == "SET") - return SET; - if (cmd == "CLR") - return CLR; - if (cmd == "MV") - return CPY; - if (cmd == "SWP") - return SWP; - if (cmd == "ADD") - return ADD; - if (cmd == "SUB") - return SUB; - if (cmd == "MUL") - return MUL; - if (cmd == "DIV") - return DIV; - if (cmd == "SKIP") - return SKIP; - if (cmd == "JMP") - return JMP; - if (cmd == "JMPTO") - return JMPTO; - if (cmd == "EQL") - return EQL; - if (cmd == "BIGR") - return BIGR; - if (cmd == "SMLR") - return SMLR; - if (cmd == "ZERO") - return ZERO; - return ERR; + if (size <= 0) + return godot::String("Size must be one or higher"); + register_memory = std::vector(size); + int i = 0; + for (int value : preloaded_values) + { + if (i >= size) + break; + register_memory[i] = value; + i++; + } + return godot::String(); } -ExecutableStep InstructionToExecutableStep(Instruction instruction, godot::PackedStringArray line, int lineNumber) +godot::PackedInt32Array CoborVM::CoborVirtualMachine::get_registers() { - ExecutableStep executableStep; - executableStep.lineNumber = lineNumber; - - godot::String arg1 = "0", arg2 = "0", arg3 = "0"; - - switch (instruction) + godot::PackedInt32Array ret; + for (int32_t value : register_memory) { - // Instructions with no arguments - case Instruction::SKIP: - if (line.size() != 1) - return executableStep; + ret.append(value); + } + return ret; +} + +int CoborVM::CoborVirtualMachine::get_program_counter() +{ + return register_memory[0]; +} + +godot::String CoborVM::CoborVirtualMachine::run_step() +{ + if (program.empty()) + return godot::String("No program is compiled to run."); + if (register_memory.empty()) + return godot::String("Registers are not set."); + + int program_counter = register_memory[0]; + if (program_counter < 0 || program_counter >= program.size()) + return godot::String("Program counter pointing outside program scope: ") + + godot::String::num_int64(program_counter); + + ExecutableStep step = program[program_counter]; + auto err = run_executable_step(step); + if (!err.is_empty()) + { + return godot::String("Execution failed at line: ") + + godot::String::num_int64(step.lineNumber) + + godot::String(" with error: ") + + err; + } + + register_memory[0]++; + return godot::String(); +} + +godot::String CoborVM::CoborVirtualMachine::run_all() +{ + while (true) + { + int program_counter = register_memory[0]; + if (program.size() <= program_counter) + break; + auto result = run_step(); + if (!result.is_empty()) + return result; + } + return godot::String(); +} + +godot::String CoborVM::CoborVirtualMachine::run_executable_step(ExecutableStep step) +{ + godot::String error_message; + + auto check_register = [this, &error_message](int64_t reg) -> bool + { + if (reg >= register_memory.size()) + { + error_message = godot::String("Pointing outside register memory at /") + godot::String::num_int64(reg); + return false; + } + return true; + }; + + switch (step.instruction) + { + case EMPTY: break; - // Instructions with one literal argument - case Instruction::JMP: - case Instruction::JMPTO: - if (line.size() != 2) - return executableStep; - arg1 = line[1]; - if (!arg1.is_valid_int()) - return executableStep; - break; - - // Instructions with one pointer argument - case Instruction::CLR: - case Instruction::ZERO: - if (line.size() != 2) - return executableStep; - - arg1 = line[1]; - if (!arg1.begins_with("/")) - return executableStep; - arg1 = arg1.trim_prefix("/"); - if (!arg1.is_valid_int()) - return executableStep; - break; - - // Instructions with one literal argument and one pointer argument - case Instruction::SET: - if (line.size() != 3) - return executableStep; - - arg1 = line[1]; - if (!arg1.is_valid_int()) - return executableStep; - arg2 = line[2]; - if (!arg2.begins_with("/")) - return executableStep; - arg2 = arg2.trim_prefix("/"); - if (!arg2.is_valid_int()) - return executableStep; - break; - - // Instructions with two pointer argument - case Instruction::CPY: - case Instruction::SWP: - case Instruction::EQL: - case Instruction::BIGR: - case Instruction::SMLR: - if (line.size() != 3) - return executableStep; - - arg1 = line[1]; - if (!arg1.begins_with("/")) - return executableStep; - arg1 = arg1.trim_prefix("/"); - if (!arg1.is_valid_int()) - return executableStep; - arg2 = line[2]; - if (!arg2.begins_with("/")) - return executableStep; - arg2 = arg2.trim_prefix("/"); - if (!arg2.is_valid_int()) - return executableStep; - break; - - // Instructions with three pointer argument - case Instruction::ADD: - case Instruction::SUB: - case Instruction::MUL: - case Instruction::DIV: - if (line.size() != 4) - return executableStep; - - arg1 = line[1]; - if (!arg1.begins_with("/")) - return executableStep; - arg1 = arg1.trim_prefix("/"); - if (!arg1.is_valid_int()) - return executableStep; - arg2 = line[2]; - if (!arg2.begins_with("/")) - return executableStep; - arg2 = arg2.trim_prefix("/"); - if (!arg2.is_valid_int()) - return executableStep; - arg3 = line[3]; - if (!arg3.begins_with("/")) - return executableStep; - arg3 = arg3.trim_prefix("/"); - if (!arg3.is_valid_int()) - return executableStep; - break; - - default: - return executableStep; + case SET: + { + if (!check_register(step.arg2)) + return error_message; + register_memory[step.arg2] = step.arg1; break; } - executableStep.instruction = instruction; - executableStep.arg1 = arg1.to_int(); - executableStep.arg2 = arg2.to_int(); - executableStep.arg3 = arg3.to_int(); - executableStep.lineNumber = lineNumber; - return executableStep; + + case CLR: + { + if (!check_register(step.arg1)) + return error_message; + register_memory[step.arg1] = 0; + break; + } + + case CPY: + { + if (!check_register(step.arg1) || !check_register(step.arg2)) + return error_message; + register_memory[step.arg2] = register_memory[step.arg1]; + break; + } + + case SWP: + { + if (!check_register(step.arg1) || !check_register(step.arg2)) + return error_message; + std::swap(register_memory[step.arg1], register_memory[step.arg2]); + break; + } + + case ADD: + { + if (!check_register(step.arg1) || !check_register(step.arg2) || !check_register(step.arg3)) + return error_message; + register_memory[step.arg3] = register_memory[step.arg1] + register_memory[step.arg2]; + break; + } + + case SUB: + { + if (!check_register(step.arg1) || !check_register(step.arg2) || !check_register(step.arg3)) + return error_message; + register_memory[step.arg3] = register_memory[step.arg1] - register_memory[step.arg2]; + break; + } + + case MUL: + { + if (!check_register(step.arg1) || !check_register(step.arg2) || !check_register(step.arg3)) + return error_message; + register_memory[step.arg3] = register_memory[step.arg1] * register_memory[step.arg2]; + break; + } + + case DIV: + { + if (!check_register(step.arg1) || !check_register(step.arg2) || !check_register(step.arg3)) + return error_message; + if (register_memory[step.arg2] == 0) + { + return godot::String("Dividing by zero! Check register: ") + godot::String::num_int64(step.arg2); + } + register_memory[step.arg3] = register_memory[step.arg1] / register_memory[step.arg2]; + break; + } + + case SKIP: + { + register_memory[0]++; + break; + } + + case JMP: + { + register_memory[0] += step.arg1; + break; + } + + case JMPTO: + { + register_memory[0] = step.arg1; + break; + } + + case EQL: + { + if (!check_register(step.arg1) || !check_register(step.arg2)) + return error_message; + if (register_memory[step.arg1] == register_memory[step.arg2]) + register_memory[0]++; + break; + } + + case BIGR: + { + if (!check_register(step.arg1) || !check_register(step.arg2)) + return error_message; + if (register_memory[step.arg1] < register_memory[step.arg2]) + register_memory[0]++; + break; + } + + case SMLR: + { + if (!check_register(step.arg1) || !check_register(step.arg2)) + return error_message; + if (register_memory[step.arg1] > register_memory[step.arg2]) + register_memory[0]++; + break; + } + + case ZERO: + { + if (!check_register(step.arg1)) + return error_message; + if (register_memory[step.arg1] == 0) + register_memory[0]++; + break; + } + + default: + return godot::String("Unknown instruction! This is not on you."); + } + return godot::String(); } +bool CoborVM::CoborVirtualMachine::string_to_instruction(godot::String command_string, Instruction &outInstruction) +{ + std::string cmd = command_string.utf8().get_data(); + + auto it = stringToInstructionMap.find(cmd); + if (it != stringToInstructionMap.end()) + { + outInstruction = it->second; + return true; + } + + return false; +} + +bool CoborVM::CoborVirtualMachine::instruction_to_executable_step(Instruction instruction, godot::PackedStringArray line, ExecutableStep &outExecutableStep) +{ + outExecutableStep.instruction = instruction; + + auto it = instructionPatterns.find(instruction); + if (it == instructionPatterns.end()) + { + return false; // Unknown instruction + } + + const std::vector &pattern = it->second; + if (line.size() != pattern.size() + 1) + { // +1 for the instruction itself + return false; // Incorrect number of arguments + } + + int arg1 = 0, arg2 = 0, arg3 = 0; + for (size_t i = 0; i < pattern.size(); ++i) + { + bool isPointer = pattern[i]; + godot::String arg = line[i + 1]; // +1 to skip the instruction + + if (isPointer) + { + if (!validate_and_parse_pointer(arg, (i == 0) ? arg1 : (i == 1) ? arg2 + : arg3)) + { + return false; + } + } + else + { + if (!validate_and_parse_literal(arg, (i == 0) ? arg1 : (i == 1) ? arg2 + : arg3)) + { + return false; + } + } + } + + outExecutableStep.arg1 = arg1; + outExecutableStep.arg2 = arg2; + outExecutableStep.arg3 = arg3; + return true; +} + +bool CoborVM::CoborVirtualMachine::validate_and_parse_pointer(const godot::String &arg, int &outValue) +{ + if (!arg.begins_with("/")) + { + return false; + } + godot::String trimmedArg = arg.trim_prefix("/"); + if (!trimmedArg.is_valid_int()) + { + return false; + } + outValue = trimmedArg.to_int(); + return true; +} + +bool CoborVM::CoborVirtualMachine::validate_and_parse_literal(const godot::String &arg, int &outValue) +{ + if (!arg.is_valid_int()) + { + return false; + } + outValue = arg.to_int(); + return true; +} \ No newline at end of file diff --git a/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.os b/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.os index a77c7aa..f2e70c0 100644 Binary files a/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.os and b/cobor_vm_extension/cobor_vm/src/cobor_virtual_machine.os differ diff --git a/cobor_vm_extension/cobor_vm/src/register_types.os b/cobor_vm_extension/cobor_vm/src/register_types.os index 064bcaa..0e2d565 100644 Binary files a/cobor_vm_extension/cobor_vm/src/register_types.os and b/cobor_vm_extension/cobor_vm/src/register_types.os differ