├── README.md ├── eva-vm.cpp ├── src ├── Logger.h ├── bytecode │ └── OpCode.h ├── compiler │ ├── EvaCompiler.h │ └── Scope.h ├── disassembler │ └── EvaDisassembler.h ├── gc │ └── EvaCollector.h ├── parser │ └── EvaGrammar.bnf └── vm │ ├── EvaVM.h │ ├── EvaValue.h │ └── Global.h └── test.eva /README.md: -------------------------------------------------------------------------------- 1 | # Building a Virtual Machine 2 | 3 | This is a repository for the [Building a Virtual Machine](http://dmitrysoshnikov.com/courses/virtual-machine/) course. 4 | 5 | As mentioned in the class description, we want our students to understand and implement every piece of detail from the VM themselves, instead of copy-pasting from the final solution. 6 | 7 | Therefore, the source code here provides only the overall structure of the project, leaving the missing parts as assignments. The _"Implement here..."_ comments with references to appropriate video lectures, show specific places which are needed to be completed in order to finalize the full working interpreter. 8 | 9 | Example: 10 | 11 | ```cpp 12 | if (op == "var") { 13 | // Implement here: see Lecture 14 14 | } 15 | ``` 16 | 17 | ## Enroll 18 | 19 | You can enroll to the full course here: 20 | 21 | - On [dmitrysoshnikov.education](https://www.dmitrysoshnikov.education/p/virtual-machine) school 22 | 23 | More details, classes, articles and info is on [http://dmitrysoshnikov.com](http://dmitrysoshnikov.com). -------------------------------------------------------------------------------- /eva-vm.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva VM executable. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "src/Logger.h" 20 | #include "src/vm/EvaVM.h" 21 | #include "src/vm/EvaValue.h" 22 | 23 | void printHelp() { 24 | std::cout << "\nUsage: eva-vm [options]\n\n" 25 | << "Options:\n" 26 | << " -e, --expression Expression to parse\n" 27 | << " -f, --file File to parse\n\n"; 28 | } 29 | 30 | /** 31 | * Eva VM main executable. 32 | */ 33 | int main(int argc, char const *argv[]) { 34 | if (argc != 3) { 35 | printHelp(); 36 | return 0; 37 | } 38 | 39 | /** 40 | * Expression mode. 41 | */ 42 | std::string mode = argv[1]; 43 | 44 | /** 45 | * Program to execute. 46 | */ 47 | std::string program; 48 | 49 | /** 50 | * Simple expression. 51 | */ 52 | if (mode == "-e") { 53 | program = argv[2]; 54 | } 55 | 56 | /** 57 | * Eva file. 58 | */ 59 | else if (mode == "-f") { 60 | // Read the file: 61 | std::ifstream programFile(argv[2]); 62 | std::stringstream buffer; 63 | buffer << programFile.rdbuf() << "\n"; 64 | 65 | // Program: 66 | program = buffer.str(); 67 | } 68 | 69 | /** 70 | * VM instance. 71 | */ 72 | EvaVM vm; 73 | 74 | /** 75 | * Evaluation result. 76 | */ 77 | auto result = vm.exec(program); 78 | 79 | std::cout << "\n"; 80 | log(result); 81 | std::cout << "\n"; 82 | 83 | return 0; 84 | } -------------------------------------------------------------------------------- /src/Logger.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Logger and error reporter. 13 | */ 14 | 15 | #ifndef Logger_h 16 | #define Logger_h 17 | 18 | #include 19 | 20 | class ErrorLogMessage : public std::basic_ostringstream { 21 | public: 22 | ~ErrorLogMessage() { 23 | std::cerr << "Fatal error: " << str().c_str(); 24 | exit(EXIT_FAILURE); 25 | } 26 | }; 27 | 28 | #define DIE ErrorLogMessage() 29 | 30 | #define log(value) std::cout << #value << " = " << (value) << "\n"; 31 | 32 | #endif -------------------------------------------------------------------------------- /src/bytecode/OpCode.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Instruciton set architecture (ISA) for Eva VM. 13 | */ 14 | 15 | #ifndef OpCode_h 16 | #define OpCode_h 17 | 18 | /** 19 | * Stops the program. 20 | */ 21 | #define OP_HALT 0x00 22 | 23 | /** 24 | * Pushes a const onto the stack. 25 | */ 26 | #define OP_CONST 0x01 27 | 28 | /** 29 | * Math instruction. 30 | */ 31 | #define OP_ADD 0x02 32 | #define OP_SUB 0x03 33 | #define OP_MUL 0x04 34 | #define OP_DIV 0x05 35 | 36 | /** 37 | * Comparison. 38 | */ 39 | #define OP_COMPARE 0x06 40 | 41 | /** 42 | * Control flow: jump if the value on the stack is false. 43 | */ 44 | #define OP_JMP_IF_FALSE 0x07 45 | 46 | /** 47 | * Unconditional jump. 48 | */ 49 | #define OP_JMP 0x08 50 | 51 | /** 52 | * Returns a global variable. 53 | */ 54 | #define OP_GET_GLOBAL 0x09 55 | 56 | /** 57 | * Sets global variable value. 58 | */ 59 | #define OP_SET_GLOBAL 0x0A 60 | 61 | /** 62 | * Pops a value from the stack. 63 | */ 64 | #define OP_POP 0x0B 65 | 66 | /** 67 | * Returns a local variable. 68 | */ 69 | #define OP_GET_LOCAL 0x0C 70 | 71 | /** 72 | * Sets a local variable value. 73 | */ 74 | #define OP_SET_LOCAL 0x0D 75 | 76 | /** 77 | * Exits scope. 78 | */ 79 | #define OP_SCOPE_EXIT 0x0E 80 | 81 | /** 82 | * Function call. 83 | */ 84 | #define OP_CALL 0x0F 85 | 86 | /** 87 | * Return from a function. 88 | */ 89 | #define OP_RETURN 0x10 90 | 91 | /** 92 | * Returns a cell value. 93 | */ 94 | #define OP_GET_CELL 0x11 95 | 96 | /** 97 | * Sets a cell value. 98 | */ 99 | #define OP_SET_CELL 0x12 100 | 101 | /** 102 | * Loads a cell onto the stack. 103 | */ 104 | #define OP_LOAD_CELL 0x13 105 | 106 | /** 107 | * Makes a function. 108 | */ 109 | #define OP_MAKE_FUNCTION 0x14 110 | 111 | /** 112 | * Create a new instance of a class. 113 | */ 114 | #define OP_NEW 0x15 115 | 116 | /** 117 | * Property access. 118 | */ 119 | #define OP_GET_PROP 0x16 120 | 121 | /** 122 | * Property write. 123 | */ 124 | #define OP_SET_PROP 0x17 125 | 126 | // ----------------------------------------------------------- 127 | 128 | std::string opcodeToString(uint8_t opcode) { 129 | // Implement here... 130 | } 131 | 132 | #endif -------------------------------------------------------------------------------- /src/compiler/EvaCompiler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva compiler. 13 | */ 14 | 15 | #ifndef EvaCompiler_h 16 | #define EvaCompiler_h 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "../disassembler/EvaDisassembler.h" 23 | #include "../parser/EvaParser.h" 24 | #include "../vm/EvaValue.h" 25 | #include "../vm/Global.h" 26 | #include "Scope.h" 27 | 28 | // ----------------------------------------------------------------- 29 | 30 | /** 31 | * Compiler class, emits bytecode, records constant pool, vars, etc. 32 | */ 33 | class EvaCompiler { 34 | public: 35 | EvaCompiler(std::shared_ptr global) 36 | : global(global), 37 | disassembler(std::make_unique(global)) {} 38 | 39 | /** 40 | * Main compile API. 41 | */ 42 | void compile(const Exp& exp) { 43 | // Implement here... 44 | } 45 | 46 | /** 47 | * Scope analysis. 48 | */ 49 | void analyze(const Exp& exp, std::shared_ptr scope) { 50 | // Implement here... 51 | } 52 | 53 | /** 54 | * Main compile loop. 55 | */ 56 | void gen(const Exp& exp) { 57 | switch (exp.type) { 58 | /** 59 | * ---------------------------------------------- 60 | * Numbers. 61 | */ 62 | case ExpType::NUMBER: 63 | // Implement here... 64 | break; 65 | 66 | /** 67 | * ---------------------------------------------- 68 | * Strings. 69 | */ 70 | case ExpType::STRING: 71 | // Implement here... 72 | break; 73 | 74 | /** 75 | * ---------------------------------------------- 76 | * Symbols (variables, operators). 77 | */ 78 | case ExpType::SYMBOL: 79 | /** 80 | * Boolean. 81 | */ 82 | if (exp.string == "true" || exp.string == "false") { 83 | // Implement here... 84 | } else { 85 | // Variables: 86 | // Implement here... 87 | } 88 | break; 89 | 90 | /** 91 | * ---------------------------------------------- 92 | * Lists. 93 | */ 94 | case ExpType::LIST: 95 | auto tag = exp.list[0]; 96 | 97 | /** 98 | * ---------------------------------------------- 99 | * Special cases. 100 | */ 101 | if (tag.type == ExpType::SYMBOL) { 102 | auto op = tag.string; 103 | 104 | // Implement here... 105 | 106 | } 107 | 108 | // -------------------------------------------- 109 | // Lambda function calls: 110 | // 111 | // ((lambda (x) (* x x)) 2) 112 | 113 | else { 114 | // Implement here... 115 | } 116 | 117 | break; 118 | } 119 | } 120 | 121 | /** 122 | * Disassemble code objects. 123 | */ 124 | void disassembleBytecode() { 125 | for (auto& co_ : codeObjects_) { 126 | disassembler->disassemble(co_); 127 | } 128 | } 129 | 130 | /** 131 | * Returns main function (entry point). 132 | */ 133 | FunctionObject* getMainFunction() { return main; } 134 | 135 | /** 136 | * Returns all constant traceable objects. 137 | */ 138 | std::set& getConstantObjects() { return constantObjects_; } 139 | 140 | private: 141 | /** 142 | * Global object. 143 | */ 144 | std::shared_ptr global; 145 | 146 | /** 147 | * Disassembler. 148 | */ 149 | std::unique_ptr disassembler; 150 | 151 | /** 152 | * Compiles a function. 153 | */ 154 | void compileFunction(const Exp& exp, const std::string fnName, 155 | const Exp& params, const Exp& body) { 156 | // Implement here... 157 | } 158 | 159 | /** 160 | * Creates a new code object. 161 | */ 162 | EvaValue createCodeObjectValue(const std::string& name, size_t arity = 0) { 163 | // Implement here... 164 | } 165 | 166 | /** 167 | * Enters a new block. 168 | */ 169 | void blockEnter() { co->scopeLevel++; } 170 | 171 | /** 172 | * Exits a block. 173 | */ 174 | void blockExit() { 175 | // Implement here... 176 | } 177 | 178 | /** 179 | * Whether it's the global scope. 180 | */ 181 | bool isGlobalScope() { return co->name == "main" && co->scopeLevel == 1; } 182 | 183 | /** 184 | * Whether it's the global scope. 185 | */ 186 | bool isFunctionBody() { return co->name != "main" && co->scopeLevel == 1; } 187 | 188 | /** 189 | * Whether the expression is a declaration. 190 | */ 191 | bool isDeclaration(const Exp& exp) { 192 | return isVarDeclaration(exp) || isFunctionDeclaration(exp) || 193 | isClassDeclaration(exp); 194 | } 195 | 196 | /** 197 | * (class ...) 198 | */ 199 | bool isClassDeclaration(const Exp& exp) { return isTaggedList(exp, "class"); } 200 | 201 | /** 202 | * (prop ...) 203 | */ 204 | bool isProp(const Exp& exp) { return isTaggedList(exp, "prop"); } 205 | 206 | /** 207 | * (var ) 208 | */ 209 | bool isVarDeclaration(const Exp& exp) { return isTaggedList(exp, "var"); } 210 | 211 | /** 212 | * (lambda ...) 213 | */ 214 | bool isLambda(const Exp& exp) { return isTaggedList(exp, "lambda"); } 215 | 216 | /** 217 | * (def ...) 218 | */ 219 | bool isFunctionDeclaration(const Exp& exp) { 220 | return isTaggedList(exp, "def"); 221 | } 222 | 223 | /** 224 | * (begin ...) 225 | */ 226 | bool isBlock(const Exp& exp) { return isTaggedList(exp, "begin"); } 227 | 228 | /** 229 | * Tagged lists. 230 | */ 231 | bool isTaggedList(const Exp& exp, const std::string& tag) { 232 | return exp.type == ExpType::LIST && exp.list[0].type == ExpType::SYMBOL && 233 | exp.list[0].string == tag; 234 | } 235 | 236 | /** 237 | * Pop the variables of the current scope, 238 | * returns number of vars used. 239 | */ 240 | size_t getVarsCountOnScopeExit() { 241 | // Implement here... 242 | } 243 | 244 | /** 245 | * Returns current bytecode offset. 246 | */ 247 | uint16_t getOffset() { return (uint16_t)co->code.size(); } 248 | 249 | /** 250 | * Allocates a numeric constant. 251 | */ 252 | size_t numericConstIdx(double value) { 253 | // Implement here... 254 | } 255 | 256 | /** 257 | * Allocates a string constant. 258 | */ 259 | size_t stringConstIdx(const std::string& value) { 260 | // Implement here... 261 | } 262 | 263 | /** 264 | * Allocates a boolean constant. 265 | */ 266 | size_t booleanConstIdx(bool value) { 267 | // Implement here... 268 | } 269 | 270 | /** 271 | * Emits data to the bytecode. 272 | */ 273 | void emit(uint8_t code) { co->code.push_back(code); } 274 | 275 | /** 276 | * Writes byte at offset. 277 | */ 278 | void writeByteAtOffset(size_t offset, uint8_t value) { 279 | co->code[offset] = value; 280 | } 281 | 282 | /** 283 | * Patches jump address. 284 | */ 285 | void patchJumpAddress(size_t offset, uint16_t value) { 286 | writeByteAtOffset(offset, (value >> 8) & 0xff); 287 | writeByteAtOffset(offset + 1, value & 0xff); 288 | } 289 | 290 | /** 291 | * Returns a class object by name. 292 | */ 293 | ClassObject* getClassByName(const std::string& name) { 294 | // Implement here... 295 | } 296 | 297 | /** 298 | * Scope info. 299 | */ 300 | std::map> scopeInfo_; 301 | 302 | /** 303 | * Scopes stack. 304 | */ 305 | std::stack> scopeStack_; 306 | 307 | /** 308 | * Compiling code object. 309 | */ 310 | CodeObject* co; 311 | 312 | /** 313 | * Main entry point (function). 314 | */ 315 | FunctionObject* main; 316 | 317 | /** 318 | * All code objects. 319 | */ 320 | std::vector codeObjects_; 321 | 322 | /** 323 | * All objects from the constant pools of all code objects. 324 | */ 325 | std::set constantObjects_; 326 | 327 | /** 328 | * Currently compiling class object. 329 | */ 330 | ClassObject* classObject_; 331 | 332 | /** 333 | * All class objects. 334 | */ 335 | std::vector classObjects_; 336 | 337 | /** 338 | * Compare ops map. 339 | */ 340 | static std::map compareOps_; 341 | }; 342 | 343 | /** 344 | * Compare ops map. 345 | */ 346 | std::map EvaCompiler::compareOps_ = { 347 | {"<", 0}, {">", 1}, {"==", 2}, {">=", 3}, {"<=", 4}, {"!=", 5}, 348 | }; 349 | 350 | #endif -------------------------------------------------------------------------------- /src/compiler/Scope.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Scope analysis. 13 | */ 14 | 15 | #ifndef Scope_h 16 | #define Scope_h 17 | 18 | #include 19 | #include 20 | 21 | /** 22 | * Scope type. 23 | */ 24 | enum class ScopeType { 25 | GLOBAL, 26 | FUNCTION, 27 | BLOCK, 28 | CLASS, 29 | }; 30 | 31 | /** 32 | * Allocation type. 33 | */ 34 | enum class AllocType { 35 | GLOBAL, 36 | LOCAL, 37 | CELL, 38 | }; 39 | 40 | /** 41 | * Scope structure. 42 | */ 43 | struct Scope { 44 | Scope(ScopeType type, std::shared_ptr parent) 45 | : type(type), parent(parent) {} 46 | 47 | /** 48 | * Registers a local. 49 | */ 50 | void addLocal(const std::string& name) { 51 | // Implement here... 52 | } 53 | 54 | /** 55 | * Registers an own cell. 56 | */ 57 | void addCell(const std::string& name) { 58 | // Implement here... 59 | } 60 | 61 | /** 62 | * Registers a free var (parent cell). 63 | */ 64 | void addFree(const std::string& name) { 65 | // Implement here... 66 | } 67 | 68 | /** 69 | * Potentially promotes a variable from local to cell. 70 | */ 71 | void maybePromote(const std::string& name) { 72 | // Implement here... 73 | } 74 | 75 | /** 76 | * Promotes a variable from local (stack) to cell (heap). 77 | */ 78 | void promote(const std::string& name, Scope* ownerScope) { 79 | // Implement here... 80 | } 81 | 82 | /** 83 | * Resolves a variable in the scope chain. 84 | * 85 | * Initially a variable is treated as local, however if during 86 | * the resolution we passed the own function boundary, it is 87 | * free, and hence should be promoted to a cell, unless global. 88 | */ 89 | std::pair resolve(const std::string& name, 90 | AllocType allocType) { 91 | // Implement here... 92 | } 93 | 94 | /** 95 | * Returns get opcode based on allocation type. 96 | */ 97 | int getNameGetter(const std::string& name) { 98 | // Implement here... 99 | } 100 | 101 | /** 102 | * Returns set opcode based on allocation type. 103 | */ 104 | int getNameSetter(const std::string& name) { 105 | // Implement here... 106 | } 107 | 108 | /** 109 | * Scope type. 110 | */ 111 | ScopeType type; 112 | 113 | /** 114 | * Parent scope. 115 | */ 116 | std::shared_ptr parent; 117 | 118 | /** 119 | * Allocation info. 120 | */ 121 | std::map allocInfo; 122 | 123 | /** 124 | * Set of free vars. 125 | */ 126 | std::set free; 127 | 128 | /** 129 | * Set of own cells. 130 | */ 131 | std::set cells; 132 | }; 133 | 134 | #endif -------------------------------------------------------------------------------- /src/disassembler/EvaDisassembler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva disassembler. 13 | */ 14 | 15 | #ifndef EvaDisassembler_h 16 | #define EvaDisassembler_h 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../bytecode/OpCode.h" 24 | #include "../vm/EvaValue.h" 25 | #include "../vm/Global.h" 26 | 27 | /** 28 | * Eva disassembler. 29 | */ 30 | class EvaDisassembler { 31 | public: 32 | EvaDisassembler(std::shared_ptr global) : global(global) {} 33 | /** 34 | * Disassembles a code unit. 35 | */ 36 | void disassemble(CodeObject* co) { 37 | // Implement here... 38 | } 39 | 40 | private: 41 | /** 42 | * Disassembles individual instruction. 43 | */ 44 | size_t disassembleInstruction(CodeObject* co, size_t offset) { 45 | // Implement here... 46 | } 47 | 48 | /** 49 | * Disassembles simple instruction. 50 | */ 51 | size_t disassembleSimple(CodeObject* co, uint8_t opcode, size_t offset) { 52 | // Implement here... 53 | } 54 | 55 | /** 56 | * Disassembles a word. 57 | */ 58 | size_t disassembleWord(CodeObject* co, uint8_t opcode, size_t offset) { 59 | // Implement here... 60 | } 61 | 62 | /** 63 | * Disassembles const instruction: OP_CONST 64 | */ 65 | size_t disassembleConst(CodeObject* co, uint8_t opcode, size_t offset) { 66 | // Implement here... 67 | } 68 | 69 | /** 70 | * Disassembles global variable instruction. 71 | */ 72 | size_t disassembleGlobal(CodeObject* co, uint8_t opcode, size_t offset) { 73 | // Implement here... 74 | } 75 | 76 | /** 77 | * Disassembles local variable instruction. 78 | */ 79 | size_t disassembleLocal(CodeObject* co, uint8_t opcode, size_t offset) { 80 | // Implement here... 81 | } 82 | 83 | /** 84 | * Disassembles property instruction. 85 | */ 86 | size_t disassembleProperty(CodeObject* co, uint8_t opcode, size_t offset) { 87 | // Implement here... 88 | } 89 | 90 | /** 91 | * Disassembles cell instruction. 92 | */ 93 | size_t disassembleCell(CodeObject* co, uint8_t opcode, size_t offset) { 94 | // Implement here... 95 | } 96 | 97 | /** 98 | * Disassembles make function. 99 | */ 100 | size_t disassembleMakeFunction(CodeObject* co, uint8_t opcode, 101 | size_t offset) { 102 | // Implement here... 103 | } 104 | 105 | /** 106 | * Dumps raw memory from the bytecode. 107 | */ 108 | void dumpBytes(CodeObject* co, size_t offset, size_t count) { 109 | // Implement here... 110 | } 111 | 112 | /** 113 | * Prints opcode. 114 | */ 115 | void printOpCode(uint8_t opcode) { 116 | std::cout << std::left << std::setfill(' ') << std::setw(20) 117 | << opcodeToString(opcode) << " "; 118 | } 119 | 120 | /** 121 | * Disassembles compare instruction. 122 | */ 123 | size_t disassembleCompare(CodeObject* co, uint8_t opcode, size_t offset) { 124 | // Implement here... 125 | } 126 | 127 | /** 128 | * Disassembles conditional jump. 129 | */ 130 | size_t disassembleJump(CodeObject* co, uint8_t opcode, size_t offset) { 131 | // Implement here... 132 | } 133 | 134 | /** 135 | * Reads a word at offset. 136 | */ 137 | uint16_t readWordAtOffset(CodeObject* co, size_t offset) { 138 | return (uint16_t)((co->code[offset] << 8) | co->code[offset + 1]); 139 | } 140 | 141 | /** 142 | * Global object. 143 | */ 144 | std::shared_ptr global; 145 | 146 | static std::array inverseCompareOps_; 147 | }; 148 | 149 | std::array EvaDisassembler::inverseCompareOps_ = { 150 | "<", ">", "==", ">=", "<=", "!=", 151 | }; 152 | 153 | #endif -------------------------------------------------------------------------------- /src/gc/EvaCollector.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Garbage Collector. 13 | */ 14 | 15 | #ifndef EvaCollector_h 16 | #define EvaCollector_h 17 | 18 | /** 19 | * Garbage collector implementing Mark-Sweep algorithm. 20 | */ 21 | struct EvaCollector { 22 | /** 23 | * Main collection cycle. 24 | */ 25 | void gc(const std::set &roots) { 26 | // Implement here... 27 | } 28 | 29 | /** 30 | * Marking phase (trace). 31 | */ 32 | void mark(const std::set &roots) { 33 | // Implement here... 34 | } 35 | 36 | /** 37 | * Returns all pointers within this object. 38 | */ 39 | std::set getPointers(const Traceable *object) { 40 | // Implement here... 41 | } 42 | 43 | /** 44 | * Sweep phase (reclaim). 45 | */ 46 | void sweep() { 47 | // Implement here... 48 | } 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /src/parser/EvaGrammar.bnf: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva grammar (S-expression). 13 | * 14 | * syntax-cli -g src/parser/EvaGrammar.bnf -m LALR1 -o src/parser/EvaParser.h 15 | * 16 | * Examples: 17 | * 18 | * Atom: 42, foo, bar, "Hello World" 19 | * 20 | * List: (), (+ 5 x), (print "hello") 21 | */ 22 | 23 | // ----------------------------------------------- 24 | // Lexical grammar (tokens): 25 | 26 | %lex 27 | 28 | %% 29 | 30 | \/\/.* %empty 31 | \/\*[\s\S]*?\*\/ %empty 32 | 33 | \s+ %empty 34 | 35 | \"[^\"]*\" STRING 36 | 37 | \d+ NUMBER 38 | 39 | [\w\-+*=!<>/]+ SYMBOL 40 | 41 | /lex 42 | 43 | // ----------------------------------------------- 44 | // Syntactic grammar (BNF): 45 | 46 | %{ 47 | 48 | #include 49 | #include 50 | 51 | /** 52 | * Expression type. 53 | */ 54 | enum class ExpType { 55 | NUMBER, 56 | STRING, 57 | SYMBOL, 58 | LIST, 59 | }; 60 | 61 | /** 62 | * Expression. 63 | */ 64 | struct Exp { 65 | ExpType type; 66 | 67 | int number; 68 | std::string string; 69 | std::vector list; 70 | 71 | // Numbers: 72 | Exp(int number) : type(ExpType::NUMBER), number(number) {} 73 | 74 | // Strings, Symbols: 75 | Exp(std::string& strVal) { 76 | if (strVal[0] == '"') { 77 | type = ExpType::STRING; 78 | string = strVal.substr(1, strVal.size() - 2); 79 | } else { 80 | type = ExpType::SYMBOL; 81 | string = strVal; 82 | } 83 | } 84 | 85 | // Lists: 86 | Exp(std::vector list) : type(ExpType::LIST), list(list) {} 87 | 88 | }; 89 | 90 | using Value = Exp; 91 | 92 | %} 93 | 94 | %% 95 | 96 | Exp 97 | : Atom 98 | | List 99 | ; 100 | 101 | Atom 102 | : NUMBER { $$ = Exp(std::stoi($1)) } 103 | | STRING { $$ = Exp($1) } 104 | | SYMBOL { $$ = Exp($1) } 105 | ; 106 | 107 | List 108 | : '(' ListEntries ')' { $$ = $2 } 109 | ; 110 | 111 | ListEntries 112 | : %empty { $$ = Exp(std::vector{}) } 113 | | ListEntries Exp { $1.list.push_back($2); $$ = $1 } 114 | ; 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/vm/EvaVM.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva Virtual Machine. 13 | */ 14 | 15 | #ifndef EvaVM_h 16 | #define EvaVM_h 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../Logger.h" 24 | #include "../bytecode/OpCode.h" 25 | #include "../compiler/EvaCompiler.h" 26 | #include "../gc/EvaCollector.h" 27 | #include "../parser/EvaParser.h" 28 | #include "EvaValue.h" 29 | #include "Global.h" 30 | 31 | using syntax::EvaParser; 32 | 33 | /** 34 | * Reads the current byte in the bytecode 35 | * and advances the ip pointer. 36 | */ 37 | #define READ_BYTE() // Implement here... 38 | 39 | /** 40 | * Reads a short word (2 bytes). 41 | */ 42 | #define READ_SHORT() // Implement here... 43 | 44 | /** 45 | * Converts bytecode index to a pointer. 46 | */ 47 | #define TO_ADDRESS(index) (&fn->co->code[index]) 48 | 49 | /** 50 | * Gets a constant from the pool. 51 | */ 52 | #define GET_CONST() (fn->co->constants[READ_BYTE()]) 53 | 54 | /** 55 | * Stack top (stack overflow after exceeding). 56 | */ 57 | #define STACK_LIMIT 512 58 | 59 | /** 60 | * Memory threshold after which GC is triggered. 61 | */ 62 | #define GC_TRESHOLD 1024 63 | 64 | /** 65 | * Runtime allocation, can call GC. 66 | */ 67 | #define MEM(allocator, ...) // Implement here... 68 | 69 | /** 70 | * Binary operation. 71 | */ 72 | #define BINARY_OP(op) // Implement here... 73 | 74 | /** 75 | * Generic values comparison. 76 | */ 77 | #define COMPARE_VALUES(op, v1, v2) // Implement here... 78 | 79 | // -------------------------------------------------- 80 | 81 | /** 82 | * Stack frame for function calls. 83 | */ 84 | struct Frame { 85 | // Implement here... 86 | }; 87 | 88 | // -------------------------------------------------- 89 | 90 | /** 91 | * Eva Virtual Machine. 92 | */ 93 | class EvaVM { 94 | public: 95 | EvaVM() 96 | : global(std::make_shared()), 97 | parser(std::make_unique()), 98 | compiler(std::make_unique(global)), 99 | collector(std::make_unique()) { 100 | setGlobalVariables(); 101 | } 102 | 103 | /** 104 | * VM shutdown. 105 | */ 106 | ~EvaVM() { Traceable::cleanup(); } 107 | 108 | //---------------------------------------------------- 109 | // Stack operations: 110 | 111 | /** 112 | * Pushes a value onto the stack. 113 | */ 114 | void push(const EvaValue& value) { 115 | // Implement here... 116 | } 117 | 118 | /** 119 | * Pops a value from the stack. 120 | */ 121 | EvaValue pop() { 122 | // Implement here... 123 | } 124 | 125 | /** 126 | * Peeks an element from the stack. 127 | */ 128 | EvaValue peek(size_t offset = 0) { 129 | // Implement here... 130 | } 131 | 132 | /** 133 | * Pops multiple values from the stack. 134 | */ 135 | void popN(size_t count) { 136 | // Implement here... 137 | } 138 | 139 | //---------------------------------------------------- 140 | // GC operations: 141 | 142 | /** 143 | * Obtains GC roots: variables on the stack, globals, constants. 144 | */ 145 | std::set getGCRoots() { 146 | // Implement here... 147 | } 148 | 149 | /** 150 | * Returns stack GC roots. 151 | */ 152 | std::set getStackGCRoots() { 153 | // Implement here... 154 | } 155 | 156 | /** 157 | * Returns GC roots for constants. 158 | */ 159 | std::set getConstantGCRoots() { 160 | // Implement here... 161 | } 162 | 163 | /** 164 | * Returns global GC roots. 165 | */ 166 | std::set getGlobalGCRoots() { 167 | // Implement here... 168 | } 169 | 170 | /** 171 | * Spawns a potential GC cycle. 172 | */ 173 | void maybeGC() { 174 | // Implement here... 175 | } 176 | 177 | //---------------------------------------------------- 178 | // Program execution 179 | 180 | /** 181 | * Executes a program. 182 | */ 183 | EvaValue exec(const std::string& program) { 184 | // 1. Parse the program 185 | auto ast = parser->parse("(begin " + program + ")"); 186 | 187 | // 2. Compile program to Eva bytecode 188 | compiler->compile(ast); 189 | 190 | // Start from the main entry point: 191 | fn = compiler->getMainFunction(); 192 | 193 | // Set instruction pointer to the beginning: 194 | ip = &fn->co->code[0]; 195 | 196 | // Init the stack: 197 | sp = &stack[0]; 198 | 199 | // Init the base (frame) pointer: 200 | bp = sp; 201 | 202 | return eval(); 203 | } 204 | 205 | /** 206 | * Main eval loop. 207 | */ 208 | EvaValue eval() { 209 | for (;;) { 210 | // Implement here... 211 | } 212 | } 213 | 214 | /** 215 | * Sets up global variables and function. 216 | */ 217 | void setGlobalVariables() { 218 | // Implement here... 219 | } 220 | 221 | /** 222 | * Global object. 223 | */ 224 | std::shared_ptr global; 225 | 226 | /** 227 | * Parser. 228 | */ 229 | std::unique_ptr parser; 230 | 231 | /** 232 | * Compiler. 233 | */ 234 | std::unique_ptr compiler; 235 | 236 | /** 237 | * Garbage collector. 238 | */ 239 | std::unique_ptr collector; 240 | 241 | /** 242 | * Instruction pointer (aka Program counter). 243 | */ 244 | uint8_t* ip; 245 | 246 | /** 247 | * Stack pointer. 248 | */ 249 | EvaValue* sp; 250 | 251 | /** 252 | * Base pointer (aka Frame pointer). 253 | */ 254 | EvaValue* bp; 255 | 256 | /** 257 | * Operands stack. 258 | */ 259 | std::array stack; 260 | 261 | /** 262 | * Separate stack for calls. Keeps return addresses. 263 | */ 264 | std::stack callStack; 265 | 266 | /** 267 | * Currently executing function. 268 | */ 269 | FunctionObject* fn; 270 | 271 | // -------------------------------------------------- 272 | // Debug functions: 273 | 274 | /** 275 | * Dumps current stack. 276 | */ 277 | void dumpStack() { 278 | // Implement here... 279 | } 280 | }; 281 | 282 | #endif -------------------------------------------------------------------------------- /src/vm/EvaValue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Eva value. 13 | */ 14 | 15 | #ifndef EvaValue_h 16 | #define EvaValue_h 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | /** 23 | * Eva value type. 24 | */ 25 | enum class EvaValueType { 26 | NUMBER, 27 | BOOLEAN, 28 | OBJECT, 29 | }; 30 | 31 | /** 32 | * Object type. 33 | */ 34 | enum class ObjectType { 35 | STRING, 36 | CODE, 37 | NATIVE, 38 | FUNCTION, 39 | CELL, 40 | CLASS, 41 | INSTANCE, 42 | }; 43 | 44 | // ---------------------------------------------------------------- 45 | 46 | /** 47 | * Base traceable object. 48 | */ 49 | struct Traceable { 50 | /** 51 | * Whether the object was marked during the trace. 52 | */ 53 | bool marked; 54 | 55 | /** 56 | * Allocated size. 57 | */ 58 | size_t size; 59 | 60 | /** 61 | * Allocator. 62 | */ 63 | static void* operator new(size_t size) { 64 | // Implement here... 65 | } 66 | 67 | /** 68 | * Deallocator. 69 | */ 70 | static void operator delete(void* object, std::size_t sz) { 71 | // Implement here... 72 | } 73 | 74 | /** 75 | * Clean up for all objects. 76 | */ 77 | static void cleanup() { 78 | // Implement here... 79 | } 80 | 81 | /** 82 | * Printes memory stats 83 | */ 84 | static void printStats() { 85 | // Implement here... 86 | } 87 | 88 | /** 89 | * Total number of allocated bytes. 90 | */ 91 | static size_t bytesAllocated; 92 | 93 | /** 94 | * List of all allocated objects. 95 | */ 96 | static std::list objects; 97 | }; 98 | 99 | /** 100 | * Total bytes allocated. 101 | */ 102 | size_t Traceable::bytesAllocated{0}; 103 | 104 | /** 105 | * List of all allocated objects. 106 | */ 107 | std::list Traceable::objects{}; 108 | 109 | // ---------------------------------------------------------------- 110 | 111 | /** 112 | * Base object. 113 | */ 114 | struct Object : public Traceable { 115 | Object(ObjectType type) : type(type) {} 116 | ObjectType type; 117 | }; 118 | 119 | // ---------------------------------------------------------------- 120 | 121 | /** 122 | * String object. 123 | */ 124 | struct StringObject : public Object { 125 | // Implement here... 126 | }; 127 | 128 | // ---------------------------------------------------------------- 129 | 130 | using NativeFn = std::function; 131 | 132 | /** 133 | * Native function. 134 | */ 135 | struct NativeObject : public Object { 136 | // Implement here... 137 | }; 138 | 139 | // ---------------------------------------------------------------- 140 | 141 | /** 142 | * Eva value (tagged union). 143 | */ 144 | struct EvaValue { 145 | EvaValueType type; 146 | union { 147 | double number; 148 | bool boolean; 149 | Object* object; 150 | }; 151 | }; 152 | 153 | // ---------------------------------------------------------------- 154 | 155 | /** 156 | * Class object. 157 | */ 158 | struct ClassObject : public Object { 159 | // Implement here... 160 | }; 161 | 162 | // ---------------------------------------------------------------- 163 | 164 | /** 165 | * Instance object. 166 | */ 167 | struct InstanceObject : public Object { 168 | // Implement here... 169 | }; 170 | 171 | // ---------------------------------------------------------------- 172 | 173 | struct LocalVar { 174 | // Implement here... 175 | }; 176 | 177 | /** 178 | * Code object. 179 | * 180 | * Contains compiling bytecode, locals and other 181 | * state needed for function execution. 182 | */ 183 | struct CodeObject : public Object { 184 | // Implement here... 185 | }; 186 | 187 | // ---------------------------------------------------------------- 188 | 189 | /** 190 | * Heap-allocated cell. 191 | * 192 | * Used to capture closured variables. 193 | */ 194 | struct CellObject : public Object { 195 | // Implement here... 196 | }; 197 | 198 | // ---------------------------------------------------------------- 199 | 200 | /** 201 | * Function object. 202 | */ 203 | struct FunctionObject : public Object { 204 | // Implement here... 205 | }; 206 | 207 | // ---------------------------------------------------------------- 208 | // Constructors: 209 | 210 | #define NUMBER(value) ((EvaValue){EvaValueType::NUMBER, .number = value}) 211 | 212 | // Implement here... 213 | 214 | // ---------------------------------------------------------------- 215 | // Accessors: 216 | 217 | #define AS_NUMBER(evaValue) ((double)(evaValue).number) 218 | 219 | // Implement here... 220 | 221 | // ---------------------------------------------------------------- 222 | // Testers: 223 | 224 | #define IS_NUMBER(evaValue) ((evaValue).type == EvaValueType::NUMBER) 225 | 226 | // Implement here... 227 | 228 | // ---------------------------------------------------------------- 229 | 230 | /** 231 | * String representation used in constants for debug. 232 | */ 233 | std::string evaValueToTypeString(const EvaValue& evaValue) { 234 | // Implement here... 235 | } 236 | 237 | /** 238 | * String representation used in constants for debug. 239 | */ 240 | std::string evaValueToConstantString(const EvaValue& evaValue) { 241 | // Implement here... 242 | } 243 | 244 | /** 245 | * Output stream. 246 | */ 247 | std::ostream& operator<<(std::ostream& os, const EvaValue& evaValue) { 248 | return os << "EvaValue (" << evaValueToTypeString(evaValue) 249 | << "): " << evaValueToConstantString(evaValue); 250 | } 251 | 252 | #endif -------------------------------------------------------------------------------- /src/vm/Global.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Eva programming language. 3 | * 4 | * VM implementation. 5 | * 6 | * Course info: http://dmitrysoshnikov.com/courses/virtual-machine/ 7 | * 8 | * (C) 2021-present Dmitry Soshnikov 9 | */ 10 | 11 | /** 12 | * Global object. 13 | */ 14 | 15 | #ifndef Global_h 16 | #define Global_h 17 | 18 | /** 19 | * Global var. 20 | */ 21 | struct GlobalVar { 22 | // Implement here... 23 | }; 24 | 25 | /** 26 | * Global object. 27 | */ 28 | struct Global { 29 | /** 30 | * Returns a global. 31 | */ 32 | GlobalVar& get(size_t index) { return globals[index]; } 33 | 34 | /** 35 | * Sets a global. 36 | */ 37 | void set(size_t index, const EvaValue& value) { 38 | // Implement here... 39 | } 40 | 41 | /** 42 | * Registers a global. 43 | */ 44 | void define(const std::string& name) { 45 | // Implement here... 46 | } 47 | 48 | /** 49 | * Adds a native function. 50 | */ 51 | void addNativeFunction(const std::string& name, std::function fn, 52 | size_t arity) { 53 | // Implement here... 54 | } 55 | 56 | /** 57 | * Adds a global constant. 58 | */ 59 | void addConst(const std::string& name, double value) { 60 | // Implement here... 61 | } 62 | 63 | /** 64 | * Get global index. 65 | */ 66 | int getGlobalIndex(const std::string& name) { 67 | // Implement here... 68 | } 69 | 70 | /** 71 | * Whether a global variable exists. 72 | */ 73 | bool exists(const std::string& name) { return getGlobalIndex(name) != -1; } 74 | 75 | /** 76 | * Global variables and functions. 77 | */ 78 | std::vector globals; 79 | }; 80 | 81 | #endif -------------------------------------------------------------------------------- /test.eva: -------------------------------------------------------------------------------- 1 | /** 2 | * Parent class. 3 | */ 4 | (class Point null 5 | (def constructor (self x y) 6 | (begin 7 | (set (prop self x) x) 8 | (set (prop self y) y))) 9 | 10 | (def calc (self) 11 | (+ (prop self x) (prop self y)))) 12 | 13 | /** 14 | * Child class. 15 | */ 16 | (class Point3D Point 17 | (def constructor (self x y z) 18 | (begin 19 | ((prop (super Point3D) constructor) self x y) 20 | (set (prop self z) z))) 21 | 22 | (def calc (self) 23 | (+ ((prop (super Point3D) calc) self) (prop self z)))) 24 | 25 | /** 26 | * Instance. 27 | */ 28 | (var p (new Point3D 10 20 30)) 29 | 30 | /** 31 | * Method call. 32 | */ 33 | ((prop p calc) p) // 60 34 | --------------------------------------------------------------------------------