├── .clang-format ├── .github └── workflows │ ├── main.yml │ └── package.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── discord.xml ├── misc.xml ├── modules.xml ├── v2.iml └── vcs.xml ├── .vscode └── launch.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── ClangFormat.cmake └── VersionFromGit.cmake ├── examples ├── example_class.cb ├── example_fun.cb ├── example_upvalue.cb └── example_var.cb ├── format.sh └── src ├── app └── main.c ├── chunk.c ├── chunk.h ├── common.h ├── compiler.c ├── compiler.h ├── debug.c ├── debug.h ├── memory.c ├── memory.h ├── natives.c ├── natives.h ├── object.c ├── object.h ├── scanner.c ├── scanner.h ├── table.c ├── table.h ├── util.c ├── util.h ├── value.c ├── value.h ├── vm.c └── vm.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Mozilla 4 | IndentCaseLabels: true 5 | IndentWidth: 4 6 | PointerAlignment: Left 7 | SortIncludes: true 8 | BreakBeforeBraces: Attach 9 | ... 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CMake Build 2 | on: [push] 3 | 4 | jobs: 5 | build-ubuntu: 6 | name: Build on Ubuntu 7 | runs-on: ubuntu-latest 8 | env: 9 | GITHUB_WORKSPACE: ${{ github.workspace }} 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | 14 | - name: Configure 15 | uses: CabooseLang/github-actions/cmake-configure@master 16 | 17 | - name: Build 18 | uses: CabooseLang/github-actions/cmake-build@master 19 | 20 | build-windows: 21 | name: Build on Windows 22 | runs-on: windows-latest 23 | env: 24 | GITHUB_WORKSPACE: ${{ github.workspace }} 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v1 28 | 29 | - name: Configure 30 | uses: CabooseLang/github-actions/cmake-configure@master 31 | 32 | - name: Build 33 | uses: CabooseLang/github-actions/cmake-build@master 34 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: CMake Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build-ubuntu: 10 | name: Build on Ubuntu 11 | runs-on: ubuntu-latest 12 | env: 13 | GITHUB_WORKSPACE: ${{ github.workspace }} 14 | steps: 15 | - name: Install NSIS 16 | uses: CabooseLang/github-actions/install-nsis@master 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v1 20 | 21 | - name: Get the version 22 | id: get_version 23 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 24 | 25 | - name: Configure 26 | uses: CabooseLang/github-actions/cmake-configure@master 27 | 28 | - name: Build 29 | uses: CabooseLang/github-actions/cmake-build@master 30 | 31 | - name: Package 32 | uses: CabooseLang/github-actions/cmake-package@master 33 | with: 34 | package_type: deb;rpm;nsis 35 | 36 | - name: Create Release 37 | id: create_release 38 | uses: actions/create-release@v1.0.0 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | tag_name: ${{ steps.get_version.outputs.VERSION }} 43 | release_name: Release ${{ steps.get_version.outputs.VERSION }} 44 | draft: true 45 | prerelease: false 46 | 47 | - name: Upload Debian Package 48 | id: upload_debian_package 49 | uses: actions/upload-release-asset@v1.0.1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | upload_url: ${{ steps.create_release.outputs.upload_url }} 54 | asset_path: ./build/Caboose-${{ steps.get_version.outputs.VERSION }}.deb 55 | asset_name: Caboose-${{ steps.get_version.outputs.VERSION }}.deb 56 | asset_content_type: application/octet-stream 57 | 58 | - name: Upload RPM Package 59 | id: upload_rpm_package 60 | uses: actions/upload-release-asset@v1.0.1 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | with: 64 | upload_url: ${{ steps.create_release.outputs.upload_url }} 65 | asset_path: ./build/Caboose-${{ steps.get_version.outputs.VERSION }}.rpm 66 | asset_name: Caboose-${{ steps.get_version.outputs.VERSION }}.rpm 67 | asset_content_type: application/octet-stream 68 | 69 | - name: Upload NSIS Package 70 | id: upload_nsis_package 71 | uses: actions/upload-release-asset@v1.0.1 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | with: 75 | upload_url: ${{ steps.create_release.outputs.upload_url }} 76 | asset_path: ./build/Caboose-${{ steps.get_version.outputs.VERSION }}.exe 77 | asset_name: Caboose-${{ steps.get_version.outputs.VERSION }}.exe 78 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built files 2 | build/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Caboose -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/v2.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/cmake-build-debug/cb", 12 | "args": ["./tests/test_upvalue_debug.cb"], 13 | "stopAtEntry": true, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | include(cmake/VersionFromGit.cmake) 4 | 5 | version_from_git() 6 | project(Caboose LANGUAGES C VERSION ${git_resolved_version}) 7 | set(CMAKE_C_STANDARD 11) 8 | 9 | file(GLOB lib_source "src/*.c") 10 | file(GLOB lib_header "src/*.h") 11 | 12 | add_library(caboose STATIC ${lib_source}) 13 | add_executable(cb src/app/main.c ${lib_source} ${lib_header}) 14 | target_link_libraries(cb caboose) 15 | 16 | set_target_properties(caboose PROPERTIES PUBLIC_HEADER "src/chunk.h;src/debug.h;src/value.h;src/memory.h") 17 | install(TARGETS caboose LIBRARY DESTINATION lib/caboose PUBLIC_HEADER DESTINATION include/caboose ARCHIVE DESTINATION lib/caboose) 18 | install(TARGETS cb DESTINATION bin) 19 | 20 | enable_testing() 21 | 22 | add_test(fun ./cb test_fun.cb) 23 | 24 | # Packaging 25 | include(InstallRequiredSystemLibraries) 26 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 27 | set(CPACK_PACKAGE_VERSION_MAJOR "${Caboose_VERSION_MAJOR}") 28 | set(CPACK_PACKAGE_VERSION_MINOR "${Caboose_VERSION_MINOR}") 29 | set(CPACK_PACKAGE_CONTACT "Caboose Maintainers") 30 | set(CPACK_PACKAGE_FILE_NAME "Caboose-${CMAKE_PROJECT_VERSION}") 31 | include(CPack) 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Caboose Authors 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Caboose 2 | 3 | > Caboose is a simple, dynamically typed, bytecode-based interpreted language built on top of a powerful VM. 4 | 5 | [![asciicast](https://asciinema.org/a/295998.svg)](https://asciinema.org/a/295998) 6 | 7 | Caboose aims to be a simple and easy to learn language while still being powerful enough for everyday use. It can compile anywhere C can run so give it a go! 8 | 9 | ## Building 10 | 11 | the easiest way to build the command line tool and library is to run the following commands in the project's base directory: 12 | 13 | ```bash 14 | cmake . -Bbuild 15 | cmake --build ./build 16 | ``` 17 | 18 | > **Note:** This does require CMake to be installed and on your system path. If you get an error about a minimum required version, just upgrade CMake from the latest package, which can be found on their download page. 19 | 20 | ## Examples 21 | ### CLI Usage - File 22 | Suppose you have an example Caboose file named `main.cb`: 23 | ```cb 24 | // main.cb 25 | fun hello(name) { 26 | print "hello, " + name; 27 | } 28 | 29 | hello("world"); 30 | ``` 31 | 32 | You can run it using the caboose interpreter like so: 33 | ```bash 34 | $ cb main.cb 35 | ``` 36 | 37 | ### CLI Usage - REPL 38 | To get going quickly, you may want to start a REPL, to do this: 39 | ```bash 40 | $ cb 41 | ``` 42 | 43 | ### Embedding 44 | Below is a minimal embedded Caboose interpreter in C 45 | ```c 46 | #include 47 | 48 | int main() { 49 | initVM(); 50 | 51 | InterpretResult result = interpret("var some = \"example source code\";"); 52 | 53 | // These exit codes represent the closest thing in Unix to what they actually mean in this context 54 | if (result == INTERPRET_COMPILE_ERROR) 55 | exit(65); 56 | if (result == INTERPRET_RUNTIME_ERROR) 57 | exit(70); 58 | 59 | freeVM(); 60 | } 61 | ``` 62 | 63 | ## License 64 | 65 | Caboose is licensed under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /cmake/ClangFormat.cmake: -------------------------------------------------------------------------------- 1 | function(clangformat_setup) 2 | if(NOT CLANGFORMAT_EXECUTABLE) 3 | set(CLANGFORMAT_EXECUTABLE clang-format) 4 | endif() 5 | 6 | if(NOT EXISTS ${CLANGFORMAT_EXECUTABLE}) 7 | find_program(clangformat_executable_tmp ${CLANGFORMAT_EXECUTABLE}) 8 | if(clangformat_executable_tmp) 9 | set(CLANGFORMAT_EXECUTABLE ${clangformat_executable_tmp}) 10 | unset(clangformat_executable_tmp) 11 | else() 12 | message(FATAL_ERROR "ClangFormat: ${CLANGFORMAT_EXECUTABLE} not found! Aborting") 13 | endif() 14 | endif() 15 | 16 | foreach(clangformat_source ${ARGV}) 17 | get_filename_component(clangformat_source ${clangformat_source} ABSOLUTE) 18 | list(APPEND clangformat_sources ${clangformat_source}) 19 | endforeach() 20 | 21 | add_custom_target(${PROJECT_NAME}_clangformat 22 | COMMAND 23 | ${CLANGFORMAT_EXECUTABLE} 24 | -style=file 25 | -i 26 | ${clangformat_sources} 27 | COMMENT 28 | "Formating with ${CLANGFORMAT_EXECUTABLE} ..." 29 | ) 30 | 31 | if(TARGET clangformat) 32 | add_dependencies(clangformat ${PROJECT_NAME}_clangformat) 33 | else() 34 | add_custom_target(clangformat DEPENDS ${PROJECT_NAME}_clangformat) 35 | endif() 36 | endfunction() 37 | 38 | function(target_clangformat_setup target) 39 | get_target_property(target_sources ${target} SOURCES) 40 | clangformat_setup(${target_sources}) 41 | endfunction() -------------------------------------------------------------------------------- /cmake/VersionFromGit.cmake: -------------------------------------------------------------------------------- 1 | function(version_from_git) 2 | execute_process( 3 | COMMAND git rev-list --tags --max-count=1 4 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 5 | RESULT_VARIABLE git_result 6 | OUTPUT_VARIABLE git_rev 7 | ERROR_VARIABLE git_error 8 | OUTPUT_STRIP_TRAILING_WHITESPACE 9 | ERROR_STRIP_TRAILING_WHITESPACE 10 | ) 11 | 12 | execute_process( 13 | COMMAND git describe --tags ${git_rev} 14 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 15 | RESULT_VARIABLE git_result 16 | OUTPUT_VARIABLE git_rev_tag 17 | ERROR_VARIABLE git_error 18 | OUTPUT_STRIP_TRAILING_WHITESPACE 19 | ERROR_STRIP_TRAILING_WHITESPACE 20 | ) 21 | 22 | if (NOT git_result EQUAL 0) 23 | message(FATAL_ERROR "Failed to execute Git: ${git_error}") 24 | endif() 25 | 26 | if(NOT "$ENV{GIT_TAG}" STREQUAL "") 27 | set(git_rev_tag, "$ENV{GIT_TAG}") 28 | endif() 29 | 30 | set(git_resolved_version ${git_rev_tag} PARENT_SCOPE) 31 | endfunction(version_from_git) 32 | -------------------------------------------------------------------------------- /examples/example_class.cb: -------------------------------------------------------------------------------- 1 | class Hello { 2 | printName() { 3 | print(this.name); 4 | } 5 | } 6 | 7 | print(Hello); 8 | 9 | var hello = Hello(); 10 | print(hello); 11 | 12 | hello.name = "Caboose"; 13 | print(hello.name); 14 | 15 | hello.name = "rocks"; 16 | hello.printName(); 17 | 18 | print(hello.printName); 19 | -------------------------------------------------------------------------------- /examples/example_fun.cb: -------------------------------------------------------------------------------- 1 | fun hello(name) { print("hello, " + name); } 2 | hello("world"); 3 | -------------------------------------------------------------------------------- /examples/example_upvalue.cb: -------------------------------------------------------------------------------- 1 | fun makeClosure(value) { 2 | fun closure() { 3 | print(value); 4 | } 5 | return closure; 6 | } 7 | 8 | makeClosure("banana")(); -------------------------------------------------------------------------------- /examples/example_var.cb: -------------------------------------------------------------------------------- 1 | var hello = "hello, world"; 2 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Format all C files in mainSRC dir. 4 | clang-format -style=file ./src/*.c -i 5 | clang-format -style=file ./src/*.h -i 6 | 7 | # Format main entrypoint file 8 | clang-format -style=file ./src/app/main.c -i -------------------------------------------------------------------------------- /src/app/main.c: -------------------------------------------------------------------------------- 1 | #include "../util.h" 2 | #include "../vm.h" 3 | #include 4 | #include 5 | 6 | static void 7 | repl() { 8 | printf("Caboose Prompt\n"); 9 | char line[1024]; 10 | for (;;) { 11 | printf("[>] "); 12 | 13 | if (!fgets(line, sizeof(line), stdin)) { 14 | printf("\n"); 15 | break; 16 | } 17 | 18 | interpret(line); 19 | } 20 | } 21 | 22 | static void 23 | runFile(const char* path) { 24 | char* source = readFile(path); 25 | InterpretResult result = interpret(source); 26 | free(source); 27 | 28 | if (result == INTERPRET_COMPILE_ERROR) 29 | exit(65); 30 | if (result == INTERPRET_RUNTIME_ERROR) 31 | exit(70); 32 | } 33 | 34 | int 35 | main(int argc, const char** argv) { 36 | initVM(argc == 2 ? argv[1] : "repl"); 37 | 38 | if (argc == 1) 39 | repl(); 40 | else if (argc == 2) 41 | runFile(argv[1]); 42 | else { 43 | fprintf(stderr, "Usage: cb [path]\n"); 44 | exit(64); 45 | } 46 | 47 | freeVM(); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/chunk.c: -------------------------------------------------------------------------------- 1 | #include "chunk.h" 2 | #include "memory.h" 3 | #include "vm.h" 4 | #include 5 | 6 | void 7 | initChunk(Chunk* chunk) { 8 | chunk->count = 0; 9 | chunk->capacity = 0; 10 | chunk->code = NULL; 11 | chunk->lines = NULL; 12 | initValueArray(&chunk->constants); 13 | } 14 | 15 | void 16 | freeChunk(Chunk* chunk) { 17 | FREE_ARRAY(uint8_t, chunk->code, chunk->capacity); 18 | FREE_ARRAY(int, chunk->lines, chunk->capacity); 19 | freeValueArray(&chunk->constants); 20 | initChunk(chunk); 21 | } 22 | 23 | void 24 | writeChunk(Chunk* chunk, uint8_t byte, int line) { 25 | if (chunk->capacity < chunk->count + 1) { 26 | int oldCapacity = chunk->capacity; 27 | chunk->capacity = GROW_CAPACITY(oldCapacity); 28 | chunk->code = 29 | GROW_ARRAY(chunk->code, uint8_t, oldCapacity, chunk->capacity); 30 | chunk->lines = 31 | GROW_ARRAY(chunk->lines, int, oldCapacity, chunk->capacity); 32 | } 33 | 34 | chunk->code[chunk->count] = byte; 35 | chunk->lines[chunk->count] = line; 36 | chunk->count++; 37 | } 38 | 39 | int 40 | addConstant(Chunk* chunk, Value value) { 41 | push(value); 42 | writeValueArray(&chunk->constants, value); 43 | pop(); 44 | return chunk->constants.count - 1; 45 | } 46 | -------------------------------------------------------------------------------- /src/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef caboose_chunk_h 2 | #define caboose_chunk_h 3 | 4 | #include "common.h" 5 | #include "value.h" 6 | 7 | typedef enum { 8 | OP_CONSTANT, 9 | OP_RETURN, 10 | OP_NEGATE, 11 | OP_ADD, 12 | OP_SUBTRACT, 13 | OP_MULTIPLY, 14 | OP_DIVIDE, 15 | OP_NIL, 16 | OP_TRUE, 17 | OP_FALSE, 18 | OP_NOT, 19 | OP_EQUAL, 20 | OP_GREATER, 21 | OP_LESS, 22 | OP_POP, 23 | OP_DEFINE_GLOBAL, 24 | OP_GET_GLOBAL, 25 | OP_SET_GLOBAL, 26 | OP_SET_LOCAL, 27 | OP_GET_LOCAL, 28 | OP_JUMP_IF_FALSE, 29 | OP_JUMP, 30 | OP_LOOP, 31 | OP_CALL, 32 | OP_CLOSURE, 33 | OP_GET_UPVALUE, 34 | OP_SET_UPVALUE, 35 | OP_CLOSE_UPVALUE, 36 | OP_IMPORT, 37 | OP_CLASS, 38 | OP_GET_PROPERTY, 39 | OP_SET_PROPERTY, 40 | OP_METHOD, 41 | OP_INVOKE, 42 | } OpCode; 43 | 44 | typedef struct { 45 | int count; 46 | int capacity; 47 | uint8_t* code; 48 | int* lines; 49 | ValueArray constants; 50 | } Chunk; 51 | 52 | void 53 | initChunk(Chunk* chunk); 54 | 55 | void 56 | freeChunk(Chunk* chunk); 57 | 58 | void 59 | writeChunk(Chunk* chunk, uint8_t byte, int line); 60 | 61 | int 62 | addConstant(Chunk* chunk, Value value); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef caboose_common_h 2 | #define caboose_common_h 3 | 4 | // #define DEBUG_PRINT_CODE 5 | // #define DEBUG_TRACE_EXECUTION 6 | 7 | // #define DEBUG_STRESS_GC 8 | // #define DEBUG_LOG_GC 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define UINT8_COUNT UINT8_MAX + 1 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/compiler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "compiler.h" 7 | #include "memory.h" 8 | #include "scanner.h" 9 | 10 | #ifdef DEBUG_PRINT_CODE 11 | #include "debug.h" 12 | #endif 13 | 14 | typedef struct { 15 | Token current; 16 | Token previous; 17 | bool hadError; 18 | bool panicMode; 19 | } Parser; 20 | 21 | typedef enum { 22 | PREC_NONE, 23 | PREC_ASSIGNMENT, // = 24 | PREC_OR, // or 25 | PREC_AND, // and 26 | PREC_EQUALITY, // == != 27 | PREC_COMPARISON, // < > <= >= 28 | PREC_TERM, // + - 29 | PREC_FACTOR, // * / 30 | PREC_UNARY, // ! - 31 | PREC_CALL, // . () 32 | PREC_PRIMARY 33 | } Precedence; 34 | 35 | typedef void (*ParseFn)(bool canAssign); 36 | 37 | typedef struct { 38 | ParseFn prefix; 39 | ParseFn infix; 40 | Precedence precedence; 41 | } ParseRule; 42 | 43 | typedef struct { 44 | Token name; 45 | int depth; 46 | bool isCaptured; 47 | } Local; 48 | 49 | typedef struct { 50 | uint8_t index; 51 | bool isLocal; 52 | } Upvalue; 53 | 54 | typedef enum { 55 | TYPE_FUNCTION, 56 | TYPE_SCRIPT, 57 | TYPE_METHOD, 58 | TYPE_INITIALIZER, 59 | } FunctionType; 60 | 61 | typedef struct Compiler { 62 | struct Compiler* enclosing; 63 | 64 | ObjFunction* function; 65 | FunctionType type; 66 | 67 | Local locals[UINT8_COUNT]; 68 | int localCount; 69 | Upvalue upvalues[UINT8_COUNT]; 70 | int scopeDepth; 71 | } Compiler; 72 | 73 | typedef struct ClassCompiler { 74 | struct ClassCompiler* enclosing; 75 | Token name; 76 | } ClassCompiler; 77 | 78 | Parser parser; 79 | 80 | Compiler* current = NULL; 81 | ClassCompiler* currentClass = NULL; 82 | 83 | static Chunk* 84 | currentChunk() { 85 | return ¤t->function->chunk; 86 | } 87 | 88 | static void 89 | errorAt(Token* token, const char* message) { 90 | if (parser.panicMode) 91 | return; 92 | 93 | parser.panicMode = true; 94 | fprintf(stderr, "[line %d] Error", token->line); 95 | 96 | if (token->type == TOKEN_EOF) 97 | fprintf(stderr, " at end"); 98 | else if (token->type == TOKEN_ERROR) 99 | ; // Nothing. 100 | else 101 | fprintf(stderr, " at '%.*s'", token->length, token->start); 102 | 103 | fprintf(stderr, ": %s\n", message); 104 | parser.hadError = true; 105 | } 106 | 107 | static void 108 | error(const char* message) { 109 | errorAt(&parser.previous, message); 110 | } 111 | 112 | static void 113 | errorAtCurrent(const char* message) { 114 | errorAt(&parser.current, message); 115 | } 116 | 117 | static void 118 | advance() { 119 | parser.previous = parser.current; 120 | 121 | for (;;) { 122 | parser.current = scanToken(); 123 | if (parser.current.type != TOKEN_ERROR) 124 | break; 125 | 126 | errorAtCurrent(parser.current.start); 127 | } 128 | } 129 | 130 | static void 131 | consume(TokenType type, const char* message) { 132 | if (parser.current.type == type) { 133 | advance(); 134 | return; 135 | } 136 | 137 | errorAtCurrent(message); 138 | } 139 | 140 | static bool 141 | check(TokenType type) { 142 | return parser.current.type == type; 143 | } 144 | 145 | static bool 146 | match(TokenType type) { 147 | if (!check(type)) 148 | return false; 149 | advance(); 150 | return true; 151 | } 152 | 153 | static void 154 | emitByte(uint8_t byte) { 155 | writeChunk(currentChunk(), byte, parser.previous.line); 156 | } 157 | 158 | static void 159 | emitBytes(uint8_t byte1, uint8_t byte2) { 160 | emitByte(byte1); 161 | emitByte(byte2); 162 | } 163 | 164 | static void 165 | emitLoop(int loopStart) { 166 | emitByte(OP_LOOP); 167 | 168 | int offset = currentChunk()->count - loopStart + 2; 169 | if (offset > UINT16_MAX) 170 | error("Loop body too large."); 171 | 172 | emitByte((offset >> 8) & 0xff); 173 | emitByte(offset & 0xff); 174 | } 175 | 176 | static int 177 | emitJump(uint8_t instruction) { 178 | emitByte(instruction); 179 | emitByte(0xff); 180 | emitByte(0xff); 181 | return currentChunk()->count - 2; 182 | } 183 | 184 | static void 185 | emitReturn() { 186 | if (current->type == TYPE_INITIALIZER) emitBytes(OP_GET_LOCAL, 0); 187 | else emitByte(OP_NIL); 188 | 189 | emitByte(OP_NIL); 190 | emitByte(OP_RETURN); 191 | } 192 | 193 | static uint8_t 194 | makeConstant(Value value) { 195 | int constant = addConstant(currentChunk(), value); 196 | if (constant > UINT8_MAX) { 197 | error("Too many constants in one chunk."); 198 | return 0; 199 | } 200 | 201 | return (uint8_t)constant; 202 | } 203 | 204 | static void 205 | emitConstant(Value value) { 206 | emitBytes(OP_CONSTANT, makeConstant(value)); 207 | } 208 | 209 | static void 210 | patchJump(int offset) { 211 | // -2 to adjust for the bytecode for the jump offset itself. 212 | int jump = currentChunk()->count - offset - 2; 213 | 214 | if (jump > UINT16_MAX) 215 | error("Too much code to jump over."); 216 | 217 | currentChunk()->code[offset] = (jump >> 8) & 0xff; 218 | currentChunk()->code[offset + 1] = jump & 0xff; 219 | } 220 | 221 | static void 222 | initCompiler(Compiler* compiler, FunctionType type) { 223 | compiler->enclosing = current; 224 | compiler->function = NULL; 225 | compiler->type = type; 226 | compiler->localCount = 0; 227 | compiler->function = newFunction(); 228 | compiler->scopeDepth = 0; 229 | 230 | current = compiler; 231 | 232 | if (type != TYPE_SCRIPT) 233 | current->function->name = 234 | copyString(parser.previous.start, parser.previous.length); 235 | 236 | Local* local = ¤t->locals[current->localCount++]; 237 | local->depth = 0; 238 | local->isCaptured = false; 239 | if (type != TYPE_FUNCTION) { 240 | local->name.start = "this"; 241 | local->name.length = 4; 242 | } else { 243 | local->name.start = ""; 244 | local->name.length = 0; 245 | } 246 | } 247 | 248 | static ObjFunction* 249 | endCompiler() { 250 | emitReturn(); 251 | ObjFunction* function = current->function; 252 | 253 | #ifdef DEBUG_PRINT_CODE 254 | if (!parser.hadError) 255 | disassembleChunk(currentChunk(), "