diff --git a/design.md b/README.md similarity index 87% rename from design.md rename to README.md index 4dd9ecd..e1e63bd 100644 --- a/design.md +++ b/README.md @@ -53,6 +53,10 @@ All symbols are reserved keywords and can therefore NOT be used as labels. ## Symbols +## Directives + +- `DECLARE` declares the first label argument to equal the second, immediate value, argument and is used to declare a constant for the virtual machine. + ### Operands - `addi` add the first to the second argument and store the result in the third argument @@ -71,6 +75,11 @@ All symbols are reserved keywords and can therefore NOT be used as labels. - `gti` execute next statement if argument 1 is greater than argument 2 else skip the next statement - `eqi` execute the next statement if argument 1 is equal to argument 2 else skip the next statement +## Memory + +- `popi` pops the first value on the stack into the register specified as the first argument +- `pushi` pushes the value on the stack from the register or immediate value as the first argument + ## Interupts - 0..9 range: diff --git a/bin/example.wasm b/bin/example.wasm index fd1c3c9..0d4a369 100644 --- a/bin/example.wasm +++ b/bin/example.wasm @@ -1,3 +1,5 @@ +DECLARE MEMORY_SIZE $4096; + addi $10 $-5 %A; subi %A $2 %B; muli $2 %B %C; @@ -7,7 +9,7 @@ divi $2 %C %D; seti %A $0; # Loop from 0 to 10 -loop: +count_loop: addi $1 %A %A; # Print the current value @@ -18,7 +20,7 @@ int $0; seti %A %B; lti %A $10; -jmp loop; +jmp count_loop; # Hello world seti %A $72; # H @@ -31,5 +33,24 @@ int $0; seti %A $111; # o int $0; +seti %A $32; # space +int $0; + +pushi $68; # D +pushi $76; # L +pushi $82; # R +pushi $79; # O +pushi $87; # W + +seti %B $5; + +world_loop: +popi %A; +int $0; + +subi %B $1 %B; +gti %B $0; +jmp world_loop; + seti %A $10; # newline int $0; \ No newline at end of file diff --git a/include/execute/error.hpp b/include/execute/error.hpp new file mode 100644 index 0000000..cfc305e --- /dev/null +++ b/include/execute/error.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace Execute +{ + struct RuntimeError + { + + }; + + struct StackUnderflow : RuntimeError + { + + }; + + struct StackOverflow : RuntimeError + { + + }; +} \ No newline at end of file diff --git a/include/execute/state.hpp b/include/execute/state.hpp index 7c82b29..955bc0d 100644 --- a/include/execute/state.hpp +++ b/include/execute/state.hpp @@ -11,7 +11,9 @@ namespace Execute unsigned nextStatement; std::unordered_map const & labelStatementIndice; std::vector const interrupts; + std::vector memory; + unsigned stackPointer; - State(std::unordered_map const & labelStatementIndice); + State(std::unordered_map const & labelStatementIndice, unsigned const memorySize); }; } \ No newline at end of file diff --git a/include/execute/virtualmachine.hpp b/include/execute/virtualmachine.hpp index a55f428..68a590f 100644 --- a/include/execute/virtualmachine.hpp +++ b/include/execute/virtualmachine.hpp @@ -17,6 +17,8 @@ namespace Execute void Step(); + VirtualMachine(Interpret::Code const & code, unsigned const memorySize); + public: void Run(); void SingleStep(); @@ -28,6 +30,6 @@ namespace Execute bool IsTerminated() const; - VirtualMachine(Interpret::Code const & code); + static VirtualMachine CreateFromCode(Interpret::Code const & code); }; } \ No newline at end of file diff --git a/include/interpret/code.hpp b/include/interpret/code.hpp index d319dd6..cc5e525 100644 --- a/include/interpret/code.hpp +++ b/include/interpret/code.hpp @@ -11,5 +11,6 @@ namespace Interpret { std::vector> statements; std::unordered_map labelStatementIndice; + std::unordered_map declarations; }; } \ No newline at end of file diff --git a/include/interpret/operanddefinitions.hpp b/include/interpret/operanddefinitions.hpp index eee1f33..348a20d 100644 --- a/include/interpret/operanddefinitions.hpp +++ b/include/interpret/operanddefinitions.hpp @@ -7,5 +7,7 @@ namespace Interpret { std::unique_ptr ExtractStatement(unsigned const operatorIndex, std::vector const & tokens); + std::tuple ExtractDeclaration(unsigned const operatorIndex, std::vector const & tokens); + int GetRequiredNumberOfArguments(Token::OperandType const type); } \ No newline at end of file diff --git a/include/interpret/statement.hpp b/include/interpret/statement.hpp index fbad40c..799da5d 100644 --- a/include/interpret/statement.hpp +++ b/include/interpret/statement.hpp @@ -69,4 +69,18 @@ namespace Interpret void Execute(Execute::Flags & flags, Execute::State & state, Execute::Registers & registers) override; }; + + struct PushStatement : Statement + { + Value firstArgument; + + void Execute(Execute::Flags & flags, Execute::State & state, Execute::Registers & registers) override; + }; + + struct PopStatement : Statement + { + Value firstArgument; + + void Execute(Execute::Flags & flags, Execute::State & state, Execute::Registers & registers) override; + }; } \ No newline at end of file diff --git a/include/token/operandtype.hpp b/include/token/operandtype.hpp index da1b903..308b0dd 100644 --- a/include/token/operandtype.hpp +++ b/include/token/operandtype.hpp @@ -17,7 +17,10 @@ namespace Token GreaterThanInteger, EqualInteger, SetInteger, - Interrupt + Interrupt, + Declaration, + PushInteger, + PopInteger }; OperandType GetOperandType(std::string const & op); diff --git a/src/execute/error.cpp b/src/execute/error.cpp new file mode 100644 index 0000000..e83a30a --- /dev/null +++ b/src/execute/error.cpp @@ -0,0 +1,6 @@ +#include + +namespace Execute +{ + +} \ No newline at end of file diff --git a/src/execute/state.cpp b/src/execute/state.cpp index 5969c41..164a621 100644 --- a/src/execute/state.cpp +++ b/src/execute/state.cpp @@ -2,11 +2,13 @@ namespace Execute { - State::State(std::unordered_map const & _labelStatementIndice) + State::State(std::unordered_map const & _labelStatementIndice, unsigned const memorySize) : currentStatement(0u), nextStatement(1u), labelStatementIndice(_labelStatementIndice), - interrupts(GetInterrupts()) + interrupts(GetInterrupts()), + memory(memorySize), + stackPointer(0u) { } } \ No newline at end of file diff --git a/src/execute/virtualmachine.cpp b/src/execute/virtualmachine.cpp index 3bf8d04..99d8d45 100644 --- a/src/execute/virtualmachine.cpp +++ b/src/execute/virtualmachine.cpp @@ -15,6 +15,15 @@ namespace Execute } } + VirtualMachine::VirtualMachine(Interpret::Code const & _code, unsigned const memorySize) + : flags(), + registers(), + state(_code.labelStatementIndice, memorySize), + terminated(false), + code(_code) + { + } + void VirtualMachine::Run() { while(!IsTerminated()) @@ -41,12 +50,16 @@ namespace Execute bool VirtualMachine::IsTerminated() const { return terminated; } - VirtualMachine::VirtualMachine(Interpret::Code const & _code) - : flags(), - registers(), - state(_code.labelStatementIndice), - terminated(false), - code(_code) + VirtualMachine VirtualMachine::CreateFromCode(Interpret::Code const & code) { + unsigned memorySize = 1024; + auto const & userMemorySizeDeclared = code.declarations.find("MEMORY_SIZE"); + if (userMemorySizeDeclared != code.declarations.end() && userMemorySizeDeclared->second >= 0) + { + memorySize = userMemorySizeDeclared->second; + } + + std::printf("Creating Wassembly virtual machine with %u bytes of memory.\n", memorySize); + return VirtualMachine(code, memorySize); } } \ No newline at end of file diff --git a/src/interpret/interpreter.cpp b/src/interpret/interpreter.cpp index 5e5e56b..960f4e1 100644 --- a/src/interpret/interpreter.cpp +++ b/src/interpret/interpreter.cpp @@ -67,7 +67,19 @@ namespace Interpret } else { - code.statements.emplace_back(ExtractStatement(operatorTokenIndex, tokens)); + switch(std::get(tokens[operatorTokenIndex].data)) + { + case Token::OperandType::Declaration: + { + auto const tuple = ExtractDeclaration(operatorTokenIndex, tokens); + code.declarations[std::get<0>(tuple)] = std::get<1>(tuple); + } + break; + + default: + code.statements.emplace_back(ExtractStatement(operatorTokenIndex, tokens)); + break; + } state = InterpreterState::FindOperand; } break; diff --git a/src/interpret/operanddefinitions.cpp b/src/interpret/operanddefinitions.cpp index 1d2005b..5fa2849 100644 --- a/src/interpret/operanddefinitions.cpp +++ b/src/interpret/operanddefinitions.cpp @@ -210,6 +210,22 @@ namespace Interpret return statement; } + case Token::OperandType::PopInteger: + { + auto statement = std::make_unique(); + statement->firstArgument = GetRegisterArgument(operandIndex + 1u, tokens); + + return statement; + } + + case Token::OperandType::PushInteger: + { + auto statement = std::make_unique(); + statement->firstArgument = GetImmediateOrRegisterArgument(operandIndex + 1u, tokens); + + return statement; + } + default: { auto statement = std::make_unique(); @@ -221,6 +237,22 @@ namespace Interpret } } + std::tuple ExtractDeclaration(unsigned const operatorIndex, std::vector const & tokens) + { + if (tokens[operatorIndex + 1u].type != Token::TokenType::Label) + { + throw ExpectedLabel(tokens[operatorIndex + 1u]); + } + if (tokens[operatorIndex + 2u].type != Token::TokenType::ImmediateInteger) + { + throw ExpectedImmediate(tokens[operatorIndex + 2u]); + } + + auto const label = std::get(tokens[operatorIndex + 1u].data); + auto const value = std::get(tokens[operatorIndex + 2u].data); + return std::make_tuple(label, value); + } + int GetRequiredNumberOfArguments(Token::OperandType const type) { switch (type) @@ -237,10 +269,13 @@ namespace Interpret case Token::OperandType::GreaterThanInteger: case Token::OperandType::EqualInteger: case Token::OperandType::SetInteger: + case Token::OperandType::Declaration: return 2; case Token::OperandType::Jump: case Token::OperandType::Interrupt: + case Token::OperandType::PushInteger: + case Token::OperandType::PopInteger: return 1; default: diff --git a/src/interpret/statement.cpp b/src/interpret/statement.cpp index ca68c4a..91ff4b9 100644 --- a/src/interpret/statement.cpp +++ b/src/interpret/statement.cpp @@ -1,3 +1,4 @@ +#include #include namespace Interpret @@ -49,4 +50,29 @@ namespace Interpret { state.interrupts[firstArgument.GetValue(registers)](registers); } + + void PopStatement::Execute(Execute::Flags & flags, Execute::State & state, Execute::Registers & registers) + { + if (state.stackPointer < sizeof(int)) + { + throw Execute::StackUnderflow(); + } + + auto const value = *(reinterpret_cast(state.memory.data() + (state.stackPointer - 1u))); + firstArgument.GetValue(registers) = value; + + state.stackPointer -= sizeof(int); + } + + void PushStatement::Execute(Execute::Flags & flags, Execute::State & state, Execute::Registers & registers) + { + if ((state.memory.size() - state.stackPointer) < sizeof(int)) + { + throw Execute::StackOverflow(); + } + + *(reinterpret_cast(state.memory.data() + state.stackPointer + (sizeof(int) - 1))) = firstArgument.GetValue(registers); + + state.stackPointer += sizeof(int); + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 39f474c..738b335 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,35 +24,8 @@ void PrintTokenError(Interpret::TokenError const & err, std::vector PrintBadToken(err.errorToken, lines); } -int main(int argc, char ** argv) +void PrintTokens(std::vector const & tokens) { - if (argc != 2) - { - std::puts("Usage: wassembly [filename.wasm]"); - return 1; - } - - std::ifstream input(argv[1]); - if (!input.is_open()) - { - std::printf("Error: Cannot open file %s for reading", argv[1]); - return 1; - } - - std::vector tokens; - Token::Tokenizer tokenizer; - std::vector lines; // DEBUG - std::string line; - unsigned lineNumber = 0; - while(std::getline(input, line)) - { - tokenizer.Tokenize(line, lineNumber, tokens); - ++lineNumber; - lines.push_back(line); // DEBUG - } - input.close(); - - // DEBUG std::puts("*** Tokenization result ***"); unsigned statementNumber = 0u; std::printf("%02u - ", statementNumber); @@ -73,7 +46,38 @@ int main(int argc, char ** argv) } } } - // END DEBUG +} + +void PrintLabels(Interpret::Code const & code) +{ + std::puts("\n*** Labels ***"); + for(auto const & labelIndice : code.labelStatementIndice) + { + std::printf("Label %s points to statement %u\n", labelIndice.first.c_str(), labelIndice.second); + } +} + +Interpret::Code GetCodeFromFile(std::string const & filePath) +{ + std::ifstream input(filePath); + if (!input.is_open()) + { + std::printf("Error: Cannot open file %s for reading", filePath.c_str()); + exit(1); + } + + std::vector tokens; + Token::Tokenizer tokenizer; + std::vector lines; + std::string line; + unsigned lineNumber = 0; + while(std::getline(input, line)) + { + tokenizer.Tokenize(line, lineNumber, tokens); + ++lineNumber; + lines.push_back(line); + } + input.close(); // Validate the syntax bool syntaxOk = true; @@ -90,7 +94,7 @@ int main(int argc, char ** argv) if (!syntaxOk) { std::puts("Aborting due to syntax error(s)"); - return 1; + exit(1); } Interpret::Interpreter interpreter; @@ -102,33 +106,23 @@ int main(int argc, char ** argv) catch(Interpret::TokenError & e) { PrintTokenError(e, lines); + exit(1); + } + + return code; +} + +int main(int argc, char ** argv) +{ + if (argc != 2) + { + std::puts("Usage: wassembly [filename.wasm]"); return 1; } - // DEBUG - std::puts("\n*** Labels ***"); - for(auto const & labelIndice : code.labelStatementIndice) - { - std::printf("Label %s points to statement %u\n", labelIndice.first.c_str(), labelIndice.second); - } - - std::puts("\n*** Execution ***"); - Execute::VirtualMachine vm(code); + auto const code = GetCodeFromFile(argv[1]); + auto vm = Execute::VirtualMachine::CreateFromCode(code); vm.Run(); - /* - while(!vm.IsTerminated()) - { - vm.SingleStep(); - auto const & registers = vm.GetRegisters(); - std::printf("A=%i B=%i C=%i D=%i\n", registers.A, registers.B, registers.C, registers.D); - auto const & state = vm.GetState(); - std::printf("current_statement=%i\n", state.currentStatement); - - std::puts("Press any key to step..."); - std::getchar(); - } - */ - // END DEBUG return 0; } \ No newline at end of file diff --git a/src/token/operandtype.cpp b/src/token/operandtype.cpp index be20d00..ce16c68 100644 --- a/src/token/operandtype.cpp +++ b/src/token/operandtype.cpp @@ -18,7 +18,10 @@ namespace Token { "gti", OperandType::GreaterThanInteger }, { "eqi", OperandType::EqualInteger }, { "seti", OperandType::SetInteger }, - { "int", OperandType::Interrupt } + { "int", OperandType::Interrupt }, + { "DECLARE", OperandType::Declaration }, + { "pushi", OperandType::PushInteger}, + { "popi", OperandType::PopInteger}, }; auto const & result = operations.find(op);