├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cli ├── CMakeLists.txt ├── commandline.hpp ├── linenoise.hpp ├── main.cpp ├── repl.hpp └── run.hpp ├── engine ├── ast.hpp ├── code.hpp ├── compiler.hpp ├── environment.hpp ├── evaluator.hpp ├── object.hpp ├── parser.hpp ├── symbol_table.hpp └── vm.hpp ├── examples ├── closure.monkey ├── fib.monkey └── map.monkey └── test ├── CMakeLists.txt ├── catch.hpp ├── test-code.cpp ├── test-compiler.cpp ├── test-evaluator.cpp ├── test-main.cpp ├── test-object.cpp ├── test-parser.cpp ├── test-symbol_table.cpp ├── test-util.hpp └── test-vm.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | AllowShortBlocksOnASingleLine: true 3 | AllowShortCaseLabelsOnASingleLine: true 4 | AllowShortIfStatementsOnASingleLine: true 5 | Cpp11BracedListStyle: true 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/* 35 | waiig_code_* 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhirose/monkey-cpp/11ef879626d2bdde87068dddf84b511332242aa7/.gitmodules -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(monkey-cpp VERSION 0.1 LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_EXTENSIONS OFF) 6 | 7 | if(MSVC) 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus /utf-8") 9 | else() 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter") 11 | endif() 12 | 13 | include(FetchContent) 14 | 15 | FetchContent_Populate( 16 | peglib 17 | GIT_REPOSITORY https://github.com/yhirose/cpp-peglib.git 18 | GIT_TAG master 19 | ) 20 | 21 | FetchContent_Declare( 22 | fmt 23 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 24 | GIT_TAG 9.1.0 25 | ) 26 | FetchContent_MakeAvailable(fmt) 27 | 28 | add_subdirectory(cli) 29 | add_subdirectory(test) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 yhirose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monkey 2 | 3 | Another implementation of Monkey programming language described in [Writing An Interpreter In Go](https://interpreterbook.com/) and [Writing A Compiler In Go](https://compilerbook.com/). 4 | This is written in C++ and uses [cpp-peglib](https://github.com/yhirose/cpp-peglib) PEG library for lexter and parser. 5 | 6 | In addition to the original Monkey language spec, this implementation supports the line comment. Macro system in Appendix A is not implemented, yet. 7 | 8 | ## Install 9 | 10 | ```bash 11 | $ git clone --recursive https://github.com/yhirose/monkey-cpp.git 12 | $ cd monkey-cpp && mkdir build && cd build 13 | $ cmake .. 14 | $ make 15 | 16 | $ ./build/cli/monkey 17 | >> puts("hello " + "world!") 18 | hello world! 19 | null 20 | >> quit 21 | 22 | $ ./build/cli/monkey ../examples/map.monkey 23 | [2, 4, 6, 8] 24 | 15 25 | ``` 26 | 27 | ## PEG grammar 28 | 29 | ``` 30 | PROGRAM <- STATEMENTS 31 | 32 | STATEMENTS <- (STATEMENT ';'?)* 33 | STATEMENT <- ASSIGNMENT / RETURN / EXPRESSION_STATEMENT 34 | 35 | ASSIGNMENT <- 'let' IDENTIFIER '=' EXPRESSION 36 | RETURN <- 'return' EXPRESSION 37 | EXPRESSION_STATEMENT <- EXPRESSION 38 | 39 | EXPRESSION <- INFIX_EXPR(PREFIX_EXPR, INFIX_OPE) 40 | INFIX_EXPR(ATOM, OPE) <- ATOM (OPE ATOM)* { 41 | precedence 42 | L == != 43 | L < > 44 | L + - 45 | L * / 46 | } 47 | 48 | IF <- 'if' '(' EXPRESSION ')' BLOCK ('else' BLOCK)? 49 | 50 | FUNCTION <- 'fn' '(' PARAMETERS ')' BLOCK 51 | PARAMETERS <- LIST(IDENTIFIER, ',') 52 | 53 | BLOCK <- '{' STATEMENTS '}' 54 | 55 | CALL <- PRIMARY (ARGUMENTS / INDEX)* 56 | ARGUMENTS <- '(' LIST(EXPRESSION, ',') ')' 57 | INDEX <- '[' EXPRESSION ']' 58 | 59 | PREFIX_EXPR <- PREFIX_OPE* CALL 60 | PRIMARY <- IF / FUNCTION / ARRAY / HASH / INTEGER / BOOLEAN / NULL / IDENTIFIER / STRING / '(' EXPRESSION ')' 61 | 62 | ARRAY <- '[' LIST(EXPRESSION, ',') ']' 63 | 64 | HASH <- '{' LIST(HASH_PAIR, ',') '}' 65 | HASH_PAIR <- EXPRESSION ':' EXPRESSION 66 | 67 | IDENTIFIER <- < [a-zA-Z]+ > 68 | INTEGER <- < [0-9]+ > 69 | STRING <- < ["] < (!["] .)* > ["] > 70 | BOOLEAN <- 'true' / 'false' 71 | NULL <- 'null' 72 | PREFIX_OPE <- < [-!] > 73 | INFIX_OPE <- < [-+/*<>] / '==' / '!=' > 74 | 75 | KEYWORD <- 'null' | 'true' | 'false' | 'let' | 'return' | 'if' | 'else' | 'fn' 76 | 77 | LIST(ITEM, DELM) <- (ITEM (~DELM ITEM)*)? 78 | 79 | LINE_COMMENT <- '//' (!LINE_END .)* &LINE_END 80 | LINE_END <- '\r\n' / '\r' / '\n' / !. 81 | 82 | %whitespace <- ([ \t\r\n]+ / LINE_COMMENT)* 83 | %word <- [a-zA-Z]+ 84 | ``` 85 | -------------------------------------------------------------------------------- /cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(cli) 3 | 4 | add_executable(monkey 5 | main.cpp 6 | ) 7 | 8 | target_include_directories(monkey PRIVATE 9 | ${peglib_SOURCE_DIR} 10 | ../engine 11 | ) 12 | 13 | target_link_libraries(monkey PRIVATE 14 | fmt::fmt 15 | ) 16 | -------------------------------------------------------------------------------- /cli/commandline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct Options { 7 | bool print_ast = false; 8 | bool shell = false; 9 | bool debug = false; 10 | bool vm = false; 11 | std::vector script_path_list; 12 | }; 13 | 14 | inline Options parse_command_line(int argc, const char **argv) { 15 | Options options; 16 | 17 | int argi = 1; 18 | while (argi < argc) { 19 | std::string arg = argv[argi++]; 20 | if (arg == "--shell") { 21 | options.shell = true; 22 | } else if (arg == "--ast") { 23 | options.print_ast = true; 24 | } else if (arg == "--debug") { 25 | options.debug = true; 26 | } else if (arg == "--vm") { 27 | options.vm = true; 28 | } else { 29 | options.script_path_list.push_back(arg); 30 | } 31 | } 32 | 33 | if (!options.shell) { options.shell = options.script_path_list.empty(); } 34 | 35 | return options; 36 | } 37 | -------------------------------------------------------------------------------- /cli/main.cpp: -------------------------------------------------------------------------------- 1 | #include "commandline.hpp" 2 | #include "repl.hpp" 3 | #include "run.hpp" 4 | 5 | int main(int argc, const char **argv) { 6 | auto options = parse_command_line(argc, argv); 7 | 8 | try { 9 | auto env = monkey::environment(); 10 | if (!run(env, options)) { return -1; } 11 | if (options.shell) { repl(env, options); } 12 | } catch (std::exception &e) { 13 | std::cerr << e.what() << std::endl; 14 | return -1; 15 | } 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /cli/repl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "linenoise.hpp" 9 | 10 | namespace monkey { 11 | 12 | inline int repl(std::shared_ptr env, 13 | const Options &options) { 14 | using namespace monkey; 15 | using namespace std; 16 | 17 | std::vector> constants; 18 | std::vector> globals(VM::GlobalSize); 19 | std::shared_ptr symbolTable = symbol_table(); 20 | 21 | { 22 | int i = 0; 23 | for (const auto &[name, _] : BUILTINS) { 24 | symbolTable->define_builtin(i, name); 25 | i++; 26 | } 27 | } 28 | 29 | for (;;) { 30 | auto line = linenoise::Readline(">> "); 31 | 32 | if (line == "exit" || line == "quit") { break; } 33 | 34 | if (!line.empty()) { 35 | vector msgs; 36 | auto ast = parse("(repl)", line.data(), line.size(), msgs); 37 | if (ast) { 38 | if (options.print_ast) { cout << peg::ast_to_s(ast); } 39 | 40 | try { 41 | if (options.vm) { 42 | Compiler compiler(symbolTable, constants); 43 | compiler.compile(ast); 44 | constants = compiler.constants; 45 | VM vm(compiler.bytecode(), globals); 46 | vm.run(); 47 | globals = vm.globals; 48 | auto last_poped = vm.last_popped_stack_elem(); 49 | cout << last_poped->inspect() << endl; 50 | linenoise::AddHistory(line.c_str()); 51 | } else { 52 | auto val = eval(ast, env); 53 | if (val->type() != ERROR_OBJ) { 54 | cout << val->inspect() << endl; 55 | linenoise::AddHistory(line.c_str()); 56 | } else { 57 | msgs.push_back(cast(val).message); 58 | } 59 | } 60 | } catch (const std::exception &e) { msgs.push_back(e.what()); } 61 | } 62 | 63 | for (const auto &msg : msgs) { 64 | cout << msg << endl; 65 | } 66 | } 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | } // namespace monkey 73 | -------------------------------------------------------------------------------- /cli/run.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | inline bool read_file(const char *path, std::vector &buff) { 8 | using namespace std; 9 | 10 | ifstream ifs(path, ios::in | ios::binary); 11 | if (ifs.fail()) { return false; } 12 | 13 | auto size = static_cast(ifs.seekg(0, ios::end).tellg()); 14 | 15 | if (size > 0) { 16 | buff.resize(size); 17 | ifs.seekg(0, ios::beg).read(&buff[0], static_cast(buff.size())); 18 | } 19 | 20 | return true; 21 | } 22 | 23 | inline bool run(std::shared_ptr env, 24 | const Options &options) { 25 | using namespace monkey; 26 | using namespace std; 27 | 28 | for (auto path : options.script_path_list) { 29 | vector buff; 30 | if (!read_file(path.c_str(), buff)) { 31 | cerr << "can't open '" << path << "'." << endl; 32 | return false; 33 | } 34 | 35 | vector msgs; 36 | auto ast = parse(path, buff.data(), buff.size(), msgs); 37 | 38 | if (ast) { 39 | if (options.print_ast) { cout << peg::ast_to_s(ast); } 40 | 41 | auto val = eval(ast, env); 42 | if (val->type() != ERROR_OBJ) { 43 | continue; 44 | } else { 45 | msgs.push_back(cast(val).message); 46 | } 47 | } 48 | 49 | for (const auto &msg : msgs) { 50 | cerr << msg << endl; 51 | } 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | -------------------------------------------------------------------------------- /engine/ast.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace monkey { 6 | 7 | struct Annotation { 8 | std::any value; 9 | 10 | bool to_bool() const { return std::any_cast(value); } 11 | int64_t to_integer() const { return std::any_cast(value); } 12 | std::string to_string() const { return std::any_cast(value); } 13 | }; 14 | 15 | using Ast = peg::AstBase; 16 | 17 | inline void annotate(std::shared_ptr &ast) { 18 | using namespace peg::udl; 19 | 20 | if (ast->is_token) { 21 | assert(ast->nodes.empty()); 22 | switch (ast->tag) { 23 | case "BOOLEAN"_: ast->value = (ast->token == "true"); break; 24 | case "INTEGER"_: ast->value = ast->token_to_number(); break; 25 | case "STRING"_: ast->value = ast->token; break; 26 | } 27 | } else { 28 | for (auto node : ast->nodes) { 29 | annotate(node); 30 | } 31 | 32 | switch (ast->tag) { 33 | case "ASSIGNMENT"_: { 34 | if (ast->nodes[1]->tag == "FUNCTION"_) { 35 | ast->nodes[1]->value = ast->nodes[0]->token_to_string(); 36 | } 37 | break; 38 | } 39 | } 40 | } 41 | } 42 | 43 | std::string to_string(const std::shared_ptr &ast); 44 | 45 | inline std::string list_to_string(const std::shared_ptr &ast) { 46 | std::string out; 47 | const auto &nodes = ast->nodes; 48 | for (size_t i = 0; i < nodes.size(); i++) { 49 | if (i > 0) { out += ", "; } 50 | out += to_string(nodes[i]); 51 | } 52 | return out; 53 | } 54 | 55 | inline std::string to_string(const std::shared_ptr &ast) { 56 | using namespace peg::udl; 57 | 58 | if (ast->is_token) { 59 | return std::string(ast->token); 60 | } else { 61 | std::string out; 62 | switch (ast->tag) { 63 | case "ASSIGNMENT"_: { 64 | out = "let " + std::string(ast->nodes[0]->token) + " = " + 65 | to_string(ast->nodes[1]) + ";"; 66 | break; 67 | } 68 | case "PREFIX_EXPR"_: { 69 | auto rit = ast->nodes.rbegin(); 70 | out = to_string(*rit); 71 | ++rit; 72 | while (rit != ast->nodes.rend()) { 73 | auto ope = (**rit).token; 74 | out = '(' + std::string(ope) + out + ')'; 75 | ++rit; 76 | } 77 | break; 78 | } 79 | case "INFIX_EXPR"_: 80 | out = '(' + to_string(ast->nodes[0]) + ' ' + 81 | std::string(ast->nodes[1]->token) + ' ' + to_string(ast->nodes[2]) + 82 | ')'; 83 | break; 84 | case "CALL"_: { 85 | auto it = ast->nodes.begin(); 86 | out = to_string(*it); 87 | ++it; 88 | while (it != ast->nodes.end()) { 89 | if ((**it).tag == "INDEX"_) { 90 | out = '(' + out + to_string(*it) + ')'; 91 | } else { 92 | out = out + to_string(*it); 93 | } 94 | ++it; 95 | } 96 | break; 97 | } 98 | case "ARGUMENTS"_: { 99 | out = '(' + list_to_string(ast) + ')'; 100 | break; 101 | } 102 | case "INDEX"_: { 103 | out = '[' + to_string(ast->nodes[0]) + ']'; 104 | break; 105 | } 106 | case "FUNCTION"_: { 107 | out = "fn" + 108 | (ast->value.has_value() ? "" : '<' + ast->to_string() + '>') + 109 | to_string(ast->nodes[0]) + '{' + to_string(ast->nodes[1]) + '}'; 110 | break; 111 | } 112 | case "PARAMETERS"_: { 113 | out = '(' + list_to_string(ast) + ')'; 114 | break; 115 | } 116 | case "ARRAY"_: { 117 | out = '[' + list_to_string(ast) + ']'; 118 | break; 119 | } 120 | case "HASH"_: { 121 | out = '{' + list_to_string(ast) + '}'; 122 | break; 123 | } 124 | case "HASH_PAIR"_: { 125 | out = to_string(ast->nodes[0]) + ": " + to_string(ast->nodes[1]); 126 | break; 127 | } 128 | default: 129 | for (auto node : ast->nodes) { 130 | out += to_string(node); 131 | } 132 | break; 133 | } 134 | return out; 135 | } 136 | } 137 | 138 | } // namespace monkey 139 | -------------------------------------------------------------------------------- /engine/code.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace monkey { 10 | 11 | using Instructions = std::vector; 12 | 13 | using Opecode = uint8_t; 14 | 15 | enum OpecodeType { 16 | OpConstant = 0, 17 | OpAdd, 18 | OpSub, 19 | OpMul, 20 | OpDiv, 21 | OpTrue, 22 | OpFalse, 23 | OpNull, 24 | OpEqual, 25 | OpNotEqual, 26 | OpGreaterThan, 27 | OpMinus, 28 | OpBang, 29 | OpPop, 30 | OpJumpNotTruthy, 31 | OpJump, 32 | OpGetGlobal, 33 | OpSetGlobal, 34 | OpArray, 35 | OpHash, 36 | OpIndex, 37 | OpCall, 38 | OpReturnValue, 39 | OpReturn, 40 | OpGetLocal, 41 | OpSetLocal, 42 | OpGetBuiltin, 43 | OpClosure, 44 | OpGetFree, 45 | OpCurrentClosure, 46 | }; 47 | 48 | struct Definition { 49 | std::string name; 50 | std::vector operand_widths; 51 | }; 52 | 53 | inline std::map &definitions() { 54 | static auto definitions_ = std::map{ 55 | {OpConstant, {"OpConstant", {2}}}, 56 | {OpAdd, {"OpAdd", {}}}, 57 | {OpSub, {"OpSub", {}}}, 58 | {OpMul, {"OpMul", {}}}, 59 | {OpDiv, {"OpDiv", {}}}, 60 | {OpTrue, {"OpTrue", {}}}, 61 | {OpFalse, {"OpFalse", {}}}, 62 | {OpNull, {"OpNull", {}}}, 63 | {OpEqual, {"OpEqual", {}}}, 64 | {OpNotEqual, {"OpNotEqual", {}}}, 65 | {OpGreaterThan, {"OpGreaterThan", {}}}, 66 | {OpMinus, {"OpMinus", {}}}, 67 | {OpBang, {"OpBang", {}}}, 68 | {OpPop, {"OpPop", {}}}, 69 | {OpJumpNotTruthy, {"OpJumpNotTruthy", {2}}}, 70 | {OpJump, {"OpJump", {2}}}, 71 | {OpGetGlobal, {"OpGetGlobal", {2}}}, 72 | {OpSetGlobal, {"OpSetGlobal", {2}}}, 73 | {OpArray, {"OpArray", {2}}}, 74 | {OpHash, {"OpHash", {2}}}, 75 | {OpIndex, {"OpIndex", {}}}, 76 | {OpCall, {"OpCall", {1}}}, 77 | {OpReturnValue, {"OpReturnValue", {}}}, 78 | {OpReturn, {"OpReturn", {}}}, 79 | {OpGetLocal, {"OpGetLocal", {1}}}, 80 | {OpSetLocal, {"OpSetLocal", {1}}}, 81 | {OpGetBuiltin, {"OpGetBuiltin", {1}}}, 82 | {OpClosure, {"OpClosure", {2, 1}}}, 83 | {OpGetFree, {"OpGetFree", {1}}}, 84 | {OpCurrentClosure, {"CurrentClosure", {}}}, 85 | }; 86 | return definitions_; 87 | } 88 | 89 | inline Definition &lookup(Opecode op) { 90 | auto it = definitions().find(op); 91 | if (it == definitions().end()) { 92 | throw std::runtime_error(fmt::format("opcode {} undefined", op)); 93 | } 94 | return it->second; 95 | } 96 | 97 | static bool is_little_endian = []() { 98 | short int n = 0x1; 99 | char *p = (char *)&n; 100 | return (p[0] == 1); 101 | }(); 102 | 103 | inline void put_uint16(uint8_t *p, uint16_t n) { 104 | if (is_little_endian) { n = (n >> 8) | (n << 8); } 105 | memcpy(p, &n, sizeof(uint16_t)); 106 | } 107 | 108 | inline uint16_t read_uint16(const uint8_t *p) { 109 | uint16_t n = *reinterpret_cast(p); 110 | if (is_little_endian) { n = (n >> 8) | (n << 8); } 111 | return n; 112 | } 113 | 114 | inline uint8_t read_uint8(const uint8_t *p) { 115 | return *p; 116 | } 117 | 118 | inline std::vector make(Opecode op, const std::vector &operands) { 119 | auto it = definitions().find(op); 120 | if (it == definitions().end()) { return std::vector(); } 121 | const auto &widths = it->second.operand_widths; 122 | 123 | auto instruction_len = std::accumulate(widths.begin(), widths.end(), 1); 124 | 125 | auto instruction = std::vector(instruction_len); 126 | instruction[0] = op; 127 | 128 | size_t offset = 1; 129 | size_t i = 0; 130 | for (auto o : operands) { 131 | auto width = widths[i]; 132 | switch (width) { 133 | case 2: put_uint16(&instruction[offset], o); break; 134 | case 1: instruction[offset] = static_cast(o); break; 135 | } 136 | offset += width; 137 | i++; 138 | } 139 | 140 | return instruction; 141 | } 142 | 143 | inline std::pair, size_t> 144 | read_operands(const Definition &def, const Instructions &ins, 145 | size_t start_offset) { 146 | std::vector operands(def.operand_widths.size()); 147 | size_t offset = start_offset; 148 | size_t i = 0; 149 | for (auto width : def.operand_widths) { 150 | switch (width) { 151 | case 2: operands[i] = read_uint16(&ins[offset]); break; 152 | case 1: operands[i] = read_uint8(&ins[offset]); break; 153 | } 154 | offset += width; 155 | i++; 156 | } 157 | return std::pair(operands, offset - start_offset); 158 | } 159 | 160 | inline std::string fmt_instruction(const Definition &def, 161 | const std::vector &operands) { 162 | auto operand_count = def.operand_widths.size(); 163 | if (operands.size() != operand_count) { 164 | return fmt::format("ERROR: operand len {} does not match defined {}\n", 165 | operands.size(), operand_count); 166 | } 167 | 168 | switch (operand_count) { 169 | case 0: return def.name; 170 | case 1: return fmt::format("{} {}", def.name, operands[0]); 171 | case 2: return fmt::format("{} {} {}", def.name, operands[0], operands[1]); 172 | } 173 | 174 | return fmt::format("ERROR: unhandled operand count for {}\n", def.name); 175 | } 176 | 177 | inline std::string to_string(const Instructions &ins, const char *ln = "\\n") { 178 | std::string out; 179 | 180 | size_t i = 0; 181 | while (i < ins.size()) { 182 | auto &def = lookup(ins[i]); 183 | auto [operands, read] = read_operands(def, ins, i + 1); 184 | out += fmt::format(R"({:04} {}{})", i, fmt_instruction(def, operands), ln); 185 | i += 1 + read; 186 | } 187 | 188 | return out; 189 | } 190 | 191 | } // namespace monkey 192 | -------------------------------------------------------------------------------- /engine/compiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace monkey { 7 | 8 | struct EmittedInstruction { 9 | Opecode opecode; 10 | int position; 11 | }; 12 | 13 | struct Bytecode { 14 | Instructions instructions; 15 | std::vector> constants; 16 | }; 17 | 18 | struct CompilerScope { 19 | Instructions instructions; 20 | EmittedInstruction lastInstruction; 21 | EmittedInstruction previousInstruction; 22 | }; 23 | 24 | struct Compiler { 25 | std::shared_ptr symbolTable; 26 | std::vector> constants; 27 | 28 | std::vector scopes{CompilerScope{}}; 29 | int scopeIndex = 0; 30 | 31 | Compiler() : symbolTable(symbol_table()) { 32 | int i = 0; 33 | for (const auto &[name, _] : BUILTINS) { 34 | symbolTable->define_builtin(i, name); 35 | i++; 36 | } 37 | }; 38 | 39 | Compiler(std::shared_ptr symbolTable, 40 | const std::vector> &constants) 41 | : symbolTable(symbolTable), constants(constants){}; 42 | 43 | void compile(const std::shared_ptr &ast) { 44 | using namespace peg::udl; 45 | 46 | switch (ast->tag) { 47 | case "STATEMENTS"_: { 48 | for (auto node : ast->nodes) { 49 | compile(node); 50 | } 51 | break; 52 | } 53 | case "ASSIGNMENT"_: { 54 | auto name = std::string(ast->nodes[0]->token); 55 | auto symbol = symbolTable->define(name); 56 | compile(ast->nodes[1]); 57 | if (symbol.scope == GlobalScope) { 58 | emit(OpSetGlobal, {symbol.index}); 59 | } else { 60 | emit(OpSetLocal, {symbol.index}); 61 | } 62 | break; 63 | } 64 | case "IDENTIFIER"_: { 65 | auto name = std::string(ast->token); 66 | const auto &symbol = symbolTable->resolve(name); 67 | if (!symbol) { 68 | throw std::runtime_error(fmt::format("undefined variable {}", name)); 69 | } 70 | load_symbol(*symbol); 71 | break; 72 | } 73 | case "EXPRESSION_STATEMENT"_: { 74 | compile(ast->nodes[0]); 75 | emit(OpPop, {}); 76 | break; 77 | } 78 | case "INFIX_EXPR"_: { 79 | auto op = ast->nodes[1]->token; 80 | 81 | if (op == "<") { 82 | compile(ast->nodes[2]); 83 | compile(ast->nodes[0]); 84 | emit(OpGreaterThan, {}); 85 | return; 86 | } 87 | 88 | compile(ast->nodes[0]); 89 | compile(ast->nodes[2]); 90 | 91 | switch (peg::str2tag(op)) { 92 | case "+"_: emit(OpAdd, {}); break; 93 | case "-"_: emit(OpSub, {}); break; 94 | case "*"_: emit(OpMul, {}); break; 95 | case "/"_: emit(OpDiv, {}); break; 96 | case ">"_: emit(OpGreaterThan, {}); break; 97 | case "=="_: emit(OpEqual, {}); break; 98 | case "!="_: emit(OpNotEqual, {}); break; 99 | default: 100 | throw std::runtime_error(fmt::format("unknown operator {}", op)); 101 | break; 102 | } 103 | break; 104 | } 105 | case "PREFIX_EXPR"_: { 106 | int i = ast->nodes.size() - 1; 107 | compile(ast->nodes[i--]); 108 | 109 | while (i >= 0) { 110 | auto op = ast->nodes[i]->token; 111 | switch (peg::str2tag(op)) { 112 | case "!"_: emit(OpBang, {}); break; 113 | case "-"_: emit(OpMinus, {}); break; 114 | default: 115 | throw std::runtime_error(fmt::format("unknown operator {}", op)); 116 | break; 117 | } 118 | i--; 119 | } 120 | break; 121 | } 122 | case "IF"_: { 123 | compile(ast->nodes[0]); 124 | 125 | // Emit an `OpJumpNotTruthy` with a bogus value 126 | auto jump_not_truthy_pos = emit(OpJumpNotTruthy, {9999}); 127 | 128 | // Consequence 129 | compile(ast->nodes[1]); 130 | 131 | if (last_instruction_is(OpPop)) { remove_last_pop(); } 132 | 133 | // Emit an `OpJump` with a bogus value 134 | auto jump_pos = emit(OpJump, {9999}); 135 | 136 | auto after_consequence_pos = current_instructions().size(); 137 | change_operand(jump_not_truthy_pos, after_consequence_pos); 138 | 139 | if (ast->nodes.size() < 3) { 140 | // Has no alternative 141 | emit(OpNull, {}); 142 | } else { 143 | // Alternative 144 | compile(ast->nodes[2]); 145 | 146 | if (last_instruction_is(OpPop)) { remove_last_pop(); } 147 | } 148 | 149 | auto after_alternative_pos = current_instructions().size(); 150 | change_operand(jump_pos, after_alternative_pos); 151 | break; 152 | } 153 | case "BLOCK"_: { 154 | compile(ast->nodes[0]); 155 | break; 156 | } 157 | case "INTEGER"_: { 158 | auto integer = std::make_shared(ast->to_integer()); 159 | emit(OpConstant, {add_constant(integer)}); 160 | break; 161 | } 162 | case "BOOLEAN"_: { 163 | if (ast->to_bool()) { 164 | emit(OpTrue, {}); 165 | } else { 166 | emit(OpFalse, {}); 167 | } 168 | break; 169 | } 170 | case "STRING"_: { 171 | auto str = std::make_shared(ast->token); 172 | emit(OpConstant, {add_constant(str)}); 173 | break; 174 | } 175 | case "ARRAY"_: { 176 | for (auto node : ast->nodes) { 177 | compile(node); 178 | } 179 | emit(OpArray, {static_cast(ast->nodes.size())}); 180 | break; 181 | } 182 | case "HASH"_: { 183 | for (auto node : ast->nodes) { 184 | compile(node->nodes[0]); 185 | compile(node->nodes[1]); 186 | } 187 | emit(OpHash, {static_cast(ast->nodes.size() * 2)}); 188 | break; 189 | } 190 | case "CALL"_: { 191 | compile(ast->nodes[0]); 192 | for (auto i = 1u; i < ast->nodes.size(); i++) { 193 | auto postfix = ast->nodes[i]; 194 | switch (postfix->original_tag) { 195 | case "INDEX"_: { 196 | compile(postfix->nodes[0]); 197 | emit(OpIndex, {}); 198 | break; 199 | } 200 | case "ARGUMENTS"_: { 201 | auto arguments = ast->nodes[1]; 202 | for (auto node : arguments->nodes) { 203 | compile(node); 204 | } 205 | emit(OpCall, {static_cast(arguments->nodes.size())}); 206 | break; 207 | }; 208 | } 209 | } 210 | break; 211 | } 212 | case "FUNCTION"_: { 213 | enter_scope(); 214 | if (ast->value.has_value()) { 215 | symbolTable->define_function_name(ast->to_string()); 216 | } 217 | auto parameters = ast->nodes[0]; 218 | for (auto node : parameters->nodes) { 219 | auto name = std::string(node->token); 220 | auto symbol = symbolTable->define(name); 221 | } 222 | compile(ast->nodes[1]); 223 | if (last_instruction_is(OpPop)) { replace_last_pop_with_return(); } 224 | if (!last_instruction_is(OpReturnValue)) { emit(OpReturn, {}); } 225 | 226 | auto freeSymbols = symbolTable->freeSymbols; 227 | auto numLocals = symbolTable->numDefinitions; 228 | auto instructions = leave_scope(); 229 | 230 | for (const auto &s : freeSymbols) { 231 | load_symbol(s); 232 | } 233 | 234 | auto compiledFn = make_compiled_function({instructions}, numLocals, 235 | parameters->nodes.size()); 236 | auto fnIndex = add_constant(compiledFn); 237 | emit(OpClosure, {fnIndex, static_cast(freeSymbols.size())}); 238 | break; 239 | } 240 | case "RETURN"_: { 241 | compile(ast->nodes[0]); 242 | emit(OpReturnValue, {}); 243 | break; 244 | } 245 | } 246 | } 247 | 248 | int add_constant(std::shared_ptr obj) { 249 | constants.push_back(obj); 250 | return constants.size() - 1; 251 | } 252 | 253 | size_t emit(Opecode op, const std::vector &operands) { 254 | auto ins = make(op, operands); 255 | auto pos = add_instruction(ins); 256 | set_last_instruction(op, pos); 257 | return pos; 258 | } 259 | 260 | size_t add_instruction(const std::vector &ins) { 261 | auto pos_new_instruction = current_instructions().size(); 262 | current_instructions().insert(current_instructions().end(), ins.begin(), 263 | ins.end()); 264 | return pos_new_instruction; 265 | } 266 | 267 | void set_last_instruction(Opecode op, int pos) { 268 | auto previous = last_instruction(); 269 | auto last = EmittedInstruction{op, pos}; 270 | previous_instruction() = previous; 271 | last_instruction() = last; 272 | } 273 | 274 | bool last_instruction_is_pop() const { 275 | return last_instruction().opecode == OpPop; 276 | } 277 | 278 | void remove_last_pop() { 279 | current_instructions().erase(current_instructions().begin() + 280 | last_instruction().position, 281 | current_instructions().end()); 282 | last_instruction() = previous_instruction(); 283 | } 284 | 285 | void replace_instruction(int pos, 286 | const std::vector &new_instructions) { 287 | for (size_t i = 0; i < new_instructions.size(); i++) { 288 | current_instructions()[pos + i] = new_instructions[i]; 289 | } 290 | } 291 | 292 | void change_operand(int op_pos, int operand) { 293 | auto op = current_instructions()[op_pos]; 294 | auto new_instruction = make(op, {operand}); 295 | replace_instruction(op_pos, new_instruction); 296 | } 297 | 298 | Bytecode bytecode() { return Bytecode{current_instructions(), constants}; } 299 | 300 | Instructions ¤t_instructions() { 301 | return scopes[scopeIndex].instructions; 302 | } 303 | 304 | const Instructions ¤t_instructions() const { 305 | return scopes[scopeIndex].instructions; 306 | } 307 | 308 | EmittedInstruction &last_instruction() { 309 | return scopes[scopeIndex].lastInstruction; 310 | } 311 | 312 | const EmittedInstruction &last_instruction() const { 313 | return scopes[scopeIndex].lastInstruction; 314 | } 315 | 316 | EmittedInstruction &previous_instruction() { 317 | return scopes[scopeIndex].previousInstruction; 318 | } 319 | 320 | void enter_scope() { 321 | scopes.push_back(CompilerScope{}); 322 | scopeIndex++; 323 | symbolTable = enclosed_symbol_table(symbolTable); 324 | } 325 | 326 | Instructions leave_scope() { 327 | auto instructions = current_instructions(); 328 | scopes.pop_back(); 329 | scopeIndex--; 330 | symbolTable = symbolTable->outer; 331 | return instructions; 332 | } 333 | 334 | bool last_instruction_is(Opecode op) const { 335 | if (current_instructions().empty()) { return false; } 336 | return last_instruction().opecode == op; 337 | } 338 | 339 | void replace_last_pop_with_return() { 340 | auto lastPos = last_instruction().position; 341 | replace_instruction(lastPos, make(OpReturnValue, {})); 342 | last_instruction().opecode = OpReturnValue; 343 | } 344 | 345 | void load_symbol(const Symbol &s) { 346 | if (s.scope == GlobalScope) { 347 | emit(OpGetGlobal, {s.index}); 348 | } else if (s.scope == LocalScope) { 349 | emit(OpGetLocal, {s.index}); 350 | } else if (s.scope == BuiltinScope) { 351 | emit(OpGetBuiltin, {s.index}); 352 | } else if (s.scope == FreeScope) { 353 | emit(OpGetFree, {s.index}); 354 | } else if (s.scope == FunctionScope) { 355 | emit(OpCurrentClosure, {}); 356 | } 357 | } 358 | }; 359 | 360 | } // namespace monkey 361 | -------------------------------------------------------------------------------- /engine/environment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace monkey { 7 | 8 | struct Environment { 9 | Environment(std::shared_ptr outer = nullptr) 10 | : level(outer ? outer->level + 1 : 0), outer(outer) {} 11 | 12 | std::shared_ptr get(std::string_view sv, 13 | std::function error_handler) const { 14 | auto s = std::string(sv); 15 | if (dictionary.find(s) != dictionary.end()) { 16 | return dictionary.at(s); 17 | } else if (outer) { 18 | return outer->get(s, error_handler); 19 | } 20 | if (error_handler) { error_handler(); } 21 | // NOTREACHED 22 | throw std::logic_error("invalid internal condition."); 23 | } 24 | 25 | void set(std::string_view sv, std::shared_ptr val) { 26 | dictionary[std::string(sv)] = std::move(val); 27 | } 28 | 29 | size_t level; 30 | std::shared_ptr outer; 31 | std::map> dictionary; 32 | }; 33 | 34 | inline void setup_built_in_functions(Environment &env) { 35 | env.set("len", builtins.at("len")); 36 | env.set("puts", builtins.at("puts")); 37 | env.set("first", builtins.at("first")); 38 | env.set("last", builtins.at("last")); 39 | env.set("rest", builtins.at("rest")); 40 | env.set("push", builtins.at("push")); 41 | } 42 | 43 | inline std::shared_ptr environment() { 44 | auto env = std::make_shared(); 45 | setup_built_in_functions(*env); 46 | return env; 47 | } 48 | 49 | } // namespace monkey 50 | -------------------------------------------------------------------------------- /engine/evaluator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace monkey { 7 | 8 | struct Evaluator { 9 | 10 | std::shared_ptr 11 | eval_bang_operator_expression(const std::shared_ptr &obj) { 12 | auto p = obj.get(); 13 | if (p == CONST_TRUE.get()) { 14 | return CONST_FALSE; 15 | } else if (p == CONST_FALSE.get()) { 16 | return CONST_TRUE; 17 | } else if (p == CONST_NULL.get()) { 18 | return CONST_TRUE; 19 | } 20 | return CONST_FALSE; 21 | } 22 | 23 | std::shared_ptr 24 | eval_minus_operator_expression(const std::shared_ptr &right) { 25 | if (right->type() != INTEGER_OBJ) { 26 | throw make_error("unknown operator: -" + right->name()); 27 | } 28 | auto val = cast(right).value; 29 | return std::make_shared(-val); 30 | } 31 | 32 | std::shared_ptr 33 | eval_prefix_expression(const Ast &node, 34 | const std::shared_ptr &env) { 35 | auto rit = node.nodes.rbegin(); 36 | auto right = eval(**rit, env); 37 | ++rit; 38 | 39 | while (rit != node.nodes.rend()) { 40 | const auto &ope = (**rit).token; 41 | assert(!ope.empty()); 42 | 43 | switch (ope[0]) { 44 | case '-': right = eval_minus_operator_expression(right); break; 45 | case '!': right = eval_bang_operator_expression(right); break; 46 | default: 47 | throw make_error("unknown operator: " + std::string(ope) + 48 | right->name()); 49 | } 50 | ++rit; 51 | } 52 | return right; 53 | } 54 | 55 | std::shared_ptr 56 | eval_integer_infix_expression(std::string_view ope, 57 | const std::shared_ptr &left, 58 | const std::shared_ptr &right) { 59 | using namespace peg::udl; 60 | 61 | auto tag = peg::str2tag(ope); 62 | auto lval = cast(left).value; 63 | auto rval = cast(right).value; 64 | 65 | switch (tag) { 66 | case "+"_: return std::make_shared(lval + rval); 67 | case "-"_: return std::make_shared(lval - rval); 68 | case "*"_: return std::make_shared(lval * rval); 69 | case "%"_: return std::make_shared(lval % rval); 70 | case "/"_: 71 | if (rval == 0) { throw make_error("divide by 0 error"); } 72 | return std::make_shared(lval / rval); 73 | case "<"_: return make_bool(lval < rval); 74 | case ">"_: return make_bool(lval > rval); 75 | case "=="_: return make_bool(lval == rval); 76 | case "!="_: return make_bool(lval != rval); 77 | default: 78 | throw make_error("unknown operator: " + left->name() + " " + 79 | std::string(ope) + " " + right->name()); 80 | } 81 | } 82 | 83 | std::shared_ptr 84 | eval_string_infix_expression(std::string_view &ope, 85 | const std::shared_ptr &left, 86 | const std::shared_ptr &right) { 87 | using namespace peg::udl; 88 | 89 | auto tag = peg::str2tag(ope); 90 | 91 | if (tag != "+"_) { 92 | throw make_error("unknown operator: " + left->name() + " " + 93 | std::string(ope) + " " + right->name()); 94 | } 95 | 96 | auto lval = cast(left).value; 97 | auto rval = cast(right).value; 98 | return make_string(lval + rval); 99 | } 100 | 101 | std::shared_ptr 102 | eval_infix_expression(const Ast &node, 103 | const std::shared_ptr &env) { 104 | using namespace peg::udl; 105 | 106 | auto left = eval(*node.nodes[0], env); 107 | auto ope = node.nodes[1]->token; 108 | auto right = eval(*node.nodes[2], env); 109 | 110 | if (left->type() == INTEGER_OBJ && right->type() == INTEGER_OBJ) { 111 | return eval_integer_infix_expression(ope, left, right); 112 | } else if (left->type() == STRING_OBJ && right->type() == STRING_OBJ) { 113 | return eval_string_infix_expression(ope, left, right); 114 | } 115 | 116 | auto tag = peg::str2tag(ope); 117 | 118 | switch (tag) { 119 | case "=="_: return make_bool(left.get() == right.get()); 120 | case "!="_: return make_bool(left.get() != right.get()); 121 | } 122 | 123 | if (left->type() != right->type()) { 124 | throw make_error("type mismatch: " + left->name() + " " + 125 | std::string(ope) + " " + right->name()); 126 | } 127 | 128 | throw make_error("unknown operator: " + left->name() + " " + 129 | std::string(ope) + " " + right->name()); 130 | } 131 | 132 | std::shared_ptr 133 | eval_statements(const Ast &node, const std::shared_ptr &env) { 134 | if (node.is_token) { 135 | return eval(node, env); 136 | } else if (node.nodes.empty()) { 137 | return CONST_NULL; 138 | } 139 | auto it = node.nodes.begin(); 140 | while (it != node.nodes.end() - 1) { 141 | auto obj = eval(**it, env); 142 | if (obj->type() == ObjectType::RETURN_OBJ) { return obj; } 143 | ++it; 144 | } 145 | return eval(**it, env); 146 | } 147 | 148 | std::shared_ptr eval_block(const Ast &node, 149 | const std::shared_ptr &env) { 150 | auto scopeEnv = std::make_shared(env); 151 | return eval(*node.nodes[0], scopeEnv); 152 | } 153 | 154 | bool is_truthy(const std::shared_ptr &obj) { 155 | auto p = obj.get(); 156 | if (p == CONST_NULL.get()) { 157 | return false; 158 | } else if (p == CONST_TRUE.get()) { 159 | return true; 160 | } else if (p == CONST_FALSE.get()) { 161 | return false; 162 | } 163 | return true; 164 | } 165 | 166 | std::shared_ptr eval_if(const Ast &node, 167 | const std::shared_ptr &env) { 168 | const auto &nodes = node.nodes; 169 | auto cond = eval(*nodes[0], env); 170 | if (is_truthy(cond)) { 171 | return eval(*nodes[1], env); 172 | } else if (nodes.size() == 3) { 173 | return eval(*nodes[2], env); 174 | } 175 | return CONST_NULL; 176 | } 177 | 178 | std::shared_ptr eval_return(const Ast &node, 179 | const std::shared_ptr &env) { 180 | return std::make_shared(eval(*node.nodes[0], env)); 181 | } 182 | 183 | std::shared_ptr 184 | eval_assignment(const Ast &node, const std::shared_ptr &env) { 185 | auto ident = node.nodes[0]->token_to_string(); 186 | auto rval = eval(*node.nodes.back(), env); 187 | env->set(ident, rval); 188 | return rval; 189 | }; 190 | 191 | std::shared_ptr 192 | eval_identifier(const Ast &node, const std::shared_ptr &env) { 193 | return env->get(node.token_to_string(), [&]() { 194 | throw make_error("identifier not found: " + node.token_to_string()); 195 | }); 196 | }; 197 | 198 | std::shared_ptr 199 | eval_function(const Ast &node, const std::shared_ptr &env) { 200 | std::vector params; 201 | for (auto node : node.nodes[0]->nodes) { 202 | params.push_back(node->token_to_string()); 203 | } 204 | auto body = node.nodes[1]; 205 | return std::make_shared(params, env, body); 206 | }; 207 | 208 | std::shared_ptr 209 | eval_function_call(const Ast &node, const std::shared_ptr &env, 210 | const std::shared_ptr &left) { 211 | if (left->type() == BUILTIN_OBJ) { 212 | const auto &builtin = cast(left); 213 | std::vector> args; 214 | for (auto arg : node.nodes) { 215 | args.emplace_back(eval(*arg, env)); 216 | } 217 | try { 218 | return builtin.fn(args); 219 | } catch (const std::shared_ptr &e) { return e; } 220 | } 221 | 222 | const auto &fn = cast(left); 223 | const auto &args = node.nodes; 224 | 225 | if (fn.params.size() <= args.size()) { 226 | auto callEnv = std::make_shared(fn.env); 227 | for (auto iprm = 0u; iprm < fn.params.size(); iprm++) { 228 | auto name = fn.params[iprm]; 229 | auto arg = args[iprm]; 230 | auto val = eval(*arg, env); 231 | callEnv->set(name, val); 232 | } 233 | 234 | auto obj = eval(*fn.body, callEnv); 235 | if (obj->type() == ObjectType::RETURN_OBJ) { 236 | return cast(obj).value; 237 | } 238 | return obj; 239 | } 240 | 241 | return make_error("arguments error..."); 242 | } 243 | 244 | std::shared_ptr 245 | eval_array_index_expression(const std::shared_ptr &left, 246 | const std::shared_ptr &index) { 247 | const auto &arr = cast(left); 248 | auto idx = cast(index).value; 249 | if (0 <= idx && idx < static_cast(arr.elements.size())) { 250 | return arr.elements[idx]; 251 | } else { 252 | return CONST_NULL; 253 | } 254 | return left; 255 | } 256 | 257 | std::shared_ptr 258 | eval_hash_index_expression(const std::shared_ptr &left, 259 | const std::shared_ptr &index) { 260 | const auto &hash = cast(left); 261 | if (!index->has_hash_key()) { 262 | throw make_error("unusable as hash key: " + index->name()); 263 | } 264 | auto hashed = index->hash_key(); 265 | auto it = hash.pairs.find(hashed); 266 | if (it == hash.pairs.end()) { return CONST_NULL; } 267 | const auto &pair = it->second; 268 | return pair.value; 269 | } 270 | 271 | std::shared_ptr 272 | eval_index_expression(const Ast &node, 273 | const std::shared_ptr &env, 274 | const std::shared_ptr &left) { 275 | auto index = eval(node, env); 276 | switch (left->type()) { 277 | case ARRAY_OBJ: return eval_array_index_expression(left, index); 278 | case HASH_OBJ: return eval_hash_index_expression(left, index); 279 | default: return make_error("index operator not supported: " + left->name()); 280 | } 281 | } 282 | 283 | std::shared_ptr eval_call(const Ast &node, 284 | const std::shared_ptr &env) { 285 | using namespace peg::udl; 286 | 287 | auto left = eval(*node.nodes[0], env); 288 | 289 | for (auto i = 1u; i < node.nodes.size(); i++) { 290 | const auto &postfix = *node.nodes[i]; 291 | 292 | switch (postfix.original_tag) { 293 | case "ARGUMENTS"_: left = eval_function_call(postfix, env, left); break; 294 | case "INDEX"_: 295 | left = eval_index_expression(*postfix.nodes[0], env, left); 296 | break; 297 | default: throw std::logic_error("invalid internal condition."); 298 | } 299 | } 300 | 301 | return left; 302 | } 303 | 304 | std::shared_ptr eval_array(const Ast &node, 305 | const std::shared_ptr &env) { 306 | auto arr = std::make_shared(); 307 | const auto &nodes = node.nodes; 308 | for (auto i = 0u; i < nodes.size(); i++) { 309 | auto expr = nodes[i]; 310 | auto val = eval(*expr, env); 311 | if (i < arr->elements.size()) { 312 | arr->elements[i] = std::move(val); 313 | } else { 314 | arr->elements.push_back(std::move(val)); 315 | } 316 | } 317 | return arr; 318 | } 319 | 320 | std::shared_ptr eval_hash(const Ast &node, 321 | const std::shared_ptr &env) { 322 | auto hash = std::make_shared(); 323 | for (auto i = 0u; i < node.nodes.size(); i++) { 324 | const auto &pair = *node.nodes[i]; 325 | auto key = eval(*pair.nodes[0], env); 326 | if (!key->has_hash_key()) { 327 | throw make_error("unusable as hash key: " + key->name()); 328 | } 329 | auto hashed = key->hash_key(); 330 | auto value = eval(*pair.nodes[1], env); 331 | hash->pairs.emplace(hashed, HashPair{key, value}); 332 | } 333 | return hash; 334 | } 335 | 336 | std::shared_ptr eval(const Ast &node, 337 | const std::shared_ptr &env) { 338 | using namespace peg::udl; 339 | 340 | switch (node.tag) { 341 | case "INTEGER"_: return std::make_shared(node.to_integer()); 342 | case "BOOLEAN"_: return make_bool(node.to_bool()); 343 | case "PREFIX_EXPR"_: return eval_prefix_expression(node, env); 344 | case "INFIX_EXPR"_: return eval_infix_expression(node, env); 345 | case "STATEMENTS"_: return eval_statements(node, env); 346 | case "BLOCK"_: return eval_block(node, env); 347 | case "IF"_: return eval_if(node, env); 348 | case "RETURN"_: return eval_return(node, env); 349 | case "ASSIGNMENT"_: return eval_assignment(node, env); 350 | case "EXPRESSION_STATEMENT"_: return eval(*node.nodes[0], env); 351 | case "IDENTIFIER"_: return eval_identifier(node, env); 352 | case "FUNCTION"_: return eval_function(node, env); 353 | case "CALL"_: return eval_call(node, env); 354 | case "ARRAY"_: return eval_array(node, env); 355 | case "HASH"_: return eval_hash(node, env); 356 | } 357 | 358 | if (node.is_token) { return make_string(node.token); } 359 | 360 | // NOTREACHED 361 | throw std::logic_error("invalid Ast type: " + node.name); 362 | } 363 | }; 364 | 365 | inline std::shared_ptr eval(const std::shared_ptr &ast, 366 | const std::shared_ptr &env) { 367 | try { 368 | auto obj = Evaluator().eval(*ast, env); 369 | if (obj->type() == ObjectType::RETURN_OBJ) { 370 | return cast(obj).value; 371 | } 372 | return obj; 373 | } catch (const std::shared_ptr &err) { return err; } 374 | return CONST_NULL; 375 | } 376 | 377 | } // namespace monkey 378 | -------------------------------------------------------------------------------- /engine/object.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace monkey { 8 | 9 | struct Environment; 10 | 11 | enum ObjectType { 12 | INTEGER_OBJ = 0, 13 | BOOLEAN_OBJ, 14 | NULL_OBJ, 15 | RETURN_OBJ, 16 | ERROR_OBJ, 17 | FUNCTION_OBJ, 18 | COMPILED_FUNCTION_OBJ, 19 | STRING_OBJ, 20 | BUILTIN_OBJ, 21 | ARRAY_OBJ, 22 | HASH_OBJ, 23 | CLOSURE_OBJ 24 | }; 25 | 26 | struct HashKey { 27 | HashKey(ObjectType type, uint64_t value) : type(type), value(value) {} 28 | bool operator==(const HashKey &rhs) const { 29 | return type == rhs.type && value == rhs.value; 30 | } 31 | bool operator<(const HashKey &rhs) const { 32 | return type == rhs.type ? value < rhs.value : type < rhs.type; 33 | } 34 | 35 | ObjectType type; 36 | uint64_t value; 37 | }; 38 | 39 | struct Object { 40 | virtual ~Object() {} 41 | virtual ObjectType type() const = 0; 42 | virtual std::string name() const = 0; 43 | virtual std::string inspect() const = 0; 44 | virtual bool has_hash_key() const { return false; }; 45 | virtual HashKey hash_key() const { 46 | throw std::logic_error("invalid internal condition."); 47 | }; 48 | 49 | protected: 50 | Object() = default; 51 | }; 52 | 53 | template inline T &cast(std::shared_ptr obj) { 54 | return dynamic_cast(*obj); 55 | } 56 | 57 | struct Integer : public Object { 58 | Integer(int64_t value) : value(value) {} 59 | ObjectType type() const override { return INTEGER_OBJ; } 60 | std::string name() const override { return "INTEGER"; } 61 | std::string inspect() const override { return std::to_string(value); } 62 | bool has_hash_key() const override { return true; } 63 | HashKey hash_key() const override { 64 | return HashKey{type(), static_cast(value)}; 65 | } 66 | 67 | const int64_t value; 68 | }; 69 | 70 | struct Boolean : public Object { 71 | Boolean(bool value) : value(value) {} 72 | ObjectType type() const override { return BOOLEAN_OBJ; } 73 | std::string name() const override { return "BOOLEAN"; } 74 | std::string inspect() const override { return value ? "true" : "false"; } 75 | bool has_hash_key() const override { return true; } 76 | HashKey hash_key() const override { 77 | return HashKey{type(), static_cast(value ? 1 : 0)}; 78 | } 79 | 80 | const bool value; 81 | }; 82 | 83 | struct Null : public Object { 84 | Null() {} 85 | ObjectType type() const override { return NULL_OBJ; } 86 | std::string name() const override { return "NULL"; } 87 | std::string inspect() const override { return "null"; } 88 | }; 89 | 90 | struct Return : public Object { 91 | Return(const std::shared_ptr &value) : value(value) {} 92 | ObjectType type() const override { return RETURN_OBJ; } 93 | std::string name() const override { return "RETURN"; } 94 | std::string inspect() const override { return value->inspect(); } 95 | std::shared_ptr value; 96 | }; 97 | 98 | struct Error : public Object { 99 | Error(const std::string &message) : message(message) {} 100 | ObjectType type() const override { return ERROR_OBJ; } 101 | std::string name() const override { return "ERROR"; } 102 | std::string inspect() const override { return "ERROR: " + message; } 103 | const std::string message; 104 | }; 105 | 106 | struct Function : public Object { 107 | Function(const std::vector ¶ms, 108 | std::shared_ptr env, std::shared_ptr body) 109 | : params(params), env(env), body(body) {} 110 | 111 | ObjectType type() const override { return FUNCTION_OBJ; } 112 | std::string name() const override { return "FUNCTION"; } 113 | 114 | std::string inspect() const override { 115 | std::stringstream ss; 116 | ss << "fn("; 117 | for (size_t i = 0; i < params.size(); i++) { 118 | if (i != 0) { ss << ", "; } 119 | ss << params[i]; 120 | } 121 | ss << ") {\n"; 122 | ss << to_string(body); 123 | ss << "\n}"; 124 | return ss.str(); 125 | } 126 | 127 | const std::vector params; 128 | const std::shared_ptr env; 129 | const std::shared_ptr body; 130 | }; 131 | 132 | struct CompiledFunction : public Object { 133 | CompiledFunction() = default; 134 | 135 | CompiledFunction(Instructions instructions) 136 | : instructions(std::move(instructions)) {} 137 | 138 | ObjectType type() const override { return COMPILED_FUNCTION_OBJ; } 139 | std::string name() const override { return "COMPILED_FUNCTION"; } 140 | 141 | std::string inspect() const override { 142 | std::stringstream ss; 143 | ss << "CompiledFunction[" << std::hex << this << std::dec << "]"; 144 | return ss.str(); 145 | } 146 | 147 | Instructions instructions; 148 | int numLocals = 0; 149 | int numParameters = 0; 150 | }; 151 | 152 | // https://docs.microsoft.com/en-us/cpp/porting/fix-your-dependencies-on-library-internals?view=vs-2019 153 | inline uint64_t fnv1a_hash_bytes(const char *first, size_t count) { 154 | const auto fnv_offset_basis = 14695981039346656037ULL; 155 | const auto fnv_prime = 1099511628211ULL; 156 | 157 | auto result = fnv_offset_basis; 158 | for (size_t next = 0; next < count; ++next) { 159 | // fold in another byte 160 | result ^= static_cast(first[next]); 161 | result *= fnv_prime; 162 | } 163 | return result; 164 | } 165 | 166 | struct String : public Object { 167 | String(std::string_view value) : value(value) {} 168 | ObjectType type() const override { return STRING_OBJ; } 169 | std::string name() const override { return "STRING"; } 170 | std::string inspect() const override { return value; } 171 | bool has_hash_key() const override { return true; } 172 | HashKey hash_key() const override { 173 | auto hash_value = fnv1a_hash_bytes(value.data(), value.size()); 174 | return HashKey{type(), hash_value}; 175 | } 176 | 177 | const std::string value; 178 | }; 179 | 180 | using Fn = std::function( 181 | const std::vector> &args)>; 182 | 183 | struct Builtin : public Object { 184 | Builtin(Fn fn) : fn(fn) {} 185 | ObjectType type() const override { return BUILTIN_OBJ; } 186 | std::string name() const override { return "BUILTIN"; } 187 | std::string inspect() const override { return "builtin function"; } 188 | const Fn fn; 189 | }; 190 | 191 | struct Array : public Object { 192 | ObjectType type() const override { return ARRAY_OBJ; } 193 | std::string name() const override { return "ARRAY"; } 194 | std::string inspect() const override { 195 | std::stringstream ss; 196 | ss << "["; 197 | for (size_t i = 0; i < elements.size(); i++) { 198 | if (i != 0) { ss << ", "; } 199 | ss << elements[i]->inspect(); 200 | } 201 | ss << "]"; 202 | return ss.str(); 203 | } 204 | 205 | std::vector> elements; 206 | }; 207 | 208 | struct HashPair { 209 | std::shared_ptr key; 210 | std::shared_ptr value; 211 | }; 212 | 213 | struct Hash : public Object { 214 | ObjectType type() const override { return HASH_OBJ; } 215 | std::string name() const override { return "HASH"; } 216 | std::string inspect() const override { 217 | std::stringstream ss; 218 | ss << "{"; 219 | auto it = pairs.begin(); 220 | for (size_t i = 0; i < pairs.size(); i++) { 221 | if (i != 0) { ss << ", "; } 222 | auto [key, value] = it->second; 223 | ss << key->inspect(); 224 | ss << ": "; 225 | ss << value->inspect(); 226 | ++it; 227 | } 228 | ss << "}"; 229 | return ss.str(); 230 | } 231 | 232 | std::map pairs; 233 | }; 234 | 235 | struct Closure : public Object { 236 | Closure(std::shared_ptr fn) : fn(fn) {} 237 | 238 | Closure(std::shared_ptr fn, 239 | const std::vector> &free) 240 | : fn(fn), free(free) {} 241 | 242 | ObjectType type() const override { return CLOSURE_OBJ; } 243 | std::string name() const override { return "CLOSURE"; } 244 | 245 | std::string inspect() const override { 246 | std::stringstream ss; 247 | ss << "Closure[" << std::hex << this << std::dec << "]"; 248 | return ss.str(); 249 | } 250 | 251 | std::shared_ptr fn; 252 | std::vector> free; 253 | }; 254 | 255 | inline std::shared_ptr make_integer(int64_t n) { 256 | return std::make_shared(n); 257 | } 258 | 259 | inline std::shared_ptr make_error(const std::string &s) { 260 | return std::make_shared(s); 261 | } 262 | 263 | inline std::shared_ptr make_string(std::string_view s) { 264 | return std::make_shared(s); 265 | } 266 | 267 | inline std::shared_ptr make_builtin(Fn fn) { 268 | return std::make_shared(fn); 269 | } 270 | 271 | inline std::shared_ptr make_array(std::vector numbers) { 272 | auto arr = std::make_shared(); 273 | for (auto n : numbers) { 274 | arr->elements.emplace_back(make_integer(n)); 275 | } 276 | return arr; 277 | } 278 | 279 | inline std::shared_ptr 280 | make_compiled_function(std::vector items, int numLocals = 0, 281 | int numParameters = 0) { 282 | auto fn = std::make_shared(); 283 | for (auto instructions : items) { 284 | fn->instructions.insert(fn->instructions.end(), instructions.begin(), 285 | instructions.end()); 286 | } 287 | fn->numLocals = numLocals; 288 | fn->numParameters = numParameters; 289 | return fn; 290 | } 291 | 292 | inline const std::shared_ptr CONST_TRUE = 293 | std::make_shared(true); 294 | inline const std::shared_ptr CONST_FALSE = 295 | std::make_shared(false); 296 | inline const std::shared_ptr CONST_NULL = std::make_shared(); 297 | 298 | inline std::shared_ptr make_bool(bool value) { 299 | return value ? CONST_TRUE : CONST_FALSE; 300 | } 301 | 302 | inline void 303 | validate_args_for_array(const std::vector> &args, 304 | const std::string &name, size_t argc) { 305 | if (args.size() != argc) { 306 | std::stringstream ss; 307 | ss << "wrong number of arguments. got=" << args.size() << ", want=" << argc; 308 | throw make_error(ss.str()); 309 | } 310 | 311 | auto arg = args[0]; 312 | if (arg->type() != ARRAY_OBJ) { 313 | std::stringstream ss; 314 | ss << "argument to `" << name << "` must be ARRAY, got " << arg->name(); 315 | throw make_error(ss.str()); 316 | } 317 | } 318 | 319 | const std::vector>> BUILTINS{ 320 | { 321 | "len", 322 | make_builtin([](const std::vector> &args) { 323 | if (args.size() != 1) { 324 | std::stringstream ss; 325 | ss << "wrong number of arguments. got=" << args.size() 326 | << ", want=1"; 327 | throw make_error(ss.str()); 328 | } 329 | auto arg = args[0]; 330 | switch (arg->type()) { 331 | case STRING_OBJ: { 332 | const auto &s = cast(arg).value; 333 | return make_integer(s.size()); 334 | } 335 | case ARRAY_OBJ: { 336 | const auto &arr = cast(arg); 337 | return make_integer(arr.elements.size()); 338 | } 339 | default: { 340 | std::stringstream ss; 341 | ss << "argument to `len` not supported, got " << arg->name(); 342 | throw make_error(ss.str()); 343 | } 344 | } 345 | }), 346 | }, 347 | { 348 | "puts", 349 | make_builtin([](const std::vector> &args) { 350 | for (auto arg : args) { 351 | std::cout << arg->inspect() << std::endl; 352 | } 353 | return CONST_NULL; 354 | }), 355 | }, 356 | { 357 | "first", 358 | make_builtin([](const std::vector> &args) { 359 | validate_args_for_array(args, "first", 1); 360 | const auto &elements = cast(args[0]).elements; 361 | if (elements.empty()) { return CONST_NULL; } 362 | return elements.front(); 363 | }), 364 | }, 365 | { 366 | "last", 367 | make_builtin([](const std::vector> &args) 368 | -> std::shared_ptr { 369 | validate_args_for_array(args, "last", 1); 370 | const auto &elements = cast(args[0]).elements; 371 | if (elements.empty()) { return CONST_NULL; } 372 | return elements.back(); 373 | }), 374 | }, 375 | { 376 | "rest", 377 | make_builtin([](const std::vector> &args) 378 | -> std::shared_ptr { 379 | validate_args_for_array(args, "rest", 1); 380 | const auto &elements = cast(args[0]).elements; 381 | if (!elements.empty()) { 382 | auto arr = std::make_shared(); 383 | arr->elements.assign(elements.begin() + 1, elements.end()); 384 | return arr; 385 | } 386 | return CONST_NULL; 387 | }), 388 | }, 389 | { 390 | "push", 391 | make_builtin([](const std::vector> &args) { 392 | validate_args_for_array(args, "push", 2); 393 | const auto &elements = cast(args[0]).elements; 394 | auto arr = std::make_shared(); 395 | arr->elements = elements; 396 | arr->elements.emplace_back(args[1]); 397 | return arr; 398 | }), 399 | }, 400 | }; 401 | 402 | inline std::shared_ptr get_builtin_by_name(const std::string &name) { 403 | auto it = std::find_if(BUILTINS.begin(), BUILTINS.end(), 404 | [&](const auto &v) { return v.first == name; }); 405 | assert(it != BUILTINS.end()); 406 | return it->second; 407 | } 408 | 409 | const std::map> builtins{ 410 | {"len", get_builtin_by_name("len")}, 411 | {"puts", get_builtin_by_name("puts")}, 412 | {"first", get_builtin_by_name("first")}, 413 | {"last", get_builtin_by_name("last")}, 414 | {"rest", get_builtin_by_name("rest")}, 415 | {"push", get_builtin_by_name("push")}, 416 | }; 417 | 418 | } // namespace monkey 419 | -------------------------------------------------------------------------------- /engine/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace monkey { 7 | 8 | const auto GRAMMAR = R"( 9 | 10 | PROGRAM <- STATEMENTS 11 | 12 | STATEMENTS <- (STATEMENT ';'?)* 13 | STATEMENT <- ASSIGNMENT / RETURN / EXPRESSION_STATEMENT 14 | 15 | ASSIGNMENT <- 'let' IDENTIFIER '=' EXPRESSION 16 | RETURN <- 'return' EXPRESSION 17 | EXPRESSION_STATEMENT <- EXPRESSION 18 | 19 | EXPRESSION <- INFIX_EXPR(PREFIX_EXPR, INFIX_OPE) 20 | INFIX_EXPR(ATOM, OPE) <- ATOM (OPE ATOM)* { 21 | precedence 22 | L == != 23 | L < > 24 | L + - 25 | L * / 26 | } 27 | 28 | IF <- 'if' '(' EXPRESSION ')' BLOCK ('else' BLOCK)? 29 | 30 | FUNCTION <- 'fn' '(' PARAMETERS ')' BLOCK 31 | PARAMETERS <- LIST(IDENTIFIER, ',') 32 | 33 | BLOCK <- '{' STATEMENTS '}' 34 | 35 | CALL <- PRIMARY (ARGUMENTS / INDEX)* 36 | ARGUMENTS <- '(' LIST(EXPRESSION, ',') ')' 37 | INDEX <- '[' EXPRESSION ']' 38 | 39 | PREFIX_EXPR <- PREFIX_OPE* CALL 40 | PRIMARY <- IF / FUNCTION / ARRAY / HASH / INTEGER / BOOLEAN / NULL / IDENTIFIER / STRING / '(' EXPRESSION ')' 41 | 42 | ARRAY <- '[' LIST(EXPRESSION, ',') ']' 43 | 44 | HASH <- '{' LIST(HASH_PAIR, ',') '}' 45 | HASH_PAIR <- EXPRESSION ':' EXPRESSION 46 | 47 | IDENTIFIER <- < !KEYWORD [a-zA-Z]+ > 48 | INTEGER <- < [0-9]+ > 49 | STRING <- < ["] < (!["] .)* > ["] > 50 | BOOLEAN <- 'true' / 'false' 51 | NULL <- 'null' 52 | PREFIX_OPE <- < [-!] > 53 | INFIX_OPE <- < [-+/*<>] / '==' / '!=' > 54 | 55 | KEYWORD <- 'null' | 'true' | 'false' | 'let' | 'return' | 'if' | 'else' | 'fn' 56 | 57 | LIST(ITEM, DELM) <- (ITEM (~DELM ITEM)*)? 58 | 59 | LINE_COMMENT <- '//' (!LINE_END .)* &LINE_END 60 | LINE_END <- '\r\n' / '\r' / '\n' / !. 61 | 62 | %whitespace <- ([ \t\r\n]+ / LINE_COMMENT)* 63 | %word <- [a-zA-Z]+ 64 | 65 | )"; 66 | 67 | inline peg::parser &get_parser() { 68 | static peg::parser parser; 69 | static bool initialized = false; 70 | 71 | if (!initialized) { 72 | initialized = true; 73 | 74 | parser.set_logger([&](size_t ln, size_t col, const std::string &msg) { 75 | std::cerr << ln << ":" << col << ": " << msg << std::endl; 76 | }); 77 | 78 | if (!parser.load_grammar(GRAMMAR)) { 79 | throw std::logic_error("invalid peg grammar"); 80 | } 81 | 82 | parser.enable_ast(); 83 | } 84 | 85 | return parser; 86 | } 87 | 88 | inline std::shared_ptr parse(const std::string &path, const char *expr, 89 | size_t len, std::vector &msgs) { 90 | auto &parser = get_parser(); 91 | 92 | parser.set_logger([&](size_t ln, size_t col, const std::string &err_msg) { 93 | std::stringstream ss; 94 | ss << path << ":" << ln << ":" << col << ": " << err_msg << std::endl; 95 | msgs.push_back(ss.str()); 96 | }); 97 | 98 | std::shared_ptr ast; 99 | if (parser.parse_n(expr, len, ast, path.c_str())) { 100 | auto opt = 101 | peg::AstOptimizer(true, {"EXPRESSION_STATEMENT", "PARAMETERS", "ARGUMENTS", 102 | "INDEX", "RETURN", "BLOCK", "ARRAY", "HASH"}); 103 | 104 | ast = opt.optimize(ast); 105 | annotate(ast); 106 | return ast; 107 | } 108 | 109 | return nullptr; 110 | } 111 | 112 | } // namespace monkey 113 | -------------------------------------------------------------------------------- /engine/symbol_table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace monkey { 6 | 7 | using SymbolScope = std::string; 8 | 9 | const SymbolScope GlobalScope = "GLOBAL"; 10 | const SymbolScope LocalScope = "LOCAL"; 11 | const SymbolScope BuiltinScope = "BUILTIN"; 12 | const SymbolScope FreeScope = "FREE"; 13 | const SymbolScope FunctionScope = "FUNCTION"; 14 | 15 | struct Symbol { 16 | std::string name; 17 | SymbolScope scope; 18 | int index; 19 | 20 | bool operator==(const Symbol &rhs) const { 21 | return name == rhs.name && scope == rhs.scope && index == rhs.index; 22 | } 23 | }; 24 | 25 | struct SymbolTable { 26 | std::shared_ptr outer; 27 | 28 | std::map store; 29 | int numDefinitions = 0; 30 | std::vector freeSymbols; 31 | 32 | const Symbol &define(const std::string &name) { 33 | store[name] = { 34 | name, 35 | outer ? LocalScope : GlobalScope, 36 | numDefinitions, 37 | }; 38 | numDefinitions++; 39 | return store[name]; 40 | } 41 | 42 | const Symbol &define_builtin(int index, const std::string &name) { 43 | store[name] = { 44 | name, 45 | BuiltinScope, 46 | index, 47 | }; 48 | return store[name]; 49 | } 50 | 51 | const Symbol &define_free(const Symbol &original) { 52 | freeSymbols.push_back(original); 53 | store[original.name] = { 54 | original.name, 55 | FreeScope, 56 | static_cast(freeSymbols.size() - 1), 57 | }; 58 | return store[original.name]; 59 | } 60 | 61 | const Symbol &define_function_name(const std::string &name) { 62 | store[name] = { 63 | name, 64 | FunctionScope, 65 | 0, 66 | }; 67 | return store[name]; 68 | } 69 | 70 | std::optional resolve(const std::string &name) { 71 | auto it = store.find(name); 72 | if (it != store.end()) { 73 | return it->second; 74 | } else if (outer) { 75 | auto obj = outer->resolve(name); 76 | if (!obj) { return obj; } 77 | if (obj->scope == GlobalScope || obj->scope == BuiltinScope) { 78 | return obj; 79 | } 80 | return define_free(*obj); 81 | } 82 | return std::nullopt; 83 | } 84 | }; 85 | 86 | inline std::shared_ptr symbol_table() { 87 | return std::make_shared(); 88 | } 89 | 90 | inline std::shared_ptr 91 | enclosed_symbol_table(std::shared_ptr outer) { 92 | auto s = std::make_shared(); 93 | s->outer = outer; 94 | return s; 95 | } 96 | 97 | } // namespace monkey 98 | -------------------------------------------------------------------------------- /engine/vm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace monkey { 6 | 7 | struct Frame { 8 | std::shared_ptr cl; 9 | int ip = -1; 10 | int basePointer = -1; 11 | 12 | Frame(std::shared_ptr cl, int basePointer) 13 | : cl(cl), basePointer(basePointer) {} 14 | 15 | const Instructions &instructions() const { return cl->fn->instructions; } 16 | }; 17 | 18 | struct VM { 19 | static const size_t StackSize = 2048; 20 | static const size_t GlobalSize = 65535; 21 | static const size_t MaxFrames = 1024; 22 | 23 | std::vector> constants; 24 | 25 | std::vector> stack; 26 | size_t sp = 0; 27 | 28 | std::vector> globals; 29 | 30 | std::vector> frames; 31 | int framesIndex = 1; 32 | 33 | VM(const Bytecode &bytecode) 34 | : constants(bytecode.constants), stack(StackSize), globals(GlobalSize), 35 | frames(MaxFrames) { 36 | auto mainFn = std::make_shared(bytecode.instructions); 37 | auto mainClosure = std::make_shared(mainFn); 38 | frames[0] = std::make_shared(mainClosure, 0); 39 | } 40 | 41 | VM(const Bytecode &bytecode, const std::vector> &s) 42 | : constants(bytecode.constants), stack(StackSize), globals(s), 43 | frames(MaxFrames) { 44 | auto mainFn = std::make_shared(bytecode.instructions); 45 | auto mainClosure = std::make_shared(mainFn); 46 | frames[0] = std::make_shared(mainClosure, 0); 47 | } 48 | 49 | std::shared_ptr stack_top() const { 50 | if (sp == 0) { return nullptr; } 51 | return stack[sp - 1]; 52 | } 53 | 54 | std::shared_ptr last_popped_stack_elem() const { return stack[sp]; } 55 | 56 | std::shared_ptr current_frame() const { 57 | return frames[framesIndex - 1]; 58 | } 59 | 60 | void push_frame(std::shared_ptr f) { 61 | frames[framesIndex] = f; 62 | framesIndex++; 63 | } 64 | 65 | std::shared_ptr pop_frame() { 66 | framesIndex--; 67 | return frames[framesIndex]; 68 | } 69 | 70 | void run() { 71 | try { 72 | while (current_frame()->ip < 73 | static_cast(current_frame()->instructions().size()) - 1) { 74 | current_frame()->ip++; 75 | 76 | auto ip = current_frame()->ip; 77 | const auto &ins = current_frame()->instructions(); 78 | Opecode op = ins[ip]; 79 | 80 | switch (op) { 81 | case OpConstant: { 82 | auto constIndex = 83 | read_uint16(¤t_frame()->instructions()[ip + 1]); 84 | current_frame()->ip += 2; 85 | push(constants[constIndex]); 86 | break; 87 | } 88 | case OpAdd: 89 | case OpSub: 90 | case OpMul: 91 | case OpDiv: execute_binary_operation(op); break; 92 | case OpTrue: push(CONST_TRUE); break; 93 | case OpFalse: push(CONST_FALSE); break; 94 | case OpNull: push(CONST_NULL); break; 95 | case OpEqual: 96 | case OpNotEqual: 97 | case OpGreaterThan: execute_comparison(op); break; 98 | case OpBang: execute_bang_operator(); break; 99 | case OpMinus: execute_minus_operator(); break; 100 | case OpPop: pop(); break; 101 | case OpJump: { 102 | auto pos = read_uint16(¤t_frame()->instructions()[ip + 1]); 103 | current_frame()->ip = pos - 1; 104 | break; 105 | } 106 | case OpJumpNotTruthy: { 107 | auto pos = read_uint16(¤t_frame()->instructions()[ip + 1]); 108 | current_frame()->ip += 2; 109 | auto condition = pop(); 110 | if (!is_truthy(condition)) { current_frame()->ip = pos - 1; } 111 | break; 112 | } 113 | case OpSetGlobal: { 114 | auto globalIndex = 115 | read_uint16(¤t_frame()->instructions()[ip + 1]); 116 | current_frame()->ip += 2; 117 | globals[globalIndex] = pop(); 118 | break; 119 | } 120 | case OpGetGlobal: { 121 | auto globalIndex = 122 | read_uint16(¤t_frame()->instructions()[ip + 1]); 123 | current_frame()->ip += 2; 124 | push(globals[globalIndex]); 125 | break; 126 | } 127 | case OpArray: { 128 | auto numElements = 129 | read_uint16(¤t_frame()->instructions()[ip + 1]); 130 | current_frame()->ip += 2; 131 | 132 | auto array = build_array(sp - numElements, sp); 133 | sp = sp - numElements; 134 | push(array); 135 | break; 136 | } 137 | case OpHash: { 138 | auto numElements = 139 | read_uint16(¤t_frame()->instructions()[ip + 1]); 140 | current_frame()->ip += 2; 141 | 142 | auto hash = build_hash(sp - numElements, sp); 143 | sp = sp - numElements; 144 | push(hash); 145 | break; 146 | } 147 | case OpIndex: { 148 | auto index = pop(); 149 | auto left = pop(); 150 | execute_index_expression(left, index); 151 | break; 152 | } 153 | case OpCall: { 154 | auto numArgs = read_uint8(¤t_frame()->instructions()[ip + 1]); 155 | current_frame()->ip += 1; 156 | execute_call(numArgs); 157 | break; 158 | } 159 | case OpReturnValue: { 160 | auto returnValue = pop(); 161 | auto frame = pop_frame(); 162 | sp = frame->basePointer - 1; 163 | push(returnValue); 164 | break; 165 | } 166 | case OpReturn: { 167 | auto frame = pop_frame(); 168 | sp = frame->basePointer - 1; 169 | push(CONST_NULL); 170 | break; 171 | } 172 | case OpSetLocal: { 173 | auto localIndex = 174 | read_uint8(¤t_frame()->instructions()[ip + 1]); 175 | current_frame()->ip += 1; 176 | auto frame = current_frame(); 177 | stack[frame->basePointer + localIndex] = pop(); 178 | break; 179 | } 180 | case OpGetLocal: { 181 | auto localIndex = 182 | read_uint8(¤t_frame()->instructions()[ip + 1]); 183 | current_frame()->ip += 1; 184 | auto frame = current_frame(); 185 | push(stack[frame->basePointer + localIndex]); 186 | break; 187 | } 188 | case OpGetBuiltin: { 189 | auto builtinIndex = read_uint8(¤t_frame()->instructions()[ip + 1]); 190 | current_frame()->ip += 1; 191 | auto definition = BUILTINS[builtinIndex]; 192 | push(definition.second); 193 | break; 194 | } 195 | case OpClosure: { 196 | auto constIndex = read_uint16(¤t_frame()->instructions()[ip + 1]); 197 | auto numFree = read_uint8(¤t_frame()->instructions()[ip + 3]); 198 | current_frame()->ip += 3; 199 | push_closure(constIndex, numFree); 200 | break; 201 | } 202 | case OpGetFree: { 203 | auto freeIndex = read_uint8(¤t_frame()->instructions()[ip + 1]); 204 | current_frame()->ip += 1; 205 | auto currentClosure = current_frame()->cl; 206 | push(currentClosure->free[freeIndex]); 207 | break; 208 | } 209 | case OpCurrentClosure: { 210 | auto currentClosure = current_frame()->cl; 211 | push(currentClosure); 212 | break; 213 | } 214 | } 215 | } 216 | } catch (const std::shared_ptr &err) { 217 | push(err); 218 | pop(); 219 | } 220 | } 221 | 222 | void push(std::shared_ptr o) { 223 | if (sp >= StackSize) { throw make_error("stack overflow"); } 224 | stack[sp] = o; 225 | sp++; 226 | } 227 | 228 | void push_closure(int constIndex, int numFree) { 229 | auto constant = constants[constIndex]; 230 | auto function = std::dynamic_pointer_cast(constant); 231 | if (!function) { 232 | throw make_error(fmt::format("not a function: {}", constIndex)); 233 | } 234 | 235 | std::vector> free; 236 | for (int i = 0; i < numFree; i++) { 237 | free.push_back(stack[sp - numFree + i]); 238 | } 239 | sp = sp - numFree; 240 | 241 | auto closure = std::make_shared(function, free); 242 | push(closure); 243 | } 244 | 245 | std::shared_ptr pop() { 246 | auto o = stack[sp - 1]; 247 | stack.pop_back(); 248 | sp--; 249 | return o; 250 | } 251 | 252 | void call_closure(std::shared_ptr cl, int numArgs) { 253 | if (numArgs != cl->fn->numParameters) { 254 | throw make_error(fmt::format("wrong number of arguments: want={}, got={}", 255 | cl->fn->numParameters, numArgs)); 256 | } 257 | auto frame = std::make_shared(cl, sp - numArgs); 258 | push_frame(frame); 259 | sp = frame->basePointer + cl->fn->numLocals; 260 | } 261 | 262 | void call_builtin(std::shared_ptr builtin, int numArgs) { 263 | std::vector> args; 264 | for (int i = 0; i < numArgs; i++) { 265 | args.push_back(stack[sp - (numArgs - i)]); 266 | } 267 | auto result = builtin->fn(args); 268 | sp = sp - numArgs; 269 | push(result); 270 | } 271 | 272 | void execute_binary_operation(Opecode op) { 273 | auto right = pop(); 274 | auto left = pop(); 275 | 276 | auto left_type = left->type(); 277 | auto right_type = right->type(); 278 | 279 | if (left_type == INTEGER_OBJ && right_type == INTEGER_OBJ) { 280 | execute_binary_integer_operation(op, left, right); 281 | return; 282 | } 283 | 284 | if (left_type == STRING_OBJ && right_type == STRING_OBJ) { 285 | execute_binary_string_operation(op, left, right); 286 | return; 287 | } 288 | 289 | throw make_error( 290 | fmt::format("unsupported types for binary operation: {} {}", left_type, 291 | right_type)); 292 | } 293 | 294 | void execute_binary_integer_operation(Opecode op, 295 | std::shared_ptr left, 296 | std::shared_ptr right) { 297 | auto left_value = cast(left).value; 298 | auto right_value = cast(right).value; 299 | 300 | int64_t result; 301 | 302 | switch (op) { 303 | case OpAdd: result = left_value + right_value; break; 304 | case OpSub: result = left_value - right_value; break; 305 | case OpMul: result = left_value * right_value; break; 306 | case OpDiv: result = left_value / right_value; break; 307 | default: throw make_error(fmt::format("unknown integer operator: {}", op)); 308 | } 309 | 310 | push(make_integer(result)); 311 | } 312 | 313 | void execute_binary_string_operation(Opecode op, std::shared_ptr left, 314 | std::shared_ptr right) { 315 | auto left_value = cast(left).value; 316 | auto right_value = cast(right).value; 317 | 318 | if (op != OpAdd) { 319 | throw make_error(fmt::format("unknown integer operator: {}", op)); 320 | } 321 | 322 | push(make_string(left_value + right_value)); 323 | } 324 | 325 | void execute_comparison(Opecode op) { 326 | auto right = pop(); 327 | auto left = pop(); 328 | 329 | auto left_type = left->type(); 330 | auto right_type = right->type(); 331 | 332 | if (left_type == INTEGER_OBJ || right_type == INTEGER_OBJ) { 333 | execute_integer_comparison(op, left, right); 334 | return; 335 | } 336 | 337 | auto left_value = cast(left).value; 338 | auto right_value = cast(right).value; 339 | 340 | switch (op) { 341 | case OpEqual: push(make_bool(right_value == left_value)); break; 342 | case OpNotEqual: push(make_bool(right_value != left_value)); break; 343 | default: 344 | throw make_error(fmt::format("unknown operator: {} ({} {})", op, 345 | left_type, right_type)); 346 | } 347 | } 348 | 349 | void execute_integer_comparison(Opecode op, std::shared_ptr left, 350 | std::shared_ptr right) { 351 | auto left_value = cast(left).value; 352 | auto right_value = cast(right).value; 353 | 354 | switch (op) { 355 | case OpEqual: push(make_bool(right_value == left_value)); break; 356 | case OpNotEqual: push(make_bool(right_value != left_value)); break; 357 | case OpGreaterThan: push(make_bool(left_value > right_value)); break; 358 | default: make_error(fmt::format("unknown operator: {}", op)); 359 | } 360 | } 361 | 362 | void execute_bang_operator() { 363 | auto operand = pop(); 364 | if (operand->type() == BOOLEAN_OBJ) { 365 | auto value = cast(operand).value; 366 | push(make_bool(!value)); 367 | } else if (operand->type() == NULL_OBJ) { 368 | push(make_bool(true)); 369 | } else { 370 | push(make_bool(false)); 371 | } 372 | } 373 | 374 | void execute_minus_operator() { 375 | auto operand = pop(); 376 | if (operand->type() != INTEGER_OBJ) { 377 | throw make_error( 378 | fmt::format("unsupported types for negation: {}", operand->type())); 379 | } 380 | 381 | auto value = cast(operand).value; 382 | push(make_integer(value * -1)); 383 | } 384 | 385 | void execute_index_expression(std::shared_ptr left, 386 | std::shared_ptr index) { 387 | if (left->type() == ARRAY_OBJ && index->type() == INTEGER_OBJ) { 388 | return execute_array_index(left, index); 389 | } else if (left->type() == HASH_OBJ) { 390 | return execute_hash_index(left, index); 391 | } else { 392 | throw make_error( 393 | fmt::format("index operator not supported: {}", left->type())); 394 | } 395 | } 396 | 397 | void execute_array_index(std::shared_ptr array, 398 | std::shared_ptr index) { 399 | auto &arrayObject = cast(array); 400 | auto i = cast(index).value; 401 | int64_t max = arrayObject.elements.size() - 1; 402 | if (i < 0 || i > max) { 403 | push(CONST_NULL); 404 | return; 405 | } 406 | push(arrayObject.elements[i]); 407 | } 408 | 409 | void execute_hash_index(std::shared_ptr hash, 410 | std::shared_ptr index) { 411 | auto &hashObject = cast(hash); 412 | auto key = index->hash_key(); 413 | auto it = hashObject.pairs.find(key); 414 | if (it == hashObject.pairs.end()) { 415 | push(CONST_NULL); 416 | return; 417 | } 418 | push(it->second.value); 419 | } 420 | 421 | void execute_call(int numArgs) { 422 | auto callee = stack[sp - 1 - numArgs]; 423 | if (callee) { 424 | if (callee->type() == CLOSURE_OBJ) { 425 | call_closure(std::dynamic_pointer_cast(callee), numArgs); 426 | return; 427 | } else if (callee->type() == BUILTIN_OBJ) { 428 | call_builtin(std::dynamic_pointer_cast(callee), numArgs); 429 | return; 430 | } 431 | } 432 | throw make_error("calling non-function and non-built-in"); 433 | } 434 | 435 | bool is_truthy(std::shared_ptr obj) const { 436 | if (obj->type() == BOOLEAN_OBJ) { 437 | return cast(obj).value; 438 | } else if (obj->type() == NULL_OBJ) { 439 | return false; 440 | } else { 441 | return true; 442 | } 443 | } 444 | 445 | std::shared_ptr build_array(int startIndex, int endIndex) { 446 | auto arr = std::make_shared(); 447 | for (auto i = startIndex; i < endIndex; i++) { 448 | arr->elements.push_back(std::move(stack[i])); 449 | } 450 | return arr; 451 | } 452 | 453 | std::shared_ptr build_hash(int startIndex, int endIndex) { 454 | auto hash = std::make_shared(); 455 | for (auto i = startIndex; i < endIndex; i += 2) { 456 | auto key = stack[i]; 457 | auto value = stack[i + 1]; 458 | hash->pairs[key->hash_key()] = HashPair{key, value}; 459 | } 460 | return hash; 461 | } 462 | }; 463 | 464 | } // namespace monkey 465 | -------------------------------------------------------------------------------- /examples/closure.monkey: -------------------------------------------------------------------------------- 1 | // `newAdder` returns a closure that makes use of the free variables `a` and `b`: 2 | let newAdder = fn(a, b) { 3 | fn(c) { a + b + c }; 4 | }; 5 | 6 | // This constructs a new `adder` function: 7 | let adder = newAdder(1, 2); 8 | 9 | puts(adder(8)); // => 11 10 | -------------------------------------------------------------------------------- /examples/fib.monkey: -------------------------------------------------------------------------------- 1 | let fibonacci = fn(x) { 2 | if (x == 0) { 3 | 0 4 | } else { 5 | if (x == 1) { 6 | return 1; 7 | } else { 8 | fibonacci(x - 1) + fibonacci(x - 2); 9 | } 10 | } 11 | }; 12 | 13 | puts(fibonacci(35)); 14 | -------------------------------------------------------------------------------- /examples/map.monkey: -------------------------------------------------------------------------------- 1 | let map = fn(arr, f) { 2 | let iter = fn(arr, accumulated) { 3 | if (len(arr) == 0) { 4 | accumulated 5 | } else { 6 | iter(rest(arr), push(accumulated, f(first(arr)))); 7 | } 8 | }; 9 | 10 | iter(arr, []); 11 | }; 12 | 13 | let a = [1, 2, 3, 4]; 14 | let double = fn(x) { x * 2 }; 15 | 16 | puts(map(a, double)); 17 | 18 | let reduce = fn(arr, initial, f) { 19 | let iter = fn(arr, result) { 20 | if (len(arr) == 0) { 21 | result 22 | } else { 23 | iter(rest(arr), f(result, first(arr))); 24 | } 25 | }; 26 | 27 | iter(arr, initial); 28 | }; 29 | 30 | let sum = fn(arr) { 31 | reduce(arr, 0, fn(initial, el) { initial + el }); 32 | }; 33 | 34 | puts(sum([1, 2, 3, 4, 5])); 35 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(test) 3 | 4 | add_executable(test-main 5 | test-code.cpp 6 | test-compiler.cpp 7 | test-evaluator.cpp 8 | test-main.cpp 9 | test-object.cpp 10 | test-parser.cpp 11 | test-symbol_table.cpp 12 | test-util.hpp 13 | test-vm.cpp 14 | test-main.cpp 15 | ) 16 | 17 | target_include_directories(test-main PRIVATE 18 | ${peglib_SOURCE_DIR} 19 | ../engine 20 | ) 21 | 22 | target_link_libraries(test-main PRIVATE 23 | fmt::fmt 24 | ) 25 | -------------------------------------------------------------------------------- /test/test-code.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace monkey; 8 | 9 | TEST_CASE("Make", "[code]") { 10 | struct Test { 11 | OpecodeType op; 12 | vector operands; 13 | vector expected; 14 | }; 15 | 16 | Test tests[] = { 17 | {OpConstant, {65534}, {OpConstant, 255, 254}}, 18 | {OpAdd, {}, {OpAdd}}, 19 | {OpGetLocal, {255}, {OpGetLocal, 255}}, 20 | {OpClosure, {65534, 255}, {OpClosure, 255, 254, 255}}, 21 | }; 22 | 23 | for (const auto &t : tests) { 24 | auto instruction = make(t.op, t.operands); 25 | REQUIRE(instruction.size() == t.expected.size()); 26 | CHECK(instruction == t.expected); 27 | } 28 | } 29 | 30 | TEST_CASE("Instructions string", "[code]") { 31 | vector instructions{ 32 | make(OpAdd, {}), 33 | make(OpGetLocal, {1}), 34 | make(OpConstant, {2}), 35 | make(OpConstant, {65535}), 36 | make(OpClosure, {65535, 255}), 37 | }; 38 | 39 | string expected = R"(0000 OpAdd\n0001 OpGetLocal 1\n0003 OpConstant 2\n0006 OpConstant 65535\n0009 OpClosure 65535 255\n)"; 40 | 41 | auto concatted = concat_instructions(instructions); 42 | 43 | CHECK(to_string(concatted) == expected); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /test/test-compiler.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace std; 9 | using namespace monkey; 10 | 11 | void test_instructions(const Instructions &expected, 12 | const Instructions &actual) { 13 | REQUIRE(to_string(expected) == to_string(actual)); 14 | 15 | size_t i = 0; 16 | for (const auto &ins : expected) { 17 | CHECK(actual[i] == ins); 18 | i++; 19 | } 20 | } 21 | 22 | void test_constants(const vector> &expected, 23 | const vector> &actual) { 24 | REQUIRE(expected.size() == actual.size()); 25 | 26 | size_t i = 0; 27 | for (const auto &constant : expected) { 28 | switch (constant->type()) { 29 | case INTEGER_OBJ: 30 | test_integer_object(cast(constant).value, actual[i]); 31 | break; 32 | case STRING_OBJ: 33 | test_string_object(cast(constant).value, actual[i]); 34 | break; 35 | case COMPILED_FUNCTION_OBJ: { 36 | test_instructions(cast(constant).instructions, 37 | cast(actual[i]).instructions); 38 | break; 39 | } 40 | default: break; 41 | } 42 | i++; 43 | } 44 | } 45 | 46 | struct CompilerTestCase { 47 | string input; 48 | vector> expectedConstants; 49 | vector expectedInstructions; 50 | }; 51 | 52 | void run_compiler_test(const char *name, 53 | const vector &tests) { 54 | for (const auto &t : tests) { 55 | auto ast = parse(name, t.input); 56 | // cerr << peg::ast_to_s(ast) << endl; 57 | REQUIRE(ast != nullptr); 58 | 59 | Compiler compiler; 60 | compiler.compile(ast); 61 | auto bytecode = compiler.bytecode(); 62 | 63 | test_instructions(concat_instructions(t.expectedInstructions), 64 | bytecode.instructions); 65 | test_constants(t.expectedConstants, bytecode.constants); 66 | } 67 | } 68 | 69 | TEST_CASE("Integer arithmetic", "[compiler]") { 70 | vector tests{ 71 | { 72 | "1 + 2", 73 | { 74 | make_integer(1), 75 | make_integer(2), 76 | }, 77 | { 78 | make(OpConstant, {0}), 79 | make(OpConstant, {1}), 80 | make(OpAdd, {}), 81 | make(OpPop, {}), 82 | }, 83 | }, 84 | { 85 | "1 - 2", 86 | { 87 | make_integer(1), 88 | make_integer(2), 89 | }, 90 | { 91 | make(OpConstant, {0}), 92 | make(OpConstant, {1}), 93 | make(OpSub, {}), 94 | make(OpPop, {}), 95 | }, 96 | }, 97 | { 98 | "1 * 2", 99 | { 100 | make_integer(1), 101 | make_integer(2), 102 | }, 103 | { 104 | make(OpConstant, {0}), 105 | make(OpConstant, {1}), 106 | make(OpMul, {}), 107 | make(OpPop, {}), 108 | }, 109 | }, 110 | { 111 | "1 / 2", 112 | { 113 | make_integer(1), 114 | make_integer(2), 115 | }, 116 | { 117 | make(OpConstant, {0}), 118 | make(OpConstant, {1}), 119 | make(OpDiv, {}), 120 | make(OpPop, {}), 121 | }, 122 | }, 123 | { 124 | "1; 2", 125 | { 126 | make_integer(1), 127 | make_integer(2), 128 | }, 129 | { 130 | make(OpConstant, {0}), 131 | make(OpPop, {}), 132 | make(OpConstant, {1}), 133 | make(OpPop, {}), 134 | }, 135 | }, 136 | { 137 | "-1", 138 | {make_integer(1)}, 139 | { 140 | make(OpConstant, {0}), 141 | make(OpMinus, {}), 142 | make(OpPop, {}), 143 | }, 144 | }, 145 | }; 146 | 147 | run_compiler_test("([compiler]: Integer arithmetic)", tests); 148 | } 149 | 150 | TEST_CASE("Boolean expressions", "[compiler]") { 151 | vector tests{ 152 | { 153 | "true", 154 | {}, 155 | { 156 | make(OpTrue, {}), 157 | make(OpPop, {}), 158 | }, 159 | }, 160 | { 161 | "false", 162 | {}, 163 | { 164 | make(OpFalse, {}), 165 | make(OpPop, {}), 166 | }, 167 | }, 168 | { 169 | "1 > 2", 170 | { 171 | make_integer(1), 172 | make_integer(2), 173 | }, 174 | { 175 | make(OpConstant, {0}), 176 | make(OpConstant, {1}), 177 | make(OpGreaterThan, {}), 178 | make(OpPop, {}), 179 | }, 180 | }, 181 | { 182 | "1 < 2", 183 | {make_integer(2), make_integer(1)}, 184 | { 185 | make(OpConstant, {0}), 186 | make(OpConstant, {1}), 187 | make(OpGreaterThan, {}), 188 | make(OpPop, {}), 189 | }, 190 | }, 191 | { 192 | "1 == 2", 193 | { 194 | make_integer(1), 195 | make_integer(2), 196 | }, 197 | { 198 | make(OpConstant, {0}), 199 | make(OpConstant, {1}), 200 | make(OpEqual, {}), 201 | make(OpPop, {}), 202 | }, 203 | }, 204 | { 205 | "1 != 2", 206 | { 207 | make_integer(1), 208 | make_integer(2), 209 | }, 210 | { 211 | make(OpConstant, {0}), 212 | make(OpConstant, {1}), 213 | make(OpNotEqual, {}), 214 | make(OpPop, {}), 215 | }, 216 | }, 217 | { 218 | "true == false", 219 | {}, 220 | { 221 | make(OpTrue, {}), 222 | make(OpFalse, {}), 223 | make(OpEqual, {}), 224 | make(OpPop, {}), 225 | }, 226 | }, 227 | { 228 | "true != false", 229 | {}, 230 | { 231 | make(OpTrue, {}), 232 | make(OpFalse, {}), 233 | make(OpNotEqual, {}), 234 | make(OpPop, {}), 235 | }, 236 | }, 237 | { 238 | "!true", 239 | {}, 240 | { 241 | make(OpTrue, {}), 242 | make(OpBang, {}), 243 | make(OpPop, {}), 244 | }, 245 | }, 246 | }; 247 | 248 | run_compiler_test("([compiler]: Boolean expressions)", tests); 249 | } 250 | 251 | TEST_CASE("Read operands", "[compiler]") { 252 | struct Test { 253 | OpecodeType op; 254 | vector operands; 255 | size_t byteRead; 256 | }; 257 | 258 | Test tests[] = { 259 | {OpConstant, {65535}, 2}, 260 | }; 261 | 262 | for (const auto &t : tests) { 263 | auto instruction = make(t.op, t.operands); 264 | auto &def = lookup(t.op); 265 | 266 | auto [operandsRead, n] = read_operands(def, instruction, 1); 267 | CHECK(n == t.byteRead); 268 | 269 | size_t i = 0; 270 | for (auto want : t.operands) { 271 | CHECK(operandsRead[i] == want); 272 | i++; 273 | } 274 | } 275 | } 276 | 277 | TEST_CASE("Conditionals", "[compiler]") { 278 | vector tests{ 279 | { 280 | "if (true) { 10 }; 3333;", 281 | { 282 | make_integer(10), 283 | make_integer(3333), 284 | }, 285 | { 286 | /* 0000 */ make(OpTrue, {}), 287 | /* 0001 */ make(OpJumpNotTruthy, {10}), 288 | /* 0004 */ make(OpConstant, {0}), 289 | /* 0007 */ make(OpJump, {11}), 290 | /* 0010 */ make(OpNull, {}), 291 | /* 0011 */ make(OpPop, {}), 292 | /* 0012 */ make(OpConstant, {1}), 293 | /* 0015 */ make(OpPop, {}), 294 | }, 295 | }, 296 | { 297 | "if (true) { 10 } else { 20 } 3333;", 298 | { 299 | make_integer(10), 300 | make_integer(20), 301 | make_integer(3333), 302 | }, 303 | { 304 | /* 0000 */ make(OpTrue, {}), 305 | /* 0001 */ make(OpJumpNotTruthy, {10}), 306 | /* 0004 */ make(OpConstant, {0}), 307 | /* 0007 */ make(OpJump, {13}), 308 | /* 0010 */ make(OpConstant, {1}), 309 | /* 0013 */ make(OpPop, {}), 310 | /* 0014 */ make(OpConstant, {2}), 311 | /* 0017 */ make(OpPop, {}), 312 | }, 313 | }, 314 | }; 315 | 316 | run_compiler_test("([compiler]: Conditionals)", tests); 317 | } 318 | 319 | TEST_CASE("Global Let Statements", "[compiler]") { 320 | vector tests{ 321 | { 322 | R"( 323 | let one = 1; 324 | let two = 2; 325 | )", 326 | { 327 | make_integer(1), 328 | make_integer(2), 329 | }, 330 | { 331 | make(OpConstant, {0}), 332 | make(OpSetGlobal, {0}), 333 | make(OpConstant, {1}), 334 | make(OpSetGlobal, {1}), 335 | }, 336 | }, 337 | { 338 | R"( 339 | let one = 1; 340 | one; 341 | )", 342 | { 343 | make_integer(1), 344 | }, 345 | { 346 | make(OpConstant, {0}), 347 | make(OpSetGlobal, {0}), 348 | make(OpGetGlobal, {0}), 349 | make(OpPop, {}), 350 | }, 351 | }, 352 | { 353 | R"( 354 | let one = 1; 355 | let two = one; 356 | two; 357 | )", 358 | {make_integer(1)}, 359 | { 360 | make(OpConstant, {0}), 361 | make(OpSetGlobal, {0}), 362 | make(OpGetGlobal, {0}), 363 | make(OpSetGlobal, {1}), 364 | make(OpGetGlobal, {1}), 365 | make(OpPop, {}), 366 | }, 367 | }, 368 | }; 369 | 370 | run_compiler_test("([compiler]: Global Let Statements)", tests); 371 | } 372 | 373 | TEST_CASE("String expressions", "[compiler]") { 374 | vector tests{ 375 | { 376 | R"("monkey")", 377 | { 378 | make_string("monkey"), 379 | }, 380 | { 381 | make(OpConstant, {0}), 382 | make(OpPop, {}), 383 | }, 384 | }, 385 | { 386 | R"("mon" + "key")", 387 | { 388 | make_string("mon"), 389 | make_string("key"), 390 | }, 391 | { 392 | make(OpConstant, {0}), 393 | make(OpConstant, {1}), 394 | make(OpAdd, {}), 395 | make(OpPop, {}), 396 | }, 397 | }, 398 | }; 399 | 400 | run_compiler_test("([compiler]: String expressions)", tests); 401 | } 402 | 403 | TEST_CASE("Array Literals", "[compiler]") { 404 | vector tests{ 405 | { 406 | "[]", 407 | {}, 408 | { 409 | make(OpArray, {0}), 410 | make(OpPop, {}), 411 | }, 412 | }, 413 | { 414 | "[1, 2, 3]", 415 | { 416 | make_integer(1), 417 | make_integer(2), 418 | make_integer(3), 419 | }, 420 | { 421 | make(OpConstant, {0}), 422 | make(OpConstant, {1}), 423 | make(OpConstant, {2}), 424 | make(OpArray, {3}), 425 | make(OpPop, {}), 426 | }, 427 | }, 428 | { 429 | "[1 + 2, 3 - 4, 5 * 6]", 430 | { 431 | make_integer(1), 432 | make_integer(2), 433 | make_integer(3), 434 | make_integer(4), 435 | make_integer(5), 436 | make_integer(6), 437 | }, 438 | { 439 | make(OpConstant, {0}), 440 | make(OpConstant, {1}), 441 | make(OpAdd, {}), 442 | make(OpConstant, {2}), 443 | make(OpConstant, {3}), 444 | make(OpSub, {}), 445 | make(OpConstant, {4}), 446 | make(OpConstant, {5}), 447 | make(OpMul, {}), 448 | make(OpArray, {3}), 449 | make(OpPop, {}), 450 | }, 451 | }, 452 | }; 453 | 454 | run_compiler_test("([compiler]: Array Literals)", tests); 455 | } 456 | 457 | TEST_CASE("Hash Literals", "[compiler]") { 458 | vector tests{ 459 | { 460 | "{}", 461 | {}, 462 | { 463 | make(OpHash, {0}), 464 | make(OpPop, {}), 465 | }, 466 | }, 467 | { 468 | "{1: 2, 3: 4, 5: 6}", 469 | { 470 | make_integer(1), 471 | make_integer(2), 472 | make_integer(3), 473 | make_integer(4), 474 | make_integer(5), 475 | make_integer(6), 476 | }, 477 | { 478 | make(OpConstant, {0}), 479 | make(OpConstant, {1}), 480 | make(OpConstant, {2}), 481 | make(OpConstant, {3}), 482 | make(OpConstant, {4}), 483 | make(OpConstant, {5}), 484 | make(OpHash, {6}), 485 | make(OpPop, {}), 486 | }, 487 | }, 488 | { 489 | "{1: 2 + 3, 4: 5 * 6}", 490 | { 491 | make_integer(1), 492 | make_integer(2), 493 | make_integer(3), 494 | make_integer(4), 495 | make_integer(5), 496 | make_integer(6), 497 | }, 498 | { 499 | make(OpConstant, {0}), 500 | make(OpConstant, {1}), 501 | make(OpConstant, {2}), 502 | make(OpAdd, {}), 503 | make(OpConstant, {3}), 504 | make(OpConstant, {4}), 505 | make(OpConstant, {5}), 506 | make(OpMul, {}), 507 | make(OpHash, {4}), 508 | make(OpPop, {}), 509 | }, 510 | }, 511 | }; 512 | 513 | run_compiler_test("([compiler]: Hash Literals)", tests); 514 | } 515 | 516 | TEST_CASE("Index Expressions", "[compiler]") { 517 | vector tests{ 518 | { 519 | "[1, 2, 3][1 + 1]", 520 | { 521 | make_integer(1), 522 | make_integer(2), 523 | make_integer(3), 524 | make_integer(1), 525 | make_integer(1), 526 | }, 527 | { 528 | make(OpConstant, {0}), 529 | make(OpConstant, {1}), 530 | make(OpConstant, {2}), 531 | make(OpArray, {3}), 532 | make(OpConstant, {3}), 533 | make(OpConstant, {4}), 534 | make(OpAdd, {}), 535 | make(OpIndex, {}), 536 | make(OpPop, {}), 537 | }, 538 | }, 539 | { 540 | "{1: 2}[2 - 1]", 541 | { 542 | make_integer(1), 543 | make_integer(2), 544 | make_integer(2), 545 | make_integer(1), 546 | }, 547 | { 548 | make(OpConstant, {0}), 549 | make(OpConstant, {1}), 550 | make(OpHash, {2}), 551 | make(OpConstant, {2}), 552 | make(OpConstant, {3}), 553 | make(OpSub, {}), 554 | make(OpIndex, {}), 555 | make(OpPop, {}), 556 | }, 557 | }, 558 | }; 559 | 560 | run_compiler_test("([compiler]: Index Expressions)", tests); 561 | } 562 | 563 | TEST_CASE("Functions", "[compiler]") { 564 | vector tests{ 565 | { 566 | "fn() { return 5 + 10 }", 567 | { 568 | make_integer(5), 569 | make_integer(10), 570 | make_compiled_function({ 571 | make(OpConstant, {0}), 572 | make(OpConstant, {1}), 573 | make(OpAdd, {}), 574 | make(OpReturnValue, {}), 575 | }), 576 | }, 577 | { 578 | make(OpClosure, {2, 0}), 579 | make(OpPop, {}), 580 | }, 581 | }, 582 | { 583 | "fn() { 5 + 10 }", 584 | { 585 | make_integer(5), 586 | make_integer(10), 587 | make_compiled_function({ 588 | make(OpConstant, {0}), 589 | make(OpConstant, {1}), 590 | make(OpAdd, {}), 591 | make(OpReturnValue, {}), 592 | }), 593 | }, 594 | { 595 | make(OpClosure, {2, 0}), 596 | make(OpPop, {}), 597 | }, 598 | }, 599 | { 600 | "fn() { 1; 2 }", 601 | { 602 | make_integer(1), 603 | make_integer(2), 604 | make_compiled_function({ 605 | make(OpConstant, {0}), 606 | make(OpPop, {}), 607 | make(OpConstant, {1}), 608 | make(OpReturnValue, {}), 609 | }), 610 | }, 611 | { 612 | make(OpClosure, {2, 0}), 613 | make(OpPop, {}), 614 | }, 615 | }, 616 | }; 617 | 618 | run_compiler_test("([compiler]: Functions)", tests); 619 | } 620 | 621 | TEST_CASE("Functions Without Return Value", "[compiler]") { 622 | vector tests{ 623 | { 624 | "fn() { }", 625 | { 626 | make_compiled_function({ 627 | make(OpReturn, {}), 628 | }), 629 | }, 630 | { 631 | make(OpClosure, {0, 0}), 632 | make(OpPop, {}), 633 | }, 634 | }, 635 | }; 636 | 637 | run_compiler_test("([compiler]: Functions Without Return Value)", tests); 638 | } 639 | 640 | TEST_CASE("Function Calls", "[compiler]") { 641 | vector tests{ 642 | { 643 | "fn() { 24 }();", 644 | { 645 | make_integer(24), 646 | make_compiled_function({ 647 | make(OpConstant, {0}), 648 | make(OpReturnValue, {}), 649 | }), 650 | }, 651 | { 652 | make(OpClosure, {1, 0}), 653 | make(OpCall, {0}), 654 | make(OpPop, {}), 655 | }, 656 | }, 657 | { 658 | R"( 659 | let noArg = fn() { 24 }; 660 | noArg(); 661 | )", 662 | { 663 | make_integer(24), 664 | make_compiled_function({ 665 | make(OpConstant, {0}), 666 | make(OpReturnValue, {}), 667 | }), 668 | }, 669 | { 670 | make(OpClosure, {1, 0}), 671 | make(OpSetGlobal, {0}), 672 | make(OpGetGlobal, {0}), 673 | make(OpCall, {0}), 674 | make(OpPop, {}), 675 | }, 676 | }, 677 | { 678 | R"( 679 | let oneArg = fn(a) { a }; 680 | oneArg(24); 681 | )", 682 | { 683 | make_compiled_function({ 684 | make(OpGetLocal, {0}), 685 | make(OpReturnValue, {}), 686 | }), 687 | make_integer(24), 688 | }, 689 | { 690 | make(OpClosure, {0, 0}), 691 | make(OpSetGlobal, {0}), 692 | make(OpGetGlobal, {0}), 693 | make(OpConstant, {1}), 694 | make(OpCall, {1}), 695 | make(OpPop, {}), 696 | }, 697 | }, 698 | { 699 | R"( 700 | let manyArg = fn(a, b, c) { a; b; c }; 701 | manyArg(24, 25, 26); 702 | )", 703 | { 704 | make_compiled_function({ 705 | make(OpGetLocal, {0}), 706 | make(OpPop, {}), 707 | make(OpGetLocal, {1}), 708 | make(OpPop, {}), 709 | make(OpGetLocal, {2}), 710 | make(OpReturnValue, {}), 711 | }), 712 | make_integer(24), 713 | make_integer(25), 714 | make_integer(26), 715 | }, 716 | { 717 | make(OpClosure, {0, 0}), 718 | make(OpSetGlobal, {0}), 719 | make(OpGetGlobal, {0}), 720 | make(OpConstant, {1}), 721 | make(OpConstant, {2}), 722 | make(OpConstant, {3}), 723 | make(OpCall, {3}), 724 | make(OpPop, {}), 725 | }, 726 | }, 727 | }; 728 | 729 | run_compiler_test("([compiler]: Function Calls)", tests); 730 | } 731 | 732 | TEST_CASE("Let Statement Scopes", "[compiler]") { 733 | vector tests{ 734 | { 735 | R"( 736 | let num = 55; 737 | fn() { num } 738 | )", 739 | { 740 | make_integer(55), 741 | make_compiled_function({ 742 | make(OpGetGlobal, {0}), 743 | make(OpReturnValue, {}), 744 | }), 745 | }, 746 | { 747 | make(OpConstant, {0}), 748 | make(OpSetGlobal, {0}), 749 | make(OpClosure, {1, 0}), 750 | make(OpPop, {}), 751 | }, 752 | }, 753 | { 754 | R"( 755 | fn() { 756 | let num = 55; 757 | num 758 | } 759 | )", 760 | { 761 | make_integer(55), 762 | make_compiled_function({ 763 | make(OpConstant, {0}), 764 | make(OpSetLocal, {0}), 765 | make(OpGetLocal, {0}), 766 | make(OpReturnValue, {}), 767 | }), 768 | }, 769 | { 770 | make(OpClosure, {1, 0}), 771 | make(OpPop, {}), 772 | }, 773 | }, 774 | { 775 | R"( 776 | fn() { 777 | let a = 55; 778 | let b = 77; 779 | a + b 780 | } 781 | )", 782 | { 783 | make_integer(55), 784 | make_integer(77), 785 | make_compiled_function({ 786 | make(OpConstant, {0}), 787 | make(OpSetLocal, {0}), 788 | make(OpConstant, {1}), 789 | make(OpSetLocal, {1}), 790 | make(OpGetLocal, {0}), 791 | make(OpGetLocal, {1}), 792 | make(OpAdd, {}), 793 | make(OpReturnValue, {}), 794 | }), 795 | }, 796 | { 797 | make(OpClosure, {2, 0}), 798 | make(OpPop, {}), 799 | }, 800 | }, 801 | }; 802 | 803 | run_compiler_test("([compiler]: Let Statement Scopes)", tests); 804 | } 805 | 806 | TEST_CASE("Compiler Scopes", "[compiler]") { 807 | Compiler compiler; 808 | REQUIRE(compiler.scopeIndex == 0); 809 | 810 | auto globalSymbolTable = compiler.symbolTable; 811 | 812 | compiler.emit(OpMul, {}); 813 | 814 | compiler.enter_scope(); 815 | REQUIRE(compiler.scopeIndex == 1); 816 | 817 | compiler.emit(OpSub, {}); 818 | 819 | REQUIRE(compiler.current_instructions().size() == 1); 820 | 821 | auto last = compiler.last_instruction(); 822 | REQUIRE(last.opecode == OpSub); 823 | 824 | REQUIRE(compiler.symbolTable->outer == globalSymbolTable); 825 | 826 | compiler.leave_scope(); 827 | REQUIRE(compiler.scopeIndex == 0); 828 | 829 | REQUIRE(compiler.symbolTable == globalSymbolTable); 830 | REQUIRE(!compiler.symbolTable->outer); 831 | 832 | compiler.emit(OpAdd, {}); 833 | REQUIRE(compiler.current_instructions().size() == 2); 834 | 835 | last = compiler.last_instruction(); 836 | REQUIRE(last.opecode == OpAdd); 837 | 838 | auto previous = compiler.previous_instruction(); 839 | REQUIRE(previous.opecode == OpMul); 840 | } 841 | 842 | TEST_CASE("Builtins", "[compiler]") { 843 | vector tests{ 844 | { 845 | R"( 846 | len([]); 847 | push([], 1); 848 | )", 849 | { 850 | make_integer(1), 851 | }, 852 | { 853 | make(OpGetBuiltin, {0}), 854 | make(OpArray, {0}), 855 | make(OpCall, {1}), 856 | make(OpPop, {}), 857 | make(OpGetBuiltin, {5}), 858 | make(OpArray, {0}), 859 | make(OpConstant, {0}), 860 | make(OpCall, {2}), 861 | make(OpPop, {}), 862 | }, 863 | }, 864 | { 865 | R"( 866 | fn() { len([]); } 867 | )", 868 | { 869 | make_compiled_function({ 870 | make(OpGetBuiltin, {0}), 871 | make(OpArray, {0}), 872 | make(OpCall, {1}), 873 | make(OpReturnValue, {}), 874 | }), 875 | }, 876 | { 877 | make(OpClosure, {0, 0}), 878 | make(OpPop, {}), 879 | }, 880 | }, 881 | }; 882 | 883 | run_compiler_test("([compiler]: Builtins)", tests); 884 | } 885 | 886 | TEST_CASE("Closure", "[compiler]") { 887 | vector tests{ 888 | { 889 | R"( 890 | fn(a) { 891 | fn(b) { 892 | a + b 893 | } 894 | } 895 | )", 896 | { 897 | make_compiled_function({ 898 | make(OpGetFree, {0}), 899 | make(OpGetLocal, {0}), 900 | make(OpAdd, {}), 901 | make(OpReturnValue, {}), 902 | }), 903 | make_compiled_function({ 904 | make(OpGetLocal, {0}), 905 | make(OpClosure, {0, 1}), 906 | make(OpReturnValue, {}), 907 | }), 908 | }, 909 | { 910 | make(OpClosure, {1, 0}), 911 | make(OpPop, {}), 912 | }, 913 | }, 914 | { 915 | R"( 916 | fn(a) { 917 | fn(b) { 918 | fn(c) { 919 | a + b + c 920 | } 921 | } 922 | } 923 | )", 924 | { 925 | make_compiled_function({ 926 | make(OpGetFree, {0}), 927 | make(OpGetFree, {1}), 928 | make(OpAdd, {}), 929 | make(OpGetLocal, {0}), 930 | make(OpAdd, {}), 931 | make(OpReturnValue, {}), 932 | }), 933 | make_compiled_function({ 934 | make(OpGetFree, {0}), 935 | make(OpGetLocal, {0}), 936 | make(OpClosure, {0, 2}), 937 | make(OpReturnValue, {}), 938 | }), 939 | make_compiled_function({ 940 | make(OpGetLocal, {0}), 941 | make(OpClosure, {1, 1}), 942 | make(OpReturnValue, {}), 943 | }), 944 | }, 945 | { 946 | make(OpClosure, {2, 0}), 947 | make(OpPop, {}), 948 | }, 949 | }, 950 | { 951 | R"( 952 | let global = 55; 953 | 954 | fn() { 955 | let a = 66; 956 | 957 | fn() { 958 | let b = 77; 959 | 960 | fn() { 961 | let c = 88; 962 | 963 | global + a + b + c; 964 | } 965 | } 966 | } 967 | )", 968 | { 969 | make_integer(55), 970 | make_integer(66), 971 | make_integer(77), 972 | make_integer(88), 973 | make_compiled_function({ 974 | make(OpConstant, {3}), 975 | make(OpSetLocal, {0}), 976 | make(OpGetGlobal, {0}), 977 | make(OpGetFree, {0}), 978 | make(OpAdd, {}), 979 | make(OpGetFree, {1}), 980 | make(OpAdd, {}), 981 | make(OpGetLocal, {0}), 982 | make(OpAdd, {}), 983 | make(OpReturnValue, {}), 984 | }), 985 | make_compiled_function({ 986 | make(OpConstant, {2}), 987 | make(OpSetLocal, {0}), 988 | make(OpGetFree, {0}), 989 | make(OpGetLocal, {0}), 990 | make(OpClosure, {4, 2}), 991 | make(OpReturnValue, {}), 992 | }), 993 | make_compiled_function({ 994 | make(OpConstant, {1}), 995 | make(OpSetLocal, {0}), 996 | make(OpGetLocal, {0}), 997 | make(OpClosure, {5, 1}), 998 | make(OpReturnValue, {}), 999 | }), 1000 | }, 1001 | { 1002 | make(OpConstant, {0}), 1003 | make(OpSetGlobal, {0}), 1004 | make(OpClosure, {6, 0}), 1005 | make(OpPop, {}), 1006 | }, 1007 | }, 1008 | }; 1009 | 1010 | run_compiler_test("([compiler]: Closure)", tests); 1011 | } 1012 | 1013 | TEST_CASE("Resursive Functions", "[compiler]") { 1014 | vector tests{ 1015 | { 1016 | R"( 1017 | let countDown = fn(x) { countDown(x - 1); }; 1018 | countDown(1); 1019 | )", 1020 | { 1021 | make_integer(1), 1022 | make_compiled_function({ 1023 | make(OpCurrentClosure, {}), 1024 | make(OpGetLocal, {0}), 1025 | make(OpConstant, {0}), 1026 | make(OpSub, {}), 1027 | make(OpCall, {1}), 1028 | make(OpReturnValue, {}), 1029 | }), 1030 | make_integer(1), 1031 | }, 1032 | { 1033 | make(OpClosure, {1, 0}), 1034 | make(OpSetGlobal, {0}), 1035 | make(OpGetGlobal, {0}), 1036 | make(OpConstant, {2}), 1037 | make(OpCall, {1}), 1038 | make(OpPop, {}), 1039 | }, 1040 | }, 1041 | { 1042 | R"( 1043 | let wrapper = fn() { 1044 | let countDown = fn(x) { countDown(x - 1); }; 1045 | countDown(1); 1046 | }; 1047 | wrapper(); 1048 | )", 1049 | { 1050 | make_integer(1), 1051 | make_compiled_function({ 1052 | make(OpCurrentClosure, {}), 1053 | make(OpGetLocal, {0}), 1054 | make(OpConstant, {0}), 1055 | make(OpSub, {}), 1056 | make(OpCall, {1}), 1057 | make(OpReturnValue, {}), 1058 | }), 1059 | make_integer(1), 1060 | make_compiled_function({ 1061 | make(OpClosure, {1, 0}), 1062 | make(OpSetLocal, {0}), 1063 | make(OpGetLocal, {0}), 1064 | make(OpConstant, {2}), 1065 | make(OpCall, {1}), 1066 | make(OpReturnValue, {}), 1067 | }), 1068 | }, 1069 | { 1070 | make(OpClosure, {3, 0}), 1071 | make(OpSetGlobal, {0}), 1072 | make(OpGetGlobal, {0}), 1073 | make(OpCall, {0}), 1074 | make(OpPop, {}), 1075 | }, 1076 | }, 1077 | }; 1078 | 1079 | run_compiler_test("([compiler]: Resursive Functions)", tests); 1080 | } 1081 | -------------------------------------------------------------------------------- /test/test-evaluator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "catch.hpp" 4 | #include "test-util.hpp" 5 | 6 | using namespace std; 7 | using namespace peg::udl; 8 | using namespace monkey; 9 | 10 | shared_ptr testEval(const string &input) { 11 | auto ast = parse("([evaluator])", input); 12 | // cout << ast_to_s(ast) << endl; 13 | REQUIRE(ast != nullptr); 14 | return eval(ast, monkey::environment()); 15 | } 16 | 17 | void testIntegerObject(shared_ptr evaluated, int64_t expected) { 18 | REQUIRE(evaluated->type() == INTEGER_OBJ); 19 | CHECK(cast(evaluated).value == expected); 20 | } 21 | 22 | void testBooleanObject(shared_ptr evaluated, int64_t expected) { 23 | REQUIRE(evaluated->type() == BOOLEAN_OBJ); 24 | CHECK(cast(evaluated).value == expected); 25 | } 26 | 27 | void testNullObject(shared_ptr evaluated) { 28 | REQUIRE(evaluated->type() == NULL_OBJ); 29 | CHECK(evaluated.get() == CONST_NULL.get()); 30 | } 31 | 32 | void testStringObject(shared_ptr evaluated, const char *expected) { 33 | CHECK(evaluated->type() == STRING_OBJ); 34 | CHECK(cast(evaluated).value == expected); 35 | } 36 | 37 | void testObject(shared_ptr evaluated, 38 | const shared_ptr expected) { 39 | CHECK(evaluated->type() == expected->type()); 40 | if (evaluated->type() == INTEGER_OBJ) { 41 | CHECK(cast(evaluated).value == cast(expected).value); 42 | } else if (evaluated->type() == ERROR_OBJ) { 43 | CHECK(cast(evaluated).message == cast(expected).message); 44 | } else if (evaluated->type() == NULL_OBJ) { 45 | testNullObject(expected); 46 | testNullObject(evaluated); 47 | } 48 | } 49 | 50 | TEST_CASE("Eval integer expression", "[evaluator]") { 51 | struct Test { 52 | string input; 53 | int64_t expected; 54 | }; 55 | 56 | Test tests[] = { 57 | {"5", 5}, 58 | {"10", 10}, 59 | {"-5", -5}, 60 | {"-10", -10}, 61 | {"5 + 5 + 5 + 5 - 10", 10}, 62 | {"2 * 2 * 2 * 2 * 2", 32}, 63 | {"-50 + 100 + -50", 0}, 64 | {"5 * 2 + 10", 20}, 65 | {"5 + 2 * 10", 25}, 66 | {"20 + 2 * -10", 0}, 67 | {"50 / 2 * 2 + 10", 60}, 68 | {"2 * (5 + 10)", 30}, 69 | {"3 * 3 * 3 + 10", 37}, 70 | {"3 * (3 * 3) + 10", 37}, 71 | {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, 72 | }; 73 | 74 | for (const auto &t : tests) { 75 | testIntegerObject(testEval(t.input), t.expected); 76 | } 77 | } 78 | 79 | TEST_CASE("Eval boolean expression", "[evaluator]") { 80 | struct Test { 81 | string input; 82 | bool expected; 83 | }; 84 | 85 | Test tests[] = { 86 | {"true", true}, 87 | {"false", false}, 88 | {"1 < 2", true}, 89 | {"1 > 2", false}, 90 | {"1 < 1", false}, 91 | {"1 > 1", false}, 92 | {"1 == 1", true}, 93 | {"1 != 1", false}, 94 | {"1 == 2", false}, 95 | {"1 != 2", true}, 96 | {"true == true", true}, 97 | {"false == false", true}, 98 | {"true == false", false}, 99 | {"true != false", true}, 100 | {"false != true", true}, 101 | {"(1 < 2) == true", true}, 102 | {"(1 < 2) == false", false}, 103 | {"(1 > 2) == true", false}, 104 | {"(1 > 2) == false", true}, 105 | }; 106 | 107 | for (const auto &t : tests) { 108 | testBooleanObject(testEval(t.input), t.expected); 109 | } 110 | } 111 | 112 | TEST_CASE("Bang operator", "[evaluator]") { 113 | struct Test { 114 | string input; 115 | bool expected; 116 | }; 117 | 118 | Test tests[] = { 119 | {"!true", false}, {"!false", true}, {"!5", false}, 120 | {"!!true", true}, {"!!false", false}, {"!!5", true}, 121 | }; 122 | 123 | for (const auto &t : tests) { 124 | testBooleanObject(testEval(t.input), t.expected); 125 | } 126 | } 127 | 128 | TEST_CASE("If else expressions", "[evaluator]") { 129 | struct Test { 130 | string input; 131 | shared_ptr expected; 132 | }; 133 | 134 | Test tests[] = { 135 | {"if (true) { 10 }", make_integer(10)}, 136 | {"if (false) { 10 }", CONST_NULL}, 137 | {"if (1) { 10 }", make_integer(10)}, 138 | {"if (1 < 2) { 10 }", make_integer(10)}, 139 | {"if (1 > 2) { 10 }", CONST_NULL}, 140 | {"if (1 > 2) { 10 } else { 20 }", make_integer(20)}, 141 | {"if (1 < 2) { 10 } else { 20 }", make_integer(10)}, 142 | }; 143 | 144 | for (const auto &t : tests) { 145 | testObject(testEval(t.input), t.expected); 146 | } 147 | } 148 | 149 | TEST_CASE("Return statements", "[evaluator]") { 150 | struct Test { 151 | string input; 152 | int64_t expected; 153 | }; 154 | 155 | Test tests[] = { 156 | {"return 10;", 10}, 157 | {"return 10; 9;", 10}, 158 | {"return 2 * 5; 9;", 10}, 159 | {"9; return 2 * 5; 9;", 10}, 160 | {"if (10 > 1) { return 10; }", 10}, 161 | { 162 | R"( 163 | if (10 > 1) { 164 | if (10 > 1) { 165 | return 10; 166 | } 167 | 168 | return 1; 169 | } 170 | )", 171 | 10, 172 | }, 173 | { 174 | R"( 175 | let f = fn(x) { 176 | return x; 177 | x + 10; 178 | }; 179 | f(10);)", 180 | 10, 181 | }, 182 | { 183 | R"( 184 | let f = fn(x) { 185 | let result = x + 10; 186 | return result; 187 | return 10; 188 | }; 189 | f(10);)", 190 | 20, 191 | }, 192 | }; 193 | 194 | for (const auto &t : tests) { 195 | testIntegerObject(testEval(t.input), t.expected); 196 | } 197 | } 198 | 199 | TEST_CASE("Error handling", "[evaluator]") { 200 | struct Test { 201 | string input; 202 | string expectedMessage; 203 | }; 204 | 205 | Test tests[] = { 206 | { 207 | "5 + true;", 208 | "type mismatch: INTEGER + BOOLEAN", 209 | }, 210 | { 211 | "5 + true; 5;", 212 | "type mismatch: INTEGER + BOOLEAN", 213 | }, 214 | { 215 | "-true", 216 | "unknown operator: -BOOLEAN", 217 | }, 218 | { 219 | "-true", 220 | "unknown operator: -BOOLEAN", 221 | }, 222 | { 223 | "true + false;", 224 | "unknown operator: BOOLEAN + BOOLEAN", 225 | }, 226 | { 227 | "true + false + true + false;", 228 | "unknown operator: BOOLEAN + BOOLEAN", 229 | }, 230 | { 231 | "5; true + false; 5", 232 | "unknown operator: BOOLEAN + BOOLEAN", 233 | }, 234 | { 235 | "if (10 > 1) { true + false; }", 236 | "unknown operator: BOOLEAN + BOOLEAN", 237 | }, 238 | { 239 | R"( 240 | if (10 > 1) { 241 | if (10 > 1) { 242 | return true + false; 243 | } 244 | 245 | return 1; 246 | } 247 | )", 248 | "unknown operator: BOOLEAN + BOOLEAN", 249 | }, 250 | { 251 | "foobar", 252 | "identifier not found: foobar", 253 | }, 254 | { 255 | R"("Hello" - "World")", 256 | "unknown operator: STRING - STRING", 257 | }, 258 | { 259 | R"({"name": "Monkey"}[fn(x) { x }];)", 260 | "unusable as hash key: FUNCTION", 261 | }, 262 | }; 263 | 264 | for (const auto &t : tests) { 265 | auto ast = parse("([evaluator])", t.input); 266 | REQUIRE(ast != nullptr); 267 | 268 | auto env = monkey::environment(); 269 | auto val = eval(ast, env); 270 | CHECK(val->type() == ERROR_OBJ); 271 | 272 | CHECK(cast(val).message == t.expectedMessage); 273 | } 274 | } 275 | 276 | TEST_CASE("Let statements", "[evaluator]") { 277 | struct Test { 278 | string input; 279 | int64_t expected; 280 | }; 281 | 282 | Test tests[] = { 283 | {"let a = 5; a;", 5}, 284 | {"let a = 5 * 5; a;", 25}, 285 | {"let a = 5; let b = a; b;", 5}, 286 | {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, 287 | }; 288 | 289 | for (const auto &t : tests) { 290 | testIntegerObject(testEval(t.input), t.expected); 291 | } 292 | } 293 | 294 | TEST_CASE("Function object", "[evaluator]") { 295 | std::string input = "fn(x) { x + 2; };"; 296 | 297 | auto val = testEval(input); 298 | REQUIRE(val->type() == FUNCTION_OBJ); 299 | 300 | const auto &fn = cast(val); 301 | 302 | REQUIRE(fn.params.size() == 1); 303 | CHECK(fn.params[0] == "x"); 304 | 305 | const auto expectedBody = "(x + 2)"; 306 | CHECK(to_string(fn.body) == expectedBody); 307 | } 308 | 309 | TEST_CASE("Function application", "[evaluator]") { 310 | struct Test { 311 | string input; 312 | int64_t expected; 313 | }; 314 | 315 | Test tests[] = { 316 | {"let identity = fn(x) { x; }; identity(5);", 5}, 317 | {"let identity = fn(x) { return x; }; identity(5);", 5}, 318 | {"let double = fn(x) { x * 2; }; double(5);", 10}, 319 | {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, 320 | {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, 321 | {"fn(x) { x; }(5)", 5}, 322 | }; 323 | 324 | for (const auto &t : tests) { 325 | testIntegerObject(testEval(t.input), t.expected); 326 | } 327 | } 328 | 329 | TEST_CASE("Enclosing environments", "[evaluator]") { 330 | std::string input = R"( 331 | let first = 10; 332 | let second = 10; 333 | let third = 10; 334 | 335 | let ourFunction = fn(first) { 336 | let second = 20; 337 | 338 | first + second + third; 339 | }; 340 | 341 | ourFunction(20) + first + second; 342 | )"; 343 | 344 | testIntegerObject(testEval(input), 70); 345 | } 346 | 347 | TEST_CASE("Closures", "[evaluator]") { 348 | std::string input = R"( 349 | let newAdder = fn(x) { 350 | fn(y) { x + y }; 351 | }; 352 | 353 | let addTwo = newAdder(2); 354 | addTwo(2); 355 | )"; 356 | 357 | testIntegerObject(testEval(input), 4); 358 | } 359 | 360 | TEST_CASE("String literal", "[evaluator]") { 361 | std::string input = R"("Hello World!")"; 362 | testStringObject(testEval(input), "Hello World!"); 363 | } 364 | 365 | TEST_CASE("String concatenation", "[evaluator]") { 366 | std::string input = R"("Hello" + " " + "World!")"; 367 | testStringObject(testEval(input), "Hello World!"); 368 | } 369 | 370 | TEST_CASE("Builtin functions", "[evaluator]") { 371 | struct Test { 372 | string input; 373 | shared_ptr expected; 374 | }; 375 | 376 | Test tests[] = { 377 | {R"(len(""))", make_integer(0)}, 378 | {R"(len("four"))", make_integer(4)}, 379 | {R"(len("hello world"))", make_integer(11)}, 380 | {R"(len(1))", make_error("argument to `len` not supported, got INTEGER")}, 381 | {R"(len("one", "two"))", 382 | make_error("wrong number of arguments. got=2, want=1")}, 383 | {R"(len([1, 2, 3]))", make_integer(3)}, 384 | {R"(len([]))", make_integer(0)}, 385 | {R"(puts("hello", "world!"))", CONST_NULL}, 386 | {R"(first([1, 2, 3]))", make_integer(1)}, 387 | {R"(first([]))", CONST_NULL}, 388 | {R"(first(1))", 389 | make_error("argument to `first` must be ARRAY, got INTEGER")}, 390 | {R"(last([1, 2, 3]))", make_integer(3)}, 391 | {R"(last([]))", CONST_NULL}, 392 | {R"(last(1))", 393 | make_error("argument to `last` must be ARRAY, got INTEGER")}, 394 | {R"(rest([1, 2, 3]))", make_array({2, 3})}, 395 | {R"(rest([]))", CONST_NULL}, 396 | {R"(push([], 1))", make_array({1})}, 397 | {R"(push(1, 1))", 398 | make_error("argument to `push` must be ARRAY, got INTEGER")}, 399 | }; 400 | 401 | for (const auto &t : tests) { 402 | testObject(testEval(t.input), t.expected); 403 | } 404 | } 405 | 406 | TEST_CASE("Array literals", "[evaluator]") { 407 | std::string input = "[1, 2 * 2, 3 + 3]"; 408 | auto val = testEval(input); 409 | REQUIRE(val->type() == ARRAY_OBJ); 410 | 411 | const auto &arr = cast(val); 412 | REQUIRE(arr.elements.size() == 3); 413 | 414 | testIntegerObject(arr.elements[0], 1); 415 | testIntegerObject(arr.elements[1], 4); 416 | testIntegerObject(arr.elements[2], 6); 417 | } 418 | 419 | TEST_CASE("Array index expressions", "[evaluator]") { 420 | struct Test { 421 | string input; 422 | shared_ptr expected; 423 | }; 424 | 425 | Test tests[] = { 426 | {"[1, 2, 3][0]", make_integer(1)}, 427 | {"[1, 2, 3][1]", make_integer(2)}, 428 | {"[1, 2, 3][2]", make_integer(3)}, 429 | {"let i = 0; [1][i];", make_integer(1)}, 430 | {"[1, 2, 3][1 + 1];", make_integer(3)}, 431 | {"let myArray = [1, 2, 3]; myArray[2];", make_integer(3)}, 432 | {"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", 433 | make_integer(6)}, 434 | {"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", 435 | make_integer(2)}, 436 | {"[1, 2, 3][3]", CONST_NULL}, 437 | {"[1, 2, 3][-1]", CONST_NULL}, 438 | }; 439 | 440 | for (const auto &t : tests) { 441 | testObject(testEval(t.input), t.expected); 442 | } 443 | } 444 | 445 | TEST_CASE("Hash literals", "[evaluator]") { 446 | std::string input = R"(let two = "two"; 447 | { 448 | "one": 10 - 9, 449 | two: 1 + 1, 450 | "thr" + "ee": 6 / 2, 451 | 4: 4, 452 | true: 5, 453 | false: 6 454 | })"; 455 | 456 | auto evaluated = testEval(input); 457 | REQUIRE(evaluated->type() == HASH_OBJ); 458 | 459 | map expected{ 460 | {make_string("one")->hash_key(), int64_t(1)}, 461 | {make_string("two")->hash_key(), int64_t(2)}, 462 | {make_string("three")->hash_key(), int64_t(3)}, 463 | {make_integer(4)->hash_key(), int64_t(4)}, 464 | {CONST_TRUE->hash_key(), int64_t(5)}, 465 | {CONST_FALSE->hash_key(), int64_t(6)}, 466 | }; 467 | 468 | REQUIRE(cast(evaluated).pairs.size() == expected.size()); 469 | 470 | for (auto [expectedKey, expectedValue] : expected) { 471 | auto &pair = cast(evaluated).pairs[expectedKey]; 472 | testIntegerObject(pair.value, expectedValue); 473 | } 474 | } 475 | 476 | TEST_CASE("Hash index expressions", "[evaluator]") { 477 | struct Test { 478 | string input; 479 | shared_ptr expected; 480 | }; 481 | 482 | Test tests[] = { 483 | {R"({"foo": 5}["foo"])", make_integer(5)}, 484 | {R"({"foo": 5}["bar"])", CONST_NULL}, 485 | {R"(let key = "foo"; {"foo": 5}[key])", make_integer(5)}, 486 | {R"({}["foo"])", CONST_NULL}, 487 | {R"({5: 5}[5])", make_integer(5)}, 488 | {R"({true: 5}[true])", make_integer(5)}, 489 | {R"({false: 5}[false])", make_integer(5)}, 490 | }; 491 | 492 | for (const auto &t : tests) { 493 | testObject(testEval(t.input), t.expected); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /test/test-main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /test/test-object.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace monkey; 8 | 9 | TEST_CASE("String hash key", "[object]") { 10 | auto hello1_obj = make_string("Hello World"); 11 | auto hello2_obj = make_string("Hello World"); 12 | 13 | auto &hello1 = cast(hello1_obj); 14 | auto &hello2 = cast(hello2_obj); 15 | 16 | auto diff1_obj = make_string("My name is johnny"); 17 | auto diff2_obj = make_string("My name is johnny"); 18 | 19 | auto &diff1 = cast(diff1_obj); 20 | auto &diff2 = cast(diff2_obj); 21 | 22 | CHECK(hello1.hash_key() == hello2.hash_key()); 23 | CHECK(diff1.hash_key() == diff2.hash_key()); 24 | CHECK_FALSE(hello1.hash_key() == diff1.hash_key()); 25 | } 26 | -------------------------------------------------------------------------------- /test/test-parser.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | 4 | using namespace std; 5 | using namespace peg::udl; 6 | using namespace monkey; 7 | 8 | void testIntegerLiteral(const shared_ptr &ast, int64_t number) { 9 | CHECK(ast->name == "INTEGER"); 10 | CHECK(any_cast(ast->value) == number); 11 | } 12 | 13 | void testIdentifier(const shared_ptr &ast, const string &token) { 14 | CHECK(ast->name == "IDENTIFIER"); 15 | CHECK(ast->token == token); 16 | } 17 | 18 | void testBooleanLiteral(const shared_ptr &ast, int64_t value) { 19 | CHECK(ast->name == "BOOLEAN"); 20 | CHECK(any_cast(ast->value) == value); 21 | } 22 | 23 | void testStringLiteral(const shared_ptr &ast, const char *token) { 24 | CHECK(ast->name == "STRING"); 25 | CHECK(ast->token == token); 26 | } 27 | 28 | void testLiteralExpression(const shared_ptr &ast, any value) { 29 | switch (ast->tag) { 30 | case "INTEGER"_: testIntegerLiteral(ast, any_cast(value)); break; 31 | case "IDENTIFIER"_: testIdentifier(ast, any_cast(value)); break; 32 | case "BOOLEAN"_: testBooleanLiteral(ast, any_cast(value)); break; 33 | } 34 | } 35 | 36 | void testInfixExpression(const shared_ptr &ast, any leftValue, 37 | const string &operatorToken, any rightValue) { 38 | CHECK(ast->name == "INFIX_EXPR"); 39 | 40 | testLiteralExpression(ast->nodes[0], leftValue); 41 | 42 | CHECK(ast->nodes[1]->name == "INFIX_OPE"); 43 | CHECK(ast->nodes[1]->token == operatorToken); 44 | 45 | testLiteralExpression(ast->nodes[2], rightValue); 46 | } 47 | 48 | TEST_CASE("'let' statements", "[parser]") { 49 | struct Test { 50 | string input; 51 | string expectedIdentifier; 52 | any expectedValue; 53 | }; 54 | 55 | Test tests[] = { 56 | {"let x = 5;", "x", int64_t(5)}, 57 | {"let y = true;", "y", true}, 58 | {"let foobar = y;", "foobar", "y"}, 59 | }; 60 | 61 | for (const auto &t : tests) { 62 | auto ast = parse("([parser]: 'let' statements)", t.input); 63 | REQUIRE(ast != nullptr); 64 | REQUIRE(ast->name == "ASSIGNMENT"); 65 | 66 | testIdentifier(ast->nodes[0], t.expectedIdentifier); 67 | testLiteralExpression(ast->nodes[1], t.expectedValue); 68 | } 69 | } 70 | 71 | TEST_CASE("'return' statements", "[parser]") { 72 | struct Test { 73 | string input; 74 | any expectedValue; 75 | }; 76 | 77 | Test tests[] = { 78 | {"return 5;", int64_t(5)}, 79 | {"return true;", true}, 80 | {"return foobar;", "foobar"}, 81 | }; 82 | 83 | for (const auto &t : tests) { 84 | auto ast = parse("([parser]: 'return' statements)", t.input); 85 | REQUIRE(ast != nullptr); 86 | REQUIRE(ast->name == "RETURN"); 87 | 88 | testLiteralExpression(ast->nodes[0], t.expectedValue); 89 | } 90 | } 91 | 92 | TEST_CASE("Identifier expression", "[parser]") { 93 | std::string input = "foobar;"; 94 | auto ast = parse("([parser]: Identifier expression)", input); 95 | REQUIRE(ast != nullptr); 96 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 97 | 98 | testIdentifier(ast->nodes[0], "foobar"); 99 | } 100 | 101 | TEST_CASE("Integer literal expression", "[parser]") { 102 | auto ast = parse("([parser]: Integer literal expression)", "5;"); 103 | REQUIRE(ast != nullptr); 104 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 105 | 106 | testIntegerLiteral(ast->nodes[0], 5); 107 | } 108 | 109 | TEST_CASE("Parsing prefix expression", "[parser]") { 110 | struct Test { 111 | string input; 112 | string operatorToken; 113 | any value; 114 | }; 115 | 116 | Test tests[] = { 117 | {"!5;", "!", int64_t(5)}, {"-15;", "-", int64_t(15)}, 118 | {"!foobar;", "!", "foobar"}, {"-foobar;", "-", "foobar"}, 119 | {"!true;", "!", true}, {"!false;", "!", false}, 120 | }; 121 | 122 | for (const auto &t : tests) { 123 | auto ast = parse("([parser]: Parsing prefix expression)", t.input); 124 | REQUIRE(ast != nullptr); 125 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 126 | 127 | auto node = ast->nodes[0]; 128 | REQUIRE(node->name == "PREFIX_EXPR"); 129 | 130 | CHECK(node->nodes[0]->name == "PREFIX_OPE"); 131 | CHECK(node->nodes[0]->token == t.operatorToken); 132 | 133 | testLiteralExpression(node->nodes[1], t.value); 134 | } 135 | } 136 | 137 | TEST_CASE("Parsing infix expression", "[parser]") { 138 | struct Test { 139 | string input; 140 | any leftValue; 141 | string operatorToken; 142 | any rightValue; 143 | }; 144 | 145 | Test tests[] = { 146 | {"5 + 5;", int64_t(5), "+", int64_t(5)}, 147 | {"5 - 5;", int64_t(5), "-", int64_t(5)}, 148 | {"5 * 5;", int64_t(5), "*", int64_t(5)}, 149 | {"5 / 5;", int64_t(5), "/", int64_t(5)}, 150 | {"5 < 5;", int64_t(5), "<", int64_t(5)}, 151 | {"5 > 5;", int64_t(5), ">", int64_t(5)}, 152 | {"5 == 5;", int64_t(5), "==", int64_t(5)}, 153 | {"5 != 5;", int64_t(5), "!=", int64_t(5)}, 154 | {"foobar + barfoo;", "foobar", "+", "barfoo"}, 155 | {"foobar - barfoo;", "foobar", "-", "barfoo"}, 156 | {"foobar * barfoo;", "foobar", "*", "barfoo"}, 157 | {"foobar / barfoo;", "foobar", "/", "barfoo"}, 158 | {"foobar < barfoo;", "foobar", "<", "barfoo"}, 159 | {"foobar > barfoo;", "foobar", ">", "barfoo"}, 160 | {"foobar == barfoo;", "foobar", "==", "barfoo"}, 161 | {"foobar != barfoo;", "foobar", "!=", "barfoo"}, 162 | {"true == true;", true, "==", true}, 163 | {"true != false;", true, "!=", false}, 164 | {"false == false;", false, "==", false}, 165 | }; 166 | 167 | for (const auto &t : tests) { 168 | auto ast = parse("([parser]: Parsing infix expression)", t.input); 169 | REQUIRE(ast != nullptr); 170 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 171 | 172 | testInfixExpression(ast->nodes[0], t.leftValue, t.operatorToken, 173 | t.rightValue); 174 | } 175 | } 176 | 177 | TEST_CASE("Operator precedence parsing", "[parser]") { 178 | struct Test { 179 | string input; 180 | string expected; 181 | }; 182 | 183 | Test tests[] = { 184 | { 185 | "-a * b", 186 | "((-a) * b)", 187 | }, 188 | { 189 | "!-a", 190 | "(!(-a))", 191 | }, 192 | { 193 | "a + b + c", 194 | "((a + b) + c)", 195 | }, 196 | { 197 | "a + b - c", 198 | "((a + b) - c)", 199 | }, 200 | { 201 | "a * b * c", 202 | "((a * b) * c)", 203 | }, 204 | { 205 | "a * b / c", 206 | "((a * b) / c)", 207 | }, 208 | { 209 | "a + b / c", 210 | "(a + (b / c))", 211 | }, 212 | { 213 | "a + b * c + d / e - f", 214 | "(((a + (b * c)) + (d / e)) - f)", 215 | }, 216 | { 217 | "3 + 4; -5 * 5", 218 | "(3 + 4)((-5) * 5)", 219 | }, 220 | { 221 | "5 > 4 == 3 < 4", 222 | "((5 > 4) == (3 < 4))", 223 | }, 224 | { 225 | "5 < 4 != 3 > 4", 226 | "((5 < 4) != (3 > 4))", 227 | }, 228 | { 229 | "3 + 4 * 5 == 3 * 1 + 4 * 5", 230 | "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", 231 | }, 232 | { 233 | "true", 234 | "true", 235 | }, 236 | { 237 | "false", 238 | "false", 239 | }, 240 | { 241 | "3 > 5 == false", 242 | "((3 > 5) == false)", 243 | }, 244 | { 245 | "3 < 5 == true", 246 | "((3 < 5) == true)", 247 | }, 248 | { 249 | "1 + (2 + 3) + 4", 250 | "((1 + (2 + 3)) + 4)", 251 | }, 252 | { 253 | "(5 + 5) * 2", 254 | "((5 + 5) * 2)", 255 | }, 256 | { 257 | "2 / (5 + 5)", 258 | "(2 / (5 + 5))", 259 | }, 260 | { 261 | "(5 + 5) * 2 * (5 + 5)", 262 | "(((5 + 5) * 2) * (5 + 5))", 263 | }, 264 | { 265 | "-(5 + 5)", 266 | "(-(5 + 5))", 267 | }, 268 | { 269 | "!(true == true)", 270 | "(!(true == true))", 271 | }, 272 | { 273 | "a + add(b * c) + d", 274 | "((a + add((b * c))) + d)", 275 | }, 276 | { 277 | "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", 278 | "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", 279 | }, 280 | { 281 | "add(a + b + c * d / f + g)", 282 | "add((((a + b) + ((c * d) / f)) + g))", 283 | }, 284 | { 285 | "a * [1, 2, 3, 4][b * c] * d", 286 | "((a * ([1, 2, 3, 4][(b * c)])) * d)", 287 | }, 288 | { 289 | "a * [1, 2, 3, 4][b * c][0] * d", 290 | "((a * (([1, 2, 3, 4][(b * c)])[0])) * d)", 291 | }, 292 | { 293 | "add(a * b[2], b[1], 2 * [1, 2][1])", 294 | "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", 295 | }, 296 | }; 297 | 298 | for (const auto &t : tests) { 299 | auto ast = parse("([parser]: Operator precedence parsing)", t.input); 300 | 301 | REQUIRE(ast != nullptr); 302 | REQUIRE(to_string(ast) == t.expected); 303 | } 304 | } 305 | 306 | TEST_CASE("Boolean expression", "[parser]") { 307 | struct Test { 308 | string input; 309 | bool expectedBoolean; 310 | }; 311 | 312 | Test tests[] = { 313 | {"true;", true}, 314 | {"false;", false}, 315 | }; 316 | 317 | for (const auto &t : tests) { 318 | auto ast = parse("([parser]: Boolean expression)", t.input); 319 | REQUIRE(ast != nullptr); 320 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 321 | 322 | testBooleanLiteral(ast->nodes[0], t.expectedBoolean); 323 | } 324 | } 325 | 326 | TEST_CASE("If expression", "[parser]") { 327 | std::string input = "if (x < y) { x };"; 328 | auto ast = parse("([parser]: If expression)", input); 329 | REQUIRE(ast != nullptr); 330 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 331 | 332 | auto node = ast->nodes[0]; 333 | REQUIRE(node->name == "IF"); 334 | 335 | testInfixExpression(node->nodes[0], "x", "<", "y"); 336 | testIdentifier(node->nodes[1]->nodes[0]->nodes[0], "x"); 337 | } 338 | 339 | TEST_CASE("If else expression", "[parser]") { 340 | std::string input = "if (x < y) { x } else { y };"; 341 | auto ast = parse("([parser]: If else expression)", input); 342 | REQUIRE(ast != nullptr); 343 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 344 | 345 | auto node = ast->nodes[0]; 346 | REQUIRE(node->name == "IF"); 347 | 348 | testInfixExpression(node->nodes[0], "x", "<", "y"); 349 | testIdentifier(node->nodes[1]->nodes[0]->nodes[0], "x"); 350 | testIdentifier(node->nodes[2]->nodes[0]->nodes[0], "y"); 351 | } 352 | 353 | TEST_CASE("Function literal parsing", "[parser]") { 354 | std::string input = "fn(x, y) { x + y; }"; 355 | auto ast = parse("([parser]: Function literal parser)", input); 356 | REQUIRE(ast != nullptr); 357 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 358 | 359 | auto node = ast->nodes[0]; 360 | REQUIRE(node->name == "FUNCTION"); 361 | 362 | testIdentifier(node->nodes[0]->nodes[0], "x"); 363 | testIdentifier(node->nodes[0]->nodes[1], "y"); 364 | 365 | { 366 | const auto &node2 = node->nodes[1]->nodes[0]; 367 | REQUIRE(node2->name == "EXPRESSION_STATEMENT"); 368 | 369 | testInfixExpression(node2->nodes[0], "x", "+", "y"); 370 | } 371 | } 372 | 373 | TEST_CASE("Function parameter parsing", "[parser]") { 374 | struct Test { 375 | string input; 376 | vector expectedParams; 377 | }; 378 | 379 | Test tests[] = { 380 | {"fn() {};", {}}, 381 | {"fn(x) {};", {"x"}}, 382 | {"fn(x, y, z) {};", {"x", "y", "z"}}, 383 | }; 384 | 385 | for (const auto &t : tests) { 386 | auto ast = parse("([parser]: Function parameter parsing)", t.input); 387 | REQUIRE(ast != nullptr); 388 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 389 | 390 | auto node = ast->nodes[0]; 391 | REQUIRE(node->name == "FUNCTION"); 392 | 393 | auto nodes = node->nodes[0]->nodes; 394 | CHECK(nodes.size() == t.expectedParams.size()); 395 | 396 | for (size_t i = 0; i < nodes.size(); i++) { 397 | testIdentifier(nodes[i], t.expectedParams[i]); 398 | } 399 | } 400 | } 401 | 402 | TEST_CASE("Call expression parsing", "[parser]") { 403 | std::string input = "add(1, 2 * 3, 4 + 5);"; 404 | auto ast = parse("([parser]: Call expression parsing)", input); 405 | REQUIRE(ast != nullptr); 406 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 407 | 408 | auto node = ast->nodes[0]; 409 | REQUIRE(node->name == "CALL"); 410 | 411 | testIdentifier(node->nodes[0], "add"); 412 | 413 | REQUIRE(node->nodes[1]->name == "ARGUMENTS"); 414 | auto nodes = node->nodes[1]->nodes; 415 | testLiteralExpression(nodes[0], int64_t(1)); 416 | testInfixExpression(nodes[1], int64_t(2), "*", int64_t(3)); 417 | testInfixExpression(nodes[2], int64_t(4), "+", int64_t(5)); 418 | } 419 | 420 | TEST_CASE("Call expression parameter parsing", "[parser]") { 421 | struct Test { 422 | string input; 423 | string expectedIdent; 424 | vector expectedArgs; 425 | }; 426 | 427 | Test tests[] = { 428 | {"add();", "add", {}}, 429 | {"add(1);", "add", {"1"}}, 430 | {"add(1, 2 * 3, 4 + 5);", "add", {"1", "(2 * 3)", "(4 + 5)"}}, 431 | }; 432 | 433 | for (const auto &t : tests) { 434 | auto ast = parse("([parser]: Call expression parameter parsing)", t.input); 435 | REQUIRE(ast != nullptr); 436 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 437 | 438 | auto node = ast->nodes[0]; 439 | REQUIRE(node->name == "CALL"); 440 | 441 | testIdentifier(node->nodes[0], t.expectedIdent); 442 | 443 | REQUIRE(node->nodes[1]->name == "ARGUMENTS"); 444 | auto nodes = node->nodes[1]->nodes; 445 | CHECK(nodes.size() == t.expectedArgs.size()); 446 | 447 | for (size_t i = 0; i < nodes.size(); i++) { 448 | CHECK(to_string(nodes[i]) == t.expectedArgs[i]); 449 | } 450 | } 451 | } 452 | 453 | TEST_CASE("String literal expression", "[parser]") { 454 | std::string input = R"("hello world";)"; 455 | auto ast = parse("([parser]: String literal expression)", input); 456 | REQUIRE(ast != nullptr); 457 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 458 | 459 | auto node = ast->nodes[0]; 460 | REQUIRE(node->name == "STRING"); 461 | 462 | testStringLiteral(node, "hello world"); 463 | } 464 | 465 | TEST_CASE("Parsing empty array literals", "[parser]") { 466 | std::string input = "[]"; 467 | auto ast = parse("([parser]: Parsing empty array literals)", input); 468 | REQUIRE(ast != nullptr); 469 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 470 | 471 | auto node = ast->nodes[0]; 472 | REQUIRE(node->name == "ARRAY"); 473 | 474 | CHECK(node->nodes.empty()); 475 | } 476 | 477 | TEST_CASE("Parsing array literals", "[parser]") { 478 | std::string input = "[1, 2 * 2, 3 + 3]"; 479 | auto ast = parse("([parser]: Parsing array literals)", input); 480 | REQUIRE(ast != nullptr); 481 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 482 | 483 | auto node = ast->nodes[0]; 484 | REQUIRE(node->name == "ARRAY"); 485 | 486 | auto nodes = node->nodes; 487 | CHECK(nodes.size() == 3); 488 | 489 | testIntegerLiteral(nodes[0], 1); 490 | testInfixExpression(nodes[1], int64_t(2), "*", int64_t(2)); 491 | testInfixExpression(nodes[2], int64_t(3), "+", int64_t(3)); 492 | } 493 | 494 | TEST_CASE("Parsing index expression", "[parser]") { 495 | std::string input = "myArray[1 + 1]"; 496 | auto ast = parse("([parser]: Parsing index expression)", input); 497 | REQUIRE(ast != nullptr); 498 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 499 | 500 | auto node = ast->nodes[0]; 501 | REQUIRE(node->name == "CALL"); 502 | 503 | testIdentifier(node->nodes[0], "myArray"); 504 | 505 | REQUIRE(node->nodes[1]->name == "INDEX"); 506 | testInfixExpression(node->nodes[1]->nodes[0], int64_t(1), "+", int64_t(1)); 507 | } 508 | 509 | TEST_CASE("Parsing empty hash literal", "[parser]") { 510 | std::string input = "{}"; 511 | auto ast = parse("([parser]: Parsing empty hash literal)", input); 512 | REQUIRE(ast != nullptr); 513 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 514 | 515 | auto node = ast->nodes[0]; 516 | REQUIRE(node->name == "HASH"); 517 | } 518 | 519 | TEST_CASE("Parsing hash literals string keys", "[parser]") { 520 | std::string input = R"({"one": 1, "two": 2, "three": 3})"; 521 | auto ast = parse("([parser]: Parsing hash literals string keys)", input); 522 | REQUIRE(ast != nullptr); 523 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 524 | 525 | auto node = ast->nodes[0]; 526 | REQUIRE(node->name == "HASH"); 527 | 528 | map expected = { 529 | {"one"sv, 1}, 530 | {"two"sv, 2}, 531 | {"three"sv, 3}, 532 | }; 533 | 534 | for (auto node : node->nodes) { 535 | auto key = node->nodes[0]; 536 | auto val = node->nodes[1]; 537 | REQUIRE(key->name == "STRING"); 538 | 539 | auto expectedValue = expected[key->token]; 540 | testIntegerLiteral(val, expectedValue); 541 | } 542 | } 543 | 544 | TEST_CASE("Parsing hash literals boolean keys", "[parser]") { 545 | std::string input = "{true: 1, false: 2}"; 546 | auto ast = parse("([parser]: Parsing hash literals boolean keys)", input); 547 | REQUIRE(ast != nullptr); 548 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 549 | 550 | auto node = ast->nodes[0]; 551 | REQUIRE(node->name == "HASH"); 552 | 553 | map expected = { 554 | {"true"sv, 1}, 555 | {"false"sv, 2}, 556 | }; 557 | 558 | for (auto node : node->nodes) { 559 | auto key = node->nodes[0]; 560 | auto val = node->nodes[1]; 561 | REQUIRE(key->name == "BOOLEAN"); 562 | 563 | auto expectedValue = expected[key->token]; 564 | testIntegerLiteral(val, expectedValue); 565 | } 566 | } 567 | 568 | TEST_CASE("Parsing hash literals integer keys", "[parser]") { 569 | std::string input = "{1: 1, 2: 2, 3: 3}"; 570 | auto ast = parse("([parser]: Parsing hash literals integer keys)", input); 571 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 572 | 573 | auto node = ast->nodes[0]; 574 | REQUIRE(node->name == "HASH"); 575 | 576 | map expected = { 577 | {"1"sv, 1}, 578 | {"2"sv, 2}, 579 | {"3"sv, 3}, 580 | }; 581 | 582 | for (auto node : node->nodes) { 583 | auto key = node->nodes[0]; 584 | auto val = node->nodes[1]; 585 | REQUIRE(key->name == "INTEGER"); 586 | 587 | auto expectedValue = expected[key->token]; 588 | testIntegerLiteral(val, expectedValue); 589 | } 590 | } 591 | 592 | TEST_CASE("Parsing hash literals with expression", "[parser]") { 593 | std::string input = R"({"one": 0 + 1, "two": 10 - 8, "three": 15 / 5})"; 594 | auto ast = parse("([parser]: Parsing hash literals with expression)", input); 595 | REQUIRE(ast != nullptr); 596 | REQUIRE(ast->name == "EXPRESSION_STATEMENT"); 597 | 598 | auto node = ast->nodes[0]; 599 | REQUIRE(node->name == "HASH"); 600 | 601 | using TestFunc = function &ast)>; 602 | map tests = { 603 | { 604 | "one"sv, 605 | [](auto &ast) { 606 | testInfixExpression(ast, int64_t(0), "+", int64_t(1)); 607 | }, 608 | }, 609 | { 610 | "two"sv, 611 | [](auto &ast) { 612 | testInfixExpression(ast, int64_t(10), "-", int64_t(8)); 613 | }, 614 | }, 615 | { 616 | "three"sv, 617 | [](auto &ast) { 618 | testInfixExpression(ast, int64_t(15), "/", int64_t(5)); 619 | }, 620 | }, 621 | }; 622 | 623 | for (auto node : node->nodes) { 624 | auto key = node->nodes[0]; 625 | auto val = node->nodes[1]; 626 | REQUIRE(key->name == "STRING"); 627 | 628 | auto testFunc = tests[key->token]; 629 | testFunc(val); 630 | } 631 | } 632 | 633 | TEST_CASE("Function literal with name", "[parser]") { 634 | std::string input = R"(let myFunction = fn() { };)"; 635 | auto ast = parse("([parser]: Function literal with name)", input); 636 | REQUIRE(ast != nullptr); 637 | REQUIRE(ast->name == "ASSIGNMENT"); 638 | 639 | auto fn = ast->nodes[1]; 640 | REQUIRE(fn->value.has_value()); 641 | REQUIRE(fn->to_string() == "myFunction"); 642 | } 643 | -------------------------------------------------------------------------------- /test/test-symbol_table.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | 4 | #include 5 | 6 | using namespace std; 7 | using namespace monkey; 8 | 9 | TEST_CASE("Define", "[symbol table]") { 10 | auto expected = map{ 11 | {"a", {"a", GlobalScope, 0}}, {"b", {"b", GlobalScope, 1}}, 12 | {"c", {"c", LocalScope, 0}}, {"d", {"d", LocalScope, 1}}, 13 | {"e", {"e", LocalScope, 0}}, {"f", {"f", LocalScope, 1}}, 14 | }; 15 | 16 | auto global = symbol_table(); 17 | 18 | auto a = global->define("a"); 19 | CHECK(a == expected["a"]); 20 | 21 | auto b = global->define("b"); 22 | CHECK(b == expected["b"]); 23 | 24 | auto firstLocal = enclosed_symbol_table(global); 25 | 26 | auto c = firstLocal->define("c"); 27 | CHECK(c == expected["c"]); 28 | 29 | auto d = firstLocal->define("d"); 30 | CHECK(d == expected["d"]); 31 | 32 | auto secondLocal = enclosed_symbol_table(firstLocal); 33 | 34 | auto e = secondLocal->define("e"); 35 | CHECK(e == expected["e"]); 36 | 37 | auto f = secondLocal->define("f"); 38 | CHECK(f == expected["f"]); 39 | } 40 | 41 | TEST_CASE("Resolve Global", "[symbol table]") { 42 | auto global = symbol_table(); 43 | global->define("a"); 44 | global->define("b"); 45 | 46 | Symbol expected[] = { 47 | {"a", GlobalScope, 0}, 48 | {"b", GlobalScope, 1}, 49 | }; 50 | 51 | for (const auto &sym : expected) { 52 | auto result = global->resolve(sym.name); 53 | CHECK(result.has_value()); 54 | CHECK(result.value() == sym); 55 | } 56 | } 57 | 58 | TEST_CASE("Resolve Local", "[symbol table]") { 59 | auto global = symbol_table(); 60 | global->define("a"); 61 | global->define("b"); 62 | 63 | auto local = enclosed_symbol_table(global); 64 | local->define("c"); 65 | local->define("d"); 66 | 67 | Symbol expected[] = { 68 | {"a", GlobalScope, 0}, 69 | {"b", GlobalScope, 1}, 70 | {"c", LocalScope, 0}, 71 | {"d", LocalScope, 1}, 72 | }; 73 | 74 | for (const auto &sym : expected) { 75 | auto result = local->resolve(sym.name); 76 | CHECK(result.has_value()); 77 | CHECK(result.value() == sym); 78 | } 79 | } 80 | 81 | TEST_CASE("Resolve Nested Local", "[symbol table]") { 82 | auto global = symbol_table(); 83 | global->define("a"); 84 | global->define("b"); 85 | 86 | auto firstLocal = enclosed_symbol_table(global); 87 | firstLocal->define("c"); 88 | firstLocal->define("d"); 89 | 90 | auto secondLocal = enclosed_symbol_table(firstLocal); 91 | secondLocal->define("e"); 92 | secondLocal->define("f"); 93 | 94 | pair, vector> tests[] = { 95 | { 96 | firstLocal, 97 | { 98 | {"a", GlobalScope, 0}, 99 | {"b", GlobalScope, 1}, 100 | {"c", LocalScope, 0}, 101 | {"d", LocalScope, 1}, 102 | }, 103 | }, 104 | { 105 | secondLocal, 106 | { 107 | {"a", GlobalScope, 0}, 108 | {"b", GlobalScope, 1}, 109 | {"e", LocalScope, 0}, 110 | {"f", LocalScope, 1}, 111 | }, 112 | }, 113 | }; 114 | 115 | for (const auto &[table, expectedSymbols] : tests) { 116 | for (const auto &sym : expectedSymbols) { 117 | auto result = table->resolve(sym.name); 118 | CHECK(result.has_value()); 119 | CHECK(result.value() == sym); 120 | } 121 | } 122 | } 123 | 124 | TEST_CASE("Define Resolve Builtins", "[symbol table]") { 125 | auto global = symbol_table(); 126 | auto firstLocal = enclosed_symbol_table(global); 127 | auto secondLocal = enclosed_symbol_table(firstLocal); 128 | 129 | vector expected = { 130 | {"a", BuiltinScope, 0}, 131 | {"c", BuiltinScope, 1}, 132 | {"e", BuiltinScope, 2}, 133 | {"f", BuiltinScope, 3}, 134 | }; 135 | 136 | size_t i = 0; 137 | for (const auto &v : expected) { 138 | global->define_builtin(i, v.name); 139 | i++; 140 | } 141 | 142 | auto tables = std::vector>{global, firstLocal, 143 | secondLocal}; 144 | 145 | for (auto table : tables) { 146 | for (const auto &sym : expected) { 147 | auto result = table->resolve(sym.name); 148 | CHECK(result.has_value()); 149 | CHECK(result.value() == sym); 150 | } 151 | } 152 | } 153 | 154 | TEST_CASE("Resolve Free", "[symbol table]") { 155 | auto global = symbol_table(); 156 | global->define("a"); 157 | global->define("b"); 158 | 159 | auto firstLocal = enclosed_symbol_table(global); 160 | firstLocal->define("c"); 161 | firstLocal->define("d"); 162 | 163 | auto secondLocal = enclosed_symbol_table(firstLocal); 164 | secondLocal->define("e"); 165 | secondLocal->define("f"); 166 | 167 | tuple, vector, vector> tests[] = { 168 | {firstLocal, 169 | { 170 | {"a", GlobalScope, 0}, 171 | {"b", GlobalScope, 1}, 172 | {"c", LocalScope, 0}, 173 | {"d", LocalScope, 1}, 174 | }, 175 | {}}, 176 | {secondLocal, 177 | { 178 | {"a", GlobalScope, 0}, 179 | {"b", GlobalScope, 1}, 180 | {"c", FreeScope, 0}, 181 | {"d", FreeScope, 1}, 182 | {"e", LocalScope, 0}, 183 | {"f", LocalScope, 1}, 184 | }, 185 | { 186 | {"c", LocalScope, 0}, 187 | {"d", LocalScope, 1}, 188 | }}, 189 | }; 190 | 191 | for (const auto &[table, expectedSymbols, expectedFreeSymbols] : tests) { 192 | for (const auto &sym : expectedSymbols) { 193 | auto result = table->resolve(sym.name); 194 | CHECK(result.has_value()); 195 | CHECK(result.value() == sym); 196 | } 197 | 198 | CHECK(table->freeSymbols.size() == expectedFreeSymbols.size()); 199 | 200 | size_t i = 0; 201 | for (const auto &sym : expectedFreeSymbols) { 202 | auto result = table->freeSymbols[i]; 203 | CHECK(result == sym); 204 | i++; 205 | } 206 | } 207 | } 208 | 209 | TEST_CASE("Resolve Unresolvable Free", "[symbol table]") { 210 | auto global = symbol_table(); 211 | global->define("a"); 212 | 213 | auto firstLocal = enclosed_symbol_table(global); 214 | firstLocal->define("c"); 215 | 216 | auto secondLocal = enclosed_symbol_table(firstLocal); 217 | secondLocal->define("e"); 218 | secondLocal->define("f"); 219 | 220 | auto expected = vector{ 221 | {"a", GlobalScope, 0}, 222 | {"c", FreeScope, 0}, 223 | {"e", LocalScope, 0}, 224 | {"f", LocalScope, 1}, 225 | }; 226 | 227 | for (const auto &sym : expected) { 228 | auto result = secondLocal->resolve(sym.name); 229 | CHECK(result.has_value()); 230 | CHECK(result.value() == sym); 231 | } 232 | 233 | auto expectedUnresolvable = vector{ 234 | "b", 235 | "d", 236 | }; 237 | 238 | for (const auto &name : expectedUnresolvable) { 239 | auto result = secondLocal->resolve(name); 240 | CHECK(!result.has_value()); 241 | } 242 | } 243 | 244 | TEST_CASE("Define and Resolve Function Name", "[symbol table]") { 245 | auto global = symbol_table(); 246 | global->define_function_name("a"); 247 | 248 | auto expected = Symbol{"a", FunctionScope, 0}; 249 | 250 | auto result = global->resolve(expected.name); 251 | CHECK(result.has_value()); 252 | CHECK(result.value() == expected); 253 | } 254 | 255 | TEST_CASE("Shadowing Function Name", "[symbol table]") { 256 | auto global = symbol_table(); 257 | global->define_function_name("a"); 258 | global->define("a"); 259 | 260 | auto expected = Symbol{"a", GlobalScope, 0}; 261 | 262 | auto result = global->resolve(expected.name); 263 | CHECK(result.has_value()); 264 | CHECK(result.value().name == expected.name); 265 | CHECK(result.value().scope == expected.scope); 266 | CHECK(result.value().index == expected.index); 267 | } 268 | -------------------------------------------------------------------------------- /test/test-util.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | inline std::shared_ptr parse(const char *name, 8 | const std::string &input) { 9 | using namespace std; 10 | using namespace monkey; 11 | 12 | vector msgs; 13 | auto ast = parse(name, input.data(), input.size(), msgs); 14 | for (auto msg : msgs) { 15 | cerr << msg << endl; 16 | } 17 | return ast; 18 | } 19 | 20 | inline void test_integer_object(int64_t expected, 21 | std::shared_ptr actual) { 22 | using namespace monkey; 23 | 24 | REQUIRE(actual); 25 | REQUIRE(actual->type() == INTEGER_OBJ); 26 | 27 | auto val = cast(actual).value; 28 | CHECK(val == expected); 29 | } 30 | 31 | inline void test_boolean_object(bool expected, 32 | std::shared_ptr actual) { 33 | using namespace monkey; 34 | 35 | REQUIRE(actual); 36 | REQUIRE(actual->type() == BOOLEAN_OBJ); 37 | 38 | auto val = cast(actual).value; 39 | CHECK(val == expected); 40 | } 41 | 42 | inline void test_null_object(std::shared_ptr actual) { 43 | using namespace monkey; 44 | 45 | REQUIRE(actual); 46 | REQUIRE(actual->type() == NULL_OBJ); 47 | 48 | CHECK(actual.get() == CONST_NULL.get()); 49 | } 50 | 51 | inline void test_string_object(const std::string &expected, 52 | std::shared_ptr actual) { 53 | using namespace monkey; 54 | 55 | REQUIRE(actual); 56 | REQUIRE(actual->type() == STRING_OBJ); 57 | 58 | auto val = cast(actual).value; 59 | CHECK(val == expected); 60 | } 61 | 62 | inline void test_error_object(const std::string &expected, 63 | std::shared_ptr actual) { 64 | using namespace monkey; 65 | 66 | REQUIRE(actual); 67 | REQUIRE(actual->type() == ERROR_OBJ); 68 | 69 | auto msg = cast(actual).message; 70 | CHECK(msg == expected); 71 | } 72 | 73 | inline monkey::Instructions 74 | concat_instructions(const std::vector &s) { 75 | monkey::Instructions out; 76 | for (const auto &ins : s) { 77 | out.insert(out.end(), ins.begin(), ins.end()); 78 | } 79 | return out; 80 | } 81 | 82 | -------------------------------------------------------------------------------- /test/test-vm.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "test-util.hpp" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace monkey; 9 | 10 | void test_expected_object(shared_ptr expected, 11 | shared_ptr actual) { 12 | switch (expected->type()) { 13 | case INTEGER_OBJ: 14 | test_integer_object(cast(expected).value, actual); 15 | break; 16 | case BOOLEAN_OBJ: 17 | test_boolean_object(cast(expected).value, actual); 18 | break; 19 | case STRING_OBJ: 20 | test_string_object(cast(expected).value, actual); 21 | break; 22 | case NULL_OBJ: test_null_object(actual); break; 23 | case ARRAY_OBJ: { 24 | REQUIRE(actual); 25 | const auto &expectedElements = cast(expected).elements; 26 | const auto &actualElements = cast(actual).elements; 27 | REQUIRE(expectedElements.size() == actualElements.size()); 28 | for (size_t i = 0; i < expectedElements.size(); i++) { 29 | REQUIRE(expectedElements[i] != actualElements[i]); 30 | } 31 | break; 32 | } 33 | case ERROR_OBJ: 34 | test_error_object(cast(expected).message, actual); 35 | break; 36 | default: break; 37 | } 38 | } 39 | 40 | struct VmTestCase { 41 | string input; 42 | shared_ptr expected; 43 | }; 44 | 45 | void run_vm_test(const char *name, const vector &tests) { 46 | for (const auto &t : tests) { 47 | auto ast = parse(name, t.input); 48 | // cerr << peg::ast_to_s(ast) << endl; 49 | REQUIRE(ast != nullptr); 50 | 51 | Compiler compiler; 52 | compiler.compile(ast); 53 | 54 | // { 55 | // size_t i = 0; 56 | // for (auto constant : compiler.bytecode().constants) { 57 | // cerr << fmt::format("CONSTANT {} ({})", i, constant->name()) 58 | // << std::endl; 59 | // if (constant->type() == COMPILED_FUNCTION_OBJ) { 60 | // cerr << " Instructions: " << std::endl 61 | // << to_string(dynamic_pointer_cast(constant) 62 | // ->instructions, 63 | // "\n") 64 | // << std::endl; 65 | // } else if (constant->type() == INTEGER_OBJ) { 66 | // cerr << fmt::format(" Value: {}", constant->inspect()) << std::endl << std::endl; 67 | // } 68 | // i++; 69 | // } 70 | // } 71 | 72 | VM vm(compiler.bytecode()); 73 | vm.run(); 74 | 75 | auto stack_elem = vm.last_popped_stack_elem(); 76 | 77 | test_expected_object(t.expected, stack_elem); 78 | } 79 | } 80 | 81 | TEST_CASE("Integer arithmetic - vm", "[vm]") { 82 | vector tests{ 83 | {"1", make_integer(1)}, 84 | {"2", make_integer(2)}, 85 | {"1 + 2", make_integer(3)}, 86 | {"1 - 2", make_integer(-1)}, 87 | {"1 * 2", make_integer(2)}, 88 | {"4 / 2", make_integer(2)}, 89 | {"50 / 2 * 2 + 10 - 5", make_integer(55)}, 90 | {"5 + 5 + 5 + 5 - 10", make_integer(10)}, 91 | {"2 * 2 * 2 * 2 * 2", make_integer(32)}, 92 | {"5 * 2 + 10", make_integer(20)}, 93 | {"5 + 2 * 10", make_integer(25)}, 94 | {"5 * (2 + 10)", make_integer(60)}, 95 | {"-5", make_integer(-5)}, 96 | {"-10", make_integer(-10)}, 97 | {"-50 + 100 + -50", make_integer(0)}, 98 | {"(5 + 10 * 2 + 15 / 3) * 2 + -10", make_integer(50)}, 99 | }; 100 | 101 | run_vm_test("([vm]: Integer arithmetic)", tests); 102 | } 103 | 104 | TEST_CASE("Boolean expressions - vm", "[vm]") { 105 | vector tests{ 106 | {"true", make_bool(true)}, 107 | {"false", make_bool(false)}, 108 | {"1 < 2", make_bool(true)}, 109 | {"1 > 2", make_bool(false)}, 110 | {"1 < 1", make_bool(false)}, 111 | {"1 > 1", make_bool(false)}, 112 | {"1 == 1", make_bool(true)}, 113 | {"1 != 1", make_bool(false)}, 114 | {"1 == 2", make_bool(false)}, 115 | {"1 != 2", make_bool(true)}, 116 | {"true == true", make_bool(true)}, 117 | {"false == false", make_bool(true)}, 118 | {"true == false", make_bool(false)}, 119 | {"true != false", make_bool(true)}, 120 | {"false != true", make_bool(true)}, 121 | {"(1 < 2) == true", make_bool(true)}, 122 | {"(1 < 2) == false", make_bool(false)}, 123 | {"(1 > 2) == true", make_bool(false)}, 124 | {"(1 > 2) == false", make_bool(true)}, 125 | {"!true", make_bool(false)}, 126 | {"!false", make_bool(true)}, 127 | {"!5", make_bool(false)}, 128 | {"!!true", make_bool(true)}, 129 | {"!!false", make_bool(false)}, 130 | {"!!5", make_bool(true)}, 131 | {"!(if (false) { 5; })", make_bool(true)}, 132 | }; 133 | 134 | run_vm_test("([vm]: Boolean expressions)", tests); 135 | } 136 | 137 | TEST_CASE("Conditionals - vm", "[vm]") { 138 | vector tests{ 139 | {"if (true) { 10 }", make_integer(10)}, 140 | {"if (true) { 10 } else { 20 }", make_integer(10)}, 141 | {"if (false) { 10 } else { 20 }", make_integer(20)}, 142 | {"if (1) { 10 }", make_integer(10)}, 143 | {"if (1 < 2) { 10 }", make_integer(10)}, 144 | {"if (1 < 2) { 10 } else { 20 }", make_integer(10)}, 145 | {"if (1 > 2) { 10 } else { 20 }", make_integer(20)}, 146 | {"if (1 > 2) { 10 }", CONST_NULL}, 147 | {"if (false) { 10 }", CONST_NULL}, 148 | {"if ((if (false) { 10 })) { 10 } else { 20 }", make_integer(20)}, 149 | }; 150 | 151 | run_vm_test("([vm]: Conditionals)", tests); 152 | } 153 | 154 | TEST_CASE("Global Let Statements - vm", "[vm]") { 155 | vector tests{ 156 | {"let one = 1; one", make_integer(1)}, 157 | {"let one = 1; let two = 2; one + two", make_integer(3)}, 158 | {"let one = 1; let two = one + one; one + two", make_integer(3)}, 159 | }; 160 | 161 | run_vm_test("([vm]: Global Let Statements)", tests); 162 | } 163 | 164 | TEST_CASE("String expressions - vm", "[vm]") { 165 | vector tests{ 166 | {R"("monkey")", make_string("monkey")}, 167 | {R"("mon" + "key")", make_string("monkey")}, 168 | {R"("mon" + "key" + "banana")", make_string("monkeybanana")}, 169 | }; 170 | 171 | run_vm_test("([vm]: String expressions)", tests); 172 | } 173 | 174 | TEST_CASE("Arrray Literals - vm", "[vm]") { 175 | vector tests{ 176 | {"[]", make_array({})}, 177 | {"[1, 2, 3]", make_array({1, 2, 3})}, 178 | {"[1 + 2, 3 * 4, 5 + 6]", make_array({3, 12, 11})}, 179 | }; 180 | 181 | run_vm_test("([vm]: Arrray Literals)", tests); 182 | } 183 | 184 | TEST_CASE("Hash Literals - vm", "[vm]") { 185 | struct VmHashTestCase { 186 | string input; 187 | map> expected; 188 | }; 189 | 190 | vector tests{ 191 | {"{}", {}}, 192 | {"{1: 2, 2: 3}", 193 | { 194 | {make_integer(1)->hash_key(), make_integer(2)}, 195 | {make_integer(2)->hash_key(), make_integer(3)}, 196 | }}, 197 | {"{1 + 1: 2 * 2, 3 + 3: 4 * 4}", 198 | { 199 | {make_integer(2)->hash_key(), make_integer(4)}, 200 | {make_integer(6)->hash_key(), make_integer(16)}, 201 | }}, 202 | }; 203 | 204 | for (const auto &t : tests) { 205 | auto ast = parse("([vm]: Arrray Literals)", t.input); 206 | REQUIRE(ast != nullptr); 207 | 208 | Compiler compiler; 209 | compiler.compile(ast); 210 | 211 | VM vm(compiler.bytecode()); 212 | vm.run(); 213 | 214 | auto actual = vm.last_popped_stack_elem(); 215 | REQUIRE(actual); 216 | 217 | auto &actualParis = cast(actual).pairs; 218 | REQUIRE(t.expected.size() == actualParis.size()); 219 | 220 | for (auto &[expectedKey, expectedValue] : t.expected) { 221 | auto pair = actualParis[expectedKey]; 222 | test_integer_object(cast(expectedValue).value, pair.value); 223 | } 224 | } 225 | } 226 | 227 | TEST_CASE("Index Expressions - vm", "[vm]") { 228 | vector tests{ 229 | {"[1, 2, 3][1]", make_integer(2)}, 230 | {"[1, 2, 3][0 + 2]", make_integer(3)}, 231 | {"[[1, 1, 1]][0][0]", make_integer(1)}, 232 | {"[][0]", CONST_NULL}, 233 | {"[1, 2, 3][99]", CONST_NULL}, 234 | {"[1][-1]", CONST_NULL}, 235 | {"{1: 1, 2: 2}[1]", make_integer(1)}, 236 | {"{1: 1, 2: 2}[2]", make_integer(2)}, 237 | {"{1: 1}[0]", CONST_NULL}, 238 | {"{}[0]", CONST_NULL}, 239 | }; 240 | 241 | run_vm_test("([vm]: Index Expressions)", tests); 242 | } 243 | 244 | TEST_CASE("Calling Functions Without Arguments - vm", "[vm]") { 245 | vector tests{ 246 | {R"( 247 | let fivePlusTen = fn() { 5 + 10; }; 248 | fivePlusTen(); 249 | )", 250 | make_integer(15)}, 251 | {R"( 252 | let one = fn() { 1; }; 253 | let two = fn() { 2; }; 254 | one() + two(); 255 | )", 256 | make_integer(3)}, 257 | {R"( 258 | let a = fn() { 1; }; 259 | let b = fn() { a() + 1; }; 260 | let c = fn() { b() + 1; }; 261 | c(); 262 | )", 263 | make_integer(3)}, 264 | }; 265 | 266 | run_vm_test("([vm]: Calling Functions Without Arguments)", tests); 267 | } 268 | 269 | TEST_CASE("Functions With Return Statement - vm", "[vm]") { 270 | vector tests{ 271 | {R"( 272 | let earlyExit = fn() { return 99; 100; }; 273 | earlyExit(); 274 | )", 275 | make_integer(99)}, 276 | {R"( 277 | let earlyExit = fn() { return 99; return 100; }; 278 | earlyExit(); 279 | )", 280 | make_integer(99)}, 281 | }; 282 | 283 | run_vm_test("([vm]: Functions With Return Statement)", tests); 284 | } 285 | 286 | TEST_CASE("Functions Without Return Statement - vm", "[vm]") { 287 | vector tests{ 288 | {R"( 289 | let noReturn = fn() { }; 290 | noReturn(); 291 | )", 292 | CONST_NULL}, 293 | {R"( 294 | let noReturn = fn() { }; 295 | let noReturnTwo = fn() { noReturn(); }; 296 | noReturn(); 297 | noReturnTwo(); 298 | )", 299 | CONST_NULL}, 300 | }; 301 | 302 | run_vm_test("([vm]: Functions Without Return Statement)", tests); 303 | } 304 | 305 | TEST_CASE("First Class Functions - vm", "[vm]") { 306 | vector tests{ 307 | {R"( 308 | let returnOne = fn() { 1; }; 309 | let returnsOneReturner = fn() { returnOne; }; 310 | returnsOneReturner()(); 311 | )", 312 | make_integer(1)}, 313 | {R"( 314 | let returnsOneReturner = fn() { 315 | let retunrsOne = fn() { 1; }; 316 | retunrsOne; 317 | }; 318 | returnsOneReturner()(); 319 | )", 320 | make_integer(1)}, 321 | }; 322 | 323 | run_vm_test("([vm]: First Class Functions)", tests); 324 | } 325 | 326 | TEST_CASE("Calling Functions With Bindings - vm", "[vm]") { 327 | vector tests{ 328 | {R"( 329 | let one = fn() { let one = 1; one }; 330 | one(); 331 | )", 332 | make_integer(1)}, 333 | {R"( 334 | let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; 335 | oneAndTwo(); 336 | )", 337 | make_integer(3)}, 338 | {R"( 339 | let oneAndTwo = fn() { let one = 1; let two = 2; one + two; }; 340 | let threeAndFour = fn() { let three = 3; let four = 4; three + four; }; 341 | oneAndTwo() + threeAndFour(); 342 | )", 343 | make_integer(10)}, 344 | {R"( 345 | let firstFoobar = fn() { let foobar = 50; foobar; }; 346 | let secondFoobar = fn() { let foobar = 100; foobar; }; 347 | firstFoobar() + secondFoobar(); 348 | )", 349 | make_integer(150)}, 350 | {R"( 351 | let globalSeed = 50; 352 | let minusOne = fn() { 353 | let num = 1; 354 | globalSeed - num; 355 | } 356 | let minusTwo = fn() { 357 | let num = 2; 358 | globalSeed - num; 359 | } 360 | minusOne() + minusTwo(); 361 | )", 362 | make_integer(97)}, 363 | }; 364 | 365 | run_vm_test("([vm]: Calling Functions With Bindings)", tests); 366 | } 367 | 368 | TEST_CASE("Calling Functions With Arguments And Bindings - vm", "[vm]") { 369 | vector tests{ 370 | {R"( 371 | let identity = fn(a) { a; } 372 | identity(4); 373 | )", 374 | make_integer(4)}, 375 | {R"( 376 | let sum = fn(a, b) { a + b; } 377 | sum(1, 2); 378 | )", 379 | make_integer(3)}, 380 | {R"( 381 | let sum = fn(a, b) { 382 | let c = a + b; 383 | c; 384 | } 385 | sum(1, 2); 386 | )", 387 | make_integer(3)}, 388 | {R"( 389 | let sum = fn(a, b) { 390 | let c = a + b; 391 | c; 392 | } 393 | sum(1, 2) + sum(3, 4); 394 | )", 395 | make_integer(10)}, 396 | {R"( 397 | let sum = fn(a, b) { 398 | let c = a + b; 399 | c; 400 | } 401 | let outer = fn() { 402 | sum(1, 2) + sum(3, 4); 403 | } 404 | outer(); 405 | )", 406 | make_integer(10)}, 407 | {R"( 408 | let globalNum = 10; 409 | let sum = fn(a, b) { 410 | let c = a + b; 411 | c + globalNum; 412 | } 413 | let outer = fn() { 414 | sum(1, 2) + sum(3, 4) + globalNum; 415 | } 416 | outer() + globalNum; 417 | )", 418 | make_integer(50)}, 419 | }; 420 | 421 | run_vm_test("([vm]: Calling Functions With Arguments And Bindings)", tests); 422 | } 423 | 424 | TEST_CASE("Calling Functions With Wrong Arguments - vm", "[vm]") { 425 | vector tests{ 426 | {R"( 427 | fn() { 1; }(1); 428 | )", 429 | make_error("wrong number of arguments: want=0, got=1")}, 430 | {R"( 431 | fn(a) { a; }(); 432 | )", 433 | make_error("wrong number of arguments: want=1, got=0")}, 434 | {R"( 435 | fn(a, b) { a + b; }(1); 436 | )", 437 | make_error("wrong number of arguments: want=2, got=1")}, 438 | }; 439 | 440 | run_vm_test("([vm]: Calling Functions With Wrong Arguments)", tests); 441 | } 442 | 443 | TEST_CASE("Builtin Functions - vm", "[vm]") { 444 | vector tests{ 445 | {R"(len(""))", make_integer(0)}, 446 | {R"(len("four"))", make_integer(4)}, 447 | {R"(len("hello world"))", make_integer(11)}, 448 | {R"(len(1))", make_error("argument to `len` not supported, got INTEGER")}, 449 | {R"(len("one", "two"))", 450 | make_error("wrong number of arguments. got=2, want=1")}, 451 | {R"(len([1, 2, 3]))", make_integer(3)}, 452 | {R"(len([]))", make_integer(0)}, 453 | {R"(puts("hello", "world!"))", CONST_NULL}, 454 | {R"(first([1, 2, 3]))", make_integer(1)}, 455 | {R"(first([]))", CONST_NULL}, 456 | {R"(first(1))", 457 | make_error("argument to `first` must be ARRAY, got INTEGER")}, 458 | {R"(last([1, 2, 3]))", make_integer(3)}, 459 | {R"(last([]))", CONST_NULL}, 460 | {R"(last(1))", 461 | make_error("argument to `last` must be ARRAY, got INTEGER")}, 462 | {R"(rest([1, 2, 3]))", make_array({2, 3})}, 463 | {R"(rest([]))", CONST_NULL}, 464 | {R"(push([], 1))", make_array({1})}, 465 | {R"(push(1, 1))", 466 | make_error("argument to `push` must be ARRAY, got INTEGER")}, 467 | }; 468 | 469 | run_vm_test("([vm]: Builtin Functions)", tests); 470 | } 471 | 472 | TEST_CASE("Closures - vm", "[vm]") { 473 | vector tests{ 474 | {R"( 475 | let newClosure = fn(a) { 476 | fn() { a; }; 477 | }; 478 | let closure = newClosure(99); 479 | closure(); 480 | )", 481 | make_integer(99)}, 482 | {R"( 483 | let newAdder = fn(a, b) { 484 | fn(c) { a + b + c; }; 485 | }; 486 | let adder = newAdder(1, 2); 487 | adder(8); 488 | )", 489 | make_integer(11)}, 490 | {R"( 491 | let newAdder = fn(a, b) { 492 | let c = a + b; 493 | fn(d) { c + d }; 494 | }; 495 | let adder = newAdder(1, 2); 496 | adder(8); 497 | )", 498 | make_integer(11)}, 499 | {R"( 500 | let newAdderOuter = fn(a, b) { 501 | let c = a + b; 502 | fn(d) { 503 | let e = c + d; 504 | fn(f) { e + f; } 505 | }; 506 | }; 507 | let newAdderInner = newAdderOuter(1, 2); 508 | let adder = newAdderInner(3); 509 | adder(8); 510 | )", 511 | make_integer(14)}, 512 | {R"( 513 | let a = 1; 514 | let newAdderOuter = fn(b) { 515 | fn(c) { 516 | fn(d) { a + b + c + d; } 517 | }; 518 | }; 519 | let newAdderInner = newAdderOuter(2); 520 | let adder = newAdderInner(3); 521 | adder(8); 522 | )", 523 | make_integer(14)}, 524 | {R"( 525 | let newClosure = fn(a, b) { 526 | let one = fn() { a; }; 527 | let two = fn() { b; }; 528 | fn() { one() + two(); }; 529 | }; 530 | let closure = newClosure(9, 90); 531 | closure(); 532 | )", 533 | make_integer(99)}, 534 | }; 535 | 536 | run_vm_test("([vm]: Closures)", tests); 537 | } 538 | 539 | TEST_CASE("Recursive Functions - vm", "[vm]") { 540 | vector tests{ 541 | {R"( 542 | let countDown = fn(x) { 543 | if (x == 0) { 544 | return 0; 545 | } else { 546 | countDown(x - 1); 547 | } 548 | }; 549 | countDown(1); 550 | )", 551 | make_integer(0)}, 552 | {R"( 553 | let countDown = fn(x) { 554 | if (x == 0) { 555 | return 0; 556 | } else { 557 | countDown(x - 1); 558 | } 559 | }; 560 | let wrapper = fn() { 561 | countDown(1); 562 | } 563 | wrapper(); 564 | )", 565 | make_integer(0)}, 566 | {R"( 567 | let wrapper = fn() { 568 | let countDown = fn(x) { 569 | if (x == 0) { 570 | return 0; 571 | } else { 572 | countDown(x - 1); 573 | } 574 | }; 575 | countDown(1); 576 | } 577 | wrapper(); 578 | )", 579 | make_integer(0)}, 580 | }; 581 | 582 | run_vm_test("([vm]: Recursive Functions)", tests); 583 | } 584 | --------------------------------------------------------------------------------