├── .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 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 | [](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(), "