├── show_test_file.bat ├── docs ├── ribbon_architecture.png └── guide.md ├── test.bat ├── src ├── parser.h ├── sdl_extension │ ├── build.bat │ ├── build_optimized.bat │ ├── bdeploy.bat │ ├── bdeploy_optimized.bat │ └── sdl_extension.h ├── compiler.h ├── value_array.h ├── sample_extension │ ├── build.bat │ ├── myextension.h │ └── bdeploy.bat ├── disassembler.h ├── value_array.c ├── python │ └── tester │ │ ├── tests │ │ ├── math_module.test │ │ ├── objects.test │ │ ├── parsing.test │ │ ├── strings.test │ │ ├── native_integ.test │ │ ├── builtin_functions.test │ │ ├── modules.test │ │ ├── control_flow.test │ │ ├── math.test │ │ ├── functions.test │ │ ├── misc.test │ │ └── classes.test │ │ └── runtests.py ├── stdlib │ ├── path.rib │ ├── math.rib │ └── utils.rib ├── pointerarray.h ├── io.h ├── memory.h ├── builtin_test_module.h ├── cell_table.h ├── pointerarray.c ├── scanner.h ├── dynamic_array.h ├── builtins.h ├── table.h ├── ribbon_utils.h ├── bytecode.h ├── value.h ├── bytecode.c ├── cell_table.c ├── vm.h ├── ribbon_api.c ├── io.c ├── common.h ├── ribbon_api.h ├── main.c ├── value.c ├── builtin_test_module.c ├── memory.c ├── ribbon_object.h ├── ast.h ├── disassembler.c ├── scanner.c ├── ribbon_utils.c └── table.c ├── .gitignore ├── btest.bat ├── LICENSE └── README.md /show_test_file.bat: -------------------------------------------------------------------------------- 1 | start notepad src\python\tester\tests\%1.test -------------------------------------------------------------------------------- /docs/ribbon_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AvivC/RibbonLang/HEAD/docs/ribbon_architecture.png -------------------------------------------------------------------------------- /test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Intentionally no CLS so we can see compiler warnings at the top 3 | python src\python\tester\runtests.py 4 | exit /B !err! 5 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_parser_h 2 | #define ribbon_parser_h 3 | 4 | #include "ast.h" 5 | #include "bytecode.h" 6 | 7 | AstNode* parser_parse(const char* source, const char* file_path); 8 | 9 | #endif -------------------------------------------------------------------------------- /src/sdl_extension/build.bat: -------------------------------------------------------------------------------- 1 | gcc -fPIC -shared -o graphics.dll -Wl,--subsystem,windows -I../ -I%RIBBON_BUILD_INCLUDE%/SDL2 *.c -g -Wall -Wno-unused -L%RIBBON_BUILD_LIB% -lmingw32 -lSDL2main -lSDL2 -lSDL2_image 2 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_compiler_h 2 | #define ribbon_compiler_h 3 | 4 | #include "ast.h" 5 | #include "bytecode.h" 6 | 7 | void compiler_compile(AstNode* node, Bytecode* chunk); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/sdl_extension/build_optimized.bat: -------------------------------------------------------------------------------- 1 | gcc -DNDEBUG -fPIC -shared -o graphics.dll -Wl,--subsystem,windows -I../ -I%RIBBON_BUILD_INCLUDE%/SDL2 *.c -Wall -Wno-unused -O2 -L%RIBBON_BUILD_LIB% -lmingw32 -lSDL2main -lSDL2 -lSDL2_image 2 | -------------------------------------------------------------------------------- /src/value_array.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_value_array_h 2 | #define ribbon_value_array_h 3 | 4 | #include "dynamic_array.h" 5 | 6 | DECLARE_DYNAMIC_ARRAY(struct Value, ValueArray, value_array) 7 | 8 | ValueArray value_array_make(int count, struct Value* values); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/sample_extension/build.bat: -------------------------------------------------------------------------------- 1 | gcc -g -fPIC myextension.c -o myextension.dll -I.. -D MYEXTENSION_EXPORTS -D EXTENSION_1 -shared -Wl,--subsystem,windows 2 | 3 | gcc -g -fPIC myextension.c -o my_second_extension.dll -I.. -D MYEXTENSION_EXPORTS -D EXTENSION_2 -shared -Wl,--subsystem,windows 4 | -------------------------------------------------------------------------------- /src/sdl_extension/bdeploy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal EnableDelayedExpansion 4 | 5 | pushd %~dp0 6 | call build.bat 7 | if %errorlevel%==0 ( 8 | echo Build successful 9 | move graphics.dll ..\stdlib\graphics.dll 10 | popd 11 | ) else ( 12 | echo Build failed 13 | exit /b -1 14 | ) 15 | -------------------------------------------------------------------------------- /src/disassembler.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_disassembler_h 2 | #define ribbon_disassembler_h 3 | 4 | #include "bytecode.h" 5 | #include "common.h" 6 | 7 | void disassembler_do_bytecode(Bytecode* chunk); 8 | int disassembler_do_single_instruction(OP_CODE opcode, Bytecode* chunk, int offset); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/sdl_extension/bdeploy_optimized.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal EnableDelayedExpansion 4 | 5 | pushd %~dp0 6 | call build_optimized.bat 7 | if %errorlevel%==0 ( 8 | echo Build successful 9 | move graphics.dll ..\stdlib\graphics.dll 10 | popd 11 | ) else ( 12 | echo Build failed 13 | exit /b -1 14 | ) 15 | -------------------------------------------------------------------------------- /src/sdl_extension/sdl_extension.h: -------------------------------------------------------------------------------- 1 | #ifndef sdl_extension_sdl_extension_h 2 | #define sdl_extension_sdl_extension_h 3 | 4 | #include "ribbon_api.h" 5 | 6 | #include "SDL2/SDL.h" 7 | #include "SDL2/SDL_image.h" 8 | 9 | __declspec(dllexport) bool ribbon_module_init(RibbonApi api, ObjectModule* module); 10 | 11 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | code.rib 2 | bdry_run.bat 3 | brun.bat 4 | dry_run.bat 5 | editscript.bat 6 | run.bat 7 | *.exe 8 | *.pyc 9 | *.dll 10 | python/tester/.idea/ 11 | python/tester/__pycache__/ 12 | Debug/ 13 | CMakeLists.txt.user 14 | .vscode/ 15 | .settings/ 16 | .project 17 | .cproject 18 | build_dev_no_gc_stress_test.bat 19 | release/ 20 | -------------------------------------------------------------------------------- /src/sample_extension/myextension.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #ifdef MYEXTENSION_EXPORTS 6 | #define MYEXTENSIONAPI __declspec(dllexport) 7 | #else 8 | #define MYEXTENSIONAPI __declspec(dllimport) 9 | #endif 10 | 11 | MYEXTENSIONAPI bool ribbon_module_init(RibbonApi api, ObjectModule* module); 12 | -------------------------------------------------------------------------------- /src/value_array.c: -------------------------------------------------------------------------------- 1 | #include "value_array.h" 2 | #include "value.h" 3 | 4 | IMPLEMENT_DYNAMIC_ARRAY(struct Value, ValueArray, value_array) 5 | 6 | ValueArray value_array_make(int count, struct Value* values) { 7 | ValueArray array; 8 | value_array_init(&array); 9 | for (int i = 0; i < count; i++) { 10 | value_array_write(&array, &values[i]); 11 | } 12 | return array; 13 | } -------------------------------------------------------------------------------- /src/sample_extension/bdeploy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal EnableDelayedExpansion 4 | 5 | pushd %~dp0 6 | call build.bat 7 | if %errorlevel%==0 ( 8 | echo Build successful 9 | copy myextension.dll ..\stdlib\myextension.dll 10 | move myextension.dll ..\myuserextension.dll 11 | move my_second_extension.dll ..\my_second_user_extension.dll 12 | popd 13 | ) else ( 14 | echo Build failed 15 | exit /b 1 16 | ) 17 | -------------------------------------------------------------------------------- /src/python/tester/tests/math_module.test: -------------------------------------------------------------------------------- 1 | test sin function 2 | import math 3 | print(math.sin(10)) 4 | print(math.sin(-10)) 5 | print(math.sin(3)) 6 | print(math.sin(0.2)) 7 | print(math.sin(18654)) 8 | print(math.sin(-1000)) 9 | print(math.sin(0.003)) 10 | expect 11 | -0.544021 12 | 0.544021 13 | 0.14112 14 | 0.198669 15 | -0.70127 16 | -0.82688 17 | 0.003 18 | end -------------------------------------------------------------------------------- /btest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | setlocal EnableDelayedExpansion 5 | 6 | echo Starting Build and Test 7 | 8 | echo Building... 9 | call build_dev.bat 10 | 11 | if %errorlevel%==0 ( 12 | echo Build successful. Ready to run tests. 13 | pause 14 | 15 | call test.bat 16 | 17 | if !errorlevel!==0 ( 18 | echo Tests successful. 19 | ) else ( 20 | echo Tests failed. 21 | ) 22 | ) else ( 23 | echo Build failed. 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /src/stdlib/path.rib: -------------------------------------------------------------------------------- 1 | # Standard library module for path operations and utilities 2 | 3 | concat = { | paths | 4 | result = "" 5 | i = 0 6 | for path in paths { 7 | result += path 8 | if i < paths.length() - 1 { 9 | result += "\\" 10 | } 11 | i += 1 12 | } 13 | return result 14 | } 15 | 16 | relative_to_main_directory = { | path | 17 | return concat([get_main_file_directory(), path]) 18 | } 19 | -------------------------------------------------------------------------------- /src/pointerarray.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_pointerarray_h 2 | #define ribbon_pointerarray_h 3 | 4 | typedef struct { 5 | int count; 6 | int capacity; 7 | void** values; 8 | const char* alloc_string; /* Kind of ugly, purely for debugging */ 9 | } PointerArray; 10 | 11 | void pointer_array_init(PointerArray* array, const char* alloc_string); 12 | void pointer_array_write(PointerArray* array, void* value); 13 | void pointer_array_free(PointerArray* array); 14 | void** pointer_array_to_plain_array(PointerArray* array, const char* what); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/io.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_io_h 2 | #define ribbon_io_h 3 | 4 | #include 5 | #include "common.h" 6 | #include "table.h" 7 | 8 | typedef enum { 9 | IO_SUCCESS, 10 | IO_OPEN_FILE_FAILURE, 11 | IO_READ_FILE_FAILURE, 12 | IO_CLOSE_FILE_FAILURE, 13 | IO_WRITE_FILE_FAILURE, 14 | IO_DELETE_FILE_FAILURE 15 | } IOResult; 16 | 17 | IOResult io_read_text_file(const char* file_name, const char* alloc_string, char** text_out, size_t* text_length_out); 18 | IOResult io_read_binary_file(const char* file_name, Table* data_out); 19 | IOResult io_write_text_file(const char* file_name, const char* string); 20 | IOResult io_write_binary_file(const char* file_name, Table* data); 21 | IOResult io_delete_file(const char* file_name); 22 | BOOL io_file_exists(LPCTSTR path); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_memory_h 2 | #define ribbon_memory_h 3 | 4 | #include "common.h" 5 | 6 | typedef struct { 7 | const char* name; 8 | size_t size; 9 | } Allocation; 10 | 11 | #define GROW_CAPACITY(capacity) (capacity) < 8 ? 8 : (capacity) * 2 12 | 13 | size_t get_allocated_memory(); 14 | size_t get_allocations_count(); 15 | 16 | void memory_init(void); 17 | 18 | void* allocate(size_t size, const char* what); 19 | void deallocate(void* pointer, size_t oldSize, const char* what); 20 | void* reallocate(void* pointer, size_t oldSize, size_t newSize, const char* what); 21 | 22 | void* allocate_no_tracking(size_t size); 23 | void deallocate_no_tracking(void* pointer); 24 | void* reallocate_no_tracking(void* pointer, size_t new_size); 25 | 26 | void memory_print_allocated_entries(); // for debugging 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/builtin_test_module.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_builtin_test_module_h 2 | #define ribbon_builtin_test_module_h 3 | 4 | #include "value.h" 5 | #include "ribbon_object.h" 6 | 7 | bool builtin_test_demo_print(Object* self, ValueArray args, Value* out); 8 | bool builtin_test_call_callback_with_args(Object* self, ValueArray args, Value* out); 9 | bool builtin_test_get_value_directly_from_object_attributes(Object* self, ValueArray args, Value* out); 10 | bool builtin_test_same_object(Object* self, ValueArray args, Value* out); 11 | bool builtin_test_get_object_address(Object* self, ValueArray args, Value* out); 12 | bool builtin_test_gc(Object* self, ValueArray args, Value* out); 13 | bool builtin_test_table_details(Object* self, ValueArray args, Value* out); 14 | bool builtin_test_table_delete(Object* self, ValueArray args, Value* out); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 avivcohn 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 | -------------------------------------------------------------------------------- /src/cell_table.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_cell_table_h 2 | #define ribbon_cell_table_h 3 | 4 | #include "table.h" 5 | #include "common.h" 6 | 7 | typedef struct { 8 | Table table; 9 | } CellTable; 10 | 11 | struct ObjectCell; 12 | struct ObjectString; 13 | 14 | void cell_table_init(CellTable* table); 15 | CellTable cell_table_new_empty(void); 16 | 17 | bool cell_table_get_value(CellTable* table, struct ObjectString* key, Value* out); 18 | void cell_table_set_value(CellTable* table, struct ObjectString* key, Value value); 19 | 20 | bool cell_table_get_value_cstring_key(CellTable* table, const char* key, Value* out); 21 | void cell_table_set_value_cstring_key(CellTable* table, const char* key, Value value); 22 | 23 | bool cell_table_get_cell(CellTable* table, struct ObjectString* key, struct ObjectCell** out); 24 | void cell_table_set_cell(CellTable* table, struct ObjectString* key, struct ObjectCell* cell); 25 | 26 | bool cell_table_get_cell_cstring_key(CellTable* table, const char* key, struct ObjectCell** out); 27 | void cell_table_set_cell_cstring_key(CellTable* table, const char* key, struct ObjectCell* cell); 28 | 29 | void cell_table_free(CellTable* table); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/pointerarray.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "pointerarray.h" 4 | #include "memory.h" 5 | 6 | void pointer_array_init(PointerArray* array, const char* alloc_string) { 7 | array->count = 0; 8 | array->capacity = 0; 9 | array->values = NULL; 10 | array->alloc_string = alloc_string; 11 | } 12 | 13 | void pointer_array_write(PointerArray* array, void* value) { 14 | if (array->count == array->capacity) { 15 | int old_capacity = array->capacity; 16 | array->capacity = GROW_CAPACITY(old_capacity); 17 | array->values = reallocate(array->values, sizeof(void*) * old_capacity, sizeof(void*) * array->capacity, array->alloc_string); 18 | } 19 | 20 | array->values[array->count++] = value; 21 | } 22 | 23 | void pointer_array_free(PointerArray* array) { 24 | deallocate(array->values, sizeof(void*) * array->capacity, array->alloc_string); 25 | pointer_array_init(array, array->alloc_string); 26 | } 27 | 28 | void** pointer_array_to_plain_array(PointerArray* array, const char* what) { 29 | void** plain_array = allocate(sizeof(void*) * array->count, what); 30 | memcpy(plain_array, array->values, array->count * sizeof(void*)); 31 | return plain_array; 32 | } 33 | -------------------------------------------------------------------------------- /src/scanner.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_scanner_h 2 | #define ribbon_scanner_h 3 | 4 | typedef enum { 5 | // Token types 6 | TOKEN_IDENTIFIER, TOKEN_NUMBER, TOKEN_STRING, 7 | 8 | // One-character symbols 9 | TOKEN_PLUS, TOKEN_MINUS, TOKEN_STAR, TOKEN_SLASH, TOKEN_MODULO, 10 | TOKEN_PLUS_EQUALS, TOKEN_MINUS_EQUALS, TOKEN_STAR_EQUALS, TOKEN_SLASH_EQUALS, TOKEN_MODULO_EQUALS, 11 | TOKEN_EQUAL, TOKEN_NOT, TOKEN_LESS_THAN, TOKEN_GREATER_THAN, 12 | TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, 13 | TOKEN_COMMA, TOKEN_NEWLINE, TOKEN_DOT, TOKEN_LEFT_SQUARE_BRACE, TOKEN_RIGHT_SQUARE_BRACE, TOKEN_COLON, TOKEN_PIPE, 14 | 15 | // Two-character symbols 16 | TOKEN_EQUAL_EQUAL, TOKEN_BANG_EQUAL, TOKEN_GREATER_EQUAL, TOKEN_LESS_EQUAL, 17 | 18 | // Alphabetic symbols 19 | TOKEN_IF, TOKEN_ELSE, TOKEN_ELSIF, TOKEN_WHILE, TOKEN_AND, TOKEN_OR, TOKEN_RETURN, 20 | TOKEN_TRUE, TOKEN_FALSE, TOKEN_IMPORT, TOKEN_CLASS, TOKEN_NIL, TOKEN_FOR, TOKEN_IN, TOKEN_EXTERNAL, 21 | 22 | // Special tokens 23 | TOKEN_EOF, TOKEN_ERROR 24 | } ScannerTokenType; 25 | 26 | typedef struct { 27 | ScannerTokenType type; 28 | const char* start; 29 | int length; 30 | int lineNumber; 31 | } Token; 32 | 33 | void scanner_init(const char* source); 34 | Token scanner_peek_next_token(); 35 | Token scanner_peek_token_at_offset(int offset); 36 | Token scanner_next_token(); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/stdlib/math.rib: -------------------------------------------------------------------------------- 1 | # Standard library module for common math operations 2 | 3 | max = { | x, y | 4 | if x > y { 5 | return x 6 | } else { 7 | return y 8 | } 9 | } 10 | 11 | min = { | x, y | 12 | if max(x, y) == x { 13 | return y 14 | } else { 15 | return x 16 | } 17 | } 18 | 19 | # TODO: Support fractions 20 | power = { | n, exp | 21 | if exp < 0 { 22 | return 1 / power(n, -1 * exp) 23 | } 24 | if exp == 0 { 25 | return 1 26 | } 27 | result = n 28 | i = 1 29 | while i < exp { 30 | result = result * n 31 | i = i + 1 32 | } 33 | return result 34 | } 35 | 36 | sqr = { | n | 37 | return power(n, 2) 38 | } 39 | 40 | abs = { | x | 41 | if x >= 0 { 42 | return x 43 | } 44 | return x * -1 45 | } 46 | 47 | _SQRT_RECURSION_LIMIT = 75 48 | 49 | _sqrt_estimate = { | x, low_bound, high_bound, counter | 50 | counter = counter + 1 51 | middle = (low_bound + high_bound) / 2 52 | 53 | if counter >= _SQRT_RECURSION_LIMIT { 54 | return middle 55 | } 56 | 57 | if sqr(middle) > x { 58 | return _sqrt_estimate(x, low_bound, middle, counter) 59 | } 60 | 61 | return _sqrt_estimate(x, middle, high_bound, counter) 62 | } 63 | 64 | sqrt = { | x | 65 | return _sqrt_estimate(x, 1, x, 0) 66 | } 67 | 68 | random_in_range = { | min, max | 69 | return random() % (max - min + 1) + min 70 | } 71 | 72 | sin = { | radians | 73 | return __sin(radians) 74 | } 75 | -------------------------------------------------------------------------------- /src/dynamic_array.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_dynamic_array_h 2 | #define ribbon_dynamic_array_h 3 | 4 | #include "common.h" 5 | #include "memory.h" 6 | 7 | #define DECLARE_DYNAMIC_ARRAY(TYPE, ARRAY_NAME, FUNCTIONS_PERFIX) \ 8 | typedef struct { \ 9 | int count; \ 10 | int capacity; \ 11 | TYPE * values; \ 12 | } ARRAY_NAME; \ 13 | \ 14 | void FUNCTIONS_PERFIX##_init(ARRAY_NAME* array); \ 15 | \ 16 | void FUNCTIONS_PERFIX##_write(ARRAY_NAME* array, TYPE* value); \ 17 | \ 18 | void FUNCTIONS_PERFIX##_free(ARRAY_NAME* array); \ 19 | 20 | #define IMPLEMENT_DYNAMIC_ARRAY(TYPE, ARRAY_NAME, FUNCTIONS_PERFIX) \ 21 | \ 22 | void FUNCTIONS_PERFIX##_init(ARRAY_NAME* array) {\ 23 | array->values = NULL;\ 24 | array->count = 0;\ 25 | array->capacity = 0;\ 26 | }\ 27 | \ 28 | void FUNCTIONS_PERFIX##_write(ARRAY_NAME* array, TYPE* value) {\ 29 | if (array->count == array->capacity) {\ 30 | int oldCapacity = array->capacity;\ 31 | array->capacity = GROW_CAPACITY(oldCapacity);\ 32 | array->values = reallocate(array->values, sizeof(TYPE) * oldCapacity, sizeof(TYPE) * array->capacity, #ARRAY_NAME " buffer");\ 33 | }\ 34 | \ 35 | array->values[array->count++] = *value;\ 36 | }\ 37 | \ 38 | void FUNCTIONS_PERFIX##_free(ARRAY_NAME* array) {\ 39 | if (array->values != NULL) {\ 40 | deallocate(array->values, array->capacity * sizeof(TYPE), #ARRAY_NAME " buffer");\ 41 | }\ 42 | FUNCTIONS_PERFIX##_init(array);\ 43 | }\ 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/builtins.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_builtins_h 2 | #define ribbon_builtins_h 3 | 4 | #include "value.h" 5 | 6 | bool builtin_print(Object* self, ValueArray args, Value* out); 7 | bool builtin_input(Object* self, ValueArray args, Value* out); 8 | bool builtin_read_text_file(Object* self, ValueArray args, Value* out); 9 | bool builtin_read_binary_file(Object* self, ValueArray args, Value* out); 10 | bool builtin_write_text_file(Object* self, ValueArray args, Value* out); 11 | bool builtin_write_binary_file(Object* self, ValueArray args, Value* out); 12 | bool builtin_delete_file(Object* self, ValueArray args, Value* out); 13 | bool builtin_file_exists(Object* self, ValueArray args, Value* out); 14 | bool builtin_spawn(Object* self, ValueArray args, Value* out); 15 | bool builtin_time(Object* self, ValueArray args, Value* out); 16 | bool builtin_to_number(Object* self, ValueArray args, Value* out); 17 | bool builtin_to_string(Object* self, ValueArray args, Value* out); 18 | bool builtin_has_attr(Object* self, ValueArray args, Value* out); 19 | bool builtin_random(Object* self, ValueArray args, Value* out); 20 | bool builtin_sin(Object* self, ValueArray args, Value* out); 21 | bool builtin_get_main_file_path(Object* self, ValueArray args, Value* out); 22 | bool builtin_get_main_file_directory(Object* self, ValueArray args, Value* out); 23 | bool builtin_is_instance(Object* self, ValueArray args, Value* out); 24 | bool builtin_get_type(Object* self, ValueArray args, Value* out); 25 | bool builtin_super(Object* self, ValueArray args, Value* out); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/table.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_table_h 2 | #define ribbon_table_h 3 | 4 | #include "common.h" 5 | #include "value.h" 6 | #include "pointerarray.h" 7 | 8 | typedef struct { 9 | Value key; 10 | Value value; 11 | char tombstone; 12 | } Entry; 13 | 14 | typedef struct { 15 | size_t capacity; /* Capacity of current underlying entries array */ 16 | size_t count; /* Number of entries + tombstones, for determining when to grow */ 17 | size_t num_entries; /* Number of logical entries */ 18 | Entry* entries; 19 | bool is_memory_infrastructure; 20 | bool is_growing; /* For debugging */ 21 | size_t collision_count; /* For debugging */ 22 | } Table; 23 | 24 | struct ObjectString; 25 | 26 | Table table_new_empty(void); 27 | 28 | void table_init(Table* table); 29 | void table_init_memory_infrastructure(Table* table); 30 | 31 | void table_set(Table* table, struct Value key, Value value); 32 | bool table_get(Table* table, Value key, Value* out); 33 | 34 | void table_set_cstring_key(Table* table, const char* key, Value value); 35 | bool table_get_cstring_key(Table* table, const char* key, Value* out); 36 | 37 | void table_set_value_in_cell(Table* table, Value key, Value value); 38 | 39 | bool table_delete(Table* table, Value key); 40 | 41 | void table_free(Table* table); 42 | 43 | PointerArray table_iterate(Table* table, const char* alloc_string); 44 | 45 | void table_print(Table* table); /* For user display */ 46 | void table_print_debug(Table* table); /* for trace execution and debugging */ 47 | 48 | #if DEBUG_TABLE_STATS 49 | void table_debug_print_general_stats(void); 50 | #endif 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/ribbon_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_ribbon_utils_h 2 | #define ribbon_ribbon_utils_h 3 | 4 | #include "common.h" 5 | #include "dynamic_array.h" 6 | #include "value_array.h" 7 | 8 | uint16_t two_bytes_to_short(uint8_t a, uint8_t b); 9 | 10 | void short_to_two_bytes(uint16_t num, uint8_t* bytes_out); 11 | 12 | char* copy_cstring(const char* string, int length, const char* what); 13 | 14 | char* copy_null_terminated_cstring(const char* string, const char* what); 15 | 16 | // TODO: void printStack(void); 17 | 18 | unsigned long hash_string(const char* string); 19 | unsigned long hash_string_bounded(const char* string, int length); 20 | unsigned int hash_int(unsigned int x); 21 | 22 | char* concat_cstrings(const char* str1, int str1_length, const char* str2, int str2_length, const char* alloc_string); 23 | char* concat_null_terminated_cstrings(const char* str1, const char* str2, const char* alloc_string); 24 | char* concat_multi_null_terminated_cstrings(int count, char** strings, const char* alloc_string); 25 | char* concat_multi_cstrings(int count, char** strings, int lengths[], char* alloc_string); 26 | 27 | bool cstrings_equal(const char* s1, int length1, const char* s2, int length2); 28 | 29 | char* concat_null_terminated_paths(char* p1, char* p2, char* alloc_string); 30 | 31 | char* find_interpreter_directory(void); 32 | char* get_current_working_directory(void); 33 | char* directory_from_path(char* path); 34 | 35 | bool arguments_valid(ValueArray args, const char* string); 36 | 37 | DECLARE_DYNAMIC_ARRAY(size_t, IntegerArray, integer_array) 38 | 39 | DECLARE_DYNAMIC_ARRAY(char, CharacterArray, character_array) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/bytecode.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_bytecode_h 2 | #define ribbon_bytecode_h 3 | 4 | #include "common.h" 5 | #include "value_array.h" 6 | #include "ribbon_utils.h" 7 | 8 | typedef enum { 9 | OP_CONSTANT, 10 | OP_ADD, 11 | OP_SUBTRACT, 12 | OP_MULTIPLY, 13 | OP_DIVIDE, 14 | OP_MODULO, 15 | OP_NEGATE, 16 | OP_GREATER_THAN, 17 | OP_LESS_THAN, 18 | OP_GREATER_EQUAL, 19 | OP_LESS_EQUAL, 20 | OP_EQUAL, 21 | OP_ACCESS_KEY, 22 | OP_SET_KEY, 23 | OP_LOAD_VARIABLE, 24 | OP_SET_VARIABLE, 25 | OP_DECLARE_EXTERNAL, 26 | OP_MAKE_TABLE, 27 | OP_CALL, 28 | OP_GET_ATTRIBUTE, 29 | OP_SET_ATTRIBUTE, 30 | OP_POP, 31 | OP_DUP, 32 | OP_DUP_TWO, 33 | OP_SWAP, 34 | OP_SWAP_TOP_WITH_NEXT_TWO, 35 | OP_GET_OFFSET_FROM_TOP, 36 | OP_SET_OFFSET_FROM_TOP, 37 | OP_JUMP_IF_FALSE, 38 | OP_JUMP_IF_TRUE, 39 | OP_JUMP_FORWARD, 40 | OP_JUMP_BACKWARD, 41 | OP_MAKE_STRING, 42 | OP_MAKE_FUNCTION, 43 | OP_MAKE_CLASS, 44 | OP_IMPORT, 45 | OP_NIL, 46 | OP_RETURN 47 | } OP_CODE; 48 | 49 | typedef struct { 50 | uint8_t* code; 51 | ValueArray constants; 52 | int capacity; 53 | int count; 54 | IntegerArray referenced_names_indices; 55 | IntegerArray assigned_names_indices; 56 | } Bytecode; 57 | 58 | void bytecode_init(Bytecode* chunk); 59 | void bytecode_write(Bytecode* chunk, uint8_t byte); 60 | void bytecode_set(Bytecode* chunk, int position, uint8_t byte); 61 | void bytecode_free(Bytecode* chunk); 62 | int bytecode_add_constant(Bytecode* chunk, struct Value* constant); 63 | 64 | void bytecode_print_constant_table(Bytecode* chunk); // For debugging 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/python/tester/tests/objects.test: -------------------------------------------------------------------------------- 1 | test attribute assignment 2 | obj = [] # just an object that happens to be a table. 3 | obj.x = 10 4 | print(obj.x) 5 | obj.x = 20 6 | print(obj.x) 7 | obj.x = obj.x + 1 8 | print(obj.x) 9 | 10 | b = [] # also just an object that happens to be a table 11 | b.y = 10 + obj.x 12 | print(b.y) 13 | expect 14 | 10 15 | 20 16 | 21 17 | 31 18 | end 19 | 20 | test objects of the same type are independent 21 | maker = { 22 | return [] # just an object which happens to be a table 23 | } 24 | 25 | s1 = maker() 26 | s2 = maker() 27 | 28 | print(s1 == s2) # identity equality, should be false 29 | 30 | s1.attr = "thing" 31 | print(has_attribute(s2, "attr")) # should print false 32 | print(s1.attr) 33 | s2.attr = "other thing" 34 | print(s1.attr) 35 | print(s2.attr) 36 | 37 | shared_thing = { 38 | print("I'm some object which happens to be a function") 39 | } 40 | 41 | s1.shared_attr = shared_thing 42 | s2.shared_attr = shared_thing 43 | 44 | print(s1.shared_attr == s2.shared_attr) 45 | s2.shared_attr.nested_attr = "I'm nested" 46 | print(s1.shared_attr.nested_attr) 47 | print(s2.shared_attr.nested_attr) 48 | 49 | s1.shared_attr = "Something not shared" 50 | print(s1.shared_attr) 51 | print(s2.shared_attr.nested_attr) 52 | print(has_attribute(s1.shared_attr, "nested_attr")) 53 | expect 54 | false 55 | false 56 | thing 57 | thing 58 | other thing 59 | true 60 | I'm nested 61 | I'm nested 62 | Something not shared 63 | I'm nested 64 | false 65 | end 66 | -------------------------------------------------------------------------------- /src/value.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_value_h 2 | #define ribbon_value_h 3 | 4 | #include "bytecode.h" 5 | #include "common.h" 6 | #include "dynamic_array.h" 7 | 8 | typedef enum { 9 | VALUE_NUMBER, 10 | VALUE_BOOLEAN, 11 | VALUE_NIL, 12 | VALUE_RAW_STRING, 13 | VALUE_OBJECT, 14 | VALUE_ALLOCATION, // Internal 15 | VALUE_ADDRESS // Internal 16 | } ValueType; 17 | 18 | typedef struct { 19 | const char* data; 20 | int length; 21 | unsigned long hash; 22 | } RawString; 23 | 24 | typedef struct Value { 25 | ValueType type; 26 | union { 27 | double number; 28 | bool boolean; 29 | RawString raw_string; 30 | struct Object* object; 31 | Allocation allocation; 32 | uintptr_t address; 33 | } as; 34 | } Value; 35 | 36 | #define MAKE_VALUE_NUMBER(n) (Value){.type = VALUE_NUMBER, .as.number = (n)} 37 | #define MAKE_VALUE_BOOLEAN(val) (Value){.type = VALUE_BOOLEAN, .as.boolean = (val)} 38 | #define MAKE_VALUE_NIL() (Value){.type = VALUE_NIL, .as.number = -1} 39 | #define MAKE_VALUE_RAW_STRING(cstring, the_length, the_hash) (Value){.type = VALUE_RAW_STRING, .as.raw_string \ 40 | = (RawString) {.data = (cstring), .length = (the_length), .hash = (the_hash)}} 41 | #define MAKE_VALUE_OBJECT(o) (Value){.type = VALUE_OBJECT, .as.object = (struct Object*)(o)} 42 | #define MAKE_VALUE_ALLOCATION(the_name, the_size) (Value) {.type = VALUE_ALLOCATION, \ 43 | .as.allocation = (Allocation) {.name = the_name, .size = the_size}} 44 | #define MAKE_VALUE_ADDRESS(the_address) (Value) {.type = VALUE_ADDRESS, .as.address = (uintptr_t) the_address } 45 | 46 | #define ASSERT_VALUE_TYPE(value, expected_type) \ 47 | do { \ 48 | if (value.type != expected_type) { \ 49 | FAIL("Expected value type: %d, found: %d", expected_type, value.type); \ 50 | } \ 51 | } while (false) 52 | 53 | void value_print(Value value); 54 | bool value_compare(Value a, Value b, int* output); 55 | 56 | bool value_hash(Value* value, unsigned long* result); 57 | 58 | const char* value_get_type(Value value); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/bytecode.c: -------------------------------------------------------------------------------- 1 | #include "bytecode.h" 2 | #include "common.h" 3 | #include "memory.h" 4 | #include "value.h" 5 | 6 | void bytecode_init(Bytecode* chunk) { 7 | chunk->capacity = 0; 8 | chunk->count = 0; 9 | chunk->code = NULL; 10 | value_array_init(&chunk->constants); 11 | integer_array_init(&chunk->referenced_names_indices); 12 | integer_array_init(&chunk->assigned_names_indices); 13 | } 14 | 15 | void bytecode_write(Bytecode* chunk, uint8_t byte) { 16 | if (chunk->count == chunk->capacity) { 17 | int oldCapacity = chunk->capacity; 18 | chunk->capacity = GROW_CAPACITY(oldCapacity); 19 | chunk->code = reallocate(chunk->code, oldCapacity, chunk->capacity, "Chunk code buffer"); 20 | } 21 | 22 | chunk->code[chunk->count++] = byte; 23 | } 24 | 25 | void bytecode_set(Bytecode* chunk, int position, uint8_t byte) { 26 | assert(position < chunk->count && position >= 0); 27 | 28 | chunk->code[position] = byte; 29 | } 30 | 31 | void bytecode_free(Bytecode* chunk) { 32 | deallocate(chunk->code, chunk->capacity * sizeof(uint8_t), "Chunk code buffer"); // the sizeof is probably stupid 33 | value_array_free(&chunk->constants); 34 | integer_array_free(&chunk->referenced_names_indices); 35 | integer_array_free(&chunk->assigned_names_indices); 36 | bytecode_init(chunk); 37 | } 38 | 39 | int bytecode_add_constant(Bytecode* chunk, struct Value* constant) { 40 | if (chunk->constants.count >= 65534) { 41 | FAIL("Too many constants to one code object (>= 65534). Cannot fit the index into a short in the bytecode."); 42 | } 43 | 44 | value_array_write(&chunk->constants, constant); 45 | return chunk->constants.count - 1; 46 | } 47 | 48 | void bytecode_print_constant_table(Bytecode* chunk) { // For debugging 49 | printf("\nConstant table [size %d] of chunk pointing at '%p':\n", chunk->constants.count, chunk->code); 50 | for (int i = 0; i < chunk->constants.count; i++) { 51 | Value constant = chunk->constants.values[i]; 52 | printf("%d: ", i); 53 | value_print(constant); 54 | printf(" [ type %d ]", constant.type); 55 | printf("\n"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/cell_table.c: -------------------------------------------------------------------------------- 1 | #include "cell_table.h" 2 | #include "table.h" 3 | #include "ribbon_object.h" 4 | 5 | void cell_table_init(CellTable* table) { 6 | table_init(&table->table); 7 | } 8 | 9 | void cell_table_set_value(CellTable* table, ObjectString* key, Value value) { 10 | /* Using special case route in table.c for optimization */ 11 | table_set_value_in_cell(&table->table, MAKE_VALUE_OBJECT(key), value); 12 | } 13 | 14 | bool cell_table_get_value(CellTable* table, struct ObjectString* key, Value* out) { 15 | ObjectCell* cell; 16 | if (cell_table_get_cell(table, key, &cell) && cell->is_filled) { 17 | *out = cell->value; 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | 24 | void cell_table_set_value_cstring_key(CellTable* table, const char* key, Value value) { 25 | cell_table_set_value(table, object_string_copy_from_null_terminated(key), value); 26 | } 27 | 28 | bool cell_table_get_value_cstring_key(CellTable* table, const char* key, Value* out) { 29 | return cell_table_get_value(table, object_string_copy_from_null_terminated(key), out); 30 | } 31 | 32 | bool cell_table_get_cell(CellTable* table, struct ObjectString* key, struct ObjectCell** out) { 33 | Table* inner_table = &table->table; 34 | Value current; 35 | if (table_get(inner_table, MAKE_VALUE_OBJECT(key), ¤t)) { 36 | assert(object_value_is(current, OBJECT_CELL)); 37 | *out = (ObjectCell*) current.as.object;; 38 | return true; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | void cell_table_set_cell(CellTable* table, struct ObjectString* key, struct ObjectCell* cell) { 45 | table_set(&table->table, MAKE_VALUE_OBJECT(key), MAKE_VALUE_OBJECT(cell)); 46 | } 47 | 48 | bool cell_table_get_cell_cstring_key(CellTable* table, const char* key, struct ObjectCell** out) { 49 | return cell_table_get_cell(table, object_string_copy_from_null_terminated(key), out); 50 | } 51 | 52 | void cell_table_set_cell_cstring_key(CellTable* table, const char* key, struct ObjectCell* cell) { 53 | cell_table_set_cell(table, object_string_copy_from_null_terminated(key), cell); 54 | } 55 | 56 | void cell_table_free(CellTable* table) { 57 | table_free(&table->table); 58 | cell_table_init(table); 59 | } 60 | 61 | CellTable cell_table_new_empty(void) { 62 | CellTable table; 63 | cell_table_init(&table); 64 | return table; 65 | } 66 | -------------------------------------------------------------------------------- /src/python/tester/tests/parsing.test: -------------------------------------------------------------------------------- 1 | test for loop 2 | for i in [1, 2, 3] { 3 | print(i) 4 | } 5 | 6 | for i in [1, 2, 3] 7 | { 8 | print(i) 9 | } 10 | 11 | for i in [1, 2, 3] { print(i) } 12 | 13 | expect 14 | 1 15 | 2 16 | 3 17 | 1 18 | 2 19 | 3 20 | 1 21 | 2 22 | 3 23 | end 24 | 25 | test if 26 | if true { 27 | print("a") 28 | } 29 | 30 | if true 31 | { 32 | print("a") 33 | } 34 | 35 | if true { print("a") } 36 | expect 37 | a 38 | a 39 | a 40 | end 41 | 42 | 43 | test while 44 | i = 0 45 | while i < 2 { 46 | print("a") 47 | i += 1 48 | } 49 | 50 | i = 0 51 | while i < 2 52 | { 53 | print("a") 54 | i += 1 55 | } 56 | 57 | i = 0 58 | while i < 2 { print("a") 59 | i += 1 60 | } 61 | expect 62 | a 63 | a 64 | a 65 | a 66 | a 67 | a 68 | end 69 | 70 | test function 71 | a = { print("a") } 72 | b = { 73 | print("a")} 74 | c = { 75 | print("a") 76 | } 77 | d = 78 | { 79 | print("a") 80 | } 81 | a() 82 | b() 83 | c() 84 | d() 85 | expect 86 | a 87 | a 88 | a 89 | a 90 | end 91 | 92 | test table 93 | t = [1, 2, 3] 94 | q = [ 95 | 1 96 | , 97 | 2, 98 | 3 99 | ] 100 | v = 101 | [ 102 | 1 103 | , 104 | 2, 105 | 3 106 | ] 107 | 108 | print(t[0]) 109 | print(t[1]) 110 | print(t[2]) 111 | 112 | print(q[0]) 113 | print(q[1]) 114 | print(q[2]) 115 | 116 | print(v[0]) 117 | print(v[1]) 118 | print(v[2]) 119 | expect 120 | 1 121 | 2 122 | 3 123 | 1 124 | 2 125 | 3 126 | 1 127 | 2 128 | 3 129 | end 130 | 131 | test class 132 | A = class { 133 | f = {print("f")} 134 | } 135 | 136 | B = class { 137 | f = {print("f")}} 138 | 139 | C = class 140 | { 141 | f = {print("f")} 142 | } 143 | 144 | D = class { f = {print("f")}} 145 | 146 | A().f() 147 | B().f() 148 | C().f() 149 | D().f() 150 | expect 151 | f 152 | f 153 | f 154 | f 155 | end 156 | -------------------------------------------------------------------------------- /src/vm.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_vm_h 2 | #define ribbon_vm_h 3 | 4 | #include "bytecode.h" 5 | #include "table.h" 6 | #include "cell_table.h" 7 | #include "ribbon_object.h" 8 | #include "value.h" 9 | 10 | typedef enum { 11 | CALL_RESULT_SUCCESS, 12 | CALL_RESULT_INVALID_ARGUMENT_COUNT, 13 | CALL_RESULT_RIBBON_CODE_EXECUTION_FAILED, 14 | CALL_RESULT_NATIVE_EXECUTION_FAILED, 15 | CALL_RESULT_CLASS_INIT_NOT_METHOD, 16 | CALL_RESULT_INVALID_CALLABLE, 17 | CALL_RESULT_NO_SUCH_ATTRIBUTE 18 | } CallResult; 19 | 20 | typedef enum { 21 | IMPORT_RESULT_SUCCESS, 22 | IMPORT_RESULT_OPEN_FAILED, 23 | IMPORT_RESULT_READ_FAILED, 24 | IMPORT_RESULT_CLOSE_FAILED, 25 | IMPORT_RESULT_EXTENSION_NO_INIT_FUNCTION, 26 | IMPORT_RESULT_MODULE_NOT_FOUND 27 | } ImportResult; 28 | 29 | #define CALL_STACK_MAX 255 30 | #define EVAL_STACK_MAX (CALL_STACK_MAX * 5) 31 | 32 | typedef struct { 33 | Object* objects; 34 | 35 | Value stack[EVAL_STACK_MAX]; 36 | Value* stack_top; 37 | StackFrame call_stack[CALL_STACK_MAX]; 38 | StackFrame* call_stack_top; 39 | 40 | uint8_t* ip; 41 | 42 | CellTable globals; 43 | CellTable imported_modules; 44 | CellTable builtin_modules; 45 | 46 | int num_objects; 47 | int max_objects; 48 | bool allow_gc; 49 | 50 | Table string_cache; 51 | 52 | /* Used as roots for locating different modules during imports, etc. */ 53 | char* main_module_path; 54 | char* interpreter_dir_path; 55 | 56 | bool currently_handling_error; 57 | } VM; 58 | 59 | extern VM vm; 60 | 61 | CallResult vm_call_object(Object* object, ValueArray args, Value* out); 62 | CallResult vm_call_function(ObjectFunction* function, ValueArray args, Value* out); 63 | CallResult vm_call_bound_method(ObjectBoundMethod* bound_method, ValueArray args, Value* out); 64 | CallResult vm_instantiate_class(ObjectClass* klass, ValueArray args, Value* out); 65 | CallResult vm_instantiate_class_no_args(ObjectClass* klass, Value* out); 66 | CallResult vm_call_attribute(Object* object, ObjectString* name, ValueArray args, Value* out); 67 | CallResult vm_call_attribute_cstring(Object* object, char* name, ValueArray args, Value* out); 68 | 69 | ImportResult vm_import_module(ObjectString* module_name); 70 | ImportResult vm_import_module_cstring(char* name); 71 | 72 | ObjectModule* vm_get_module(ObjectString* name); 73 | ObjectModule* vm_get_module_cstring(char* name); 74 | 75 | void vm_push_object(Object* value); 76 | Object* vm_pop_object(void); 77 | 78 | StackFrame* vm_peek_current_frame(void); 79 | StackFrame* vm_peek_previous_frame(void); 80 | 81 | void vm_gc(void); 82 | 83 | void vm_init(void); 84 | void vm_free(void); 85 | bool vm_interpret_program(Bytecode* bytecode, char* main_module_path); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/ribbon_api.c: -------------------------------------------------------------------------------- 1 | #include "ribbon_api.h" 2 | 3 | RibbonApi API = { 4 | .EXTENSION_ALLOC_STRING_CSTRING = "extension cstring", 5 | .EXTENSION_ALLOC_STRING_GC_LEEFS = "extension gc leefs", 6 | .EXTENSION_ALLOC_STRING_MISC = "extension memory allocation", 7 | .allocate = allocate, 8 | .deallocate = deallocate, 9 | .reallocate = reallocate, 10 | .copy_null_terminated_cstring = copy_null_terminated_cstring, 11 | .object_native_function_new = object_native_function_new, 12 | .object_set_attribute_cstring_key = object_set_attribute_cstring_key, 13 | .make_native_function_with_params = make_native_function_with_params, 14 | .object_class_native_new = object_class_native_new, 15 | .object_instance_new = object_instance_new, 16 | .copy_cstring = copy_cstring, 17 | .table_set = table_set, 18 | .table_get = table_get, 19 | .table_set_cstring_key = table_set_cstring_key, 20 | .table_get_cstring_key = table_get_cstring_key, 21 | .object_table_new_empty = object_table_new_empty, 22 | .object_load_attribute = object_load_attribute, 23 | .object_load_attribute_cstring_key = object_load_attribute_cstring_key, 24 | .object_string_take = object_string_take, 25 | .object_string_copy_from_null_terminated = object_string_copy_from_null_terminated, 26 | .object_string_clone = object_string_clone, 27 | .object_string_new_partial_from_null_terminated = object_string_new_partial_from_null_terminated, 28 | .object_strings_equal = object_strings_equal, 29 | .cstrings_equal = cstrings_equal, 30 | .value_array_init = value_array_init, 31 | .value_array_write = value_array_write, 32 | .value_array_free = value_array_free, 33 | .value_array_make = value_array_make, 34 | .object_value_is = object_value_is, 35 | .vm_call_object = vm_call_object, 36 | .vm_call_function = vm_call_function, 37 | .vm_call_bound_method = vm_call_bound_method, 38 | .vm_instantiate_class = vm_instantiate_class, 39 | .vm_instantiate_class_no_args = vm_instantiate_class_no_args, 40 | .vm_call_attribute = vm_call_attribute, 41 | .vm_call_attribute_cstring = vm_call_attribute_cstring, 42 | .vm_import_module = vm_import_module, 43 | .vm_import_module_cstring = vm_import_module_cstring, 44 | .vm_get_module = vm_get_module, 45 | .vm_get_module_cstring = vm_get_module_cstring, 46 | .vm_push_object = vm_push_object, 47 | .vm_pop_object = vm_pop_object, 48 | .is_instance_of_class = is_instance_of_class, 49 | .is_value_instance_of_class = is_value_instance_of_class, 50 | .object_make_constructor = object_make_constructor, 51 | .object_descriptor_new = object_descriptor_new, 52 | .object_descriptor_new_native = object_descriptor_new_native, 53 | .arguments_valid = arguments_valid 54 | }; 55 | -------------------------------------------------------------------------------- /src/python/tester/tests/strings.test: -------------------------------------------------------------------------------- 1 | test adding strings 2 | print("abc" + "abc") 3 | print("abc" + " " + "abc") 4 | print("abc" + " " + "def") 5 | 6 | a = "abc" 7 | b = "def" 8 | print(a + " " + b) 9 | 10 | str = "" 11 | n = 0 12 | while n <= 3 { 13 | if n == 0 { 14 | x = "0" 15 | } elsif n == 1 { 16 | x = "1" 17 | } elsif n == 2 { 18 | x = "2" 19 | } elsif n == 3 { 20 | x = "3" 21 | } 22 | 23 | str = str + x 24 | n = n + 1 25 | } 26 | print(str) 27 | expect 28 | abcabc 29 | abc abc 30 | abc def 31 | abc def 32 | 0123 33 | end 34 | 35 | test illegal to set attribute on string 36 | # this is a fragile test because we rely on the very specific error output, which is very likely to change. 37 | # this is why at the time of writing this, we usually don't have tests for specific error conditions (which isn't a great thing). 38 | # this case is an exception, and we might have to update this test if and when the error output changes. 39 | 40 | text = "hello world" 41 | text.x = 10 42 | expect 43 | An error has occured. Stack trace (most recent call on top): 44 | ->
45 | Cannot set attribute on strings. 46 | end 47 | 48 | test string length method 49 | text = "abc def g" 50 | n = text.length() 51 | print(n) 52 | expect 53 | 9 54 | end 55 | 56 | test string interning 57 | import _testing 58 | 59 | same = _testing.same_object 60 | 61 | a = "s1" 62 | b = "s2" 63 | c = "s1" 64 | d = a + b 65 | e = "s1" + "s2" 66 | f = "" + d 67 | 68 | print(same(a, c)) 69 | print(same(d, e)) 70 | print(same(f, d)) 71 | print(same(f, e)) 72 | 73 | print(same(a, b)) 74 | 75 | a = "s2" 76 | print(same(a, b)) 77 | expect 78 | true 79 | true 80 | true 81 | true 82 | false 83 | true 84 | end 85 | 86 | test advanced string interning 87 | # This test uses _testing.gc() in order to try and validate correct string interning and string cleanup behavior. 88 | # The GC doesn't only run when invoking _testing.gc(), for this matter it may run at any given time. We use 89 | # _testing.gc() to make sure it runs at a particular moment. 90 | 91 | # TODO: Write this test 92 | expect 93 | end 94 | 95 | test string key accessor 96 | text = "abc def g" 97 | print(text[2]) 98 | print(text[0]) 99 | print(text[8]) 100 | print(text[1] + text[3] + text[6]) 101 | expect 102 | c 103 | a 104 | g 105 | b f 106 | end 107 | 108 | test string operations 109 | text = "hello world!" 110 | reversed = "" 111 | i = text.length() - 1 112 | while i >= 0 { 113 | reversed += text[i] 114 | i -= 1 115 | } 116 | print(reversed) 117 | expect 118 | !dlrow olleh 119 | end 120 | 121 | test string special characters 122 | print("abc\ndef") 123 | print("abc\\ndef") 124 | 125 | # Note: should add tests for the other special characters, \t, \v and \r. Currently not sure how to test those. 126 | expect 127 | abc 128 | def 129 | abc\ndef 130 | end 131 | 132 | test multiline strings 133 | s = "This is a long, and convoluted, multiline string " 134 | "with a long and interesting story to tell.\n" "Actually, the implementation " 135 | "of this is surprisingly pretty cool.\n" 136 | "It involves pretty cool stuff both in the parser and in the compiler.\n" 137 | "Surprisingly, the scanner is unaware of this delicious feature." 138 | 139 | print(s) 140 | expect 141 | This is a long, and convoluted, multiline string with a long and interesting story to tell. 142 | Actually, the implementation of this is surprisingly pretty cool. 143 | It involves pretty cool stuff both in the parser and in the compiler. 144 | Surprisingly, the scanner is unaware of this delicious feature. 145 | end 146 | -------------------------------------------------------------------------------- /src/io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "io.h" 6 | #include "memory.h" 7 | #include "ribbon_utils.h" 8 | #include "vm.h" 9 | 10 | IOResult io_read_text_file(const char* file_name, const char* alloc_string, char** text_out, size_t* text_length_out) { 11 | FILE* file = fopen(file_name, "r"); 12 | 13 | if (file == NULL) { 14 | return IO_OPEN_FILE_FAILURE; 15 | } 16 | 17 | CharacterArray char_array; 18 | character_array_init(&char_array); 19 | int ch; 20 | while ((ch = fgetc(file)) != EOF) { 21 | char cast_char = (char) ch; 22 | character_array_write(&char_array, &cast_char); 23 | } 24 | 25 | IOResult result = IO_SUCCESS; 26 | 27 | if (!feof(file)) { 28 | // EOF encountered because of an error 29 | result = IO_READ_FILE_FAILURE; 30 | goto cleanup; 31 | } 32 | 33 | char nullbyte = '\0'; // Work around C not allowing passing pointers to literals... 34 | character_array_write(&char_array, &nullbyte); 35 | 36 | *text_out = copy_null_terminated_cstring(char_array.values, alloc_string); 37 | *text_length_out = char_array.count; 38 | 39 | cleanup: 40 | character_array_free(&char_array); 41 | if (fclose(file) == EOF) { 42 | result = IO_CLOSE_FILE_FAILURE; 43 | } 44 | 45 | return result; 46 | } 47 | 48 | IOResult io_read_binary_file(const char* file_name, Table* data_out) { 49 | FILE* file = fopen(file_name, "rb"); 50 | 51 | if (file == NULL) { 52 | return IO_OPEN_FILE_FAILURE; 53 | } 54 | 55 | IntegerArray integer_array; 56 | integer_array_init(&integer_array); 57 | int byte; 58 | while ((byte = fgetc(file)) != EOF) { 59 | size_t cast = (size_t) byte; 60 | integer_array_write(&integer_array, &cast); 61 | } 62 | 63 | IOResult result = IO_SUCCESS; 64 | 65 | if (!feof(file)) { 66 | // EOF encountered because of an error 67 | result = IO_READ_FILE_FAILURE; 68 | goto cleanup; 69 | } 70 | 71 | *data_out = table_new_empty(); 72 | 73 | for (size_t i = 0; i < integer_array.count; i++) { 74 | Value value = MAKE_VALUE_NUMBER(integer_array.values[i]); 75 | table_set(data_out, MAKE_VALUE_NUMBER(i), value); 76 | } 77 | 78 | RIBBON_ASSERT(data_out->num_entries == integer_array.count, "Data buffer from file and table have a different count"); 79 | 80 | cleanup: 81 | integer_array_free(&integer_array); 82 | if (fclose(file) == EOF) { 83 | result = IO_CLOSE_FILE_FAILURE; 84 | } 85 | 86 | return result; 87 | } 88 | 89 | IOResult io_write_text_file(const char* file_name, const char* string) { 90 | IOResult result = IO_SUCCESS; 91 | 92 | FILE* file = fopen(file_name, "w"); 93 | 94 | if (file == NULL) { 95 | result = IO_OPEN_FILE_FAILURE; 96 | goto cleanup; 97 | } 98 | 99 | int write_result = fputs(string, file); 100 | if (write_result < 0 || write_result == EOF) { 101 | result = IO_WRITE_FILE_FAILURE; 102 | } 103 | 104 | cleanup: 105 | fclose(file); 106 | return result; 107 | } 108 | 109 | IOResult io_write_binary_file(const char* file_name, Table* data) { 110 | IOResult result = IO_SUCCESS; 111 | 112 | FILE* file = fopen(file_name, "wb"); 113 | 114 | if (file == NULL) { 115 | result = IO_OPEN_FILE_FAILURE; 116 | goto cleanup; 117 | } 118 | 119 | for (int i = 0; i < data->num_entries; i++) { 120 | Value value; 121 | 122 | if (!table_get(data, MAKE_VALUE_NUMBER(i), &value)) { 123 | result = IO_WRITE_FILE_FAILURE; 124 | goto cleanup; 125 | } 126 | 127 | if (value.as.number != floor(value.as.number)) { 128 | result = IO_WRITE_FILE_FAILURE; 129 | goto cleanup; 130 | } 131 | 132 | const int byte = (int) value.as.number; 133 | if (fputc(value.as.number, file) == EOF) { 134 | result = IO_WRITE_FILE_FAILURE; 135 | goto cleanup; 136 | } 137 | } 138 | 139 | cleanup: 140 | fclose(file); 141 | return result; 142 | } 143 | 144 | IOResult io_delete_file(const char* file_name) { 145 | if (remove(file_name) == 0) { 146 | return IO_SUCCESS; 147 | } 148 | return IO_DELETE_FILE_FAILURE; 149 | } 150 | 151 | BOOL io_file_exists(LPCTSTR path) { 152 | DWORD file_attributes = GetFileAttributes(path); 153 | return (file_attributes != INVALID_FILE_ATTRIBUTES && !(file_attributes & FILE_ATTRIBUTE_DIRECTORY)); 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_common_h 2 | #define ribbon_common_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* Generally keep these OFF unless you need them specifically */ 13 | 14 | #define DISABLE_GC 0 // Only set to 1 for debugging purposes when you need the GC to not run 15 | 16 | #define DEBUG 0 // General debug printing 17 | #define DEBUG_TRACE_EXECUTION 0 // Show stack operations 18 | #define DEBUG_GC 0 // Show GC operations 19 | #define DEBUG_OBJECTS 0 // Show object operations 20 | #define DEBUG_MEMORY_EXECUTION 0 // Show low-level memory operations 21 | #define DEBUG_SCANNER 0 // Show low level lexing output and such 22 | #define DEBUG_PAUSE_AFTER_OPCODES 0 // Wait for user input after each opcode 23 | #define DEBUG_TABLE_STATS 0 // Collect statistics on general hash table behavior 24 | 25 | /* ****************** */ 26 | 27 | /* Probably leave this ON most of the time during DEV. Disable for release. */ 28 | #ifndef GC_STRESS_TEST 29 | // Run GC every loop iteration. Used to help GC bugs surface. Obviously really bad for performance 30 | #define GC_STRESS_TEST 0 31 | #endif 32 | 33 | /* ************** */ 34 | 35 | /* Always leave these two ON in DEV. Disable for release */ 36 | 37 | #ifndef MEMORY_DIAGNOSTICS 38 | #define MEMORY_DIAGNOSTICS 0 // Usually leave on in dev. Disable for release 39 | #endif 40 | 41 | #ifndef DEBUG_IMPORTANT 42 | #if MEMORY_DIAGNOSTICS 43 | // Pretty much always leave this on, at least in dev - printing critical diagnosis and such 44 | #define DEBUG_IMPORTANT 1 45 | #else 46 | #define DEBUG_IMPORTANT 0 47 | #endif 48 | #endif 49 | 50 | /* **************** */ 51 | 52 | #if DEBUG 53 | #define DEBUG_PRINT(...) do { \ 54 | fprintf(stdout, "DEBUG: "); \ 55 | fprintf (stdout, __VA_ARGS__); \ 56 | fprintf(stdout, "\n"); \ 57 | } while (false) 58 | 59 | #else 60 | #define DEBUG_PRINT(...) do {} while(false) 61 | #endif 62 | 63 | #if DEBUG_MEMORY_EXECUTION 64 | #define DEBUG_MEMORY(...) do { \ 65 | fprintf(stdout, "MEMORY: "); \ 66 | fprintf (stdout, __VA_ARGS__); \ 67 | fprintf(stdout, "\n"); \ 68 | } while (false) 69 | 70 | #else 71 | #define DEBUG_MEMORY(...) do {} while(false) 72 | #endif 73 | 74 | #if DEBUG_IMPORTANT 75 | #define DEBUG_IMPORTANT_PRINT(...) do { \ 76 | fprintf (stdout, __VA_ARGS__); \ 77 | } while (false) 78 | 79 | #else 80 | #define DEBUG_IMPORTANT_PRINT(...) do {} while(false) 81 | #endif 82 | 83 | 84 | #if DEBUG_TRACE_EXECUTION 85 | #define DEBUG_TRACE(...) do { \ 86 | fprintf (stdout, __VA_ARGS__); \ 87 | fprintf (stdout, "\n"); \ 88 | } while (false) 89 | 90 | #else 91 | #define DEBUG_TRACE(...) do {} while(false) 92 | #endif 93 | 94 | #if DEBUG_SCANNER 95 | #define DEBUG_SCANNER_PRINT(...) do { \ 96 | fprintf (stdout, "Scanner: " __VA_ARGS__); \ 97 | fprintf (stdout, "\n"); \ 98 | } while (false) 99 | 100 | #else 101 | #define DEBUG_SCANNER_PRINT(...) do {} while(false) 102 | #endif 103 | 104 | #if DEBUG_OBJECTS 105 | #define DEBUG_OBJECTS_PRINT(...) do { \ 106 | fprintf (stdout, __VA_ARGS__); \ 107 | fprintf (stdout, "\n"); \ 108 | } while (false) 109 | 110 | #else 111 | #define DEBUG_OBJECTS_PRINT(...) do {} while(false) 112 | #endif 113 | 114 | #if DEBUG_GC 115 | #define DEBUG_GC_PRINT(...) do { \ 116 | fprintf (stdout, __VA_ARGS__); \ 117 | fprintf (stdout, "\n"); \ 118 | } while (false) 119 | 120 | #else 121 | #define DEBUG_GC_PRINT(...) do {} while(false) 122 | #endif 123 | 124 | // TODO: actual assertion or error mechanisms 125 | #define FAIL(...) do { \ 126 | fprintf(stdout, "\nFAILING! Reason:'"); \ 127 | fprintf(stdout, __VA_ARGS__); \ 128 | fprintf(stdout, "'\n"); \ 129 | exit(EXIT_FAILURE); \ 130 | } while(false) 131 | 132 | // TODO: Consider disabling in release build 133 | #define RIBBON_ASSERT(condition, message, ...) do { \ 134 | if (!(condition)) { \ 135 | FAIL(message, ##__VA_ARGS__); \ 136 | } \ 137 | } while(false) 138 | 139 | #endif 140 | 141 | #define PRINTLN(str) do { \ 142 | printf("\n"); \ 143 | printf(str); \ 144 | printf("\n"); \ 145 | } while (false) 146 | 147 | #ifdef _WIN32 148 | # ifdef _WIN64 149 | # define PRI_SIZET PRIu64 150 | # else 151 | # define PRI_SIZET PRIu32 152 | # endif 153 | #else 154 | # define PRI_SIZET "zu" 155 | #endif 156 | -------------------------------------------------------------------------------- /src/python/tester/tests/native_integ.test: -------------------------------------------------------------------------------- 1 | test native function calls user function 2 | import _testing 3 | f = { 4 | print("I'm a user function") 5 | } 6 | _testing.demo_print(f) 7 | expect 8 | I'm a native function 9 | I'm a user function 10 | I'm a native function 11 | end 12 | 13 | test native function calls user function with arguments and return value 14 | import _testing 15 | f = { | a, b | 16 | print("I'm a user callback") 17 | return a + b 18 | } 19 | retval = _testing.call_callback_with_args(f, 10, 5) 20 | print(retval) 21 | expect 22 | I'm a native function 23 | I'm a user callback 24 | 15 25 | end 26 | 27 | test native function calls user function that fails 28 | import _testing 29 | callback = { | a, b | 30 | return "" + a + b 31 | } 32 | retval = _testing.call_callback_with_args(callback, 10, 20) 33 | print(retval) 34 | expect 35 | I'm a native function 36 | An error has occured. Stack trace (most recent call on top): 37 | -> callback 38 | -> call_callback_with_args 39 | ->
40 | @add function failed. 41 | end 42 | 43 | test user code calls native code calls user code calls native code 44 | import _testing 45 | 46 | outer_var1 = 10 47 | outer_var2 = 20 48 | 49 | callback1 = { | a, b | 50 | print("User function 1") 51 | return _testing.call_callback_with_args(callback2, a * 10, b * 10) 52 | } 53 | 54 | callback2 = { | x, y | 55 | print("User function 2") 56 | return x + y + outer_var1 + outer_var2 57 | } 58 | 59 | print("Starting") 60 | retval = _testing.call_callback_with_args(callback1, 6, 8) 61 | print(retval) 62 | expect 63 | Starting 64 | I'm a native function 65 | User function 1 66 | I'm a native function 67 | User function 2 68 | 170 69 | end 70 | 71 | test with nested closures 72 | import _testing 73 | 74 | callback1 = { | a, b | 75 | print("User function 1") 76 | return _testing.call_callback_with_args({ | x, y | 77 | print("User function 2") 78 | return x + y + a + b 79 | }, a * 10, b * 10) 80 | } 81 | 82 | print("Starting") 83 | retval = _testing.call_callback_with_args(callback1, 6, 8) 84 | print(retval) 85 | expect 86 | Starting 87 | I'm a native function 88 | User function 1 89 | I'm a native function 90 | User function 2 91 | 154 92 | end 93 | 94 | test cross module 95 | import _testing 96 | import mymodule 97 | 98 | print("Starting") 99 | retval = _testing.call_callback_with_args(mymodule.callback1, 6, 8) 100 | print(retval) 101 | file mymodule.rib 102 | outer_var1 = 10 103 | outer_var2 = 20 104 | 105 | callback2 = { | x, y | 106 | print("User function 2") 107 | return x + y + outer_var1 + outer_var2 108 | } 109 | 110 | callback1 = { | a, b | 111 | import _testing 112 | print("User function 1") 113 | return _testing.call_callback_with_args(callback2, a * 10, b * 10) 114 | } 115 | expect 116 | Starting 117 | I'm a native function 118 | User function 1 119 | I'm a native function 120 | User function 2 121 | 170 122 | end 123 | 124 | test descriptors 125 | import _testing 126 | import myuserextension 127 | 128 | MyThingA = myuserextension.MyThingA 129 | thing = MyThingA("Some text :D") 130 | 131 | thing.a = 20 132 | print(_testing.get_value_directly_from_object_attributes(thing, "a")) # Expected to print 20 133 | 134 | _testing.get_value_directly_from_object_attributes(thing, "x") # Expected to print "Attribute not found" 135 | 136 | thing.x = 10 137 | 138 | _testing.get_value_directly_from_object_attributes(thing, "x") # Expected to print "Attribute not found" 139 | 140 | print(thing.x) 141 | 142 | _testing.get_value_directly_from_object_attributes(thing, "x") # Expected to print "Attribute not found" 143 | 144 | thing.x = 30 145 | print(thing.x) 146 | 147 | print(_testing.get_value_directly_from_object_attributes(thing, "a")) # Expected to print 20 148 | 149 | _testing.get_value_directly_from_object_attributes(thing, "x") # Expected to print "Attribute not found" 150 | expect 151 | 20 152 | Attribute not found 153 | Running MyThingA descriptor @set for attribute x. Current value: 0. New value: 10 154 | Attribute not found 155 | Running MyThingA descriptor @get for attribute x. Value: 10 156 | 10 157 | Attribute not found 158 | Running MyThingA descriptor @set for attribute x. Current value: 10. New value: 30 159 | Running MyThingA descriptor @get for attribute x. Value: 30 160 | 30 161 | 20 162 | Attribute not found 163 | end 164 | -------------------------------------------------------------------------------- /src/ribbon_api.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_ribbon_api_h 2 | #define ribbon_ribbon_api_h 3 | 4 | /* Declaring an interface passed in extension modules */ 5 | 6 | /* TODO: Need to include all these? Review */ 7 | #include "common.h" 8 | #include "vm.h" 9 | #include "memory.h" 10 | #include "ribbon_object.h" 11 | #include "value.h" 12 | #include "table.h" 13 | #include "value_array.h" 14 | 15 | typedef struct { 16 | const char* EXTENSION_ALLOC_STRING_CSTRING; 17 | const char* EXTENSION_ALLOC_STRING_GC_LEEFS; 18 | const char* EXTENSION_ALLOC_STRING_MISC; 19 | 20 | void* (*allocate) (size_t size, const char* what); 21 | void (*deallocate) (void* pointer, size_t oldSize, const char* what); 22 | void* (*reallocate) (void* pointer, size_t oldSize, size_t newSize, const char* what); 23 | 24 | // ObjectFunction* (*object_native_function_new) (NativeFunction nativeFunction, char** parameters, int numParams); 25 | ObjectFunction* (*object_native_function_new) (NativeFunction nativeFunction, ObjectString** parameters, int numParams); 26 | ObjectFunction* (*make_native_function_with_params) (char* name, int num_params, char** params, NativeFunction function); 27 | 28 | ObjectClass* (*object_class_native_new) (char* name, size_t instance_size, DeallocationFunction dealloc_func, 29 | GcMarkFunction gc_mark_func, ObjectFunction* constructor, void* descriptors[][2]); 30 | ObjectInstance* (*object_instance_new) (ObjectClass* klass); 31 | 32 | void (*object_set_attribute_cstring_key) (Object* object, const char* key, Value value); 33 | 34 | ObjectString* (*object_string_take) (char* chars, int length); 35 | ObjectString* (*object_string_copy_from_null_terminated) (const char* string); 36 | ObjectString* (*object_string_clone) (ObjectString* original); 37 | ObjectString* (*object_string_new_partial_from_null_terminated) (char* chars); 38 | bool (*object_strings_equal) (ObjectString* a, ObjectString* b); 39 | bool (*cstrings_equal) (const char* s1, int length1, const char* s2, int length2); 40 | 41 | char* (*copy_null_terminated_cstring) (const char* string, const char* what); 42 | char* (*copy_cstring) (const char* string, int length, const char* what); 43 | 44 | void (*table_set) (Table* table, struct Value key, Value value); 45 | bool (*table_get) (Table* table, struct Value key, Value* out); 46 | void (*table_set_cstring_key) (Table* table, const char* key, Value value); 47 | bool (*table_get_cstring_key) (Table* table, const char* key, Value* out); 48 | ObjectTable* (*object_table_new_empty) (void); 49 | 50 | bool (*object_load_attribute) (Object* object, ObjectString* name, Value* out); 51 | bool (*object_load_attribute_cstring_key) (Object* object, const char* name, Value* out); 52 | 53 | void (*value_array_init) (ValueArray*); 54 | void (*value_array_write) (ValueArray*, Value*); 55 | void (*value_array_free) (ValueArray*); 56 | ValueArray (*value_array_make) (int count, struct Value* values); 57 | 58 | bool (*object_value_is) (Value value, ObjectType type); 59 | 60 | CallResult (*vm_call_object) (Object* object, ValueArray args, Value* out); 61 | CallResult (*vm_call_function) (ObjectFunction* function, ValueArray args, Value* out); 62 | CallResult (*vm_call_bound_method) (ObjectBoundMethod* bound_method, ValueArray args, Value* out); 63 | CallResult (*vm_instantiate_class) (ObjectClass* klass, ValueArray args, Value* out); 64 | CallResult (*vm_instantiate_class_no_args) (ObjectClass* klass, Value* out); 65 | CallResult (*vm_call_attribute) (Object* object, ObjectString* name, ValueArray args, Value* out); 66 | CallResult (*vm_call_attribute_cstring) (Object* object, char* name, ValueArray args, Value* out); 67 | 68 | ImportResult (*vm_import_module) (ObjectString* module_name); 69 | ImportResult (*vm_import_module_cstring) (char* module_name); 70 | ObjectModule* (*vm_get_module) (ObjectString* name); 71 | ObjectModule* (*vm_get_module_cstring) (char* name); 72 | 73 | /* The push and pop functions are exposed to extensions only for one purpose: 74 | to mark an object as reachable during GC. Don't use them for any other purpose externally. And don't forget to pop(). */ 75 | void (*vm_push_object) (Object* value); 76 | Object* (*vm_pop_object) (void); 77 | 78 | bool (*is_instance_of_class) (Object* object, char* klass_name); 79 | bool (*is_value_instance_of_class) (Value value, char* klass_name); 80 | 81 | ObjectFunction* (*object_make_constructor) (int num_params, char** params, NativeFunction function); 82 | 83 | ObjectInstance* (*object_descriptor_new) (ObjectFunction* get, ObjectFunction* set); 84 | ObjectInstance* (*object_descriptor_new_native) (NativeFunction get, NativeFunction set); 85 | 86 | bool (*arguments_valid) (ValueArray args, const char* string); 87 | } RibbonApi; 88 | 89 | extern RibbonApi API; 90 | 91 | typedef bool (*ExtensionInitFunction) (RibbonApi, ObjectModule*); 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "common.h" 7 | #include "io.h" 8 | #include "compiler.h" 9 | #include "parser.h" 10 | #include "ast.h" 11 | #include "bytecode.h" 12 | #include "disassembler.h" 13 | #include "value.h" 14 | #include "ribbon_object.h" 15 | #include "vm.h" 16 | #include "memory.h" 17 | 18 | static bool checkCmdArg(char** argv, int argc, int index, const char* value) { 19 | return argc >= index + 1 && strncmp(argv[index], value, strlen(value)) == 0; 20 | } 21 | 22 | static bool cmdArgExists(char** argv, int argc, const char* value) { 23 | for (int i = 0; i < argc; i++) { 24 | if (checkCmdArg(argv, argc, i, value)) { 25 | return true; 26 | } 27 | } 28 | 29 | return false; 30 | } 31 | 32 | static void printStructures(int argc, char* argv[], Bytecode* chunk, AstNode* ast) { 33 | bool showBytecode = cmdArgExists(argv, argc, "-asm"); 34 | bool showTree = cmdArgExists(argv, argc, "-tree"); 35 | 36 | if (showTree) { 37 | printf("==== AST ====\n\n"); 38 | ast_print_tree(ast); 39 | printf("\n"); 40 | } 41 | 42 | if (showBytecode) { 43 | printf("==== Bytecode ====\n\n"); 44 | disassembler_do_bytecode(chunk); 45 | printf("\n"); 46 | } 47 | 48 | if (showTree || showBytecode) { 49 | printf("================\n"); 50 | } 51 | } 52 | 53 | static void print_memory_diagnostic() { 54 | printf("======== Memory diagnostics ========"); 55 | 56 | bool problem = false; 57 | 58 | size_t allocated_memory = get_allocated_memory(); 59 | if (allocated_memory == 0) { 60 | DEBUG_IMPORTANT_PRINT("\n*******\nAll memory freed.\n*******\n"); 61 | } else { 62 | DEBUG_IMPORTANT_PRINT("\n*******\nAllocated memory is %" PRI_SIZET ", not 0!\n*******\n", allocated_memory); 63 | problem = true; 64 | } 65 | 66 | size_t num_allocations = get_allocations_count(); 67 | if (num_allocations == 0) { 68 | DEBUG_IMPORTANT_PRINT("*******\nAll allocations freed.\n*******\n"); 69 | } else { 70 | DEBUG_IMPORTANT_PRINT("*******\nNumber of allocations which have not been freed is %" PRI_SIZET ", not 0!\n*******\n", num_allocations); 71 | problem = true; 72 | } 73 | 74 | if (problem) { 75 | memory_print_allocated_entries(); 76 | } 77 | 78 | printf("*******\n"); 79 | object_print_all_objects(); 80 | printf("*******\n"); 81 | 82 | printf("======== End memory diagnostics ========\n"); 83 | } 84 | 85 | int main(int argc, char* argv[]) { 86 | if (argc < 2 || argc > 5) { 87 | fprintf(stdout, "Usage: ribbon [[-asm] [-tree] [-dry]]"); 88 | return -1; 89 | } 90 | 91 | memory_init(); 92 | 93 | char* source = NULL; 94 | size_t text_length = 0; 95 | char* main_file_path = argv[1]; 96 | char* abs_main_file_path; 97 | 98 | char* abs_path_alloc_string = "main module absolute path"; 99 | 100 | if (PathIsRelativeA(main_file_path)) { 101 | char* working_dir = get_current_working_directory(); 102 | abs_main_file_path = concat_multi_null_terminated_cstrings( 103 | 3, (char*[]) {working_dir, "\\", main_file_path}, abs_path_alloc_string); 104 | deallocate(working_dir, strlen(working_dir) + 1, "working directory path"); 105 | } else { 106 | /* Heap allocated because is later freed in vm_free, because it has to incase it was heap allocated when it's a relative path */ 107 | abs_main_file_path = copy_null_terminated_cstring(main_file_path, abs_path_alloc_string); 108 | } 109 | 110 | if (io_read_text_file(abs_main_file_path, "Source file content", &source, &text_length) != IO_SUCCESS) { 111 | printf("Failed to open file.\n"); 112 | return -1; 113 | } 114 | 115 | DEBUG_PRINT("Starting Ribbon\n\n"); 116 | 117 | /* Must first init the VM because some parts of the compiler depend on it */ 118 | vm_init(); 119 | 120 | Bytecode bytecode; 121 | bytecode_init(&bytecode); 122 | AstNode* ast = parser_parse(source, abs_main_file_path); 123 | compiler_compile(ast, &bytecode); 124 | 125 | printStructures(argc, argv, &bytecode, ast); 126 | ast_free_tree(ast); 127 | deallocate(source, text_length, "Source file content"); 128 | 129 | bool dryRun = checkCmdArg(argv, argc, 2, "-dry") || checkCmdArg(argv, argc, 3, "-dry") || checkCmdArg(argv, argc, 4, "-dry"); 130 | if (!dryRun) { 131 | bool result = vm_interpret_program(&bytecode, abs_main_file_path); 132 | } 133 | 134 | vm_free(); 135 | 136 | #if DEBUG_IMPORTANT 137 | if (!dryRun) { 138 | // Dry running does no GC, so some objects from the compiler aren't cleaned... So no point in printing diagnostics. 139 | #if MEMORY_DIAGNOSTICS 140 | print_memory_diagnostic(); 141 | #endif 142 | } 143 | #endif 144 | 145 | return 0; 146 | } 147 | -------------------------------------------------------------------------------- /src/value.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "value.h" 6 | #include "ribbon_object.h" 7 | #include "memory.h" 8 | 9 | void value_print(Value value) { 10 | switch (value.type) { 11 | case VALUE_NUMBER: { 12 | printf("%g", value.as.number); 13 | return; 14 | } 15 | case VALUE_OBJECT: { 16 | object_print(value.as.object); 17 | return; 18 | } 19 | case VALUE_BOOLEAN: { 20 | printf(value.as.boolean ? "true" : "false"); 21 | return; 22 | } 23 | case VALUE_RAW_STRING: { 24 | RawString string = value.as.raw_string; 25 | printf("\"%.*s\"", string.length, string.data); 26 | return; 27 | } 28 | case VALUE_NIL: { 29 | printf("nil"); 30 | return; 31 | } 32 | case VALUE_ADDRESS: { 33 | printf("%" PRIxPTR , value.as.address); 34 | return; 35 | } 36 | case VALUE_ALLOCATION: { 37 | Allocation allocation = value.as.allocation; 38 | printf("", allocation.name, allocation.size); 39 | return; 40 | } 41 | } 42 | 43 | FAIL("Unrecognized VALUE_TYPE: %d", value.type); 44 | } 45 | 46 | bool value_compare(Value a, Value b, int* output) { 47 | if (a.type != b.type) { 48 | *output = -1; 49 | return true; 50 | } 51 | 52 | switch (a.type) { 53 | case VALUE_NUMBER: { 54 | double n1 = a.as.number; 55 | double n2 = b.as.number; 56 | 57 | if (n1 == n2) { 58 | *output = 0; 59 | } else if (n1 > n2) { 60 | *output = 1; 61 | } else { 62 | *output = -1; 63 | } 64 | return true; 65 | } 66 | 67 | case VALUE_BOOLEAN: { 68 | bool b1 = a.as.boolean; 69 | bool b2 = b.as.boolean; 70 | if (b1 == b2) { 71 | *output = 0; 72 | } else { 73 | *output = -1; 74 | } 75 | return true; 76 | } 77 | 78 | case VALUE_OBJECT: { 79 | bool objectsEqual = object_compare(a.as.object, b.as.object); 80 | if (objectsEqual) { 81 | *output = 0; 82 | } else { 83 | *output = -1; 84 | } 85 | return true; 86 | } 87 | 88 | case VALUE_NIL: { 89 | *output = 0; 90 | return true; 91 | } 92 | 93 | case VALUE_ADDRESS: { 94 | uintptr_t addr1 = a.as.address; 95 | uintptr_t addr2 = b.as.address; 96 | 97 | if (addr1 == addr2) { 98 | *output = 0; 99 | } else if (addr1 < addr2) { 100 | *output = -1; 101 | } else { 102 | *output = 1; 103 | } 104 | 105 | return true; 106 | } 107 | 108 | case VALUE_ALLOCATION: { 109 | Allocation alloc1 = a.as.allocation; 110 | Allocation alloc2 = b.as.allocation; 111 | 112 | *output = (alloc1.size == alloc2.size) && (strcmp(alloc1.name, alloc2.name) == 0); /* BUG ??? */ 113 | return true; 114 | } 115 | 116 | case VALUE_RAW_STRING: { 117 | RawString s1 = a.as.raw_string; 118 | RawString s2 = b.as.raw_string; 119 | if (s1.hash == s2.hash && cstrings_equal(s1.data, s1.length, s2.data, s2.length)) { 120 | *output = 0; 121 | } else { 122 | *output = -1; 123 | } 124 | return true; 125 | } 126 | } 127 | 128 | FAIL("Couldn't compare values. Type A: %d, type B: %d", a.type, b.type); 129 | return false; 130 | } 131 | 132 | bool value_hash(Value* value, unsigned long* result) { 133 | ValueType type = value->type; 134 | switch (type) { 135 | case VALUE_OBJECT: { 136 | unsigned long hash; 137 | if (object_hash(value->as.object, &hash)) { 138 | *result = hash; 139 | return true; 140 | } 141 | return false; 142 | } 143 | case VALUE_BOOLEAN: { 144 | *result = value->as.boolean ? 0 : 1; 145 | return true; 146 | } 147 | case VALUE_NUMBER: { 148 | *result = hash_int(floor(value->as.number)); // TODO: Not good at all, redo this 149 | return true; 150 | } 151 | case VALUE_NIL: { 152 | *result = 0; 153 | return true; 154 | } 155 | case VALUE_RAW_STRING: { 156 | RawString string = value->as.raw_string; 157 | *result = string.hash; 158 | return true; 159 | } 160 | case VALUE_ADDRESS: { 161 | *result = hash_int(value->as.address); // Not good at all, but should logically work 162 | return true; 163 | } 164 | case VALUE_ALLOCATION: { 165 | return false; 166 | } 167 | } 168 | 169 | FAIL("value.c:hash_value - shouldn't get here."); 170 | return false; 171 | } 172 | 173 | const char* value_get_type(Value value) { 174 | switch (value.type) { 175 | case VALUE_NUMBER: return "Number"; 176 | case VALUE_BOOLEAN: return "Boolean"; 177 | case VALUE_NIL: return "Nil"; 178 | case VALUE_OBJECT: return object_get_type_name(value.as.object); 179 | case VALUE_ALLOCATION: FAIL("Shouldn't ever get type of Allocation value."); 180 | case VALUE_ADDRESS: FAIL("Shouldn't ever get type of Address value."); 181 | case VALUE_RAW_STRING: FAIL("Shouldn't ever get type of RawString value."); 182 | } 183 | 184 | FAIL("Illegal value type passed in value_get_type(): %d", value.type); 185 | return NULL; 186 | } -------------------------------------------------------------------------------- /src/python/tester/tests/builtin_functions.test: -------------------------------------------------------------------------------- 1 | test empty_print_prints_newline 2 | print("") 3 | expect 4 | 5 | end 6 | 7 | test print_line 8 | print("Hello!") 9 | expect 10 | Hello! 11 | end 12 | 13 | test print_multiple_lines 14 | print("Hello,") 15 | print("world!") 16 | expect 17 | Hello, 18 | world! 19 | end 20 | 21 | test read_text_file 22 | import path 23 | file_path = path.relative_to_main_directory("some_file.txt") 24 | print(read_text_file(file_path)) 25 | file some_file.txt 26 | this is 27 | some file text 28 | expect 29 | this is 30 | some file text 31 | end 32 | 33 | test read_binary_file 34 | import path 35 | file_path = path.relative_to_main_directory("some_file.txt") 36 | file_data = read_binary_file(file_path) 37 | 38 | print("Length: " + to_string(file_data.length())) 39 | 40 | for byte in file_data { 41 | print(byte) 42 | } 43 | file some_file.txt 44 | abcdefg 45 | ABCDEFG 46 | expect 47 | Length: 16 48 | 97 49 | 98 50 | 99 51 | 100 52 | 101 53 | 102 54 | 103 55 | 13 56 | 10 57 | 65 58 | 66 59 | 67 60 | 68 61 | 69 62 | 70 63 | 71 64 | end 65 | 66 | test read and write binary files 67 | original_data = [1, 2, 3, 4, 10, 20, 30, 40, 0, 100] 68 | file_name = "some_file.data" 69 | 70 | write_binary_file(file_name, original_data) 71 | read_data = read_binary_file(file_name) 72 | 73 | print("File length: " + to_string(read_data.length())) 74 | for byte in read_data { 75 | print(byte) 76 | } 77 | 78 | delete_file(file_name) 79 | expect 80 | File length: 10 81 | 1 82 | 2 83 | 3 84 | 4 85 | 10 86 | 20 87 | 30 88 | 40 89 | 0 90 | 100 91 | end 92 | 93 | test file io cycle 94 | print(file_exists("a1.txt")) # prints false 95 | 96 | write_text_file("a1.txt", "Haaands!") 97 | 98 | print(file_exists("a1.txt")) # prints true 99 | 100 | print(read_text_file("a1.txt")) # prints Haaands! 101 | 102 | print(file_exists("a1.txt")) # prints true 103 | 104 | delete_file("a1.txt") 105 | 106 | print(file_exists("a1.txt")) 107 | expect 108 | false 109 | true 110 | Haaands! 111 | true 112 | false 113 | end 114 | 115 | test has_attribute function 116 | C = class { 117 | @init = { 118 | self.x = 10 119 | } 120 | } 121 | 122 | c = C() 123 | s = "hello" 124 | 125 | print(has_attribute(c, "x")) 126 | print(has_attribute(c, "y")) 127 | 128 | print(has_attribute(s, "x")) 129 | print(has_attribute(s, "length")) 130 | expect 131 | true 132 | false 133 | false 134 | true 135 | end 136 | 137 | test is_instance function 138 | import math 139 | 140 | s = "abc" 141 | t = [] 142 | f = {} 143 | 144 | C = class { 145 | m = {} 146 | } 147 | 148 | o = C() 149 | 150 | m = o.m 151 | 152 | print(is_instance(math, "Module")) 153 | 154 | print(is_instance(s, "String")) 155 | print(is_instance(s, "Table")) 156 | 157 | print(is_instance(t, "String")) 158 | print(is_instance(t, "Table")) 159 | 160 | print(is_instance(f, "Function")) 161 | print(is_instance(f, "")) 162 | 163 | print(is_instance(C, "Class")) 164 | print(is_instance(C, "C")) 165 | 166 | print(is_instance(o, "C")) 167 | print(is_instance(o, "Instance")) 168 | print(is_instance(o, "Class")) 169 | 170 | print(is_instance(m, "BoundMethod")) 171 | print(is_instance(m, "Function")) 172 | 173 | print(is_instance(2, "Number")) 174 | print(is_instance(2, "Boolean")) 175 | 176 | print(is_instance(true, "Boolean")) 177 | print(is_instance(true, "Number")) 178 | 179 | print(is_instance(nil, "Nil")) 180 | expect 181 | true 182 | true 183 | false 184 | false 185 | true 186 | true 187 | false 188 | true 189 | false 190 | true 191 | false 192 | false 193 | true 194 | false 195 | true 196 | false 197 | true 198 | false 199 | true 200 | end 201 | 202 | test type function 203 | import math 204 | 205 | s = "abc" 206 | t = [] 207 | f = {} 208 | 209 | Thing = class { 210 | m = {} 211 | } 212 | 213 | o = Thing() 214 | 215 | m = o.m 216 | 217 | print(type(math)) 218 | print(type(s)) 219 | print(type(t)) 220 | print(type(f)) 221 | print(type(Thing)) 222 | print(type(o)) 223 | print(type(m)) 224 | print(type(100)) 225 | print(type(false)) 226 | print(type(nil)) 227 | expect 228 | Module 229 | String 230 | Table 231 | Function 232 | Class 233 | Thing 234 | BoundMethod 235 | Number 236 | Boolean 237 | Nil 238 | end 239 | 240 | test is_instance function with inheritance 241 | A = class { } 242 | 243 | B = class : A { } 244 | 245 | b = B() 246 | print(is_instance(b, "B")) 247 | print(is_instance(b, "A")) 248 | print(is_instance(b, "C")) 249 | expect 250 | true 251 | true 252 | false 253 | end 254 | -------------------------------------------------------------------------------- /src/builtin_test_module.c: -------------------------------------------------------------------------------- 1 | #include "builtin_test_module.h" 2 | #include "common.h" 3 | #include "ribbon_object.h" 4 | #include "vm.h" 5 | 6 | /* Functions for the _testing module, only for use in tests. Consider not creating _testing module in release build. */ 7 | 8 | bool builtin_test_demo_print(Object* self, ValueArray args, Value* out) { 9 | assert(object_value_is(args.values[0], OBJECT_FUNCTION)); 10 | ObjectFunction* function = (ObjectFunction*) args.values[0].as.object; 11 | 12 | printf("I'm a native function\n"); 13 | 14 | ValueArray func_args; 15 | value_array_init(&func_args); 16 | Value callback_out; 17 | CallResult func_exec_result = vm_call_object((Object*) function, func_args, &callback_out); 18 | value_array_free(&func_args); 19 | 20 | printf("I'm a native function\n"); 21 | 22 | *out = MAKE_VALUE_NIL(); 23 | return func_exec_result == CALL_RESULT_SUCCESS; 24 | } 25 | 26 | bool builtin_test_call_callback_with_args(Object* self, ValueArray args, Value* out) { 27 | assert(args.count == 3); 28 | 29 | assert(object_value_is(args.values[0], OBJECT_FUNCTION)); 30 | ObjectFunction* callback = (ObjectFunction*) args.values[0].as.object; 31 | 32 | Value arg1 = args.values[1]; 33 | Value arg2 = args.values[2]; 34 | 35 | printf("I'm a native function\n"); 36 | 37 | ValueArray callback_args; 38 | value_array_init(&callback_args); 39 | value_array_write(&callback_args, &arg1); 40 | value_array_write(&callback_args, &arg2); 41 | 42 | Value callback_out; 43 | CallResult func_exec_result = vm_call_object((Object*) callback, callback_args, &callback_out); 44 | 45 | value_array_free(&callback_args); 46 | 47 | if (func_exec_result == CALL_RESULT_SUCCESS) { 48 | *out = callback_out; 49 | return true; 50 | } 51 | 52 | *out = MAKE_VALUE_NIL(); 53 | return false; 54 | } 55 | 56 | bool builtin_test_get_value_directly_from_object_attributes(Object* self, ValueArray args, Value* out) { 57 | /* Bypass the attribute lookup mechanism (mainly classes and descriptors), and get 58 | a value directly from an object's attribute table. Used to test some internals of the system. */ 59 | 60 | assert(args.count == 2); 61 | assert(args.values[0].type == VALUE_OBJECT); 62 | assert(object_value_is(args.values[1], OBJECT_STRING)); 63 | 64 | Object* object = args.values[0].as.object; 65 | ObjectString* attr_name = (ObjectString*) args.values[1].as.object; 66 | 67 | if (!load_attribute_bypass_descriptors(object, attr_name, out) || is_value_instance_of_class(*out, "Descriptor")) { 68 | /* Not super elegant, but fine for now. The tests are based on stdout reading anyway. They look for this when appropriate. */ 69 | printf("Attribute not found\n"); 70 | *out = MAKE_VALUE_NIL(); 71 | return true; 72 | } 73 | 74 | return true; 75 | } 76 | 77 | bool builtin_test_same_object(Object* self, ValueArray args, Value* out) { 78 | if (args.count != 2) { 79 | return false; 80 | } 81 | if (args.values[0].type != VALUE_OBJECT || args.values[1].type != VALUE_OBJECT) { 82 | return false; 83 | } 84 | *out = MAKE_VALUE_BOOLEAN(args.values[0].as.object == args.values[1].as.object); 85 | return true; 86 | } 87 | 88 | bool builtin_test_get_object_address(Object* self, ValueArray args, Value* out) { 89 | if (args.count != 1) { 90 | return false; 91 | } 92 | if (args.values[0].type != VALUE_OBJECT) { 93 | return false; 94 | } 95 | *out = MAKE_VALUE_NUMBER((uintptr_t) args.values[0].as.object); 96 | return true; 97 | } 98 | 99 | /* Used to invoke the gc in very specific tests using the _testing module - use for nothing else. */ 100 | bool builtin_test_gc(Object* self, ValueArray args, Value* out) { 101 | vm_gc(); 102 | *out = MAKE_VALUE_NIL(); 103 | return true; 104 | } 105 | 106 | bool builtin_test_table_details(Object* self, ValueArray args, Value* out) { 107 | if (!object_value_is(args.values[0], OBJECT_TABLE)) { 108 | return false; 109 | } 110 | 111 | Table table = ((ObjectTable*) args.values[0].as.object)->table; 112 | 113 | Table result = table_new_empty(); 114 | table_set(&result, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated("num_entries")), 115 | MAKE_VALUE_NUMBER(table.num_entries)); 116 | table_set(&result, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated("count")), 117 | MAKE_VALUE_NUMBER(table.count)); 118 | table_set(&result, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated("capacity")), 119 | MAKE_VALUE_NUMBER(table.capacity)); 120 | table_set(&result, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated("collision_count")), 121 | MAKE_VALUE_NUMBER(table.collision_count)); 122 | 123 | *out = MAKE_VALUE_OBJECT(object_table_new(result)); 124 | return true; 125 | } 126 | 127 | bool builtin_test_table_delete(Object* self, ValueArray args, Value* out) { 128 | if (!object_value_is(args.values[0], OBJECT_TABLE)) { 129 | return false; 130 | } 131 | 132 | ObjectTable* table = (ObjectTable*) args.values[0].as.object; 133 | Value key = args.values[1]; 134 | 135 | table_delete(&table->table, key); 136 | 137 | *out = MAKE_VALUE_NIL(); 138 | return true; 139 | } -------------------------------------------------------------------------------- /src/python/tester/tests/modules.test: -------------------------------------------------------------------------------- 1 | test basic import 2 | import testmodule 3 | file testmodule.rib 4 | print("Importing testmodule") 5 | expect 6 | Importing testmodule 7 | end 8 | 9 | test nested imports 10 | import testmodule1 11 | print("main") 12 | file testmodule1.rib 13 | import testmodule2 14 | print("1") 15 | file testmodule2.rib 16 | print("2") 17 | expect 18 | 2 19 | 1 20 | main 21 | end 22 | 23 | test module caching 24 | import testmodule1 25 | import testmodule1 26 | import testmodule2 27 | import testmodule1 28 | import testmodule2 29 | file testmodule1.rib 30 | print("I'm testmodule1") 31 | file testmodule2.rib 32 | print("I'm testmodule2") 33 | expect 34 | I'm testmodule1 35 | I'm testmodule2 36 | end 37 | 38 | test module attributes 39 | import testmodule1 40 | 41 | print(testmodule1.attr1) 42 | print(testmodule1.func1(2)) 43 | print(testmodule1.func2(2)) 44 | 45 | testmodule1.func1 = { | x | 46 | return x * 10 47 | } 48 | 49 | print(testmodule1.func1(2)) 50 | print(testmodule1.func2(2)) 51 | 52 | testmodule1.nonexistent # this will crash the program with an error 53 | file testmodule1.rib 54 | attr1 = "I'm an attribute" 55 | func1 = { | x | 56 | return x * 2 57 | } 58 | func2 = { | n | 59 | return func1(n * 2) 60 | } 61 | expect 62 | I'm an attribute 63 | 4 64 | 8 65 | 20 66 | 40 67 | An error has occured. Stack trace (most recent call on top): 68 | ->
69 | Cannot find attribute nonexistent of object. 70 | end 71 | 72 | test import stdlib module 73 | import math 74 | print(math.abs(-4)) 75 | expect 76 | 4 77 | end 78 | 79 | test user module takes precedence over stdlib module 80 | import math 81 | print(math.user_module_variable) 82 | import math # won't print anything because module is already cached 83 | print(math.user_module_variable) 84 | file math.rib 85 | print("I'm the math.rib user module") 86 | user_module_variable = 10 87 | expect 88 | I'm the math.rib user module 89 | 10 90 | 10 91 | end 92 | 93 | test unfound module raises error 94 | # Generally we currently lack error-case tests, because the output of errors is temporary 95 | # and the tests will be fragile. In this case we do write one, and will rewrite it in the future 96 | 97 | import the_pretty_horse_module 98 | print("This will never print") 99 | expect 100 | An error has occured. Stack trace (most recent call on top): 101 | ->
102 | Couldn't find module the_pretty_horse_module. 103 | end 104 | 105 | test load stdlib extension module 106 | import myextension 107 | print(myextension.multiply(2, 8.5)) 108 | expect 109 | 17 110 | end 111 | 112 | test load user extension module 113 | import myuserextension 114 | print(myuserextension.multiply(8, 4)) 115 | expect 116 | 32 117 | end 118 | 119 | test extension classes 120 | import myuserextension 121 | import my_second_user_extension 122 | 123 | MyThingA = myuserextension.MyThingA 124 | 125 | thing_a = MyThingA("Hello world :D") 126 | print(thing_a.get_text()) 127 | 128 | thing_b = my_second_user_extension.MyThingB(thing_a) 129 | multiply_func = thing_b.get_text_multiplied 130 | print(multiply_func(3)) 131 | expect 132 | Hello world :D 133 | Hello world :DHello world :DHello world :D 134 | end 135 | 136 | test extension imports and uses other extension 137 | import myuserextension 138 | import my_second_user_extension 139 | 140 | sqr_6 = my_second_user_extension.square(6) 141 | print(sqr_6) 142 | expect 143 | Getting other extension. 144 | Getting the multiply() method from the extension. 145 | Calling multiply(). 146 | Returned from multiply(). Returning the value. 147 | 36 148 | end 149 | 150 | test extension causes the import of another extension 151 | import my_second_user_extension 152 | 153 | sqr_7 = my_second_user_extension.square(7) 154 | print(sqr_7) 155 | expect 156 | Getting other extension. 157 | Other extension not imported yet. Importing it. 158 | Imported successfully. 159 | Now getting the extension. 160 | Got the extension successfully. 161 | Getting the multiply() method from the extension. 162 | Calling multiply(). 163 | Returned from multiply(). Returning the value. 164 | 49 165 | end 166 | 167 | test more extension communication and also importing ribbon module from extension 168 | # Description of the native function my_second_user_extension.more_talk_with_other_extension(): 169 | # 170 | # It utilizes vm_call_bound_method, vm_instantiate_class, vm_call_attribute_cstring and object_load_attribute 171 | # to call C functions defined in one extension, from the running extension. 172 | # It also utilizes vm_import_module and vm_get_module to load the stdlib math module, written in Place, 173 | # and call a function from it. 174 | # 175 | # MaxResult is the result of calling math.max, 176 | # Text is a result of calling MyThingA.get_text() in the other extension and appending stuff to it, 177 | # and Number is the result of calling multiply() in the other extension. 178 | 179 | import my_second_user_extension 180 | result = my_second_user_extension.more_talk_with_other_extension() 181 | 182 | print(result["MaxResult"]) 183 | print(result["Text"]) 184 | print(result["Number"]) 185 | expect 186 | 8 187 | Text received from MyThingA::get_text(): Hyelloooo! 188 | 350 189 | end 190 | -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "memory.h" 7 | #include "common.h" 8 | #include "table.h" 9 | 10 | static Table allocations; 11 | static size_t allocated_memory = 0; 12 | 13 | void memory_init(void) { 14 | table_init_memory_infrastructure(&allocations); 15 | allocated_memory = 0; // For consistency 16 | } 17 | 18 | size_t get_allocated_memory() { 19 | return allocated_memory; 20 | } 21 | 22 | size_t get_allocations_count() { 23 | return allocations.num_entries; 24 | } 25 | 26 | static bool is_same_allocation(size_t size, const char* what, Allocation allocation) { 27 | return (allocation.size == size) && (strcmp(allocation.name, what) == 0); 28 | } 29 | 30 | void* allocate(size_t size, const char* what) { 31 | if (size <= 0) { 32 | FAIL("allocate :: size <= 0"); 33 | } 34 | 35 | #if MEMORY_DIAGNOSTICS 36 | return reallocate(NULL, 0, size, what); 37 | 38 | #else 39 | 40 | return allocate_no_tracking(size); 41 | 42 | #endif 43 | } 44 | 45 | void deallocate(void* pointer, size_t oldSize, const char* what) { 46 | #if MEMORY_DIAGNOSTICS 47 | 48 | reallocate(pointer, oldSize, 0, what); 49 | 50 | #else 51 | 52 | return deallocate_no_tracking(pointer); 53 | 54 | #endif 55 | } 56 | 57 | static void* do_deallocation(void* pointer, size_t old_size, const char* what) { 58 | // We allow the pointer to be NULL, and if it is we do nothing 59 | 60 | if (pointer != NULL) { 61 | if (!table_delete(&allocations, MAKE_VALUE_ADDRESS(pointer))) { 62 | FAIL("Couldn't remove existing key in allocations table: %p. Allocation tag: '%s'", pointer, what); 63 | } 64 | } 65 | 66 | DEBUG_MEMORY("Freeing '%s' ('%p') and %" PRI_SIZET " bytes.", what, pointer, old_size); 67 | free(pointer); /* If pointer == NULL, free on NULL is a legal noop */ 68 | allocated_memory -= old_size; 69 | 70 | return NULL; 71 | } 72 | 73 | static void* do_reallocation(void* pointer, size_t old_size, size_t new_size, const char* what) { 74 | DEBUG_MEMORY("Attempting to reallocate for '%s' %" PRI_SIZET " bytes instead of %" PRI_SIZET " bytes.", what, new_size, old_size); 75 | void* newpointer = realloc(pointer, new_size); 76 | 77 | if (newpointer == NULL) { 78 | // TODO: Should be a severe runtime error, not a FAIL? 79 | FAIL("Reallocation of '%s' failed! " 80 | "Pointer: %p, new_size: %" PRI_SIZET " . Total allocated memory: %" PRI_SIZET, what, pointer, new_size, get_allocated_memory()); 81 | return NULL; 82 | } 83 | 84 | Value allocation_out; 85 | if (table_get(&allocations, MAKE_VALUE_ADDRESS(pointer), &allocation_out)) { 86 | Allocation existing = allocation_out.as.allocation; 87 | if (!is_same_allocation(old_size, what, existing)) { 88 | FAIL("When attempting relocation, table returned wrong allocation marker."); 89 | } 90 | 91 | if (table_delete(&allocations, MAKE_VALUE_ADDRESS(pointer))) { 92 | Value new_allocation_key = MAKE_VALUE_ADDRESS(newpointer); 93 | Value new_allocation = MAKE_VALUE_ALLOCATION(what, new_size); 94 | table_set(&allocations, new_allocation_key, new_allocation); 95 | } else { 96 | FAIL("In reallocation, couldn't remove entry which was found in the table."); 97 | } 98 | } else { 99 | FAIL("memory do_reallocation(): Couldn't find marker to replace."); 100 | } 101 | 102 | allocated_memory -= old_size; 103 | allocated_memory += new_size; 104 | 105 | return newpointer; 106 | } 107 | 108 | static void* do_allocation(void* pointer, size_t new_size, const char* what) { 109 | DEBUG_MEMORY("Allocating for '%s' %" PRI_SIZET " bytes.", what, new_size); 110 | 111 | pointer = realloc(pointer, new_size); // realloc() with NULL is equal to malloc() 112 | 113 | if (pointer == NULL) { 114 | FAIL("Couldn't allocate new memory."); // Temp 115 | } 116 | 117 | table_set(&allocations, MAKE_VALUE_ADDRESS(pointer), MAKE_VALUE_ALLOCATION(what, new_size)); 118 | allocated_memory += new_size; 119 | 120 | return pointer; 121 | } 122 | 123 | void* reallocate(void* pointer, size_t old_size, size_t new_size, const char* what) { 124 | #if MEMORY_DIAGNOSTICS 125 | 126 | if (new_size == 0) { 127 | return do_deallocation(pointer, old_size, what); 128 | } 129 | 130 | if (old_size == 0) { 131 | return do_allocation(pointer, new_size, what); 132 | } 133 | 134 | return do_reallocation(pointer, old_size, new_size, what); 135 | 136 | #else 137 | 138 | if (new_size == 0) { 139 | deallocate_no_tracking(pointer); 140 | return NULL; 141 | } 142 | 143 | return reallocate_no_tracking(pointer, new_size); 144 | 145 | #endif 146 | } 147 | 148 | void* allocate_no_tracking(size_t size) { 149 | if (size <= 0) { 150 | FAIL("allocate_no_tracking :: size <= 0"); 151 | } 152 | 153 | return malloc(size); 154 | } 155 | 156 | void deallocate_no_tracking(void* pointer) { 157 | free(pointer); 158 | } 159 | 160 | void* reallocate_no_tracking(void* pointer, size_t new_size) { 161 | if (new_size <= 0) { 162 | FAIL("reallocate_no_tracking :: new_size <= 0"); 163 | } 164 | 165 | return realloc(pointer, new_size); 166 | } 167 | 168 | void memory_print_allocated_entries() { // for debugging 169 | DEBUG_IMPORTANT_PRINT("Allocated memory entries:\n"); 170 | 171 | printf("\n"); 172 | // table_print_debug_as_buckets(&allocations, false); 173 | table_print_debug(&allocations); 174 | printf("\n"); 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Ribbon programming language 2 | 3 | Ribbon is a dynamic interpreted programming language. It's inspired by the likes of Python, Javascript and a little bit of Lua. 4 | 5 | Here is a Fizzbuzz implementation to get a look and feel for Ribbon: 6 | 7 | ```Python 8 | Numbers = class { 9 | @init = { 10 | self.n = 0 11 | } 12 | 13 | next = { 14 | n = self.n 15 | self.n += 1 16 | return n 17 | } 18 | } 19 | 20 | fizzbuzz = { | max | 21 | numbers = Numbers() 22 | 23 | n = numbers.next() 24 | while n < max { 25 | if n % 3 == 0 and n % 5 == 0{ 26 | print("fizzbuzz") 27 | } elsif n % 3 == 0 { 28 | print("fizz") 29 | } elsif n % 5 == 0 { 30 | print("buzz") 31 | } else { 32 | print(n) 33 | } 34 | 35 | n = numbers.next() 36 | } 37 | } 38 | 39 | fizzbuzz(20) 40 | ``` 41 | 42 | The Ribbon language guide can be found [here](docs/guide.md). 43 | 44 | ### Ribbon high level architecture 45 | 46 | ![Ribbon architecture](docs/ribbon_architecture.png "Ribbon architecture") 47 | 48 | As illustrated above, the processing of a Ribbon program goes through the following main modules of the interpreter: 49 | 50 | * **Scanner**: converts the user's source code into a stream of meaningful tokens 51 | * **Parser**: parses the stream of tokens into an Abstract Syntax Tree - a hierarchical tree representing the program structure 52 | * **Compiler**: compiles the AST into a linear sequence of bytecode instructions 53 | * **VM**: iterates over the bytecode instructions and executes them one by one. The VM also includes the garbage collector, among additional facilities of the interpreter 54 | 55 | There are additional modules at play which are mainly used by the primary modules. One such example example would be the **Memory** module. It manages memory allocations and may alert in case of a native memory leak. 56 | 57 | ### The main traits of Ribbon: 58 | 59 | * Multi-paradigm: has classes and methods alongside closures and first class functions 60 | * Everything is an expression - including classes and functions 61 | * Extensible through the C extension system 62 | * Minimal syntax, no ceremony or boilerplate 63 | * 2D graphics programming supported out of the box via a standard library module 64 | * Comprehensive test suite for the interpreter 65 | 66 | ### How to install Ribbon: 67 | 68 | In order to install Ribbon we first need to build it from source. 69 | 70 | After building Ribbon, it can be easily distributed and installed as a self contained directory, containing the interpreter `ribbon.exe` with the standard library `stdlib\` next to it. 71 | 72 | #### Requirements to run Ribbon: 73 | 74 | * Ribbon currently only runs on Windows 64 bit 75 | 76 | #### Requirements to build Ribbon: 77 | 78 | * Mingw-w64 GCC for building the interpreter, visible on `PATH` as `gcc` 79 | * Python 3 for running the test suite of the interpreter, visible on `PATH` as `python` 80 | * **Optional:** in order to build the included `graphics` standard library module, we need the SDL2 library installed. More on this later 81 | 82 | #### Steps to build Ribbon: 83 | 84 | 1. Clone this repository 85 | 86 | 2. `CD` to the root directory of the repo 87 | 88 | 3. Set environment variables required for building the interpreter: 89 | * Point `RIBBON_BUILD_INCLUDE` to the directory of the C libraries header files (For example: `C:/msys64/mingw64/include`) 90 | * Point `RIBBON_BUILD_LIB` to the directory of the C libraries binaries (For example: `C:/msys64/mingw64/lib`) 91 | 92 | 4. Run `build_dev.bat` in order to build in development mode. This turns on several diagnostics, and is necessary for the tests to pass. 93 | After building, the development output binary `ribbon.exe` will be located at `src\ribbon.exe`. 94 | 95 | > build_dev.bat 96 | 97 | 5. Ribbon comes with a dummy C module which is used in the test suite for the extension system. Build it from the current directory by running 98 | 99 | > src\sample_extension\bdeploy.bat 100 | 101 | 6. Run the test suite for the Ribbon interpreter: 102 | 103 | > test.bat 104 | 105 | You should see all of the tests passing. 106 | 107 | 7. Now we can build Ribbon for release. After building, this copies `ribbon.exe` and the adjacent `stdlib` directory, from `src` to a specified installation directory. If unspecified, the default is `release\ribbon`. 108 | 109 | > build_release.bat [] 110 | 111 | 8. Add the installation directory containing `ribbon.exe` to your `PATH`, so `ribbon` can be easily launched 112 | from the command line. 113 | 114 | 9. We can now run Ribbon programs like so: 115 | 116 | > ribbon your_program.rib 117 | 118 | Happy coding! 119 | 120 | 10. **Optional:** Ribbon comes with a standard library `graphics` module for 2D graphics programming. It's a wrapper around the SDL2 C library. The steps to build it: 121 | 1. Acquire the SDL2 official binaries: `SDL2.dll` and `SDL2_image.dll`, with their corresponding headers and static libraries. 122 | 2. Place the SDL2 `.a` files under `%RIBBON_BUILD_LIB%` (among the other binaries of the C libraries on your system) 123 | 3. Place the SDL2 header files under `%RIBBON_BUILD_INCLUDE%/SDL2` 124 | 4. During runtime, our `graphics` module will need to find the SDL2 `.dll` files. Place `SDL2.dll` and `SDL2_image.dll` under `src\stdlib\`, next to the other standard library files. Be sure to also copy other DLLs required by SDL2, for example: libpng and zlib. 125 | 5. Build the `graphics` module like so: 126 | 127 | > src\sdl_extension\bdeploy_optimized.bat 128 | 129 | 6. You should now see the `graphics.dll` module in the `src\stdlib` directory. 130 | 131 | 7. To include `graphics` in the release build of Ribbon, build Ribbon in release mode again: 132 | 133 | > build_release.bat [] 134 | 135 | Under `\stdlib`, you should now be able to find `graphics.dll` and the SDL `.dll` files, 136 | among the other standard library files. 137 | 138 | 8. The module `graphics` is now available to `import`. 139 | 140 | ----- 141 | 142 | If you find this project interesting or would like to continue its development, please let me know what you think. 143 | -------------------------------------------------------------------------------- /src/python/tester/tests/control_flow.test: -------------------------------------------------------------------------------- 1 | test plain_if 2 | if 10 > 5 { 3 | print("yes") 4 | } 5 | expect 6 | yes 7 | end 8 | 9 | test if_else 10 | if 10 < 5 { 11 | print("yes") 12 | } else { 13 | print("no") 14 | } 15 | expect 16 | no 17 | end 18 | 19 | test if_elsif_else 20 | if 10 > 5 { 21 | print("a") 22 | } elsif 10 == 5 { 23 | print("b") 24 | } else { 25 | print("c") 26 | } 27 | 28 | if 10 < 5 { 29 | print("a") 30 | } elsif 10 == 5 { 31 | print("b") 32 | } else { 33 | print("c") 34 | } 35 | 36 | if 10 < 5 { 37 | print("a") 38 | } elsif 10 == 10 { 39 | print("b") 40 | } else { 41 | print("c") 42 | } 43 | 44 | if 10 < 5 { 45 | print("a") 46 | } elsif 10 != 10 { 47 | print("b") 48 | } 49 | 50 | if 10 < 5 { 51 | print("a") 52 | } elsif 10 < 8 { 53 | print("b") 54 | } elsif 10 < 11 { 55 | print("c") 56 | } else { 57 | print("d") 58 | } 59 | 60 | if 10 < 5 { 61 | print("a") 62 | } elsif 10 < 8 { 63 | print("b") 64 | } elsif 10 < 11 { 65 | print("c") 66 | } 67 | expect 68 | a 69 | c 70 | b 71 | c 72 | c 73 | end 74 | 75 | test nested_if_elsif_else 76 | if 10 < 5 { 77 | print("a") 78 | } elsif 10 < 8 { 79 | print("b") 80 | } elsif 10 < 11 { 81 | if 20 / 2 == 5 { 82 | print("c.1") 83 | } else { 84 | print("c.2") 85 | } 86 | } else { 87 | print("d") 88 | } 89 | 90 | if 10 < 5 { 91 | print("a") 92 | } elsif 10 < 8 { 93 | print("b") 94 | } elsif 10 < 11 { 95 | if 20 / 2 == 5 { 96 | print("c.1") 97 | } elsif 10 / 5 == 2 { 98 | print("c.2") 99 | } 100 | } else { 101 | print("d") 102 | } 103 | 104 | something = 10 < 5 105 | if something { 106 | print("a") 107 | } elsif 10 < 8 { 108 | print("b") 109 | } elsif 10 < 10 { 110 | if 20 / 2 == 5 { 111 | print("c.1") 112 | } elsif 10 / 5 == 1 { 113 | print("c.2") 114 | } 115 | } else { 116 | if 10 / 5 == 2 { 117 | print("d.1") 118 | } else { 119 | print("d.2") 120 | } 121 | } 122 | expect 123 | c.2 124 | c.2 125 | d.1 126 | end 127 | 128 | test more if elsif else 129 | char = "b" 130 | if char == "a" { 131 | upper_char = "A" 132 | } elsif char == "b" { 133 | upper_char = "B" 134 | } elsif char == "c" { 135 | upper_char = "C" 136 | } elsif char == "d" { 137 | upper_char = "D" 138 | } elsif char == "e" { 139 | upper_char = "E" 140 | } elsif char == "f" { 141 | upper_char = "F" 142 | } elsif char == "g" { 143 | upper_char = "G" 144 | } elsif char == "h" { 145 | upper_char = "H" 146 | } elsif char == "i" { 147 | upper_char = "I" 148 | } elsif char == "j" { 149 | upper_char = "J" 150 | } elsif char == "k" { 151 | upper_char = "K" 152 | } elsif char == "l" { 153 | upper_char = "L" 154 | } elsif char == "m" { 155 | upper_char = "M" 156 | } elsif char == "n" { 157 | upper_char = "N" 158 | } elsif char == "o" { 159 | upper_char = "O" 160 | } elsif char == "p" { 161 | upper_char = "P" 162 | } elsif char == "q" { 163 | upper_char = "Q" 164 | } elsif char == "r" { 165 | upper_char = "R" 166 | } elsif char == "s" { 167 | upper_char = "S" 168 | } elsif char == "t" { 169 | upper_char = "T" 170 | } elsif char == "u" { 171 | upper_char = "U" 172 | } elsif char == "v" { 173 | upper_char = "V" 174 | } elsif char == "w" { 175 | upper_char = "W" 176 | } elsif char == "x" { 177 | upper_char = "X" 178 | } elsif char == "y" { 179 | upper_char = "Y" 180 | } elsif char == "z" { 181 | upper_char = "Z" 182 | } else { 183 | upper_char = char 184 | } 185 | print(upper_char) 186 | expect 187 | B 188 | end 189 | 190 | test counting_while_loop 191 | x = 0 192 | while x < 3 { 193 | print(x) 194 | x = x + 1 195 | } 196 | expect 197 | 0 198 | 1 199 | 2 200 | end 201 | 202 | test while_inside_if 203 | if 10 < 20 { 204 | i = 3 205 | while i > 0 { 206 | print("hi") 207 | i = i - 1 208 | } 209 | } else { 210 | print("Shouldn't happen") 211 | } 212 | expect 213 | hi 214 | hi 215 | hi 216 | end 217 | 218 | test if_inside_while 219 | i = 4 220 | while i > 0 { 221 | print("hi") 222 | if i == 2 { 223 | print(i) 224 | } 225 | i = i - 1 226 | } 227 | expect 228 | hi 229 | hi 230 | hi 231 | 2 232 | hi 233 | end 234 | 235 | test basic for loop 236 | for word in ["hello", "my", "friends"] { 237 | print(word) 238 | } 239 | expect 240 | hello 241 | my 242 | friends 243 | end 244 | 245 | test for loop on string 246 | table = [] 247 | for character in "Kangaroo!" { 248 | table[table.length()] = character 249 | } 250 | 251 | for c in table { 252 | print(c) 253 | } 254 | expect 255 | K 256 | a 257 | n 258 | g 259 | a 260 | r 261 | o 262 | o 263 | ! 264 | end 265 | 266 | test nested for loops 267 | t1 = [10, 20 30] 268 | t2 = [0.5, 2, 4] 269 | result = [] 270 | for x in t1 { 271 | for y in t2 { 272 | result[result.length()] = x * y 273 | } 274 | } 275 | 276 | for n in result { 277 | print(n) 278 | } 279 | expect 280 | 5 281 | 20 282 | 40 283 | 10 284 | 40 285 | 80 286 | 15 287 | 60 288 | 120 289 | end 290 | 291 | test all control structures together 292 | for c in "hello" { 293 | i = 2 294 | while i < 4 { 295 | print(c + c) 296 | if i == 3 { 297 | print("Kawabanga") 298 | } 299 | i = i + 1 300 | } 301 | } 302 | expect 303 | hh 304 | hh 305 | Kawabanga 306 | ee 307 | ee 308 | Kawabanga 309 | ll 310 | ll 311 | Kawabanga 312 | ll 313 | ll 314 | Kawabanga 315 | oo 316 | oo 317 | Kawabanga 318 | end -------------------------------------------------------------------------------- /src/stdlib/utils.rib: -------------------------------------------------------------------------------- 1 | substring = { | string, start, end | 2 | result = "" 3 | for i in range(start, end) { 4 | result += string[i] 5 | } 6 | return result 7 | } 8 | 9 | range = { | min, max | 10 | result = [] 11 | i = min 12 | while i < max { 13 | result.add(i) 14 | i += 1 15 | } 16 | return result 17 | } 18 | 19 | range_reverse = { | max, min | 20 | result = [] 21 | i = max 22 | while i >= min { 23 | result.add(i) 24 | i -= 1 25 | } 26 | return result 27 | } 28 | 29 | reverse_string = { | string | 30 | result = "" 31 | for i in range_reverse(string.length() - 1, 0) { 32 | result += string[i] 33 | } 34 | return result 35 | } 36 | 37 | left_trim_string = { | string, unwanted_char | 38 | result = "" 39 | passed_starting_unwanted_chars = false 40 | 41 | for current_char in string { 42 | if current_char != unwanted_char or passed_starting_unwanted_chars { 43 | result += current_char 44 | passed_starting_unwanted_chars = true 45 | } 46 | } 47 | 48 | return result 49 | } 50 | 51 | right_trim_string = { | string, unwanted_char | 52 | return reverse_string(left_trim_string(reverse_string(string), unwanted_char)) 53 | } 54 | 55 | trim_string = { | string, unwanted_char | 56 | return left_trim_string(right_trim_string(string, unwanted_char), unwanted_char) 57 | } 58 | 59 | split_string = { | string, separator | 60 | # currently assuming separator.length() == 1 61 | 62 | result = [] 63 | prev_location = 0 64 | 65 | for i in range(0, string.length() + 1) { 66 | if i == string.length() or string[i] == separator { 67 | segment = substring(string, prev_location, i) 68 | if segment != "" { 69 | result.add(segment) 70 | } 71 | prev_location = i + 1 72 | } 73 | } 74 | 75 | return result 76 | } 77 | 78 | multiply_string = { | string, times | 79 | result = "" 80 | for i in range(0, times) { 81 | result += string 82 | } 83 | return result 84 | } 85 | 86 | list_has_value = { | list, value | 87 | for item in list { 88 | if item == value { 89 | return true 90 | } 91 | } 92 | return false 93 | } 94 | 95 | as_string = { | object | 96 | # The builtin to_string function is very lacking... we try to make up for it using this function 97 | 98 | object_type = type(object) 99 | builtin_supported_types = ["Number"] # currently only number conversion is supported in builtin to_string 100 | 101 | if list_has_value(builtin_supported_types, object_type) { 102 | return to_string(object) 103 | } 104 | 105 | if object_type == "Number" { 106 | return to_string(object) 107 | } elsif object_type == "String" { 108 | return object 109 | } elsif object_type == "Boolean" { 110 | if object { 111 | return "true" 112 | } else { 113 | return "false" 114 | } 115 | } elsif object_type == "Table" { 116 | # Assuming a list-table... not very robust 117 | result = "" 118 | for item in object { 119 | stringed = as_string(item) 120 | result += "[" + as_string(item) + "]" 121 | } 122 | return result 123 | } else { 124 | return "" 125 | } 126 | } 127 | 128 | _assert = { | test_description, actual, expected | 129 | objects_equal = { | o1, o2 | 130 | # the builtin-in "equals" support in the interpreter for objects is very lacking: 131 | # currently only compares identity (or logically for strings, which are always unique in the heap). 132 | # we try to make up for it by comparing their textual representation 133 | 134 | return o1 == o2 or as_string(o1) == as_string(o2) 135 | } 136 | 137 | cosmetic_space_length = 45 - test_description.length() 138 | cosmetic_space = multiply_string(" ", cosmetic_space_length) 139 | text = "Test " + test_description + ":" + cosmetic_space 140 | 141 | if objects_equal(actual, expected) { 142 | text += "[ PASSED ]" 143 | } else { 144 | text += "[ FAILED ]" 145 | text += "\n" 146 | text += " Expected: " + as_string(expected) + "\n" 147 | text += " Actual: " + as_string(actual) + "\n" 148 | } 149 | 150 | print(text) 151 | } 152 | 153 | _test = { 154 | _assert("split_string 1", split_string("a b c", " "), ["a", "b", "c"]) 155 | _assert("split_string 2", split_string("aaa bbb ccc", " "), ["aaa", "bbb", "ccc"]) 156 | _assert("split_string 3", split_string(" aaa bbb ccc", " "), ["aaa", "bbb", "ccc"]) 157 | _assert("split_string 4", split_string(" aaa bbb ccc ", " "), ["aaa", "bbb", "ccc"]) 158 | 159 | _assert("substring 1", substring("abcdefg", 2, 5), "cde") 160 | _assert("substring 2", substring("abcdefg", 4, 7), "efg") 161 | _assert("substring 3", substring("a b c", 4, 5), "c") 162 | 163 | _assert("as_string on number 1", as_string(100), "100") 164 | 165 | _assert("range 1", range(0, 5), [0, 1, 2, 3, 4]) 166 | _assert("range 2", range(3, 5), [3, 4]) 167 | _assert("range 3", range(2, 2), []) 168 | 169 | _assert("range_reverse 1", range_reverse(5, 0), [5, 4, 3, 2, 1, 0]) 170 | _assert("range_reverse 1", range_reverse(5, 1), [5, 4, 3, 2, 1]) 171 | 172 | _assert("reverse_string 1", reverse_string("abcde fg"), "gf edcba") 173 | 174 | _assert("left_trim_string 1", left_trim_string("abcde", "x"), "abcde") 175 | _assert("left_trim_string 2", left_trim_string(" abcde ", "x"), " abcde ") 176 | _assert("left_trim_string 3", left_trim_string("xabcde", "x"), "abcde") 177 | _assert("left_trim_string 4", left_trim_string("xxxabcde", "x"), "abcde") 178 | _assert("left_trim_string 5", left_trim_string("xxxabcdexxx", "x"), "abcdexxx") 179 | 180 | _assert("right_trim_string 1", right_trim_string("xxxabcdexxx", "x"), "xxxabcde") 181 | _assert("right_trim_string 2", right_trim_string("xxxabcde", "x"), "xxxabcde") 182 | _assert("right_trim_string 3", right_trim_string("xxxabcde ", "x"), "xxxabcde ") 183 | _assert("right_trim_string 4", right_trim_string("xxxabcde ", " "), "xxxabcde") 184 | _assert("right_trim_string 5", right_trim_string(" xxxabcde ", " "), " xxxabcde") 185 | 186 | _assert("trim_string 1", trim_string(" xxxabcde ", " "), "xxxabcde") 187 | } 188 | -------------------------------------------------------------------------------- /src/python/tester/tests/math.test: -------------------------------------------------------------------------------- 1 | test binary_operations 2 | print(10 + 5) 3 | print(10 - 5) 4 | print(10 * 5) 5 | print(10 / 5) 6 | print(10.0 / 5.0) 7 | print(10 / 5.0) 8 | print(-10 + 5) 9 | 10 | print(10 % 5) 11 | print(8 % 3.5) 12 | expect 13 | 15 14 | 5 15 | 50 16 | 2 17 | 2 18 | 2 19 | -5 20 | 0 21 | 1 22 | end 23 | 24 | test expression 25 | print(10 + 5 / 2) 26 | print((10 + 5) / 2) 27 | print((10 + 5) * 2 + -5) 28 | print((10 + 5) * (2 + -5) / 10) 29 | print((10 + 5.0) * (2 + -5) / 10) 30 | print(((10 + 5.0) * (2 + 5) / 10) % 6) 31 | print((10 + 5.0) * (2 + 5) / 10 % 6) 32 | print((10 + 5.0) * (2 + 5) / (10 % 6)) 33 | expect 34 | 12.5 35 | 7.5 36 | 25 37 | -4.5 38 | -4.5 39 | 4.5 40 | 4.5 41 | 26.25 42 | end 43 | 44 | test modulo with negative numbers unsupported 45 | 10 % -2 46 | expect 47 | An error has occured. Stack trace (most recent call on top): 48 | ->
49 | Modulo with negative numbers not supported. 50 | end 51 | 52 | test boolean expressions 53 | print(not true) 54 | print(not not true) 55 | print(not false) 56 | print(not (5 == 5)) 57 | print(not (5 == 5) or not false) 58 | 59 | print(5 > 10) 60 | print(5 < 10) 61 | print(5 == 10) 62 | print(5 != 10) 63 | 64 | print(10 == 10) 65 | print(10 != 10) 66 | print(10 < 10) 67 | print(10 > 10) 68 | print(10 >= 10) 69 | 70 | print("hello" == "hello") 71 | print("hello" != "hello") 72 | 73 | print(true == true) 74 | print(true != false) 75 | print(true != true) 76 | 77 | print(true and true) 78 | print(true or true) 79 | print(false and false) 80 | print(false or false) 81 | print(true and false) 82 | print(true or false) 83 | print(false and true) 84 | print(false or true) 85 | 86 | print(10 + 20 > 5) 87 | print(10 + 20 > 5 and 50 / 2 == 25) 88 | print(10 + 20 > 5 and 50 / 2 != 25) 89 | print(10 + 20 > 5 or 50 / 2 != 25) 90 | print(10 + 20 > 50 or 50 / 2 != 25) 91 | print(10 + 20 > 50 or 50 / 2 != 25 or true) 92 | print(10 + 20 > 50 or 50 / 2 != 25 or 100 / 2 == 51) 93 | print((10 + 20 > 50 or 50 / 2 != 25 or 100 / 2 == 51) and 10 == 10) 94 | print((10 + 20 > 50 or 50 / 2 != 25 or 100 / 2 == 51) or 10 == 10) 95 | print(20 / 2 == 10 and (10 * 2 < 10 or 5 == 5)) 96 | 97 | a = 50 / 2 != 25 98 | print(a) 99 | expect 100 | false 101 | true 102 | true 103 | false 104 | true 105 | false 106 | true 107 | false 108 | true 109 | true 110 | false 111 | false 112 | false 113 | true 114 | true 115 | false 116 | true 117 | true 118 | false 119 | true 120 | true 121 | false 122 | false 123 | false 124 | true 125 | false 126 | true 127 | true 128 | true 129 | false 130 | true 131 | false 132 | true 133 | false 134 | false 135 | true 136 | true 137 | false 138 | end 139 | 140 | test boolean expression short circuiting 141 | f1 = { 142 | print("Called") 143 | return false 144 | } 145 | 146 | f2 = { 147 | print("Called") 148 | return true 149 | } 150 | 151 | print(f1() and f1()) 152 | print(f2() and f2()) 153 | 154 | print( 10 < 5 and f2() ) 155 | print( 10 > 5 and f1() ) 156 | 157 | print( (f2() and f1()) or ((10 < 5) or f2()) ) 158 | 159 | print(f2() and true and f2() and f1()) 160 | 161 | print(f2() and true or f2() and f1()) 162 | 163 | print(8 + 2 >= 10 and true or f2() and f1()) 164 | expect 165 | Called 166 | false 167 | Called 168 | Called 169 | true 170 | false 171 | Called 172 | false 173 | Called 174 | Called 175 | Called 176 | true 177 | Called 178 | Called 179 | Called 180 | false 181 | Called 182 | true 183 | true 184 | end 185 | 186 | test mutation assignment statements 187 | a = 100 188 | 189 | a += 50 190 | print(a) 191 | 192 | a /= 2 * 10 193 | print(a) 194 | 195 | a *= -1 196 | print(a) 197 | 198 | a -= a * 2 199 | print(a) 200 | 201 | a += 0.5 202 | print(a) 203 | 204 | a %= 3 205 | print(a) 206 | expect 207 | 150 208 | 7.5 209 | -7.5 210 | 7.5 211 | 8 212 | 2 213 | end 214 | 215 | test inplace attribute binary 216 | C = class {} 217 | 218 | c = C() 219 | c.x = 10 220 | c.y = 20 221 | 222 | c.x += 5 223 | print(c.x) 224 | c.x *= 2 225 | print(c.x) 226 | c.x /= 4 227 | print(c.x) 228 | c.x -= 4 229 | print(c.x) 230 | c.x %= 2 231 | print(c.x) 232 | expect 233 | 15 234 | 30 235 | 7.5 236 | 3.5 237 | 1.5 238 | end 239 | 240 | test object expression evaluated only once in inplace attribute binary 241 | C = class { 242 | @init = { | n | 243 | self.n = n 244 | } 245 | } 246 | 247 | o = C(10) 248 | 249 | f = { 250 | print("f() called") 251 | return o 252 | } 253 | 254 | # We're testing that f() only gets called once per assignment 255 | f().n *= 2 256 | print(o.n) 257 | 258 | f().n -= 5 259 | print(o.n) 260 | expect 261 | f() called 262 | 20 263 | f() called 264 | 15 265 | end 266 | 267 | test inplace key binary 268 | t = ["a": 2] 269 | 270 | t["a"] += 4 271 | print(t["a"]) 272 | t["a"] -= 3 273 | print(t["a"]) 274 | t["a"] *= 100 275 | print(t["a"]) 276 | t["a"] /= 10 277 | print(t["a"]) 278 | t["a"] %= 2 279 | print(t["a"]) 280 | expect 281 | 6 282 | 3 283 | 300 284 | 30 285 | 0 286 | end 287 | 288 | test object expression in inplace key binary mutation is evaluated only once 289 | t = ["a": 2] 290 | 291 | C = class { 292 | @init = { 293 | print("@init() called") 294 | } 295 | 296 | f = { | x | 297 | print("f() called") 298 | return t 299 | } 300 | } 301 | 302 | C().f(true)["a"] += 4 303 | print(t["a"]) 304 | 305 | C().f(true)["a"] -= 3 306 | print(t["a"]) 307 | 308 | C().f(true)["a"] *= 100 309 | print(t["a"]) 310 | 311 | C().f(true)["a"] /= 10 312 | print(t["a"]) 313 | 314 | C().f(true)["a"] %= 2 315 | print(t["a"]) 316 | 317 | expect 318 | @init() called 319 | f() called 320 | 6 321 | @init() called 322 | f() called 323 | 3 324 | @init() called 325 | f() called 326 | 300 327 | @init() called 328 | f() called 329 | 30 330 | @init() called 331 | f() called 332 | 0 333 | end -------------------------------------------------------------------------------- /src/ribbon_object.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_object_h 2 | #define ribbon_object_h 3 | 4 | #include 5 | 6 | #include "bytecode.h" 7 | #include "common.h" 8 | #include "table.h" 9 | #include "value.h" 10 | #include "cell_table.h" 11 | 12 | typedef enum { 13 | OBJECT_STRING, 14 | OBJECT_FUNCTION, 15 | OBJECT_CODE, 16 | OBJECT_TABLE, 17 | OBJECT_CELL, 18 | OBJECT_MODULE, 19 | OBJECT_CLASS, 20 | OBJECT_INSTANCE, 21 | OBJECT_BOUND_METHOD 22 | } ObjectType; 23 | 24 | typedef enum { 25 | METHOD_ACCESS_SUCCESS, 26 | METHOD_ACCESS_NO_SUCH_ATTR, 27 | METHOD_ACCESS_ATTR_NOT_BOUND_METHOD 28 | } MethodAccessResult; 29 | 30 | typedef struct Object { 31 | ObjectType type; 32 | struct Object* next; 33 | CellTable attributes; 34 | bool is_reachable; 35 | } Object; 36 | 37 | typedef struct ObjectString { 38 | Object base; 39 | char* chars; /* Guaranteed to be NULL terminated */ 40 | int length; 41 | unsigned long hash; 42 | } ObjectString; 43 | 44 | typedef struct ObjectTable { 45 | Object base; 46 | Table table; 47 | } ObjectTable; 48 | 49 | typedef struct ObjectCode { 50 | Object base; 51 | Bytecode bytecode; 52 | } ObjectCode; 53 | 54 | typedef bool (*NativeFunction)(Object*, ValueArray, Value*); 55 | 56 | typedef struct ObjectCell { 57 | Object base; 58 | Value value; 59 | bool is_filled; 60 | } ObjectCell; 61 | 62 | typedef struct ObjectFunction { 63 | Object base; 64 | char* name; 65 | ObjectString** parameters; 66 | int num_params; 67 | bool is_native; 68 | CellTable free_vars; 69 | union { 70 | NativeFunction native_function; 71 | ObjectCode* code; 72 | }; 73 | } ObjectFunction; 74 | 75 | typedef struct ObjectModule { 76 | Object base; 77 | ObjectString* name; 78 | ObjectFunction* function; 79 | HMODULE dll; 80 | } ObjectModule; 81 | 82 | typedef struct { 83 | uint8_t* return_address; 84 | ObjectFunction* function; 85 | Object* base_entity; 86 | CellTable local_variables; 87 | bool is_entity_base; 88 | bool is_native; 89 | unsigned int eval_stack_frame_base_offset; 90 | } StackFrame; 91 | 92 | typedef struct ObjectInstance ObjectInstance; 93 | typedef void (*DeallocationFunction)(struct ObjectInstance *); 94 | typedef Object** (*GcMarkFunction)(struct ObjectInstance *); 95 | 96 | typedef struct ObjectClass { 97 | /* TODO: Make distinction between ribbon and native classes clearer. Different types? Flag? Union? */ 98 | Object base; 99 | struct ObjectClass* superclass; 100 | char* name; 101 | ObjectFunction* base_function; 102 | size_t instance_size; 103 | DeallocationFunction dealloc_func; 104 | GcMarkFunction gc_mark_func; 105 | } ObjectClass; 106 | 107 | typedef struct ObjectInstance { 108 | Object base; 109 | ObjectClass* klass; 110 | bool is_initialized; 111 | } ObjectInstance; 112 | 113 | typedef struct ObjectBoundMethod { 114 | Object base; 115 | Object* self; 116 | ObjectFunction* method; 117 | } ObjectBoundMethod; 118 | 119 | ObjectString* object_string_copy(const char* string, int length); 120 | ObjectString* object_string_take(char* chars, int length); 121 | ObjectString* object_string_clone(ObjectString* original); 122 | ObjectString** object_create_copied_strings_array(const char** strings, int num, const char* allocDescription); 123 | ObjectString* object_string_copy_from_null_terminated(const char* string); 124 | ObjectString* object_string_new_partial_from_null_terminated(char* chars); 125 | 126 | ObjectFunction* object_user_function_new(ObjectCode* code, ObjectString** parameters, int numParams, CellTable free_vars); 127 | ObjectFunction* object_native_function_new(NativeFunction nativeFunction, ObjectString** parameters, int numParams); 128 | void object_function_set_name(ObjectFunction* function, char* name); 129 | ObjectFunction* make_native_function_with_params(char* name, int num_params, char** params, NativeFunction function); 130 | 131 | ObjectCode* object_code_new(Bytecode chunk); 132 | 133 | ObjectTable* object_table_new(Table table); 134 | ObjectTable* object_table_new_empty(void); 135 | 136 | ObjectCell* object_cell_new(Value value); 137 | ObjectCell* object_cell_new_empty(void); 138 | 139 | ObjectClass* object_class_new(ObjectFunction* base_function, ObjectClass* superclass, char* name); 140 | ObjectClass* object_class_native_new( 141 | char* name, size_t instance_size, DeallocationFunction dealloc_func, 142 | GcMarkFunction gc_mark_func, ObjectFunction* constructor, void* descriptors[][2]); 143 | void object_class_set_name(ObjectClass* klass, char* name); 144 | 145 | ObjectInstance* object_instance_new(ObjectClass* klass); 146 | 147 | ObjectModule* object_module_new(ObjectString* name, ObjectFunction* function); 148 | ObjectModule* object_module_native_new(ObjectString* name, HMODULE dll); 149 | 150 | ObjectBoundMethod* object_bound_method_new(ObjectFunction* method, Object* self); 151 | 152 | bool object_compare(Object* a, Object* b); 153 | 154 | bool object_strings_equal(ObjectString* a, ObjectString* b); 155 | 156 | void object_free(Object* object); 157 | void object_print(Object* o); 158 | void object_print_all_objects(void); 159 | 160 | bool object_hash(Object* object, unsigned long* result); 161 | 162 | // MethodAccessResult object_get_method(Object* object, const char* method_name, ObjectFunction** out); 163 | MethodAccessResult object_get_method(Object* object, const char* method_name, ObjectBoundMethod** out); 164 | 165 | #define OBJECT_AS_STRING(o) (object_as_string(o)) 166 | #define OBJECT_AS_FUNCTION(o) (object_as_function(o)) 167 | 168 | ObjectString* object_as_string(Object* o); 169 | ObjectFunction* object_as_function(Object* o); 170 | 171 | bool object_value_is(Value value, ObjectType type); 172 | 173 | void object_set_attribute_cstring_key(Object* object, const char* key, Value value); 174 | 175 | bool object_load_attribute(Object* object, ObjectString* name, Value* out); 176 | bool object_load_attribute_cstring_key(Object* object, const char* name, Value* out); 177 | bool load_attribute_bypass_descriptors(Object* object, ObjectString* name, Value* out); /* Internal: only external to be used by some tests */ 178 | 179 | ObjectInstance* object_descriptor_new(ObjectFunction* get, ObjectFunction* set); 180 | ObjectInstance* object_descriptor_new_native(NativeFunction get, NativeFunction set); 181 | 182 | bool is_instance_of_class(Object* object, char* klass_name); 183 | bool is_value_instance_of_class(Value value, char* klass_name); 184 | 185 | ObjectFunction* object_make_constructor(int num_params, char** params, NativeFunction function); 186 | 187 | bool object_is_callable(Object* object); 188 | char* object_get_callable_name(Object* object); 189 | 190 | const char* object_get_type_name(Object* object); 191 | 192 | ObjectClass* object_get_superclass(Object* object); 193 | 194 | #define VALUE_AS_OBJECT(value, object_type, cast) object_value_is(value, object_type) ? (cast*) value.as.object : NULL 195 | 196 | #define ASSERT_VALUE_AS_OBJECT(variable, value, object_type, cast, error) \ 197 | do { \ 198 | variable = VALUE_AS_OBJECT((value), object_type, cast); \ 199 | if (variable == NULL) { \ 200 | FAIL(error); \ 201 | } \ 202 | } while (false); 203 | 204 | #define ASSERT_VALUE_IS_OBJECT(value, object_type, error_message) \ 205 | do { \ 206 | if (!object_value_is(value, object_type)) { \ 207 | FAIL(error_message); \ 208 | } \ 209 | } while (false); \ 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /src/ast.h: -------------------------------------------------------------------------------- 1 | #ifndef ribbon_ast_h 2 | #define ribbon_ast_h 3 | 4 | #include "scanner.h" 5 | #include "value.h" 6 | #include "pointerarray.h" 7 | 8 | typedef enum { 9 | AST_NODE_CONSTANT, 10 | AST_NODE_BINARY, 11 | AST_NODE_IN_PLACE_ATTRIBUTE_BINARY, 12 | AST_NODE_IN_PLACE_KEY_BINARY, 13 | AST_NODE_UNARY, 14 | AST_NODE_VARIABLE, 15 | AST_NODE_EXTERNAL, 16 | AST_NODE_ASSIGNMENT, 17 | AST_NODE_STATEMENTS, 18 | AST_NODE_FUNCTION, 19 | AST_NODE_CALL, 20 | AST_NODE_EXPR_STATEMENT, 21 | AST_NODE_RETURN, 22 | AST_NODE_IF, 23 | AST_NODE_WHILE, 24 | AST_NODE_FOR, 25 | AST_NODE_AND, 26 | AST_NODE_OR, 27 | AST_NODE_ATTRIBUTE, 28 | AST_NODE_ATTRIBUTE_ASSIGNMENT, 29 | AST_NODE_STRING, 30 | AST_NODE_KEY_ACCESS, 31 | AST_NODE_KEY_ASSIGNMENT, 32 | AST_NODE_TABLE, 33 | AST_NODE_IMPORT, 34 | AST_NODE_CLASS, 35 | AST_NODE_NIL 36 | } AstNodeType; 37 | 38 | extern const char* AST_NODE_TYPE_NAMES[]; 39 | 40 | typedef struct { 41 | AstNodeType type; 42 | } AstNode; 43 | 44 | typedef struct { 45 | AstNode base; 46 | Value value; 47 | } AstNodeConstant; 48 | 49 | typedef struct { 50 | AstNode base; 51 | const char* name; 52 | int length; 53 | } AstNodeVariable; 54 | 55 | typedef struct { 56 | AstNode base; 57 | const char* name; 58 | int length; 59 | } AstNodeExternal; 60 | 61 | typedef struct { 62 | AstNode base; 63 | AstNode* object; 64 | const char* name; 65 | int length; 66 | } AstNodeAttribute; 67 | 68 | typedef struct { 69 | AstNode base; 70 | AstNode* object; 71 | const char* name; 72 | int length; 73 | AstNode* value; 74 | } AstNodeAttributeAssignment; 75 | 76 | typedef struct { 77 | AstNode base; 78 | AstNode* operand; 79 | } AstNodeUnary; 80 | 81 | typedef struct { 82 | AstNode base; 83 | } AstNodeNil; 84 | 85 | typedef struct { 86 | AstNode base; 87 | ScannerTokenType operator; 88 | AstNode* left_operand; 89 | AstNode* right_operand; 90 | } AstNodeBinary; 91 | 92 | typedef struct { 93 | AstNode base; 94 | ScannerTokenType operator; 95 | AstNode* subject; 96 | const char* attribute; 97 | int attribute_length; 98 | AstNode* value; 99 | } AstNodeInPlaceAttributeBinary; 100 | 101 | 102 | typedef struct { 103 | AstNode base; 104 | ScannerTokenType operator; 105 | AstNode* subject; 106 | AstNode* key; 107 | AstNode* value; 108 | } AstNodeInPlaceKeyBinary; 109 | 110 | typedef struct { 111 | AstNode base; 112 | const char* name; 113 | int length; 114 | AstNode* value; 115 | } AstNodeAssignment; 116 | 117 | typedef struct { 118 | AstNode base; 119 | PointerArray statements; 120 | } AstNodeStatements; 121 | 122 | typedef struct { 123 | AstNode base; 124 | AstNodeStatements* statements; 125 | ValueArray parameters; 126 | } AstNodeFunction; 127 | 128 | typedef struct { 129 | AstNode base; 130 | AstNodeStatements* body; 131 | AstNode* superclass; 132 | } AstNodeClass; 133 | 134 | typedef struct { 135 | AstNode base; 136 | AstNode* target; 137 | PointerArray arguments; 138 | } AstNodeCall; 139 | 140 | typedef struct { 141 | AstNode base; 142 | AstNode* expression; 143 | } AstNodeExprStatement; 144 | 145 | typedef struct { 146 | AstNode base; 147 | AstNode* expression; 148 | } AstNodeReturn; 149 | 150 | typedef struct { 151 | AstNode base; 152 | AstNode* condition; 153 | AstNodeStatements* body; 154 | PointerArray elsif_clauses; 155 | AstNodeStatements* else_body; 156 | } AstNodeIf; 157 | 158 | typedef struct { 159 | AstNode base; 160 | AstNode* condition; 161 | AstNodeStatements* body; 162 | } AstNodeWhile; 163 | 164 | typedef struct { 165 | AstNode base; 166 | const char* variable_name; 167 | int variable_length; 168 | AstNode* container; 169 | AstNodeStatements* body; 170 | } AstNodeFor; 171 | 172 | typedef struct { 173 | AstNode base; 174 | AstNode* left; 175 | AstNode* right; 176 | } AstNodeAnd; 177 | 178 | typedef struct { 179 | AstNode base; 180 | AstNode* left; 181 | AstNode* right; 182 | } AstNodeOr; 183 | 184 | typedef struct AstNodeString { 185 | AstNode base; 186 | CharacterArray string; 187 | // char* string; 188 | // int length; 189 | } AstNodeString; 190 | 191 | typedef struct { 192 | AstNode base; 193 | AstNode* key; 194 | AstNode* subject; 195 | } AstNodeKeyAccess; 196 | 197 | typedef struct { 198 | AstNode base; 199 | AstNode* subject; 200 | AstNode* key; 201 | AstNode* value; 202 | } AstNodeKeyAssignment; 203 | 204 | typedef struct { 205 | AstNode base; 206 | const char* name; // Points to source code - do not free! 207 | int name_length; 208 | } AstNodeImport; 209 | 210 | typedef struct { 211 | AstNode* key; 212 | AstNode* value; 213 | } AstNodesKeyValuePair; 214 | 215 | DECLARE_DYNAMIC_ARRAY(AstNodesKeyValuePair, AstKeyValuePairArray, ast_key_value_pair_array) 216 | 217 | typedef struct { 218 | AstNode base; 219 | AstKeyValuePairArray pairs; 220 | } AstNodeTable; 221 | 222 | void ast_print_tree(AstNode* tree); 223 | void ast_free_tree(AstNode* node); 224 | 225 | AstNodeConstant* ast_new_node_constant(Value value); 226 | AstNodeNil* ast_new_node_nil(void); 227 | AstNodeBinary* ast_new_node_binary(ScannerTokenType operator, AstNode* left_operand, AstNode* right_operand); 228 | AstNodeInPlaceAttributeBinary* ast_new_node_in_place_attribute_binary( 229 | ScannerTokenType operator, AstNode* subject, const char* attribute, int attribute_length, AstNode* value); 230 | AstNodeInPlaceKeyBinary* ast_new_node_in_place_key_binary(ScannerTokenType operator, AstNode* subject, AstNode* key, AstNode* value); 231 | AstNodeStatements* ast_new_node_statements(void); 232 | AstNodeVariable* ast_new_node_variable(const char* name, int length); 233 | AstNodeExternal* ast_new_node_external(const char* name, int length); 234 | AstNodeAssignment* ast_new_node_assignment(const char* name, int name_length, AstNode* value); 235 | AstNodeConstant* ast_new_node_number(double number); 236 | AstNodeExprStatement* ast_new_node_expr_statement(AstNode* expression); 237 | AstNodeReturn* ast_new_node_return(AstNode* expression); 238 | AstNodeCall* ast_new_node_call(AstNode* expression, PointerArray arguments); 239 | AstNodeFunction* ast_new_node_function(AstNodeStatements* statements, ValueArray parameters); 240 | AstNodeIf* ast_new_node_if(AstNode* condition, AstNodeStatements* body, PointerArray elsif_clauses, AstNodeStatements* else_body); 241 | AstNodeFor* ast_new_node_for(const char* variable_name, int variable_length, AstNode* container, AstNodeStatements* body); 242 | AstNodeWhile* ast_new_node_while(AstNode* condition, AstNodeStatements* body); 243 | AstNodeAnd* ast_new_node_and(AstNode* left, AstNode* right); 244 | AstNodeOr* ast_new_node_or(AstNode* left, AstNode* right); 245 | AstNodeAttribute* ast_new_node_attribute(AstNode* object, const char* name, int length); 246 | AstNodeAttributeAssignment* ast_new_node_attribute_assignment(AstNode* object, const char* name, int length, AstNode* value); 247 | AstNodeString* ast_new_node_string(CharacterArray string); 248 | AstNodeKeyAccess* ast_new_node_key_access(AstNode* key, AstNode* subject); 249 | AstNodeKeyAssignment* ast_new_node_key_assignment(AstNode* key, AstNode* value, AstNode* subject); 250 | AstNodeUnary* ast_new_node_unary(AstNode* expression); 251 | AstNodeTable* ast_new_node_table(AstKeyValuePairArray pairs); 252 | AstNodeImport* ast_new_node_import(const char* name, int name_length); 253 | AstNodeClass* ast_new_node_class(AstNodeStatements* body, AstNode* superclass); 254 | 255 | AstNodesKeyValuePair ast_new_key_value_pair(AstNode* key, AstNode* value); 256 | 257 | #define ALLOCATE_AST_NODE(type, tag) (type*) ast_allocate_node(tag, sizeof(type)) 258 | 259 | AstNode* ast_allocate_node(AstNodeType type, size_t size); 260 | 261 | #endif 262 | -------------------------------------------------------------------------------- /src/disassembler.c: -------------------------------------------------------------------------------- 1 | #include "disassembler.h" 2 | 3 | #include 4 | #include "ribbon_object.h" 5 | #include "value.h" 6 | #include "ribbon_utils.h" 7 | 8 | // TODO: Add tests maybe? 9 | 10 | static int single_operand_instruction(const char* name, Bytecode* chunk, int offset) { 11 | int operand = chunk->code[offset + 1]; 12 | 13 | printf("%p %-28s %d\n", chunk->code + offset, name, operand); 14 | return offset + 2; 15 | } 16 | 17 | static int short_operand_instruction(const char* name, Bytecode* chunk, int offset) { 18 | uint16_t operand = two_bytes_to_short(chunk->code[offset + 1], chunk->code[offset + 2]); 19 | 20 | printf("%p %-28s %d\n", chunk->code + offset, name, operand); 21 | return offset + 3; 22 | } 23 | 24 | static Value read_constant_operand(Bytecode* chunk, int offset) { 25 | uint8_t constant_index_byte_1 = chunk->code[offset]; 26 | uint8_t constant_index_byte_2 = chunk->code[offset + 1]; 27 | uint16_t index = two_bytes_to_short(constant_index_byte_1, constant_index_byte_2); 28 | return chunk->constants.values[index]; 29 | } 30 | 31 | static int constant_instruction(const char* name, Bytecode* chunk, int offset) { 32 | Value constant = read_constant_operand(chunk, offset + 1); 33 | 34 | // int constantIndex = chunk->code[offset + 1]; 35 | // Value constant = chunk->constants.values[constantIndex]; 36 | 37 | printf("%p %-28s ", chunk->code + offset, name); 38 | value_print(constant); 39 | printf("\n"); 40 | 41 | return offset + 3; 42 | } 43 | 44 | static int constant_and_variable_length_constants_instruction(const char* name, Bytecode* chunk, int offset) { 45 | // int constantIndex = chunk->code[offset + 1]; 46 | // Value constant = chunk->constants.values[constantIndex]; 47 | 48 | Value constant = read_constant_operand(chunk, offset + 1); 49 | 50 | printf("%p %-28s ", chunk->code + offset, name); 51 | value_print(constant); 52 | 53 | uint16_t additional_operands_count = two_bytes_to_short(chunk->code[offset + 3], chunk->code[offset + 4]); 54 | 55 | for (int i = 0; i < additional_operands_count * 2; i += 2) { 56 | int additional_operand_offset = offset + 5 + i; 57 | Value operand = read_constant_operand(chunk, additional_operand_offset); 58 | // uint8_t additional_operand_index = chunk->code[additional_operand_offset]; 59 | // Value operand = chunk->constants.values[additional_operand_index]; 60 | printf(", "); 61 | value_print(operand); 62 | } 63 | 64 | printf("\n"); 65 | 66 | return offset + 5 + (additional_operands_count * 2); 67 | } 68 | 69 | static int simple_instruction(const char* name, Bytecode* chunk, int offset) { 70 | printf("%p %s\n", chunk->code + offset, name); 71 | return offset + 1; 72 | } 73 | 74 | int disassembler_do_single_instruction(OP_CODE opcode, Bytecode* chunk, int offset) { 75 | printf("%-3d ", offset); 76 | 77 | switch (opcode) { 78 | case OP_CONSTANT: { 79 | return constant_instruction("OP_CONSTANT", chunk, offset); 80 | } 81 | case OP_ADD: { 82 | return simple_instruction("OP_ADD", chunk, offset); 83 | } 84 | case OP_SUBTRACT: { 85 | return simple_instruction("OP_SUBTRACT", chunk, offset); 86 | } 87 | case OP_DIVIDE: { 88 | return simple_instruction("OP_DIVIDE", chunk, offset); 89 | } 90 | case OP_MODULO: { 91 | return simple_instruction("OP_MODULO", chunk, offset); 92 | } 93 | case OP_MULTIPLY: { 94 | return simple_instruction("OP_MULTIPLY", chunk, offset); 95 | } 96 | case OP_LESS_THAN: { 97 | return simple_instruction("OP_LESS_THAN", chunk, offset); 98 | } 99 | case OP_GREATER_THAN: { 100 | return simple_instruction("OP_GREATER_THAN", chunk, offset); 101 | } 102 | case OP_LESS_EQUAL: { 103 | return simple_instruction("OP_LESS_EQUAL", chunk, offset); 104 | } 105 | case OP_GREATER_EQUAL: { 106 | return simple_instruction("OP_GREATER_EQUAL", chunk, offset); 107 | } 108 | case OP_EQUAL: { 109 | return simple_instruction("OP_EQUAL", chunk, offset); 110 | } 111 | case OP_NEGATE: { 112 | return simple_instruction("OP_NEGATE", chunk, offset); 113 | } 114 | case OP_LOAD_VARIABLE: { 115 | return constant_instruction("OP_LOAD_VARIABLE", chunk, offset); 116 | } 117 | case OP_DECLARE_EXTERNAL: { 118 | return constant_instruction("OP_DECLARE_EXTERNAL", chunk, offset); 119 | } 120 | case OP_SET_VARIABLE: { 121 | return constant_instruction("OP_SET_VARIABLE", chunk, offset); 122 | } 123 | case OP_CALL: { 124 | return single_operand_instruction("OP_CALL", chunk, offset); 125 | } 126 | case OP_ACCESS_KEY: { 127 | return simple_instruction("OP_ACCESS_KEY", chunk, offset); 128 | } 129 | case OP_SET_KEY: { 130 | return simple_instruction("OP_SET_KEY", chunk, offset); 131 | } 132 | case OP_IMPORT: { 133 | return constant_instruction("OP_IMPORT", chunk, offset); 134 | } 135 | case OP_GET_ATTRIBUTE: { 136 | return constant_instruction("OP_GET_ATTRIBUTE", chunk, offset); 137 | } 138 | case OP_SET_ATTRIBUTE: { 139 | return constant_instruction("OP_SET_ATTRIBUTE", chunk, offset); 140 | } 141 | case OP_POP: { 142 | return simple_instruction("OP_POP", chunk, offset); 143 | } 144 | case OP_DUP: { 145 | return simple_instruction("OP_DUP", chunk, offset); 146 | } 147 | case OP_DUP_TWO: { 148 | return simple_instruction("OP_DUP_TWO", chunk, offset); 149 | } 150 | case OP_SWAP: { 151 | return simple_instruction("OP_SWAP", chunk, offset); 152 | } 153 | case OP_SWAP_TOP_WITH_NEXT_TWO: { 154 | return simple_instruction("OP_SWAP_TOP_WITH_NEXT_TWO", chunk, offset); 155 | } 156 | case OP_JUMP_IF_FALSE: { 157 | return short_operand_instruction("OP_JUMP_IF_FALSE", chunk, offset); 158 | } 159 | case OP_JUMP_IF_TRUE: { 160 | return short_operand_instruction("OP_JUMP_IF_TRUE", chunk, offset); 161 | } 162 | case OP_GET_OFFSET_FROM_TOP: { 163 | return short_operand_instruction("OP_GET_OFFSET_FROM_TOP", chunk, offset); 164 | } 165 | case OP_SET_OFFSET_FROM_TOP: { 166 | return short_operand_instruction("OP_SET_OFFSET_FROM_TOP", chunk, offset); 167 | } 168 | case OP_JUMP_FORWARD: { 169 | return short_operand_instruction("OP_JUMP_FORWARD", chunk, offset); 170 | } 171 | case OP_JUMP_BACKWARD: { 172 | return short_operand_instruction("OP_JUMP_BACKWARD", chunk, offset); 173 | } 174 | case OP_MAKE_STRING: { 175 | return constant_instruction("OP_MAKE_STRING", chunk, offset); 176 | } 177 | case OP_MAKE_TABLE: { 178 | return single_operand_instruction("OP_MAKE_TABLE", chunk, offset); 179 | } 180 | case OP_MAKE_FUNCTION: { 181 | return constant_and_variable_length_constants_instruction("OP_MAKE_FUNCTION", chunk, offset); 182 | } 183 | case OP_MAKE_CLASS: { 184 | return constant_instruction("OP_MAKE_CLASS", chunk, offset); 185 | } 186 | case OP_NIL: { 187 | return simple_instruction("OP_NIL", chunk, offset); 188 | } 189 | case OP_RETURN: { 190 | return simple_instruction("OP_RETURN", chunk, offset); 191 | } 192 | } 193 | 194 | FAIL("Unknown opcode when disassembling: %d", opcode); 195 | return -1; 196 | } 197 | 198 | void disassembler_do_bytecode(Bytecode* chunk) { 199 | int offset = 0; 200 | for (; offset < chunk->count; ) { 201 | offset = disassembler_do_single_instruction(chunk->code[offset], chunk, offset); 202 | } 203 | 204 | for (int i = 0; i < chunk->constants.count; i++) { 205 | Value constant = chunk->constants.values[i]; 206 | if (constant.type == VALUE_OBJECT && constant.as.object->type == OBJECT_CODE) { 207 | printf("\nInner chunk [index %d]:\n", i); 208 | ObjectCode* inner_code_object = (ObjectCode*) constant.as.object; 209 | Bytecode inner_chunk = inner_code_object->bytecode; 210 | disassembler_do_bytecode(&inner_chunk); 211 | } 212 | } 213 | 214 | bytecode_print_constant_table(chunk); 215 | } 216 | -------------------------------------------------------------------------------- /src/scanner.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "scanner.h" 5 | #include "common.h" 6 | 7 | typedef struct { 8 | const char* start; 9 | const char* current; 10 | int line; 11 | } Scanner; 12 | 13 | static Scanner scanner; 14 | 15 | static char current() { 16 | return *scanner.current; 17 | } 18 | 19 | static char peek() { 20 | return *(scanner.current + 1); 21 | } 22 | 23 | static char advance() { 24 | char current_char = current(); 25 | scanner.current++; 26 | if (current_char == '\n') { 27 | scanner.line++; 28 | } 29 | return current_char; 30 | } 31 | 32 | static bool match(char c) { 33 | if (current() == c) { 34 | advance(); 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | static bool is_end_of_code(char c) { 42 | return c == '\0'; 43 | } 44 | 45 | static bool is_alpha(char c) { 46 | return isalpha(c) || c == '_' || c == '@'; 47 | } 48 | 49 | static bool is_digit(char c) { 50 | return isdigit(c); 51 | } 52 | 53 | static Token make_token(ScannerTokenType type) { 54 | Token token; 55 | token.type = type; 56 | token.start = scanner.start; 57 | token.length = scanner.current - scanner.start; 58 | token.lineNumber = scanner.line; 59 | 60 | DEBUG_SCANNER_PRINT("Token %d", type); 61 | return token; 62 | } 63 | 64 | static Token error_token(const char* message) { 65 | Token token; 66 | token.type = TOKEN_ERROR; 67 | token.start = message; 68 | token.length = strlen(message); // assuming message is always a null-terminated string-literal 69 | token.lineNumber = scanner.line; 70 | 71 | DEBUG_SCANNER_PRINT("Error token '%s'", message); 72 | 73 | return token; 74 | } 75 | 76 | static bool check_token(const char* text) { 77 | int token_length = scanner.current - scanner.start; 78 | return (strlen(text) == token_length) && (strncmp(scanner.start, text, token_length) == 0); 79 | } 80 | 81 | static Token parse_identifier() { 82 | while (is_alpha(current()) || is_digit(current())) { 83 | advance(); 84 | } 85 | 86 | if (check_token("if")) { 87 | return make_token(TOKEN_IF); 88 | } else if (check_token("else")) { 89 | return make_token(TOKEN_ELSE); 90 | } else if (check_token("elsif")) { 91 | return make_token(TOKEN_ELSIF); 92 | } else if (check_token("while")) { 93 | return make_token(TOKEN_WHILE); 94 | } else if (check_token("for")) { 95 | return make_token(TOKEN_FOR); 96 | } else if (check_token("and")) { 97 | return make_token(TOKEN_AND); 98 | } else if (check_token("or")) { 99 | return make_token(TOKEN_OR); 100 | } else if (check_token("return")) { 101 | return make_token(TOKEN_RETURN); 102 | } else if (check_token("true")) { 103 | return make_token(TOKEN_TRUE); 104 | } else if (check_token("false")) { 105 | return make_token(TOKEN_FALSE); 106 | } else if (check_token("not")) { 107 | return make_token(TOKEN_NOT); 108 | } else if (check_token("import")) { 109 | return make_token(TOKEN_IMPORT); 110 | } else if (check_token("class")) { 111 | return make_token(TOKEN_CLASS); 112 | } else if (check_token("nil")) { 113 | return make_token(TOKEN_NIL); 114 | } else if (check_token("in")) { 115 | return make_token(TOKEN_IN); 116 | } else if (check_token("external")) { 117 | return make_token(TOKEN_EXTERNAL); 118 | } 119 | 120 | return make_token(TOKEN_IDENTIFIER); 121 | } 122 | 123 | static Token parse_number() { 124 | while (is_digit(current())) { 125 | advance(); 126 | } 127 | 128 | if (current() == '.' && is_digit(peek())) { 129 | advance(); 130 | while(is_digit(current())) { 131 | advance(); 132 | } 133 | } 134 | 135 | return make_token(TOKEN_NUMBER); 136 | } 137 | 138 | static Token parse_string() { 139 | while (current() != '"' && !is_end_of_code(current()) && current() != '\n') { 140 | advance(); 141 | } 142 | 143 | if (is_end_of_code(current())) { 144 | return error_token("Unterminated string."); 145 | } 146 | 147 | if (current() == '\n') { 148 | return error_token("Newline in string not allowed."); 149 | } 150 | 151 | advance(); // Skip ending '"' 152 | 153 | return make_token(TOKEN_STRING); 154 | } 155 | 156 | static void skip_whitespace() { 157 | // Newline is significant 158 | while (current() == ' ' || current() == '\t' || current() == '\v' || current() == '\r') { 159 | advance(); 160 | } 161 | } 162 | 163 | void scanner_init(const char* source) { 164 | scanner.start = source; 165 | scanner.current = source; 166 | scanner.line = 1; // 1-based indexing for humans 167 | } 168 | 169 | Token scanner_peek_next_token() { 170 | return scanner_peek_token_at_offset(1); 171 | } 172 | 173 | Token scanner_peek_token_at_offset(int offset) { 174 | assert(offset >= 1); 175 | 176 | const char* old_start = scanner.start; 177 | const char* old_current = scanner.current; 178 | int old_line_number = scanner.line; 179 | 180 | Token token; 181 | for (int i = 0; i < offset; i++) { 182 | token = scanner_next_token(); 183 | } 184 | 185 | scanner.start = old_start; 186 | scanner.current = old_current; 187 | scanner.line = old_line_number; 188 | return token; 189 | } 190 | 191 | Token scanner_next_token() { 192 | skip_whitespace(); 193 | while (current() == '#') { 194 | do { 195 | advance(); 196 | } while (current() != '\n' && !is_end_of_code(current())); 197 | } 198 | 199 | scanner.start = scanner.current; 200 | 201 | if (is_end_of_code(current())) { 202 | return make_token(TOKEN_EOF); 203 | } 204 | 205 | char c = advance(); 206 | 207 | if (is_alpha(c)) { 208 | return parse_identifier(); 209 | } 210 | 211 | if (is_digit(c)) { 212 | return parse_number(); 213 | } 214 | 215 | if (c == '"') { 216 | return parse_string(); 217 | } 218 | 219 | switch (c) { 220 | case '+': { 221 | if (match('=')) { 222 | return make_token(TOKEN_PLUS_EQUALS); 223 | } 224 | return make_token(TOKEN_PLUS); 225 | } 226 | case '-': { 227 | if (match('=')) { 228 | return make_token(TOKEN_MINUS_EQUALS); 229 | } 230 | return make_token(TOKEN_MINUS); 231 | } 232 | case '*': { 233 | if (match('=')) { 234 | return make_token(TOKEN_STAR_EQUALS); 235 | } 236 | return make_token(TOKEN_STAR); 237 | } 238 | case '/': { 239 | if (match('=')) { 240 | return make_token(TOKEN_SLASH_EQUALS); 241 | } 242 | return make_token(TOKEN_SLASH); 243 | } 244 | case '%': { 245 | if (match('=')) { 246 | return make_token(TOKEN_MODULO_EQUALS); 247 | } 248 | return make_token(TOKEN_MODULO); 249 | } 250 | case '=': return (match('=') ? make_token(TOKEN_EQUAL_EQUAL) : make_token(TOKEN_EQUAL)); 251 | case '<': return (match('=') ? make_token(TOKEN_LESS_EQUAL) : make_token(TOKEN_LESS_THAN)); 252 | case '>': return (match('=') ? make_token(TOKEN_GREATER_EQUAL) : make_token(TOKEN_GREATER_THAN)); 253 | case '(': return make_token(TOKEN_LEFT_PAREN); 254 | case ')': return make_token(TOKEN_RIGHT_PAREN); 255 | case '{': return make_token(TOKEN_LEFT_BRACE); 256 | case '}': return make_token(TOKEN_RIGHT_BRACE); 257 | case ',': return make_token(TOKEN_COMMA); 258 | case '.': return make_token(TOKEN_DOT); 259 | case '[': return make_token(TOKEN_LEFT_SQUARE_BRACE); 260 | case ']': return make_token(TOKEN_RIGHT_SQUARE_BRACE); 261 | case '\n': return make_token(TOKEN_NEWLINE); 262 | case ':': return make_token(TOKEN_COLON); 263 | case '|': return make_token(TOKEN_PIPE); 264 | case '!': { 265 | if (match('=')) { 266 | return make_token(TOKEN_BANG_EQUAL); 267 | } 268 | break; 269 | } 270 | } 271 | 272 | // TODO: More specific error reporting 273 | 274 | return error_token("Unknown character."); 275 | } 276 | -------------------------------------------------------------------------------- /src/ribbon_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ribbon_utils.h" 5 | #include "memory.h" 6 | #include "value.h" 7 | #include "ribbon_object.h" 8 | 9 | uint16_t two_bytes_to_short(uint8_t a, uint8_t b) { 10 | return (a << 8) + b; 11 | } 12 | 13 | void short_to_two_bytes(uint16_t num, uint8_t* bytes_out) { 14 | bytes_out[0] = (num >> 8) & 0xFF; 15 | bytes_out[1] = num & 0xFF; 16 | } 17 | 18 | char* copy_cstring(const char* string, int length, const char* what) { 19 | // argument length should not include the null-terminator 20 | DEBUG_OBJECTS_PRINT("Allocating string buffer '%.*s' of length %d, tag: %s.", length, string, length, what); 21 | 22 | char* chars = allocate(sizeof(char) * length + 1, what); 23 | memcpy(chars, string, length); 24 | chars[length] = '\0'; 25 | 26 | return chars; 27 | } 28 | 29 | char* copy_null_terminated_cstring(const char* string, const char* what) { 30 | return copy_cstring(string, strlen(string), what); 31 | } 32 | 33 | unsigned long hash_string(const char* string) { 34 | unsigned long hash = 5381; 35 | int c; 36 | 37 | while ((c = *string++)) { 38 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 39 | } 40 | 41 | return hash; 42 | } 43 | 44 | unsigned long hash_string_bounded(const char* string, int length) { 45 | /* TODO: This wasn't tested throughly after converting it from the regular hash_string. 46 | General tests of course pass, but possibly this doesn't really hash correctly etc. 47 | Possibly look into this in the future. */ 48 | 49 | unsigned long hash = 5381; 50 | int c; 51 | 52 | for (const char* p = string; p - string < length; p++) { 53 | c = *p; 54 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 55 | } 56 | 57 | return hash; 58 | } 59 | 60 | unsigned int hash_int(unsigned int x) { 61 | x = ((x >> 16) ^ x) * 0x45d9f3b; 62 | x = ((x >> 16) ^ x) * 0x45d9f3b; 63 | x = (x >> 16) ^ x; 64 | return x; 65 | } 66 | 67 | char* concat_cstrings(const char* str1, int str1_length, const char* str2, int str2_length, const char* alloc_string) { 68 | size_t file_name_buffer_size = str1_length + str2_length + 1; 69 | char* result = allocate(file_name_buffer_size, alloc_string); 70 | memcpy(result, str1, str1_length); 71 | memcpy(result + str1_length, str2, str2_length); 72 | result[str1_length + str2_length] = '\0'; 73 | 74 | return result; 75 | } 76 | 77 | char* concat_null_terminated_cstrings(const char* str1, const char* str2, const char* alloc_string) { 78 | return concat_cstrings(str1, strlen(str1), str2, strlen(str2), alloc_string); 79 | } 80 | 81 | char* concat_multi_null_terminated_cstrings(int count, char** strings, const char* alloc_string) { 82 | char* result = allocate(strlen(strings[0]) + 1, alloc_string); 83 | result[strlen(strings[0])] == '\0'; 84 | strcpy(result, strings[0]); 85 | 86 | for (int i = 1; i < count; i++) { 87 | char* string = strings[i]; 88 | char* old_result = result; 89 | result = concat_null_terminated_cstrings(result, string, alloc_string); 90 | deallocate(old_result, strlen(old_result) + 1, alloc_string); 91 | } 92 | 93 | return result; 94 | } 95 | 96 | char* concat_multi_cstrings(int count, char** strings, int lengths[], char* alloc_string) { 97 | char* result = allocate(lengths[0] + 1, alloc_string); 98 | memcpy(result, strings[0], lengths[0]); 99 | result[lengths[0]] == '\0'; 100 | 101 | int length_sum = lengths[0]; 102 | 103 | for (int i = 1; i < count; i++) { 104 | char* string = strings[i]; 105 | int length = lengths[i]; 106 | 107 | char* old_result = result; 108 | result = concat_cstrings(result, length_sum, string, length, alloc_string); 109 | deallocate(old_result, length_sum + 1, alloc_string); 110 | 111 | length_sum += length; 112 | } 113 | 114 | return result; 115 | } 116 | 117 | bool cstrings_equal(const char* s1, int length1, const char* s2, int length2) { 118 | return length1 == length2 && (strncmp(s1, s2, length1) == 0); 119 | } 120 | 121 | char* find_interpreter_directory(void) { 122 | char* dir_path = NULL; 123 | 124 | /* TODO: Use Windows MAX_PATH instead */ 125 | DWORD MAX_LENGTH = 500; 126 | char* exec_path = allocate(MAX_LENGTH, "interpreter executable path"); 127 | 128 | DWORD get_module_name_result = GetModuleFileNameA(NULL, exec_path, MAX_LENGTH); 129 | if (get_module_name_result == 0 || get_module_name_result == MAX_LENGTH) { 130 | goto cleanup; 131 | } 132 | 133 | char* last_slash; 134 | if ((last_slash = strrchr(exec_path, '\\')) == NULL) { 135 | goto cleanup; 136 | } 137 | 138 | int directory_length = last_slash - exec_path; 139 | dir_path = allocate(directory_length + 1, "interpreter directory path"); 140 | memcpy(dir_path, exec_path, directory_length); 141 | dir_path[directory_length] = '\0'; 142 | 143 | cleanup: 144 | deallocate(exec_path, MAX_LENGTH, "interpreter executable path"); 145 | 146 | return dir_path; 147 | } 148 | 149 | char* directory_from_path(char* path) { 150 | char* last_slash = NULL; 151 | 152 | char* last_back_slash = strrchr(path, '\\'); 153 | char* last_forward_slash = strrchr(path, '/'); 154 | 155 | if (last_back_slash == NULL && last_forward_slash != NULL) { 156 | last_slash = last_forward_slash; 157 | } else if (last_forward_slash == NULL && last_back_slash != NULL) { 158 | last_slash = last_back_slash; 159 | } else if (last_forward_slash == NULL && last_back_slash == NULL) { 160 | return NULL; 161 | } else { 162 | if (last_back_slash - path > last_forward_slash - path) { 163 | last_slash = last_back_slash; 164 | } else { 165 | last_slash = last_forward_slash; 166 | } 167 | } 168 | 169 | int directory_length = last_slash - path; 170 | char* dir_path = allocate(directory_length + 1, "directory path"); 171 | 172 | if (dir_path == NULL) { 173 | return NULL; 174 | } 175 | 176 | memcpy(dir_path, path, directory_length); 177 | dir_path[directory_length] = '\0'; 178 | 179 | return dir_path; 180 | } 181 | 182 | char* get_current_working_directory(void) { 183 | LPTSTR dir = allocate(MAX_PATH, "working directory path"); 184 | DWORD result = GetCurrentDirectory(MAX_PATH, dir); 185 | if (result == 0 || result >= MAX_PATH - 1) { 186 | return NULL; 187 | } 188 | return reallocate(dir, MAX_PATH, strlen(dir) + 1, "working directory path"); 189 | } 190 | 191 | char* concat_null_terminated_paths(char* p1, char* p2, char* alloc_string) { 192 | return concat_multi_null_terminated_cstrings(3, (char*[]) {p1, "\\", p2}, alloc_string); 193 | } 194 | 195 | static bool argument_matches(Value value, const char** c) { 196 | bool matching = true; 197 | 198 | int type_length = strcspn(*c, " |"); 199 | 200 | if (**c == 'n') { 201 | if (value.type != VALUE_NUMBER) { 202 | matching = false; 203 | } 204 | } else if (**c == 'b') { 205 | if (value.type != VALUE_BOOLEAN) { 206 | matching = false; 207 | } 208 | } else if (**c == 'i') { 209 | if (value.type != VALUE_NIL) { 210 | matching = false; 211 | } 212 | } else if (**c == 'o') { 213 | if (value.type == VALUE_OBJECT) { 214 | Object* object = value.as.object; 215 | 216 | if (cstrings_equal((*c) + 1, type_length - 1, "String", strlen("String"))) { 217 | if (object->type != OBJECT_STRING) { 218 | matching = false; 219 | } 220 | } else if (cstrings_equal((*c) + 1, type_length - 1, "Table", strlen("Table"))) { 221 | if (object->type != OBJECT_TABLE) { 222 | matching = false; 223 | } 224 | } else if (cstrings_equal((*c) + 1, type_length - 1, "Function", strlen("Function"))) { 225 | if (object->type != OBJECT_FUNCTION) { 226 | matching = false; 227 | } 228 | } else if (cstrings_equal((*c) + 1, type_length - 1, "Module", strlen("Module"))) { 229 | if (object->type != OBJECT_MODULE) { 230 | matching = false; 231 | } 232 | } else if (cstrings_equal((*c) + 1, type_length - 1, "Class", strlen("Class"))) { 233 | if (object->type != OBJECT_CLASS) { 234 | matching = false; 235 | } 236 | } else { 237 | char* class_name = copy_cstring((*c) + 1, type_length - 1, "Validator class name string"); 238 | if (!is_instance_of_class(object, class_name)) { 239 | matching = false; 240 | } 241 | deallocate(class_name, strlen(class_name) + 1, "Validator class name string"); 242 | } 243 | } else { 244 | matching = false; 245 | } 246 | } else { 247 | return false; /* Illegal format string, might as well fail the caller */ 248 | } 249 | 250 | *c += type_length; 251 | 252 | if (matching && **c == '|') { 253 | while (**c != ' ' && **c != '\0') { 254 | (*c)++; 255 | } 256 | } 257 | 258 | return matching; 259 | } 260 | 261 | static bool match_character(const char** c, char expected) { 262 | if (**c == expected) { 263 | (*c)++; 264 | return true; 265 | } 266 | return false; 267 | } 268 | 269 | bool arguments_valid(ValueArray args, const char* string) { 270 | const char* c = string; 271 | 272 | int i = 0; 273 | while (*c != '\0') { 274 | while (*c == ' ') { 275 | c++; 276 | } 277 | 278 | Value value = args.values[i]; 279 | 280 | bool matching; 281 | do { 282 | matching = argument_matches(value, &c); 283 | } while (!matching && match_character(&c, '|')); 284 | 285 | if (!matching) { 286 | return false; 287 | } 288 | 289 | i++; 290 | } 291 | 292 | if (i < args.count) { 293 | return false; 294 | } 295 | 296 | return true; 297 | } 298 | 299 | IMPLEMENT_DYNAMIC_ARRAY(size_t, IntegerArray, integer_array) 300 | 301 | IMPLEMENT_DYNAMIC_ARRAY(char, CharacterArray, character_array) 302 | -------------------------------------------------------------------------------- /src/python/tester/runtests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess 4 | import uuid 5 | import itertools 6 | 7 | 8 | TEST_FILE_SUFFIX = '.test' 9 | DEFAULT_TESTS_DIR = 'tests' 10 | INTERPRETER_NAME = 'ribbon.exe' 11 | INTERPRETER_ARGS = '' 12 | 13 | 14 | def _run_on_interpreter(interpreter_path, input_text, additional_files): 15 | input_file_name = _relative_path_to_abs(os.path.join('..', '..', f'{str(uuid.uuid4())}.rib')) 16 | with open(input_file_name, 'w') as f: 17 | f.write(input_text) 18 | 19 | try: 20 | for file_name, file_text in additional_files.items(): 21 | # file_path = file_name # relative to working directory 22 | file_path = _relative_path_to_abs(os.path.join('..', '..', file_name)) 23 | with open(file_path, 'w') as f: 24 | f.write(file_text) 25 | 26 | interpreter_cmd = f'{interpreter_path} {input_file_name} {INTERPRETER_ARGS}' 27 | # print(interpreter_cmd) 28 | output = subprocess.run(interpreter_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 29 | finally: 30 | os.remove(input_file_name) 31 | for additional_file_name in additional_files: 32 | # os.remove(additional_file_name) 33 | os.remove(_relative_path_to_abs(os.path.join('..', '..', additional_file_name))) 34 | 35 | output_text = output.stdout.decode().replace('\r\n', '\n') 36 | 37 | # always assert no memory leaks - kinda ugly, probably refactor this later 38 | assert "All memory freed" in output_text 39 | assert "All allocations freed" in output_text 40 | assert "No live objects" in output_text 41 | 42 | memory_diagnostics_start = output_text.index('======== Memory diagnostics ========') 43 | 44 | return output_text[:memory_diagnostics_start] 45 | 46 | 47 | def _parse_test_lines(lines_iter, end_tokens): 48 | lines = [] 49 | line = next(lines_iter) 50 | while line.strip() not in end_tokens: 51 | if line == '\n': # allow a special case of bare empty lines, for convenience 52 | indent = 0 53 | elif line.startswith('\t'): 54 | indent = 1 55 | elif line.startswith(' '): 56 | indent = 4 57 | else: 58 | print() 59 | print('Parsing error: Indentation missing in test code or expected result.') 60 | return None 61 | 62 | line = line[indent:] # deindent 63 | lines.append(line) 64 | line = next(lines_iter) 65 | 66 | return ''.join(lines) 67 | 68 | 69 | def _run_test_file(absolute_path): 70 | with open(absolute_path) as f: 71 | text = f.readlines() 72 | 73 | all_success = True 74 | num_tests = 0 75 | 76 | lines = iter(text) 77 | while True: 78 | try: 79 | line = next(lines) 80 | except StopIteration: 81 | break 82 | 83 | while line.isspace(): 84 | line = next(lines) 85 | 86 | if not 'test ' in line: 87 | raise RuntimeError('Text outside test bounds') 88 | 89 | test_prefix, test_name = [s.strip() for s in line.split('test ')] 90 | 91 | test_annotations = [s.strip() for s in test_prefix.split()] 92 | 93 | for annotation in test_annotations: 94 | if annotation not in ['repeat', 'skip']: 95 | raise RuntimeError('Unknown test annotation: {}'.format(annotation)) 96 | 97 | #if test_prefix == 'skip': 98 | if 'skip' in test_annotations: 99 | while line.strip() != 'end': 100 | line = next(lines) 101 | try: 102 | line = next(lines) # skip 'end' 103 | except StopIteration: 104 | # This was the last test 105 | pass 106 | print('Test %-75s [SKIPPED]' % test_name) 107 | continue 108 | 109 | repeat = 'repeat' in test_annotations 110 | 111 | print('Test %-77s' % test_name, end='') 112 | 113 | num_tests += 1 114 | 115 | test_lines = [] 116 | line = next(lines) 117 | while line.strip() != 'expect' and not line.strip().startswith('file '): 118 | if line == '\n': # allow a special case of bare empty lines, for convenience 119 | indent = 0 120 | elif line.startswith('\t'): 121 | indent = 1 122 | elif line.startswith(' '): 123 | indent = 4 124 | else: 125 | print() 126 | print('Parsing error: Indentation missing in test code or expected result.') 127 | test_code = None 128 | break 129 | 130 | line = line[indent:] # deindent 131 | test_lines.append(line) 132 | line = next(lines) 133 | 134 | else: 135 | test_code = ''.join(test_lines) 136 | 137 | if test_code is None: 138 | all_success = False 139 | break 140 | 141 | additional_files = {} 142 | while line.strip().startswith('file '): 143 | file_name = line.split(' ')[1].strip() 144 | 145 | additional_file_lines = [] 146 | line = next(lines) 147 | while line.strip() != 'expect' and not line.strip().startswith('file '): 148 | if line == '\n': # allow a special case of bare empty lines, for convenience 149 | indent = 0 150 | elif line.startswith('\t'): 151 | indent = 1 152 | elif line.startswith(' '): 153 | indent = 4 154 | else: 155 | print() 156 | print('Parsing error: Indentation missing in test code or expected result.') 157 | additional_file_text = None 158 | break 159 | 160 | line = line[indent:] # deindent 161 | additional_file_lines.append(line) 162 | line = next(lines) 163 | 164 | else: 165 | additional_file_text = ''.join(additional_file_lines) 166 | 167 | # The ending '\n' of the file text is not considered part of the file, 168 | # but part of the test syntax itself, so we remove it 169 | if additional_file_text[-1] != '\n': 170 | raise AssertionError("Additional file text should always end with a '\\n'") 171 | additional_file_text = additional_file_text[:-1] 172 | 173 | additional_files[file_name] = additional_file_text 174 | 175 | expect_output = _parse_test_lines(lines, ['end']) 176 | if expect_output is None: 177 | all_success = False 178 | break 179 | 180 | interpreter_path = _relative_path_to_abs(os.path.join('..', '..', INTERPRETER_NAME)) 181 | 182 | if repeat: 183 | success = True 184 | REPEAT_TIMES = 100 185 | for i in range(REPEAT_TIMES): 186 | output = _run_on_interpreter(interpreter_path, test_code, additional_files) 187 | if output != expect_output: 188 | success = False 189 | failure_repeat = i 190 | break 191 | 192 | else: 193 | output = _run_on_interpreter(interpreter_path, test_code, additional_files) 194 | success = output == expect_output 195 | 196 | if success: 197 | print(f'SUCCESS') 198 | else: 199 | all_success = False 200 | if repeat: 201 | print('FAILURE [repeat #{}]'.format(failure_repeat)) 202 | else: 203 | print(f'FAILURE') 204 | print() 205 | print('Ln. %-14s | Actual' % 'Expected') 206 | print() 207 | for i, (expected_line, actual_line) in enumerate(itertools.zip_longest(expect_output.splitlines(), output.splitlines())): 208 | if expected_line is None: 209 | expected_line = '' 210 | if actual_line is None: 211 | actual_line = '' 212 | print('%-3d %-15s | %-15s' % (i, expected_line, actual_line), end='') 213 | if expected_line != actual_line: 214 | print('[DIFF]') 215 | else: 216 | print() 217 | 218 | return all_success, num_tests 219 | 220 | 221 | def _relative_path_to_abs(path): 222 | current_abs_dir = os.path.dirname(os.path.abspath(__file__)) 223 | return os.path.join(current_abs_dir, path) 224 | 225 | 226 | def _run_test_directory(relative_dir_path): 227 | num_tests = 0 228 | all_success = True 229 | abs_dir_path = _relative_path_to_abs(relative_dir_path) 230 | for f in os.listdir(abs_dir_path): 231 | abs_test_path = os.path.join(abs_dir_path, f) 232 | if os.path.isfile(abs_test_path) and abs_test_path.endswith(TEST_FILE_SUFFIX): 233 | print(f'Running tests in {f}') 234 | print() 235 | file_all_success, num_tests_in_file = _run_test_file(abs_test_path) 236 | num_tests += num_tests_in_file 237 | if not file_all_success: 238 | all_success = False 239 | print() 240 | return all_success, num_tests 241 | 242 | 243 | def main(): 244 | if len(sys.argv) == 2: 245 | testdir = sys.argv[1] 246 | else: 247 | testdir = DEFAULT_TESTS_DIR 248 | 249 | all_success, num_tests = _run_test_directory(testdir) 250 | 251 | print(f'Found total {num_tests} tests.') 252 | 253 | if all_success: 254 | print('All tests successful') 255 | exit(0) 256 | else: 257 | print('Some tests failed!') 258 | exit(-1) 259 | 260 | 261 | if __name__ == '__main__': 262 | main() 263 | -------------------------------------------------------------------------------- /src/python/tester/tests/functions.test: -------------------------------------------------------------------------------- 1 | test empty_function_does_nothing 2 | {} 3 | {}() 4 | expect 5 | end 6 | 7 | test empty pipes take no parameters 8 | # The best way to take no parameters is by just leaving the pipes out. 9 | # However we also support having empty pipes. 10 | 11 | f = { | | 12 | print("Hello") 13 | } 14 | f() 15 | expect 16 | Hello 17 | end 18 | 19 | test function_with_no_return_returns_nil 20 | print({}()) 21 | f = { 22 | 23 | } 24 | print(f()) 25 | expect 26 | nil 27 | nil 28 | end 29 | 30 | test return with no value returns nil 31 | f = { | n | 32 | if n < 0 { 33 | print("Can't do negative!") 34 | return 35 | } 36 | 37 | return n * n 38 | } 39 | 40 | print(f(10)) 41 | print(f(-10)) 42 | 43 | f2 = { 44 | return 45 | } 46 | 47 | print(f2()) 48 | expect 49 | 100 50 | Can't do negative! 51 | nil 52 | nil 53 | end 54 | 55 | test recursion 56 | factorial = { | n | 57 | if n == 1 { 58 | return n 59 | } 60 | return n * factorial(n - 1) 61 | } 62 | 63 | print(factorial(4)) 64 | expect 65 | 24 66 | end 67 | 68 | test function_calls 69 | f1 = { | x | 70 | print("f1") 71 | return x * 10 72 | } 73 | f2 = { | x | 74 | print("f2") 75 | return f1(x) * 10 76 | } 77 | f3 = { | x | 78 | print("f3") 79 | return f2(x) * 10 80 | } 81 | print(f3(8)) 82 | expect 83 | f3 84 | f2 85 | f1 86 | 8000 87 | end 88 | 89 | test fuction_calls_multi_arg 90 | divide = {| x, y | 91 | print("In function divide") 92 | return x / y 93 | } 94 | print(divide(20, 5)) 95 | expect 96 | In function divide 97 | 4 98 | end 99 | 100 | test function_as_argument 101 | f1 = {| f2 | 102 | return f2(50) 103 | } 104 | 105 | q = { | n | 106 | return n * n 107 | } 108 | 109 | print(f1(q)) 110 | expect 111 | 2500 112 | end 113 | 114 | test function_call_in_expression 115 | f = { | x, y | 116 | print("In f") 117 | return x + y 118 | } 119 | print(5 + 8 * f(10, f(5, 15)) - 1) 120 | expect 121 | In f 122 | In f 123 | 244 124 | end 125 | 126 | test function instances are independent objects 127 | func_creator = { 128 | return { | food | 129 | print("I received a " + food) 130 | } 131 | } 132 | 133 | f1 = func_creator() 134 | f2 = func_creator() 135 | 136 | print(f1 == f1) 137 | print(f1 == f2) 138 | 139 | f1.attr = "Thing1" 140 | f2.attr = "Thing2" 141 | 142 | print(f1.attr) 143 | print(f2.attr) 144 | 145 | f1("pizza") 146 | f2("pasta") 147 | f1("pizza") 148 | f2("pasta") 149 | f1("steak") 150 | f1("orange") 151 | expect 152 | true 153 | false 154 | Thing1 155 | Thing2 156 | I received a pizza 157 | I received a pasta 158 | I received a pizza 159 | I received a pasta 160 | I received a steak 161 | I received a orange 162 | end 163 | 164 | test basic closures 165 | outer = { 166 | x = 10 167 | inner = { 168 | print(x) 169 | } 170 | return inner 171 | } 172 | inner = outer() 173 | inner() 174 | expect 175 | 10 176 | end 177 | 178 | test deeply nested closures 179 | outer = { 180 | x = "abc" 181 | middle = { 182 | inner = { 183 | print(x) 184 | } 185 | return inner 186 | } 187 | return middle 188 | } 189 | middle = outer() 190 | inner = middle() 191 | inner() 192 | expect 193 | abc 194 | end 195 | 196 | test deeply nested closures with different names 197 | outer = { 198 | x = "abc" 199 | middle = { 200 | inner = { 201 | print(x) 202 | } 203 | return inner 204 | } 205 | return middle 206 | } 207 | 208 | a = outer() 209 | b = a() 210 | b() 211 | expect 212 | abc 213 | end 214 | 215 | test higher order closures 216 | concat = { | value, concat_fn | 217 | result = concat_fn(value) 218 | print(result) 219 | } 220 | 221 | concat_factory = { | x | 222 | return { | y | 223 | return x + " " + y 224 | } 225 | } 226 | 227 | concat_hello = concat_factory("hello") 228 | concat_hi = concat_factory("hi") 229 | 230 | concat("Aviv", concat_hello) 231 | concat("Aviv", concat_hi) 232 | expect 233 | hello Aviv 234 | hi Aviv 235 | end 236 | 237 | test closure semantics 238 | outer = { 239 | x = "original" 240 | middle = { 241 | inner = { 242 | print(x) 243 | } 244 | x = "something else" 245 | return inner 246 | } 247 | x = "updated" 248 | return middle 249 | } 250 | 251 | m = outer() 252 | i = m() 253 | i() 254 | expect 255 | something else 256 | end 257 | 258 | test closure with arguments 259 | outer = {| x | 260 | middle = { 261 | inner = { 262 | return x 263 | } 264 | x = "something else" 265 | return inner 266 | } 267 | x = "updated" 268 | return middle 269 | } 270 | 271 | m = outer("original") 272 | i = m() 273 | print(i()) 274 | expect 275 | something else 276 | end 277 | 278 | test function reaches to base function 279 | x = 200 280 | f = { | n | 281 | return x * n 282 | } 283 | print(f(10)) 284 | expect 285 | 2000 286 | end 287 | 288 | test weird closure behavior 289 | outer = { 290 | middle = { 291 | inner = { 292 | return x * 10 293 | } 294 | x = 10 295 | return inner 296 | } 297 | return middle 298 | } 299 | middle = outer() 300 | inner = middle() 301 | print(inner()) 302 | expect 303 | 100 304 | end 305 | 306 | test closure cannot alter enclosed variable 307 | outer = { 308 | x = "a" 309 | inner1 = { 310 | print(x) 311 | x = "b" 312 | print(x) 313 | } 314 | x = "A" 315 | inner2 = { 316 | print(x) 317 | } 318 | return ["inner1": inner1, "inner2": inner2] 319 | } 320 | 321 | closures = outer() 322 | inner1 = closures["inner1"] 323 | inner2 = closures["inner2"] 324 | 325 | inner2() 326 | inner1() 327 | inner2() 328 | expect 329 | A 330 | A 331 | b 332 | A 333 | end 334 | 335 | test nested closure cannot alter enclosed variable 336 | outer = { 337 | x = "a" 338 | middle = { 339 | inner1 = { 340 | print(x) 341 | x = "b" 342 | print(x) 343 | } 344 | inner2 = { 345 | print(x) 346 | } 347 | return ["inner1": inner1, "inner2": inner2] 348 | } 349 | x = "A" 350 | return middle 351 | } 352 | 353 | middle = outer() 354 | closures = middle() 355 | inner1 = closures["inner1"] 356 | inner2 = closures["inner2"] 357 | 358 | inner2() 359 | inner1() 360 | inner2() 361 | expect 362 | A 363 | A 364 | b 365 | A 366 | end 367 | 368 | test free variable referenced before assignment raises error 369 | # this test relies on the ugly, and possibly temporary, error output. 370 | 371 | outer = { 372 | inner = { 373 | print(x) 374 | } 375 | if false { 376 | x = 10 377 | } 378 | return inner 379 | } 380 | inner = outer() 381 | inner() 382 | expect 383 | An error has occured. Stack trace (most recent call on top): 384 | -> inner 385 | ->
386 | Variable x not found. 387 | end 388 | 389 | test set outside variable part 1 390 | x = 10 391 | 392 | q = { 393 | x = 5 394 | f = { 395 | external x 396 | print(x) 397 | x = 20 398 | print(x) 399 | } 400 | return f 401 | } 402 | 403 | q()() 404 | print(x) 405 | expect 406 | 5 407 | 20 408 | 10 409 | end 410 | 411 | test set outside variable part 2 412 | x = 10 413 | 414 | q = { 415 | f = { 416 | external x 417 | print(x) 418 | x = 20 419 | print(x) 420 | } 421 | return f 422 | } 423 | 424 | q()() 425 | print(x) 426 | expect 427 | 10 428 | 20 429 | 20 430 | end 431 | 432 | test some more testing for scope rules 433 | x = 10 434 | 435 | q = { 436 | f = { 437 | # no external x declaration here 438 | print(x) 439 | x = 20 440 | print(x) 441 | } 442 | return f 443 | } 444 | 445 | q()() 446 | print(x) 447 | expect 448 | 10 449 | 20 450 | 10 451 | end 452 | 453 | test outside variable setting with classes 454 | x = 0 455 | 456 | C = class { 457 | @init = { |incrementor| 458 | self.incrementor = incrementor 459 | } 460 | f = { 461 | external x 462 | x += self.incrementor 463 | } 464 | } 465 | 466 | o = C(1) 467 | o.f() 468 | o.f() 469 | o.f() 470 | print(x) 471 | expect 472 | 3 473 | end 474 | 475 | test outside variable setting with classes and some more scope stuff 476 | x = 0 477 | 478 | f = { 479 | return class { 480 | @init = { |incrementor| 481 | self.incrementor = incrementor 482 | } 483 | f = { 484 | x = -2 485 | external x # this overrides the existing local in the table, with the cell of the external one 486 | x += self.incrementor 487 | } 488 | } 489 | } 490 | 491 | o = f()(1) 492 | o.f() 493 | o.f() 494 | o.f() 495 | print(x) 496 | expect 497 | 3 498 | end -------------------------------------------------------------------------------- /src/python/tester/tests/misc.test: -------------------------------------------------------------------------------- 1 | test nil generally 2 | print(nil) 3 | x = nil 4 | print(x) 5 | expect 6 | nil 7 | nil 8 | end 9 | 10 | test nil attributes 11 | C = class { 12 | @init = { | x, z | 13 | self.x = x 14 | self.z = z 15 | self.y = nil 16 | } 17 | } 18 | 19 | c = C(nil, 10) 20 | c.q = nil 21 | 22 | print(c.x) 23 | print(c.z) 24 | print(c.y) 25 | print(c.q) 26 | expect 27 | nil 28 | 10 29 | nil 30 | nil 31 | end 32 | 33 | test nil returned from function 34 | f1 = { 35 | print("Some function") 36 | # returning nil implicitly 37 | } 38 | 39 | print(f1()) 40 | 41 | f2 = { 42 | print("Another function") 43 | return nil 44 | } 45 | 46 | print(f2()) 47 | 48 | C = class { 49 | method = { 50 | print("Some method") 51 | return nil 52 | } 53 | } 54 | 55 | print(C().method()) 56 | expect 57 | Some function 58 | nil 59 | Another function 60 | nil 61 | Some method 62 | nil 63 | end 64 | 65 | repeat test surface memory corruptions by validating consistent stdout output 66 | # By specifying "repeat" on the test, it will not run once, but hundreds of times. 67 | # Trying to help inconsistent undefined behavior etc surface. 68 | 69 | print("Some text") 70 | 71 | i = 1 72 | while i <= 12 { 73 | print("More text: " + to_string(i)) 74 | i = i + 1 75 | } 76 | expect 77 | Some text 78 | More text: 1 79 | More text: 2 80 | More text: 3 81 | More text: 4 82 | More text: 5 83 | More text: 6 84 | More text: 7 85 | More text: 8 86 | More text: 9 87 | More text: 10 88 | More text: 11 89 | More text: 12 90 | end 91 | 92 | test string concat does not overflow stack 93 | # We had a bug where string concating would fail to pop the original 94 | # string, resulting in an overflow in long loops. This test makes sure that bug doesn't come back 95 | 96 | i = 0 97 | while i < 2500 { 98 | "s1" + "s2" 99 | i = i + 1 100 | } 101 | print("Success") 102 | expect 103 | Success 104 | end 105 | 106 | test table access does not overflow stack 107 | t = ["a": 1] 108 | 109 | i = 0 110 | while i < 2500 { 111 | t["a"] 112 | i = i + 1 113 | } 114 | 115 | print("Success") 116 | expect 117 | Success 118 | end 119 | 120 | test more than 255 constants is handled correctly 121 | # this code should create 306 constants in the same code object. 122 | # things should still work (because in the bytecode the index of a constant is represented using two bytes, not one) 123 | # this test might not be the best logically, but this is good enough for now. 124 | 125 | a = 20 126 | a 127 | a 128 | a 129 | a 130 | a 131 | a 132 | a 133 | a 134 | a 135 | a 136 | a 137 | a 138 | a 139 | a 140 | a 141 | a 142 | a 143 | a 144 | a 145 | a 146 | a 147 | a 148 | a 149 | a 150 | a 151 | a 152 | a 153 | a 154 | a 155 | a 156 | a 157 | a 158 | a 159 | a 160 | a 161 | a 162 | a 163 | a 164 | a 165 | a 166 | a 167 | a 168 | a 169 | a 170 | a 171 | a 172 | a 173 | a 174 | a 175 | a 176 | a 177 | a 178 | a 179 | a 180 | a 181 | a 182 | a 183 | a 184 | a 185 | a 186 | a 187 | a 188 | a 189 | a 190 | a 191 | a 192 | a 193 | a 194 | a 195 | a 196 | a 197 | a 198 | a 199 | a 200 | a 201 | a 202 | a 203 | a 204 | a 205 | a 206 | a 207 | a 208 | a 209 | a 210 | a 211 | a 212 | a 213 | a 214 | a 215 | a 216 | a 217 | a 218 | a 219 | a 220 | a 221 | a 222 | a 223 | a 224 | a 225 | a 226 | a 227 | a 228 | a 229 | a 230 | a 231 | a 232 | a 233 | a 234 | a 235 | a 236 | a 237 | a 238 | a 239 | a 240 | a 241 | a 242 | a 243 | a 244 | a 245 | a 246 | a 247 | a 248 | a 249 | a 250 | a 251 | a 252 | a 253 | a 254 | a 255 | a 256 | a 257 | a 258 | a 259 | a 260 | a 261 | a 262 | a 263 | a 264 | a 265 | a 266 | a 267 | a 268 | a 269 | a 270 | a 271 | a 272 | a 273 | a 274 | a 275 | a 276 | a 277 | a 278 | a 279 | a 280 | a 281 | a 282 | a 283 | a 284 | a 285 | a 286 | a 287 | a 288 | a 289 | a 290 | a 291 | a 292 | a 293 | a 294 | a 295 | a 296 | a 297 | a 298 | a 299 | a 300 | a 301 | a 302 | a 303 | a 304 | a 305 | a 306 | a 307 | a 308 | a 309 | a 310 | a 311 | a 312 | a 313 | a 314 | a 315 | a 316 | a 317 | a 318 | a 319 | a 320 | a 321 | a 322 | a 323 | a 324 | a 325 | a 326 | a 327 | a 328 | a 329 | a 330 | a 331 | a 332 | a 333 | a 334 | a 335 | a 336 | a 337 | a 338 | a 339 | a 340 | a 341 | a 342 | a 343 | a 344 | a 345 | a 346 | a 347 | a 348 | a 349 | a 350 | a 351 | a 352 | a 353 | a 354 | a 355 | a 356 | a 357 | a 358 | a 359 | a 360 | a 361 | a 362 | a 363 | a 364 | a 365 | a 366 | a 367 | a 368 | a 369 | a 370 | a 371 | a 372 | a 373 | a 374 | a 375 | a 376 | a 377 | a 378 | a 379 | a 380 | a 381 | a 382 | a 383 | a 384 | a 385 | a 386 | a 387 | a 388 | a 389 | a 390 | a 391 | a 392 | a 393 | a 394 | a 395 | a 396 | a 397 | a 398 | a 399 | a 400 | a 401 | a 402 | a 403 | a 404 | a 405 | a 406 | a 407 | a 408 | a 409 | a 410 | a = 12 411 | a 412 | a 413 | a 414 | a 415 | a 416 | a 417 | a 418 | a 419 | a 420 | print("Just a print passing through here") 421 | a 422 | a 423 | a 424 | a 425 | a 426 | print(a) 427 | print("Done") 428 | expect 429 | Just a print passing through here 430 | 12 431 | Done 432 | end 433 | 434 | test stack overflow error 435 | # Note: currently if a native function causes a stack overflow in the language level (obviously by calling a language function), 436 | # it will be reported as generally a "Native function failed" message, because of the very basic boolean based error reporting mechanism 437 | # currently in place. 438 | # In case of a stack overflow caused directly by language code, the test exhibits what happens. 439 | 440 | f = { 441 | f() 442 | } 443 | f() 444 | expect 445 | An error has occured. Stack trace (most recent call on top): 446 | -> f 447 | -> f 448 | -> f 449 | -> f 450 | -> f 451 | -> f 452 | -> f 453 | -> f 454 | -> f 455 | -> f 456 | -> f 457 | -> f 458 | -> f 459 | -> f 460 | -> f 461 | -> f 462 | -> f 463 | -> f 464 | -> f 465 | -> f 466 | -> f 467 | -> f 468 | -> f 469 | -> f 470 | -> f 471 | -> f 472 | -> f 473 | -> f 474 | -> f 475 | -> f 476 | -> f 477 | -> f 478 | -> f 479 | -> f 480 | -> f 481 | -> f 482 | -> f 483 | -> f 484 | -> f 485 | -> f 486 | -> [... 215 lower frames truncated ...] 487 | Stack overflow 488 | end 489 | 490 | test temporaries are popped off the stack when function returns in the middle 491 | 492 | # Test explanation: 493 | # 494 | # We had a bug where when a function returns in the middle, some temporaries may 495 | # be left on the evaluation stack. In specific circumstances, this would surface to the script level and ruin the logic. 496 | # The code triggering the bug used a for-loop, which behind the scenes stores temporaries on the stack. 497 | # 498 | # We fixed the bug in the VM by (at the time of writing this), when calling a function, storing the current 499 | # eval-stack offset on the stackframe, and re-setting the stack top to that offset (+ 1 for the return value) on OP_RETURN. 500 | # 501 | # This test used to fail when the bug existed, and should help guard against its return. 502 | # The way it used to fail is it assinged one of the arguments of func() to be the object list_has_value, 503 | # which has been forgotten on the stack from earlier. It should have been popped, being the "callee" of an earlier invocation, 504 | # but a left-over temporary has been popped instead, leaving it on the stack. 505 | # It would get assigned to the argument variable instead of the correct passed in value. 506 | # The test validates that the correct passed-in values are printed out. 507 | 508 | list_has_value = { | list, value | 509 | # This function will return in the middle. 510 | # At that point, some temporary values will be present on the stack. 511 | # The VM should correctly pop all of them. 512 | 513 | for item in list { 514 | if item == value { 515 | return true 516 | } 517 | } 518 | 519 | return false 520 | } 521 | 522 | as_string = { | object | 523 | builtin_supported_types = ["Number"] 524 | 525 | if list_has_value(builtin_supported_types, type(object)) { 526 | return to_string(object) 527 | } 528 | 529 | return "" 530 | 531 | } 532 | 533 | func = { | a, b | 534 | print("func called on:") 535 | print(a) 536 | print(b) 537 | } 538 | 539 | func(as_string(10), as_string(20)) 540 | expect 541 | func called on: 542 | 10 543 | 20 544 | end 545 | -------------------------------------------------------------------------------- /src/python/tester/tests/classes.test: -------------------------------------------------------------------------------- 1 | test empty named class 2 | Dog = class {} 3 | print(Dog) 4 | expect 5 | 6 | end 7 | 8 | test empty anonymous class 9 | print(class {}) 10 | expect 11 | > 12 | end 13 | 14 | test class attributes 15 | Dog = class { 16 | x = 10 17 | y = "20" 18 | z = { 19 | print("Hello") 20 | } 21 | } 22 | 23 | print(Dog.x) 24 | print(Dog.y) 25 | Dog.z() 26 | 27 | dog = Dog() 28 | print(dog.x) 29 | print(dog.y) 30 | dog.z() 31 | 32 | Dog.y = 30 33 | 34 | print(Dog.x) 35 | print(Dog.y) 36 | Dog.z() 37 | 38 | print(dog.x) 39 | print(dog.y) 40 | dog.z() 41 | expect 42 | 10 43 | 20 44 | Hello 45 | 10 46 | 20 47 | Hello 48 | 10 49 | 30 50 | Hello 51 | 10 52 | 30 53 | Hello 54 | end 55 | 56 | test constructors 57 | Cat = class { 58 | @init = { | name, favorite_food | 59 | self.name = name 60 | self.favorite_food = favorite_food 61 | } 62 | 63 | meow = { | to_whom | 64 | print("Meow, " + to_whom) 65 | print("My name is " + self.name + " and I love " + self.favorite_food + "!") 66 | } 67 | 68 | breed = { | other_cat | 69 | self.meow(other_cat.name) 70 | print("Let's make kittens!") 71 | return [Cat("Catti", "pizza"), Cat("Cattul", "pasta")] 72 | } 73 | } 74 | 75 | cat = Cat("ElCatoo", "lasagna") 76 | cat.meow("Aviv") 77 | 78 | offspring = cat.breed(Cat("Cattaluna", "kitkat")) 79 | catti = offspring[0] 80 | cattul = offspring[1] 81 | 82 | print("First kitten is " + catti.name + " and likes " + catti.favorite_food) 83 | print("Second kitten is " + cattul.name + " and likes " + cattul.favorite_food) 84 | expect 85 | Meow, Aviv 86 | My name is ElCatoo and I love lasagna! 87 | Meow, Cattaluna 88 | My name is ElCatoo and I love lasagna! 89 | Let's make kittens! 90 | First kitten is Catti and likes pizza 91 | Second kitten is Cattul and likes pasta 92 | end 93 | 94 | test more constructors 95 | Kangaroo = class { 96 | @init = {| name | 97 | self.name = name 98 | self.color = "brown" 99 | } 100 | 101 | greet = { | who | 102 | print("Hello, " + who + "! My name is " + self.name + " and I'm " + self.color + " :D") 103 | } 104 | } 105 | 106 | oscar = Kangaroo("Oscar") 107 | jonblow = Kangaroo("Jonblow") 108 | 109 | oscar.greet("Sonny") 110 | jonblow.greet("Elisa") 111 | oscar.greet("Elisa") 112 | jonblow.greet("Sonny") 113 | 114 | oscargreet = oscar.greet 115 | jonblowgreet = jonblow.greet 116 | oscargreet("Bonna") 117 | jonblowgreet("Bonna") 118 | expect 119 | Hello, Sonny! My name is Oscar and I'm brown :D 120 | Hello, Elisa! My name is Jonblow and I'm brown :D 121 | Hello, Elisa! My name is Oscar and I'm brown :D 122 | Hello, Sonny! My name is Jonblow and I'm brown :D 123 | Hello, Bonna! My name is Oscar and I'm brown :D 124 | Hello, Bonna! My name is Jonblow and I'm brown :D 125 | end 126 | 127 | test tribe of proud happy kangaroos living together in peace and harmony jumpin around 128 | Kangaroo = class { 129 | @init = {| name | 130 | self.name = name 131 | self.color = "brown" 132 | } 133 | 134 | greet = { | who | 135 | print("Hello, " + who + "! My name is " + self.name + " and I'm " + self.color + " :D") 136 | } 137 | 138 | make_baby_kanga = { | name | 139 | baby = Kangaroo("Little " + name) 140 | baby.greet(self.name) 141 | self.greet(baby.name) 142 | return baby 143 | } 144 | } 145 | 146 | KangarooTribe = class { 147 | @init = { | kangaroos | 148 | self._kangaroos = kangaroos 149 | self._index = 0 150 | } 151 | 152 | next_kanga = { 153 | if self._index >= self._kangaroos.length() { 154 | return false 155 | } 156 | 157 | kanga = self._kangaroos[self._index] 158 | self._index = self._index + 1 159 | return kanga 160 | } 161 | } 162 | 163 | kangaroos = [ Kangaroo("Sonny"), Kangaroo("Melissa"), Kangaroo("Mr. Anthrex") ] 164 | tribe = KangarooTribe(kangaroos) 165 | 166 | more_kangas = true 167 | while more_kangas { 168 | kanga = tribe.next_kanga() 169 | if kanga == false { 170 | more_kangas = false 171 | } else { 172 | kanga.greet("Aviv") 173 | kangreet = kanga.greet 174 | kangreet("Aviv") 175 | } 176 | } 177 | 178 | baby = kangaroos[0].make_baby_kanga("Richard") 179 | print(baby.name) 180 | expect 181 | Hello, Aviv! My name is Sonny and I'm brown :D 182 | Hello, Aviv! My name is Sonny and I'm brown :D 183 | Hello, Aviv! My name is Melissa and I'm brown :D 184 | Hello, Aviv! My name is Melissa and I'm brown :D 185 | Hello, Aviv! My name is Mr. Anthrex and I'm brown :D 186 | Hello, Aviv! My name is Mr. Anthrex and I'm brown :D 187 | Hello, Sonny! My name is Little Richard and I'm brown :D 188 | Hello, Little Richard! My name is Sonny and I'm brown :D 189 | Little Richard 190 | end 191 | 192 | test self is captured by closures in methods 193 | also_just_some_variable = 100 194 | 195 | Guitar = class { 196 | @init = { | type | 197 | if type == "electric" { 198 | self.style = "rock" 199 | } else { 200 | self.style = "classical" 201 | } 202 | } 203 | 204 | make_song = { | times | 205 | return { 206 | i = 0 207 | while i < times { 208 | print("I'm a " + self.style + " song") 209 | i = i + 1 210 | } 211 | 212 | print(also_just_some_variable) 213 | } 214 | } 215 | } 216 | 217 | myguitar = Guitar("electric") 218 | song = myguitar.make_song(2) 219 | song() 220 | 221 | myguitar = Guitar("acoustic") 222 | song = myguitar.make_song(3) 223 | song() 224 | expect 225 | I'm a rock song 226 | I'm a rock song 227 | 100 228 | I'm a classical song 229 | I'm a classical song 230 | I'm a classical song 231 | 100 232 | end 233 | 234 | test more funky classes stuff 235 | SpeciesMaker = class { 236 | 237 | make_animal = { | species_name, is_carnivor, height | 238 | 239 | return class { 240 | @init = { | name | 241 | self.name = name 242 | self.species_name = species_name 243 | self.is_carnivor = is_carnivor 244 | # height will be used directly from the free variables 245 | } 246 | 247 | greet = { 248 | print("Hello, my name is " + self.name + " and I am a " + self.species_name + "!") 249 | 250 | if self.is_carnivor { 251 | print("I'm eating MEAT") 252 | } else { 253 | print("I like a good salad :)") 254 | } 255 | 256 | if height > 5 { 257 | print("I'm very tall") 258 | } else { 259 | print("I'm short") 260 | } 261 | 262 | return true # just for kicks 263 | } 264 | } 265 | 266 | } 267 | 268 | } 269 | 270 | maker = SpeciesMaker() 271 | 272 | Dog = maker.make_animal("Dog", true, 2) 273 | Kangaroo = maker.make_animal("Kangaroo", false, 6) 274 | 275 | milki = Dog("Milki") 276 | brandy = Dog("Brandy") 277 | milki.greet() 278 | brandy.greet() 279 | 280 | chicko = Kangaroo("Chicko") 281 | lukka = Kangaroo("Lukka") 282 | chicko.greet() 283 | lukka.greet() 284 | expect 285 | Hello, my name is Milki and I am a Dog! 286 | I'm eating MEAT 287 | I'm short 288 | Hello, my name is Brandy and I am a Dog! 289 | I'm eating MEAT 290 | I'm short 291 | Hello, my name is Chicko and I am a Kangaroo! 292 | I like a good salad :) 293 | I'm very tall 294 | Hello, my name is Lukka and I am a Kangaroo! 295 | I like a good salad :) 296 | I'm very tall 297 | end 298 | 299 | test inheritance 300 | O = class { 301 | method_o1 = { 302 | print("o1") 303 | } 304 | 305 | method_o2 = { 306 | print("o2") 307 | } 308 | 309 | method_o3 = { 310 | print("o3") 311 | } 312 | } 313 | 314 | A = class : O { 315 | method_a1 = { 316 | print("a1") 317 | } 318 | 319 | method_a2 = { 320 | print("a2") 321 | } 322 | 323 | method_a3 = { |y| 324 | return self.x * y 325 | } 326 | 327 | method_o1 = { 328 | print("o1 A override") 329 | } 330 | 331 | method_o2 = { 332 | print("o2 A override") 333 | } 334 | 335 | some_other_thing = 8 336 | } 337 | 338 | B = class : A { 339 | @init = { |x| 340 | self.x = x 341 | } 342 | 343 | method_b1 = { 344 | print("b1") 345 | } 346 | 347 | method_a2 = { 348 | print("b2") 349 | } 350 | 351 | method_o1 = { 352 | print("o1 B override") 353 | } 354 | } 355 | 356 | b = B(5) 357 | b.method_a1() 358 | b.method_a2() 359 | b.method_b1() 360 | b.method_o1() 361 | b.method_o2() 362 | b.method_o3() 363 | print(b.method_a3(10)) 364 | print(b.some_other_thing) 365 | 366 | print(is_instance(b, "A")) 367 | expect 368 | a1 369 | b2 370 | b1 371 | o1 B override 372 | o2 A override 373 | o3 374 | 50 375 | 8 376 | true 377 | end 378 | 379 | test inheritance with the super function 380 | Entity = class { 381 | @init = { |x, y| 382 | self.x = x 383 | self.y = y 384 | } 385 | } 386 | 387 | Bullet = class : Entity { 388 | @init = { 389 | super([50, 60]) 390 | self.dx = 10 391 | self.dy = 20 392 | } 393 | } 394 | 395 | b = Bullet() 396 | print(b.x) 397 | print(b.y) 398 | print(b.dx) 399 | print(b.dy) 400 | expect 401 | 50 402 | 60 403 | 10 404 | 20 405 | end 406 | -------------------------------------------------------------------------------- /docs/guide.md: -------------------------------------------------------------------------------- 1 | # Programming in Ribbon 2 | 3 | This page is a general guide for writing code in Ribbon. Please go through the [README](../README.md) before reading this guide. 4 | 5 | The code examples use Python syntax highlighting, since the syntax of Ribbon is similar to Python's. 6 | 7 | ### Table of contents 8 | 9 | * [Datatypes](#datatypes) 10 | * [Standard IO](#standard-io) 11 | * [Boolean and arithmetic expressions](#boolean-and-arithmetic-expressions) 12 | * [Control flow](#control-flow) 13 | * [Functions](#functions) 14 | * [Classes](#classes) 15 | * [Polymorphism](#polymorphism) 16 | * [Modules](#modules) 17 | * [The extension system](#the-extension-system) 18 | * [Miscellaneous](#miscellaneous) 19 | 20 | ### Datatypes 21 | 22 | Ribbon has two types of data types - values and objects. Values are immutable and live on the stack. Objects are often mutable, 23 | live on the heap and are managed by the garbage collector. New object types can be defined as a new class by the programmer. 24 | 25 | Instances of all types are treated using the same syntax. 26 | 27 | Builtin value types: 28 | 29 | * Number 30 | * Boolean 31 | * Nil 32 | 33 | Builtin object types: 34 | 35 | * String 36 | * Table 37 | * Function 38 | * Class 39 | * Module 40 | 41 | #### Number, Boolean and Nil 42 | 43 | ```python 44 | # This is a comment 45 | 46 | number = 30.5 # Can be an integer or a floating point 47 | bool = true # true or false 48 | my_nil = nil # represents no value 49 | ``` 50 | 51 | #### String 52 | 53 | Strings are immutable objects. We can perform different operations on them, among concatenation and accessing individual characters. 54 | Every string operation returns a new string. 55 | 56 | ```python 57 | string = "Hello Ribbon!" 58 | second_char = string[1] 59 | string_length = string.length() 60 | longer_string = string + " Some more words" 61 | ``` 62 | 63 | #### Table 64 | 65 | In Ribbon, Tables are used both as list-style structures and as dictionary containers. It is a hashtable under the hood. 66 | 67 | Table used as a list: 68 | 69 | ```python 70 | list = ["these", "are", "some", "items"] 71 | 72 | print(list[1]) # prints "are" 73 | print(list.length()) # prints 4 74 | list.add("another item") 75 | list.pop() # remove last item 76 | 77 | for item in list { 78 | print(item + ", ") # > these, are, some, items 79 | } 80 | ``` 81 | 82 | Table used as a dictionary 83 | 84 | ```python 85 | dictionary = ["python": "elegant", "javascript": "nice", "ribbon": "great"] 86 | print(dictionary["ribbon"]) # > great 87 | ``` 88 | 89 | ### Standard IO 90 | 91 | Ribbon comes with a variety of builtin functions (along with a few standard library modules - more on this later). 92 | 93 | We will not cover all of them here. Please take a look at the source code in `vm.c` and `builtins.c` to learn more. 94 | 95 | The `print()` builtin function, well, prints a string to the terminal. The `input()` builtin takes input from the user 96 | and returns it as a string. 97 | 98 | ```python 99 | print("What is your name?") 100 | name = input() 101 | print("Hello, " + name) 102 | ``` 103 | 104 | ### Boolean and arithmetic expressions 105 | 106 | The arithmetic operators are: 107 | 108 | ```python 109 | Addition + 110 | Subtraction - 111 | Multiplicaiton * 112 | Division / 113 | Modulo % 114 | ``` 115 | 116 | The comparison operators are: 117 | 118 | ```python 119 | Less than < 120 | Less than equals <= 121 | Greater than > 122 | Greater than equals >= 123 | Equals == 124 | ``` 125 | 126 | And the boolean relation operators are `and` and `or`. These are *short circuiting*. 127 | 128 | For example: 129 | 130 | ```python 131 | a = 10 132 | b = 20 133 | 134 | if a * b > 100 or b % 2 == 0 { 135 | # Do something 136 | } 137 | ``` 138 | 139 | ### Control flow 140 | 141 | Ribbon has the classic `if`, `while` and `for` statements that we know and love. 142 | 143 | #### If, Elsif and Else 144 | 145 | ```python 146 | if some_condition() { 147 | # ... 148 | } elsif other_condition() { 149 | # ... 150 | } else { 151 | # ... 152 | } 153 | ``` 154 | 155 | #### While 156 | 157 | ```python 158 | while some_condition() { 159 | # ... 160 | } 161 | ``` 162 | 163 | #### For 164 | 165 | `for` takes any object that implements (borrowing the terminology from Python) the sequence protocol. 166 | That includes the methods `length` and `@get_key` (the `[]` accessor). 167 | 168 | Table objects are an example of an iterable object: 169 | 170 | ```python 171 | numbers = [10, 20, 30] 172 | 173 | sum = 0 174 | for n in numbers { 175 | sum += n 176 | } 177 | 178 | print("The sum is: " + to_string(sum)) 179 | ``` 180 | 181 | ### Functions 182 | 183 | In Ribbon, all functions are basically lambdas. We can assign a function to a variable in order to give it a name. 184 | 185 | ```python 186 | empty_function = {} # No need to specify a parameter list if there aren't any 187 | 188 | multiply = { | x, y | 189 | return x * y 190 | } 191 | ``` 192 | 193 | Like most things in Ribbon, functions are first class - we can pass them into other functions, in a functional programming style. 194 | 195 | ```python 196 | # Example of a higher order function 197 | map = { | list, func | 198 | result = [] 199 | for item in list { 200 | result.add(func(item)) 201 | } 202 | return result 203 | } 204 | 205 | # Passing an anonymous function into map. This returns [1, 4, 9] 206 | map([1, 2, 3], { | n | 207 | return n * n 208 | }) 209 | ``` 210 | 211 | Functions in Ribbon are **closures** - they can remember the surrounding variables from the scope they were defined in. 212 | This allows neat things such as: 213 | 214 | ```python 215 | greet_maker = { | name | 216 | return { 217 | return "Hello, " + name + "!" 218 | } 219 | } 220 | 221 | my_greet = greet_maker("Ribbon") 222 | my_greet() # "Hello, Ribbon!" 223 | ``` 224 | 225 | ### Classes 226 | 227 | Class declarations, like functions, are simply anonymous expressions. Like all expressions, they can be assigned to variables. 228 | 229 | ```python 230 | Dog = class { 231 | # constructor is optional 232 | @init = { | name | 233 | self.name = name 234 | } 235 | 236 | bark = { 237 | print(self.name + ": Woof!") 238 | } 239 | } 240 | 241 | rex = Dog("Rex") 242 | brown = Dog("Brown") 243 | rex.bark() # "rex: Woof!" 244 | brown.bark() # "brown: Woof!" 245 | ``` 246 | 247 | Ribbon has single inheritance: 248 | 249 | ```python 250 | Labrador = class : Dog { 251 | bark = { 252 | # Override superclass bark() 253 | print(self.name + ": Ruff!") 254 | } 255 | 256 | play = { | ball | 257 | return ball 258 | } 259 | } 260 | 261 | Shepard = class : Dog { 262 | @init = { | name | 263 | # Override superclass constructor 264 | 265 | name += " the shepard" 266 | super([name]) # call superclass constructor 267 | } 268 | 269 | # Inherit bark() from superclass 270 | } 271 | 272 | milki = Labrador("Milki") 273 | barak = Shepard("Barak") 274 | 275 | milki.bark() # Milki: Ruff! 276 | milki.play("the ball") # returns "the ball" 277 | barak.bark() # Barak the shepard: Woof! 278 | ``` 279 | 280 | ### Polymorphism 281 | 282 | Polymorphism in Ribbon is based on "duck typing" - if it quacks like a duck, it's a duck. 283 | 284 | Meaning, a method call on an object is resolved at runtime. If it has a matching method, that method is invoked. 285 | Inheritance isn't needed to acheive polymorphism. 286 | 287 | ```python 288 | Wolf = class { 289 | bark = { 290 | print("Wolf: ahoooo") 291 | } 292 | } 293 | 294 | dogs = [Labrador("Milki"), Shepard("Barak"), Wolf()] 295 | 296 | # prints: 297 | # Milki, Ruff! 298 | # Barak the shepard: Woof! 299 | # Wolf: ahoooo 300 | for dog in dogs { 301 | dog.bark() 302 | } 303 | ``` 304 | 305 | ### Modules 306 | 307 | Ribbon modules are other `.rib` code files that you can access from your file. After a module is imported, its global variables are 308 | exposed for use in the importing module. 309 | 310 | File `program.rib`: 311 | 312 | ```python 313 | import myutils 314 | 315 | my_string = "Hello Ribbon" 316 | tripple = myutils.multiply_string(my_string, 3) 317 | 318 | print(tripple) 319 | ``` 320 | 321 | File `myutils.rib`: 322 | 323 | ```python 324 | multiply_string = { | string, n | 325 | result = "" 326 | i = 0 327 | 328 | while i < n { 329 | result += string 330 | i += 1 331 | } 332 | 333 | return result 334 | } 335 | ``` 336 | 337 | Ribbon has a standard library of modules. When `import`ing a module, if one of a matching name can't be found next to your main program, 338 | the module is searched in the standard library. 339 | 340 | The standard library is currently very minimal. It consists of `math`, `path` and `graphics`. 341 | 342 | * The `math` module offers basic math operations implemented directly in Ribbon, such as square root and power. 343 | * The `path` module offers a few convenience functions for working with file paths. 344 | * The `graphics` module facilitates 2D graphics programming in Ribbon. It is a native module written in C. 345 | 346 | For example: 347 | 348 | ```python 349 | import math 350 | 351 | print(math.sqrt(25)) # 5 352 | ``` 353 | 354 | ### The extension system 355 | 356 | The Ribbon interpreter is extensible through writing custom extension modules in C. 357 | 358 | An extension module is visible to Ribbon code the same way as a normal module. And since it is written in C, it can access resources 359 | and OS functions which may not be available directly to Ribbon code. 360 | 361 | Ribbon includes an extension module named `graphics` in its standard library. [Its source code can be found here](../src/sdl_extension). 362 | This guide doesn't go into detail on developing extension modules. Learning this can be done by studying the source code. 363 | 364 | ### Miscellaneous 365 | 366 | #### Scopes 367 | 368 | Ribbon has only two scopes - functions scope and file scope. As mentioned, functions can be nested. A variable is only available 369 | in the scope it was defined in. 370 | 371 | By default, setting a variable always shadows any outer scope that happens to have a variable of that name. For example: 372 | 373 | ```python 374 | x = 10 375 | 376 | f = { 377 | x = 20 # Creates a new variable. Does not change the outer variable 378 | print(x) # Prints 20 379 | } 380 | 381 | f() 382 | print(x) # Prints 10 383 | ``` 384 | 385 | In case you do need to set an outer variable inside a function, use the `external` keyword: 386 | 387 | ```python 388 | x = 10 389 | 390 | f = { 391 | external x 392 | x = 20 # Sets the value of the outer variable 393 | print(x) # Prints 20 394 | } 395 | 396 | f() 397 | print(x) # Prints 20. f() changed the value of x 398 | ``` 399 | 400 | ***** 401 | 402 | This concludes this guide for programming in Ribbon. There are additional builtin functions and corners of the language. Please 403 | explore the source code to learn more. 404 | -------------------------------------------------------------------------------- /src/table.c: -------------------------------------------------------------------------------- 1 | #include "table.h" 2 | #include "ribbon_object.h" 3 | 4 | #define TABLE_LOAD_FACTOR 0.75 5 | 6 | #if DEBUG_TABLE_STATS 7 | 8 | /* For debugging */ 9 | static size_t times_called = 0; 10 | static size_t times_called_and_found = 0; 11 | static size_t capacity_sum = 0; 12 | static size_t max_capacity = 0; 13 | static size_t bucket_sum = 0; 14 | static size_t avg_bucket_count = 0; 15 | static size_t entries_sum = 0; 16 | static size_t avg_entries_sum = 0; 17 | static double avg_capacity = 0; 18 | 19 | static size_t total_collision_count = 0; 20 | static double avg_collision_count = 0; 21 | 22 | static size_t table_iterate_times_called = 0; 23 | static size_t table_iterate_length_sum = 0; 24 | static double avg_table_iterate_length = 0; 25 | static size_t max_table_iterate_length = 0; 26 | /* ..... */ 27 | 28 | void table_debug_print_general_stats(void) { 29 | printf("find_entry times called: %" PRI_SIZET "\n", times_called); 30 | printf("find_entry times called and found: %" PRI_SIZET "\n", times_called_and_found); 31 | 32 | printf("Sum capacity: %" PRI_SIZET "\n", capacity_sum); 33 | printf("Avg capacity: %g\n", avg_capacity); 34 | printf("Max capacity: %" PRI_SIZET "\n", max_capacity); 35 | 36 | printf("Total collison count: %" PRI_SIZET "\n", total_collision_count); 37 | printf("Avg collison count: %g\n", avg_collision_count); 38 | printf("Collision count / times called and found: %g\n", (double) total_collision_count / (double) times_called_and_found); 39 | 40 | printf("table_iterate times called: %" PRI_SIZET "\n", table_iterate_times_called); 41 | printf("Sum table_iterate lengths: %" PRI_SIZET "\n", table_iterate_length_sum); 42 | printf("Avg table_iterate length: %g\n", avg_table_iterate_length); 43 | printf("Max table_iterate length: %" PRI_SIZET "\n", max_table_iterate_length); 44 | } 45 | 46 | #endif 47 | 48 | static void* allocate_suitably(Table* table, size_t size, const char* what) { 49 | if (!table->is_memory_infrastructure) { 50 | return allocate(size, what); 51 | } 52 | return allocate_no_tracking(size); 53 | } 54 | 55 | static void deallocate_suitably(Table* table, void* pointer, size_t size, const char* what) { 56 | if (!table->is_memory_infrastructure) { 57 | deallocate(pointer, size, what); 58 | return; 59 | } 60 | deallocate_no_tracking(pointer); 61 | } 62 | 63 | void table_init(Table* table) { 64 | table->capacity = 0; 65 | table->count = 0; 66 | table->num_entries = 0; 67 | table->entries = NULL; 68 | table->is_memory_infrastructure = false; 69 | table->is_growing = false; 70 | table->collision_count = 0; 71 | } 72 | 73 | void table_init_memory_infrastructure(Table* table) { 74 | table_init(table); 75 | table->is_memory_infrastructure = true; 76 | } 77 | 78 | Table table_new_empty(void) { 79 | Table table; 80 | table_init(&table); 81 | return table; 82 | } 83 | 84 | static bool keys_equal(Value v1, Value v2) { 85 | int compare_result = -1; 86 | bool compare_success = value_compare(v1, v2, &compare_result); 87 | return compare_success && (compare_result == 0); 88 | } 89 | 90 | static bool is_empty(Entry* e) { 91 | return e->key.type == VALUE_NIL; 92 | } 93 | 94 | static Entry* find_entry(Table* table, Value key) { 95 | unsigned long hash; 96 | if (!value_hash(&key, &hash)) { 97 | FAIL("Couldn't hash in table::find_entry."); /* Temporary? */ 98 | } 99 | 100 | size_t capacity = table->capacity; 101 | Entry* entries = table->entries; 102 | 103 | // size_t slot = hash % table->capacity; 104 | size_t slot = hash & (capacity - 1); 105 | // Entry* entry = &table->entries[slot]; 106 | Entry* entry = &entries[slot]; 107 | 108 | Entry* tombstone = NULL; 109 | 110 | bool collision = false; 111 | 112 | while (true) { 113 | assert(entry->tombstone == 1 || entry->tombstone == 0); 114 | 115 | if (is_empty(entry)) { 116 | if (entry->tombstone == 1) { 117 | if (tombstone == NULL) { 118 | tombstone = entry; 119 | } 120 | } else { 121 | entry = tombstone == NULL ? entry : tombstone; 122 | break; 123 | } 124 | } else { 125 | if (keys_equal(key, entry->key)) { 126 | break; 127 | } 128 | } 129 | 130 | collision = true; 131 | // entry = &table->entries[++slot % table->capacity]; 132 | entry = &entries[++slot % capacity]; 133 | } 134 | 135 | if (collision) { 136 | table->collision_count++; 137 | } 138 | 139 | #if DEBUG_TABLE_STATS 140 | // total_collision_count += table->collision_count; 141 | total_collision_count += collision ? 1 : 0; 142 | times_called++; 143 | if (!is_empty(entry)) { 144 | times_called_and_found++; 145 | } 146 | capacity_sum += table->capacity; 147 | avg_capacity = (double) capacity_sum / (double) times_called; 148 | if (table->capacity > max_capacity) { 149 | max_capacity = table->capacity; 150 | } 151 | avg_collision_count = (double) total_collision_count / (double) times_called; 152 | #endif 153 | 154 | return entry; 155 | } 156 | 157 | static void grow_table(Table* table) { 158 | assert(!table->is_growing); 159 | 160 | table->is_growing = true; 161 | 162 | size_t old_capacity = table->capacity; 163 | Entry* old_entries = table->entries; 164 | 165 | /* TODO: Grow to a prime number here? */ 166 | table->capacity = table->capacity < 8 ? 8 : table->capacity * 2;; 167 | table->count = 0; 168 | table->num_entries = 0; 169 | table->collision_count = 0; 170 | table->entries = allocate_suitably(table, table->capacity * sizeof(Entry), "Hash table array"); 171 | 172 | size_t capacity = table->capacity; 173 | Entry* entries = table->entries; 174 | 175 | for (size_t i = 0; i < capacity; i++) { 176 | entries[i] = (Entry) {.key = MAKE_VALUE_NIL(), .value = MAKE_VALUE_NIL(), .tombstone = 0}; 177 | } 178 | 179 | for (size_t i = 0; i < old_capacity; i++) { 180 | Entry* old_entry = &old_entries[i]; 181 | if (!is_empty(old_entry)) { 182 | assert(old_entry->tombstone == 0); 183 | table_set(table, old_entry->key, old_entry->value); 184 | } 185 | } 186 | 187 | deallocate_suitably(table, old_entries, sizeof(Entry) * old_capacity, "Hash table array"); 188 | 189 | table->is_growing = false; 190 | } 191 | 192 | bool table_get(Table* table, Value key, Value* out) { 193 | if (table->capacity == 0) { 194 | return false; 195 | } 196 | 197 | Entry* entry = find_entry(table, key); 198 | 199 | assert(is_empty(entry) || keys_equal(entry->key, key)); /* Remove later */ 200 | 201 | if (is_empty(entry)) { 202 | return false; 203 | } 204 | 205 | *out = entry->value; 206 | return true; 207 | } 208 | 209 | void table_set(Table* table, struct Value key, Value value) { 210 | if (table->count + 1 >= table->capacity * TABLE_LOAD_FACTOR) { 211 | grow_table(table); 212 | } 213 | 214 | Entry* entry = find_entry(table, key); 215 | assert(is_empty(entry) || keys_equal(entry->key, key)); 216 | 217 | if (is_empty(entry)) { 218 | table->count++; 219 | table->num_entries++; 220 | entry->key = key; 221 | } 222 | 223 | entry->value = value; 224 | entry->tombstone = 0; 225 | } 226 | 227 | bool table_get_cstring_key(Table* table, const char* key, Value* out) { 228 | return table_get(table, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated(key)), out); 229 | } 230 | 231 | void table_set_cstring_key(Table* table, const char* key, Value value) { 232 | table_set(table, MAKE_VALUE_OBJECT(object_string_copy_from_null_terminated(key)), value); 233 | } 234 | 235 | bool table_delete(Table* table, Value key) { 236 | if (table->capacity == 0) { 237 | return false; 238 | } 239 | 240 | Entry* entry = find_entry(table, key); 241 | if (is_empty(entry)) { 242 | return false; 243 | } 244 | 245 | entry->key = MAKE_VALUE_NIL(); 246 | entry->value = MAKE_VALUE_NIL(); 247 | entry->tombstone = 1; 248 | 249 | table->num_entries--; 250 | 251 | return true; 252 | } 253 | 254 | /* A special-case route for cell_table.c solely for optimization reasons. */ 255 | void table_set_value_in_cell(Table* table, Value key, Value value) { 256 | if (table->count + 1 >= table->capacity * TABLE_LOAD_FACTOR) { 257 | grow_table(table); 258 | } 259 | 260 | Entry* entry = find_entry(table, key); 261 | 262 | assert(is_empty(entry) || keys_equal(entry->key, key)); 263 | 264 | if (is_empty(entry)) { 265 | table->count++; 266 | table->num_entries++; 267 | entry->key = key; 268 | entry->value = MAKE_VALUE_OBJECT(object_cell_new(value)); 269 | entry->tombstone = 0; 270 | } else { 271 | assert(entry->tombstone == 0); 272 | assert(object_value_is(entry->value, OBJECT_CELL)); 273 | 274 | ObjectCell* cell = (ObjectCell*) entry->value.as.object; 275 | cell->value = value; 276 | cell->is_filled = true; 277 | } 278 | } 279 | 280 | void table_free(Table* table) { 281 | deallocate_suitably(table, table->entries, table->capacity * sizeof(Entry), "Hash table array"); 282 | table_init(table); 283 | } 284 | 285 | PointerArray table_iterate(Table* table, const char* alloc_string) { 286 | PointerArray array; 287 | pointer_array_init(&array, alloc_string); 288 | 289 | for (size_t i = 0; i < table->capacity; i++) { 290 | Entry* entry = &table->entries[i]; 291 | 292 | if (!is_empty(entry)) { 293 | assert(entry->tombstone == 0); 294 | pointer_array_write(&array, entry); 295 | } 296 | } 297 | 298 | #if DEBUG_TABLE_STATS 299 | 300 | table_iterate_times_called++; 301 | table_iterate_length_sum += array.count; 302 | avg_table_iterate_length = (double) table_iterate_length_sum / (double) table_iterate_times_called; 303 | if (array.count > max_table_iterate_length) { 304 | max_table_iterate_length = array.count; 305 | } 306 | 307 | #endif 308 | 309 | return array; 310 | } 311 | 312 | void table_print(Table* table) { 313 | PointerArray entries = table_iterate(table, "table print table_iterate buffer"); 314 | 315 | printf("["); 316 | for (int i = 0; i < entries.count; i++) { 317 | Entry* entry = entries.values[i]; 318 | Value value = entry->value; 319 | value_print(entry->key); 320 | printf(": "); 321 | value_print(value); 322 | 323 | if (i < entries.count - 1) { 324 | printf(", "); 325 | } 326 | } 327 | printf("]"); 328 | 329 | pointer_array_free(&entries); 330 | } 331 | 332 | void table_print_debug(Table* table) { 333 | printf("Capacity: %" PRI_SIZET " \nCount: %" PRI_SIZET " \n", table->capacity, table->count); 334 | 335 | if (table->capacity > 0) { 336 | printf("Data: \n"); 337 | for (size_t i = 0; i < table->capacity; i++) { 338 | Entry* entry = &table->entries[i]; 339 | if (!is_empty(entry)) { 340 | printf("%" PRI_SIZET " = [Key: ", i); 341 | value_print(entry->key); 342 | printf(", Value: "); 343 | value_print(entry->value); 344 | printf("]\n"); 345 | } 346 | } 347 | } else { 348 | printf("Table is empty.\n"); 349 | } 350 | } 351 | --------------------------------------------------------------------------------