diff --git a/.clang-format b/.clang-format index e8f8d8a..68a9601 100644 --- a/.clang-format +++ b/.clang-format @@ -5,3 +5,5 @@ BreakBeforeBraces: Allman IndentWidth: 2 TabWidth: 2 UseTab: Never +AlignConsecutiveAssignments: Consecutive +IndentCaseLabels: True diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..56124a5 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,18 @@ +skip peek +skip consume +skip next +skip is +skip fzl_mknode +skip fzl_mktoken +skip fzl_symt_put +skip fzl_symt_get +skip add_const +skip push_op +skip push_word +skip fzl_mkenv +skip fzl_freeenv +skip fzl_env_lookup +skip fzl_env_contains +skip fzl_env_storevalue +skip fzl_env_writevalue + diff --git a/.gitignore b/.gitignore index 889eacd..bd3a3a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,17 @@ /* # Dirs +!/docs !/exl !/include !/src # Files !/.clang-format +!/.gdbinit !/.gitignore !/CMakeLists.txt !/Makefile !/README.md +!/README.ru.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 0013498..925fd71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,9 +15,98 @@ add_executable(fzc src/compiler/fzl_walker.c src/compiler/fzl_writer.c src/compiler/utils/fzl_const_pool.c + src/compiler/utils/fzl_symtable.c + src/compiler/utils/fzl_linked.c + src/compiler/utils/fzl_options.c + src/compiler/utils/fzl_ctable.c src/compiler/main.c) -# VM -# add_executable(fzvm -# src/vm/main.c) +# Virtual machine +add_executable(fz + src/vm/fzl_vm.c + src/vm/fzl_vm_env.c + src/vm/fzl_vm_debug.c + src/vm/main.c) + +# +# ========== Compilation tests ========== +# + +enable_testing() + +add_test( + NAME compile_hw + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/hw.fzs" +) +add_test( + NAME compile_print_empty + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/print_empty.fzs" +) +add_test( + NAME compile_problematic_hw + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/problematic_hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/problematic_hw.fzs" +) +add_test( + NAME compile_base_math + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/base_math.fzs" +) +add_test( + NAME compile_variables + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/variables.fzs" +) +add_test( + NAME compile_conditions + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/conditions.fzs" +) +add_test( + NAME compile_fn + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/fn.fzs" +) +add_test( + NAME compile_comment + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/comment.fzs" +) +add_test( + NAME compile_class + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/class.fzs" +) + +set_tests_properties(compile_problematic_hw PROPERTIES WILL_FAIL TRUE) + +# +# ========== Runtime tests ========== +# + +add_test( + NAME runtime_hw + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb" +) +add_test( + NAME runtime_print_empty + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" +) +add_test( + NAME runtime_base_math + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" +) +add_test( + NAME runtime_variables + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" +) +add_test( + NAME runtime_conditions + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" +) +add_test( + NAME runtime_fn + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" +) +add_test( + NAME runtime_comment + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb" +) +add_test( + NAME runtime_class + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" +) diff --git a/Makefile b/Makefile index 6f024a9..304ff91 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: configure build +.PHONY: configure build debug-c debug-vm test configure: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug @@ -6,3 +6,12 @@ configure: build: configure cmake --build build +debug-c: build + gdb --args ./build/fzc ${FILE} + +debug-vm: build + gdb --args ./build/fz ${FILE} + +test: build + ctest --output-on-failure --test-dir build + diff --git a/README.md b/README.md index e69de29..80001d9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,61 @@ +# FromZero / Language + +[README in Russian](./README.ru.md) + +**FromZero/Language** is a package consisting of a JIT compiler and a virtual machine for the FZL language. + +## Features + +At the moment, the language has the following capabilities: + +- Builtin `printline` function for printing argument to stdout +- String and numeric literals +- Arithmetic operations: addition, subtraction, multiplication, division, modulo, power +- Variables declaration with following types: + - `uint`, `int` - for the machine-word size integers + - `i8`..`i64` - integers with specified size + - `u8`..`u64` - unsigned integers with specified size + - `str` - strings + - `bool` - boolean +- Assignments +- Functions +- Classes and objects + +Working examples of code can be found in `exl/` directory. + + +## File extensions + +Standard file extensions: + +- `.fzs` - source code +- `.fzb` - compiled binary file + + +## Building + +1. Clone the repository: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` +2. Build: `make build` + +After this, a "build" directory will appear in the project root. It will contain two executable files: + +- `fzc` - a compiler +- `fz` - virtual machine + +Both executables have usage help that can be displayed using `--help` flag (or `-h`). + + +## Development guideline + +You need a code editor with Clang LSP support because of `.clang-format` file in the project root. +This file provides set up codestyle rules. + +There is two targets in Makefile for debugging purposes: `build-c` for the compiler and `build-vm` for the VM. +Each target can be called with `FILE` variable. For example: + +```shell +$ make debug-c FILE=exl/hw.fzs +``` + +To test new features, use `test` make target and the tests, defined in `CMakeLists.txt`. + diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..875cdb5 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,61 @@ +# FromZero / Language + +[README in English](./README.md) + +**FromZero/Language** - пакет из JIT-компилятора и виртуальной машины для языка FZL. + +## Текущие возможности + +На данный момент язык имеет: + +- Встроенную функцию `printline` для вывода аргумента в stdout +- Строковый, булев и числовой литералы +- Арифметические операции: сложение, вычитание, умножение, деление, модуло, возведение в степень +- Декларация переменных со следующими типами: + - `uint`, `int` - целые числа размером с машинное слово + - `i8`..`i64` - целые числа со знаком + - `u8`..`u64` - целые числа без знака + - `str` - строки + - `bool` - булевы +- Присваивание +- Функции +- Классы и объекты + +Примеры кода лежат в директории `exl/`. + + +## Расширения файлов + +Стандартными для языка являются два расширения файлов: + +- `.fzs` - исходный код +- `.fzb` - скомпилированный бинарный файл + + +## Инструкции по сборке + +1. Склонировать проект: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` +2. Собрать проект: `make build` + +После этого в корне появится директория **"build"**. В ней будут лежать два скомпилированных бинарных файла: + +- `fzc` - компилятор +- `fz` - виртуальная машина + +Оба бинарных файла предоставляют справку по использованию при запуске с флагом `--help` (или `-h`). + + +## Гайдлайн по разработке + +Желательно иметь редактор кода, поддерживающий Clang LSP, так как в проекте есть готовые настройки для +форматтера в виде файла `.clang-format`. + +Для дебаггинга есть два таргета в CMake: `build-c` для компилятора и `build-vm` для виртуальной машины. +Оба таргета позволяют указать переменную с путём файла. Например, так: + +```shell +$ make debug-c FILE=exl/hw.fzs +``` + +Для проверки новых фич, нужно использовать make-таргет `test`, а тесты писать в `CMakeLists.txt`. + diff --git a/docs/grammar.ebnf b/docs/grammar.ebnf new file mode 100644 index 0000000..7d2d23e --- /dev/null +++ b/docs/grammar.ebnf @@ -0,0 +1,6 @@ +program := statement* +statement := expression +expression := call | string_literal +call := name "(" argument-list? ")" +argument-list := string_literal ("," string_literal)* + diff --git a/exl/base_math.fzs b/exl/base_math.fzs new file mode 100644 index 0000000..b7e15c7 --- /dev/null +++ b/exl/base_math.fzs @@ -0,0 +1,7 @@ +printline(2 + 2 * 2); +printline(-1 + 1); +printline(2 ^ 2); +printline(1 + 1 * 1 / 1); +printline(4 % 2); +printline(10 / 2); + diff --git a/exl/class.fzs b/exl/class.fzs new file mode 100644 index 0000000..f73a12d --- /dev/null +++ b/exl/class.fzs @@ -0,0 +1,32 @@ +class Point +{ + int x; + int y; + + // Move point by [dx,dy] + fn move(int dx, int dy) { + this.x = this.x + dx; + this.y = this.y + dy; + } +} + +Point obj = new Point; +obj.x = 1; +obj.y = 2; + +printline("Current X,Y:"); +printline(obj.x); +printline(obj.y); + +// NOTE: WHY THE FUCK THIS FUCKING COMPILER REQUIRES ';' AFTER '='?! +// | +// v +obj.move(9, 8); +printline("X,Y after move:"); +printline(obj.x); +printline(obj.y); + +if (obj.x == 10 && obj.y == 10) + printline("Test passed"); +else printline("Test failed"); + diff --git a/exl/comment.fzs b/exl/comment.fzs new file mode 100644 index 0000000..0fd39ee --- /dev/null +++ b/exl/comment.fzs @@ -0,0 +1,3 @@ +// I'm a comment +printline("Comment is skipped");;;;; + diff --git a/exl/conditions.fzs b/exl/conditions.fzs new file mode 100644 index 0000000..b7c5a83 --- /dev/null +++ b/exl/conditions.fzs @@ -0,0 +1,16 @@ +bool flag = true; + +if (!flag) + printline("buhhh, flag is not true"); +else if (2 + 2 >= 4) +{ + printline ("two plus two is four"); +} +else { + printline("gotcha"); +} + +{ + printline("block after"); +} + diff --git a/exl/fn.fzs b/exl/fn.fzs new file mode 100644 index 0000000..dde5dad --- /dev/null +++ b/exl/fn.fzs @@ -0,0 +1,24 @@ +fn fib(int n) int +{ + if (n <= 1) + return n; + + return fib(n - 1) + fib(n - 2); +} + +fn sum(uint a, uint b) uint +{ + return a + b; +} + +printline("Test #1: fibonacci"); +int result = fib(7); +if (result == 13) + printline("Recurse test passed"); +else printline("Recurse test failed"); + +printline("Test #2: sum"); +if (sum(2, 2) == 4) + printline("Sum test passed"); +else printline("Sum test failed"); + diff --git a/exl/hw.fzl b/exl/hw.fzl deleted file mode 100644 index 173a105..0000000 --- a/exl/hw.fzl +++ /dev/null @@ -1,2 +0,0 @@ -printline('hello, world') - diff --git a/exl/hw.fzs b/exl/hw.fzs new file mode 100644 index 0000000..131b32e --- /dev/null +++ b/exl/hw.fzs @@ -0,0 +1,2 @@ +printline('hello, world'); + diff --git a/exl/print_empty.fzs b/exl/print_empty.fzs new file mode 100644 index 0000000..67c1545 --- /dev/null +++ b/exl/print_empty.fzs @@ -0,0 +1 @@ +printline(""); diff --git a/exl/problematic_hw.fzs b/exl/problematic_hw.fzs new file mode 100644 index 0000000..ec0ce93 --- /dev/null +++ b/exl/problematic_hw.fzs @@ -0,0 +1 @@ +printline("hello); diff --git a/exl/variables.fzs b/exl/variables.fzs new file mode 100644 index 0000000..6ba588d --- /dev/null +++ b/exl/variables.fzs @@ -0,0 +1,13 @@ +uint work_it = 21 * 2 ^ 2; +int make_it = 10; + +u64 do_it; +do_it = 15; + +printline(work_it); +printline(make_it + do_it); + +char make_us; +str floyd = "hello, Pink Floyd!"; +printline(floyd); + diff --git a/include/common/fzl_colors.h b/include/common/fzl_colors.h new file mode 100644 index 0000000..528537d --- /dev/null +++ b/include/common/fzl_colors.h @@ -0,0 +1,10 @@ +#ifndef FZL_COLORS_H +#define FZL_COLORS_H + +#define FZL_CERROR "\033[0;31m" +#define FZL_CWARNING "\033[0;33m" +#define FZL_COK "\033[0;32m" +#define FZL_CDEBUG "\033[0;34m" +#define FZL_CRST "\033[0m" + +#endif // FZL_COLORS_H diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h index aab733f..a0d8ac3 100644 --- a/include/common/fzl_opcodes.h +++ b/include/common/fzl_opcodes.h @@ -4,9 +4,46 @@ // Available opcodes typedef enum { - FZL_OPCODE_LOADCONST, // Load const into T (temp) reg - FZL_OPCODE_PUSHARG, // Push argument from T into args stack - FZL_OPCODE_CALL_BUILTIN // Call builtin function + FZL_OPCODE_LOADCONST = 0xF00001, // Load const into T (temp) reg + FZL_OPCODE_CALL_BUILTIN = 0xF00002, // Call builtin function + FZL_OPCODE_PUSHNUM = 0xF00003, // Push number into stack + FZL_OPCODE_PUSHBOOL = 0xF00004, // Push boolean into stack + + FZL_OPCODE_MK_ENVTABLE = 0xF10001, // Allocate new environment table + FZL_OPCODE_RM_ENVTABLE = 0xF10002, // Free environment table + FZL_OPCODE_STORE = 0xF10003, // Push variable to environment table + FZL_OPCODE_LOOKUP_ENV = 0xF10004, // Lookup name in environment table + FZL_OPCODE_WRITE = 0xF10005, // Wirte into variable + + FZL_OPCODE_JMP = 0xF20001, // Jump to specified IP address + FZL_OPCODE_JZ = 0xF20002, // Jump if top value in dstack is zero + + FZL_OPCODE_CALL = 0xF30001, // Call function + FZL_OPCODE_RET = 0xF30002, // Return from function + + FZL_OPCODE_ALLOC_OBJECT = 0xFA0001, // Allocate N slots + FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack + FZL_OPCODE_SET_FIELD = 0xFA0003, // Set field N of an object at dstack + + FZL_OPCODE_ADD = 0xA00001, // Sum two numbers in dstack + FZL_OPCODE_SUB = 0xA00002, // Subract two numbers in dstack + FZL_OPCODE_MUL = 0xA00003, // Multiply two numbers in dstack + FZL_OPCODE_DIV = 0xA00004, // Divide two numbers in dstack + FZL_OPCODE_POW = 0xA00005, // Power first number in dstack by second + FZL_OPCODE_MOD = 0xA00006, // Get module of two numbers in dstack + FZL_OPCODE_NEG = 0xA00007, // Negotiate number in dstack + + FZL_OPCODE_CMP_EQ = 0xB00001, // Equal + FZL_OPCODE_CMP_NEQ = 0xB00002, // Not equal + FZL_OPCODE_CMP_LT = 0xB00003, // Less than + FZL_OPCODE_CMP_LTEQ = 0xB00004, // Less than or equal + FZL_OPCODE_CMP_GT = 0xB00005, // Greater than + FZL_OPCODE_CMP_GTEQ = 0xB00006, // Greater than or equal + + FZL_OPCODE_AND = 0xC00001, // A && B + FZL_OPCODE_OR = 0xC00002, // A || B + + FZL_OPCODE_NOT = 0xD00001, // !A } fzl_opcode_t; #endif // FZL_OPCODES_H diff --git a/include/common/fzl_types.h b/include/common/fzl_types.h new file mode 100644 index 0000000..346cb96 --- /dev/null +++ b/include/common/fzl_types.h @@ -0,0 +1,25 @@ +#ifndef FZL_TYPES_H +#define FZL_TYPES_H + +typedef enum +{ + FZL_TYPE_NULL, + FZL_TYPE_OBJECT, + + FZL_TYPE_STRING, + FZL_TYPE_CHAR, + FZL_TYPE_BOOL, + + FZL_TYPE_INT8, + FZL_TYPE_UINT8, + FZL_TYPE_INT16, + FZL_TYPE_UINT16, + FZL_TYPE_INT32, + FZL_TYPE_UINT32, + FZL_TYPE_INT64, + FZL_TYPE_UINT64, + FZL_TYPE_INT, // machine word int + FZL_TYPE_UINT // machine word unsigned int +} fzl_type_t; + +#endif // FZL_TYPES_H diff --git a/include/common/fzl_value.h b/include/common/fzl_value.h new file mode 100644 index 0000000..c6e2db4 --- /dev/null +++ b/include/common/fzl_value.h @@ -0,0 +1,44 @@ +#ifndef FZL_VALUE_H +#define FZL_VALUE_H + +#include +#include +#include +#include + +// Value container descriptor +typedef struct +{ + fzl_type_t type; + + union + { + void *obj; + + struct + { + const char *value; + size_t length; + } s; + + char c; + bool b; + + int8_t i8; + uint8_t u8; + + int16_t i16; + uint16_t u16; + + int32_t i32; + uint32_t u32; + + int64_t i64; + uint64_t u64; + + intptr_t i; + uintptr_t u; + } as; +} fzl_value_t; + +#endif // FZL_VALUE_H diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index 023ea0d..f96c307 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -11,6 +11,38 @@ typedef enum FZL_NODETYPE_BLOCK, FZL_NODETYPE_CALL, FZL_NODETYPE_LITERAL, + FZL_NODETYPE_IDENTIFIER, + FZL_NODETYPE_CONDITIONAL, + FZL_NODETYPE_FUNCTION, + FZL_NODETYPE_RETURN, + FZL_NODETYPE_CLASS, + FZL_NODETYPE_FIELD, + FZL_NODETYPE_CONSTRUCT, + + FZL_NODETYPE_ADD, + FZL_NODETYPE_SUB, + FZL_NODETYPE_MUL, + FZL_NODETYPE_DIV, + FZL_NODETYPE_POW, + FZL_NODETYPE_MOD, + + FZL_NODETYPE_UNARY_PLUS, + FZL_NODETYPE_UNARY_MINUS, + + FZL_NODETYPE_OR, + FZL_NODETYPE_AND, + + FZL_NODETYPE_EQUAL, + FZL_NODETYPE_NEQUAL, + FZL_NODETYPE_LESS, + FZL_NODETYPE_LESS_EQUAL, + FZL_NODETYPE_GREATER, + FZL_NODETYPE_GREATER_EQUAL, + + FZL_NODETYPE_NOT, + + FZL_NODETYPE_DECLARATION, + FZL_NODETYPE_ASSIGN } fzl_nodetype_t; // AST node descriptor @@ -23,11 +55,91 @@ typedef struct fzl_node union { + // Array of nested nodes (for blocks, etc) struct { struct fzl_node **entries; size_t entry_count; } list; + + // Call data + struct + { + struct fzl_node *callee; + + struct fzl_node **args; + size_t args_count; + } call; + + // Binary operations + struct + { + struct fzl_node *left; + struct fzl_node *right; + } binary_op; + + // Unary operations + struct + { + struct fzl_node *operand; + } unary_op; + + // Variables declaration and initializing + struct + { + fzl_token_t *type; + struct fzl_node *identifier; + } var; + + // Conditional blocks + struct + { + struct fzl_node *condition; + struct fzl_node *true_block; + struct fzl_node *else_block; + } cond; + + // Function declaration + struct + { + struct fzl_node **args; + size_t args_count; + + struct fzl_node *body; + fzl_token_t *name; + fzl_token_t *type; + } fn; + + // Return value + struct + { + struct fzl_node *value; + } ret; + + // Class declaration + struct + { + fzl_token_t *name; + + struct fzl_node **fields; + size_t fields_count; + + struct fzl_node **methods; + size_t methods_count; + } class; + + // Member access + struct + { + struct fzl_node *parent; + struct fzl_node *field; + } memb; + + // Object construct + struct + { + struct fzl_node *name; + } construct; }; } fzl_node_t; diff --git a/include/compiler/fzl_reader.h b/include/compiler/fzl_reader.h index 818459b..140b0f3 100644 --- a/include/compiler/fzl_reader.h +++ b/include/compiler/fzl_reader.h @@ -2,6 +2,7 @@ #define FZL_READER_H #include +#include #include #include #include @@ -19,13 +20,16 @@ typedef struct fzl_reader_ll_t *first; fzl_reader_ll_t *last; + fzl_linked_t *linked; + char *source; + size_t size; + size_t position; - bool at_end; } fzl_reader_t; // Allocate new reader -fzl_reader_t *fzl_mkreader(char *source); +fzl_reader_t *fzl_mkreader(fzl_linked_t *linked); // Read source file int fzl_read(fzl_reader_t *reader); diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index 3bc5432..30b5f81 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -8,27 +8,69 @@ // Token type typedef enum { - FZL_TOKTYPE_NAME, - FZL_TOKTYPE_LPAIR, - FZL_TOKTYPE_RPAIR, - FZL_TOKTYPE_COMMA, - FZL_TOKTYPE_STR_LITERAL + FZL_TOKTYPE_CLASS, // class + FZL_TOKTYPE_NEW, // new + + FZL_TOKTYPE_FUNCTION, // fn + FZL_TOKTYPE_RETURN, // return + + FZL_TOKTYPE_IF, // if + FZL_TOKTYPE_ELSE, // else + + FZL_TOKTYPE_NAME, // abc + FZL_TOKTYPE_TYPE, // i16 + + FZL_TOKTYPE_DDASH, // // :D + + FZL_TOKTYPE_LPAREN, // ( + FZL_TOKTYPE_RPAREN, // ) + FZL_TOKTYPE_LBRACE, // { + FZL_TOKTYPE_RBRACE, // } + + FZL_TOKTYPE_COMMA, // , + FZL_TOKTYPE_DOT, // . + FZL_TOKTYPE_SEMICOLON, // ; + + FZL_TOKTYPE_STR_LITERAL, // "" + FZL_TOKTYPE_NUM_LITERAL, // 123 + FZL_TOKTYPE_BOOL_LITERAL, // true + + FZL_TOKTYPE_DAMPERSAND, // && + FZL_TOKTYPE_DVBAR, // || + + FZL_TOKTYPE_EQEQ, // == + FZL_TOKTYPE_REQ, // >= + FZL_TOKTYPE_LEQ, // <= + FZL_TOKTYPE_NEQ, // != + FZL_TOKTYPE_LANGLE, // < + FZL_TOKTYPE_RANGLE, // > + FZL_TOKTYPE_NOT, // ! + + FZL_TOKTYPE_STAR, // * + FZL_TOKTYPE_SLASH, // / + FZL_TOKTYPE_PLUS, // + + FZL_TOKTYPE_MINUS, // - + FZL_TOKTYPE_CARET, // ^ + FZL_TOKTYPE_PERCENT, // % + + FZL_TOKTYPE_EQ // = } fzl_toktype_t; // Token descriptor typedef struct { fzl_toktype_t type; - uint16_t i0; - uint16_t i1; + size_t ln; + size_t cn; bool store_value; void *value; uint16_t value_sz; + char *filename; } fzl_token_t; // Allocate new token -fzl_token_t *fzl_mktoken(const fzl_toktype_t type, uint16_t from, uint16_t to, - void *value, size_t value_sz); +fzl_token_t *fzl_mktoken(const fzl_toktype_t type, size_t line, size_t col, + void *value, size_t value_sz, char *filename); // Free token and its contents void fzl_freetoken(fzl_token_t *token); diff --git a/include/compiler/fzl_walker.h b/include/compiler/fzl_walker.h index 0300873..a984e68 100644 --- a/include/compiler/fzl_walker.h +++ b/include/compiler/fzl_walker.h @@ -2,10 +2,44 @@ #define FZL_WALKER_H #include +#include #include #include +#include +#include +#include #include +// Type of context +typedef enum +{ + FZL_WALK_CTX_TYPE_GLOBAL, + FZL_WALK_CTX_TYPE_FN, + FZL_WALK_CTX_TYPE_CLASS, + FZL_WALK_CTX_TYPE_ASSIGN +} fzl_walk_ctx_type_t; + +// Internal walk function result +typedef struct +{ + fzl_type_t type; + + char *error_msg; + bool error; + + char *classname; +} fzl_walk_res_t; + +// Function context descriptor +typedef struct fzl_walk_ctx +{ + fzl_walk_ctx_type_t type; + struct fzl_walk_ctx *parent; + + fzl_node_t *fn; + size_t initial_env_lvl; +} fzl_walk_ctx_t; + // Walker descriptor typedef struct { @@ -15,6 +49,14 @@ typedef struct uint64_t *ops; size_t op_count; size_t op_remainder; + + fzl_symtable_t *syms[64]; + size_t sym_top; + + fzl_walk_ctx_t *ctx; + + fzl_ctable_t *classes[64]; + size_t classes_count; } fzl_walker_t; // Allocate new walker diff --git a/include/compiler/utils/fzl_const_pool.h b/include/compiler/utils/fzl_const_pool.h index 402a934..0d1b128 100644 --- a/include/compiler/utils/fzl_const_pool.h +++ b/include/compiler/utils/fzl_const_pool.h @@ -8,7 +8,8 @@ // Constant types typedef enum { - FZL_CONSTTYPE_STRING + FZL_CONSTTYPE_STRING, + FZL_CONSTTYPE_NUMBER } fzl_consttype_t; // Constant descriptor @@ -23,6 +24,11 @@ typedef struct const char *chars; size_t length; } string; + + struct + { + uint64_t value; + } num; } as; } fzl_const_t; diff --git a/include/compiler/utils/fzl_ctable.h b/include/compiler/utils/fzl_ctable.h new file mode 100644 index 0000000..edfa495 --- /dev/null +++ b/include/compiler/utils/fzl_ctable.h @@ -0,0 +1,30 @@ +#ifndef FZL_CTABLE_H +#define FZL_CTABLE_H + +#include +#include + +// Class field descriptor +typedef struct +{ + char *name; + size_t slot_index; + fzl_type_t type; +} fzl_ctable_field_t; + +// Class table descriptor +typedef struct +{ + char *name; + fzl_ctable_field_t *fields; + size_t fields_count; + size_t fields_remainder; +} fzl_ctable_t; + +// Allocate class table +fzl_ctable_t *fzl_mkctable(char *name); + +// Free class table +void fzl_freectable(fzl_ctable_t *ct); + +#endif // FZL_CTABLE_H diff --git a/include/compiler/utils/fzl_linked.h b/include/compiler/utils/fzl_linked.h new file mode 100644 index 0000000..0c7c360 --- /dev/null +++ b/include/compiler/utils/fzl_linked.h @@ -0,0 +1,49 @@ +#ifndef FZL_LINKED_H +#define FZL_LINKED_H + +#include +#include + +// Linked file descriptor +typedef struct +{ + char *name; + size_t start; + size_t end; +} fzl_linked_entry_t; + +// Linked descriptor +typedef struct +{ + fzl_linked_entry_t *entries; + size_t entries_count; + + char *source; + size_t total_sz; +} fzl_linked_t; + +// Position in file +typedef struct +{ + size_t line; + size_t col; + + char *filename; +} fzl_file_loc; + +// Allocated new linked +fzl_linked_t *fzl_mklinked(); + +// Read specified files +bool fzl_linked_add_files(fzl_linked_t *l, char **files, size_t files_count); + +// Get last error from linked +char *fzl_linked_geterror(); + +// Get filename and position in it by reader cursor +bool fzl_linked_getpos(fzl_linked_t *l, size_t pos, fzl_file_loc *res); + +// Free linked obj +void fzl_freelinked(fzl_linked_t *linked); + +#endif // FZL_LINKED_H diff --git a/include/compiler/utils/fzl_options.h b/include/compiler/utils/fzl_options.h new file mode 100644 index 0000000..896693c --- /dev/null +++ b/include/compiler/utils/fzl_options.h @@ -0,0 +1,25 @@ +#ifndef FZL_OPTIONS_H +#define FZL_OPTIONS_H + +#include +#include + +// Compiler options +typedef struct +{ + char **files; + size_t files_count; + + char *outname; + + bool should_continue; + bool verbose; +} fzl_options_t; + +// Parse command line arguments +fzl_options_t *fzl_mkoptions(int argc, char **argv); + +// Free options +void fzl_freeoptions(fzl_options_t *options); + +#endif // FZL_OPTIONS_H diff --git a/include/compiler/utils/fzl_symtable.h b/include/compiler/utils/fzl_symtable.h new file mode 100644 index 0000000..ebaacdc --- /dev/null +++ b/include/compiler/utils/fzl_symtable.h @@ -0,0 +1,53 @@ +#ifndef FZL_SYMTABLE_H +#define FZL_SYMTABLE_H + +#include +#include +#include +#include + +typedef enum +{ + FZL_SYMTABLE_RESULT_OK, + FZL_SYMTABLE_RESULT_ERR, + FZL_SYMTABLE_RESULT_EXISTS, + FZL_SYMTABLE_RESULT_NOT_OBJECT +} fzl_symtable_res_t; + +// Symbols table entry descriptor +typedef struct +{ + char *name; + fzl_type_t type; + size_t ip; + + char *classname; +} fzl_sym_t; + +// Symbols table descriptor +typedef struct +{ + size_t size; + size_t remainder; + + fzl_sym_t *symbols; +} fzl_symtable_t; + +// Allocate new symbtable +fzl_symtable_t *fzl_mksymt(); + +// Put new entry into symtable +fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, char *name, + fzl_type_t type, uint64_t ip); + +// Assign class name to object type +fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name, + char *classname); + +// Get entry from specified symtable +bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res); + +// Free symbtable +void fzl_freesymt(fzl_symtable_t *symt); + +#endif // FZL_SYMTABLE_H diff --git a/include/vm/fzl_vm.h b/include/vm/fzl_vm.h new file mode 100644 index 0000000..9d0075d --- /dev/null +++ b/include/vm/fzl_vm.h @@ -0,0 +1,53 @@ +#ifndef FZL_VM_H +#define FZL_VM_H + +#include +#include // NOTE: move outside compiler scope to common +#include +#include +#include +#include + +// VM descriptor +typedef struct +{ + fzl_const_t *consts; + size_t consts_count; + + uint64_t *ops; + size_t ops_count; + + size_t ip; + + // Data stack + fzl_value_t dstack[256]; + size_t dtop; + + // Call stack + size_t cstack[256]; + size_t ctop; + + // Environment stack + fzl_vm_env_t *estack[256]; + size_t etop; + + fzl_vm_object_t *objects[256]; + size_t objects_count; +} fzl_vm_t; + +// Allocate FZL VM +fzl_vm_t *fzl_mkvm(const char *filepath); + +// Load and run code +int fzl_vm_run(fzl_vm_t *vm); + +// Get last VM error message +char *fzl_vm_geterror(); + +// Print opcodes sequence +void fzl_vm_print_ops(fzl_vm_t *vm); + +// Free VM +void fzl_freevm(fzl_vm_t *vm); + +#endif // FZL_VM_H diff --git a/include/vm/fzl_vm_env.h b/include/vm/fzl_vm_env.h new file mode 100644 index 0000000..3bd1ecc --- /dev/null +++ b/include/vm/fzl_vm_env.h @@ -0,0 +1,52 @@ +#ifndef FZL_VM_ENV_H +#define FZL_VM_ENV_H + +#include +#include +#include + +typedef enum +{ + FZL_VM_ENV_WRESULT_OK, + FZL_VM_ENV_WRESULT_ERR, + FZL_VM_ENV_WRESULT_ERR_EXISTS, + FZL_VM_ENV_WRESULT_ERR_NOT_FOUND +} fzl_vm_env_wresult_t; + +// Environment table entry descriptor +typedef struct +{ + char *name; + fzl_value_t value; +} fzl_vm_env_entry_t; + +// Environment table descriptor +typedef struct +{ + size_t size; + size_t remainder; + + fzl_vm_env_entry_t *table; +} fzl_vm_env_t; + +// Allocate env table +fzl_vm_env_t *fzl_mkenv(); + +// Try get value by name +bool fzl_env_lookup(fzl_vm_env_t *env, char *name, fzl_value_t *res); + +// Check name in env table +bool fzl_env_contains(fzl_vm_env_t *env, char *name); + +// Store new value +fzl_vm_env_wresult_t fzl_env_storevalue(fzl_vm_env_t *env, char *name, + fzl_value_t val); + +// Overwrite existing value +fzl_vm_env_wresult_t fzl_env_writevalue(fzl_vm_env_t *env, char *name, + fzl_value_t newval); + +// Free env table +void fzl_freeenv(fzl_vm_env_t *env); + +#endif // FZL_VM_ENV_H diff --git a/include/vm/fzl_vm_object.h b/include/vm/fzl_vm_object.h new file mode 100644 index 0000000..67cbff2 --- /dev/null +++ b/include/vm/fzl_vm_object.h @@ -0,0 +1,14 @@ +#ifndef FZL_VM_OBJECT_H +#define FZL_VM_OBJECT_H + +#include +#include + +// Object descriptor +typedef struct +{ + fzl_value_t *values; + size_t values_count; +} fzl_vm_object_t; + +#endif // FZL_VM_OBJECT_H diff --git a/src/compiler/fzl_debug.c b/src/compiler/fzl_debug.c index 8744dac..2a78659 100644 --- a/src/compiler/fzl_debug.c +++ b/src/compiler/fzl_debug.c @@ -1,3 +1,4 @@ +#include "compiler/fzl_parser.h" #include #include #include @@ -31,40 +32,134 @@ void print_node(fzl_node_t *node, int level) switch (node->type) { - case FZL_NODETYPE_CALL: - if (node->list.entry_count > 0) - { - printf("%s Call fn '%s' with args:\n", levelbuf, - (char *)node->token->value); + case FZL_NODETYPE_CONSTRUCT: + printf("%s Construct class '%s':\n", levelbuf, + (char *)node->construct.name->token->value); + break; + + case FZL_NODETYPE_CLASS: + printf("%s Class '%s':\n", levelbuf, (char *)node->token->value); + printf("-%s Fields:\n", levelbuf); + for (size_t i = 0; i < node->class.fields_count; i++) + print_node(node->class.fields[i], level + 2); + printf("-%s Methods:\n", levelbuf); + for (size_t i = 0; i < node->class.methods_count; i++) + print_node(node->class.methods[i], level + 2); + break; + + case FZL_NODETYPE_FIELD: + printf("%s Field '%s' access:\n", levelbuf, + (char *)node->memb.field->token->value); + print_node(node->memb.parent, level + 1); + break; + + case FZL_NODETYPE_DECLARATION: + printf("%s Declare variable with type %d:\n", levelbuf, + node->var.type->type); + print_node(node->var.identifier, level + 1); + break; + + case FZL_NODETYPE_ASSIGN: + printf("%s Assign:\n", levelbuf); + printf("-%s Target:\n", levelbuf); + print_node(node->binary_op.left, level + 2); + printf("-%s Source:\n", levelbuf); + print_node(node->binary_op.right, level + 2); + break; + + case FZL_NODETYPE_CALL: + if (node->call.args_count > 0) + { + printf("%s Call fn '%s' with args:\n", levelbuf, + (char *)node->token->value); + for (size_t i = 0; i < node->call.args_count; i++) + print_node(node->call.args[i], level + 1); + } + else + printf("%s Call fn '%s' w/o args\n", levelbuf, + (char *)node->token->value); + break; + + case FZL_NODETYPE_LITERAL: + printf("%s Literal '%s'\n", levelbuf, (char *)node->token->value); + break; + + case FZL_NODETYPE_CONDITIONAL: + printf("%s Conditional block:\n", levelbuf); + printf("-%s Condition:\n", levelbuf); + print_node(node->cond.condition, level + 2); + printf("-%s True block:\n", levelbuf); + print_node(node->cond.true_block, level + 2); + if (node->cond.else_block) + { + printf("-%s Else block:\n", levelbuf); + print_node(node->cond.else_block, level + 2); + } + break; + + case FZL_NODETYPE_BLOCK: + printf("%s Block: ", levelbuf); + + if (node->list.entry_count < 1) + { + printf("\n"); + break; + } + + printf("\n"); for (size_t i = 0; i < node->list.entry_count; i++) print_node(node->list.entries[i], level + 1); - } - else - printf("%s Call fn '%s' w/o args\n", levelbuf, - (char *)node->token->value); - break; - case FZL_NODETYPE_LITERAL: - printf("%s Literal '%s'\n", levelbuf, (char *)node->token->value); - break; - - case FZL_NODETYPE_BLOCK: - printf("%s Block: ", levelbuf); - - if (node->list.entry_count < 1) - { - printf("\n"); break; - } - printf("\n"); - for (size_t i = 0; i < node->list.entry_count; i++) - print_node(node->list.entries[i], level + 1); + case FZL_NODETYPE_NOT: + printf("%s Inverse:\n", levelbuf); + print_node(node->unary_op.operand, level + 1); + break; - break; + case FZL_NODETYPE_FUNCTION: + printf("%s Function named '%s' with ret type '%s':\n", levelbuf, + (char *)node->fn.name->value, (char *)node->fn.type->value); + printf("-%s Args:\n", levelbuf); + for (size_t i = 0; i < node->fn.args_count; i++) + print_node(node->fn.args[i], level + 2); + printf("-%s Body:\n", levelbuf); + print_node(node->fn.body, level + 2); + break; - default: - printf("%s Secure node\n", levelbuf); + case FZL_NODETYPE_RETURN: + printf("%s Return:\n", levelbuf); + if (node->ret.value) + print_node(node->ret.value, level + 1); + break; + + case FZL_NODETYPE_AND: + case FZL_NODETYPE_OR: + case FZL_NODETYPE_EQUAL: + case FZL_NODETYPE_NEQUAL: + case FZL_NODETYPE_LESS: + case FZL_NODETYPE_LESS_EQUAL: + case FZL_NODETYPE_GREATER: + case FZL_NODETYPE_GREATER_EQUAL: + case FZL_NODETYPE_ADD: + case FZL_NODETYPE_SUB: + case FZL_NODETYPE_MUL: + case FZL_NODETYPE_DIV: + case FZL_NODETYPE_POW: + case FZL_NODETYPE_MOD: + printf("%s Binary op (%d)\n", levelbuf, node->type); + printf("-%s LEFT:\n", levelbuf); + print_node(node->binary_op.left, level + 2); + printf("-%s RIGHT:\n", levelbuf); + print_node(node->binary_op.right, level + 2); + break; + + case FZL_NODETYPE_IDENTIFIER: + printf("%s Identifier ('%s')\n", levelbuf, (char *)node->token->value); + break; + + default: + printf("%s Secure node\n", levelbuf); } } diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index b93ab88..8c6f8ba 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -1,8 +1,13 @@ +#include "compiler/fzl_token.h" #include +#include +#include #include #include +#include char plast_err[256]; +fzl_token_t *last_known_tok = NULL; fzl_parser_t *fzl_mkparser(fzl_reader_ll_t *first) { @@ -16,7 +21,7 @@ fzl_parser_t *fzl_mkparser(fzl_reader_ll_t *first) if (!parser) return NULL; - parser->root = NULL; + parser->root = NULL; parser->cursor = first->next; if (!parser->cursor) return NULL; @@ -33,10 +38,10 @@ fzl_node_t *fzl_mknode(fzl_nodetype_t type, fzl_token_t *token) if (!node) return NULL; - node->type = type; - node->token = token; + node->type = type; + node->token = token; node->parent = NULL; - node->next = NULL; + node->next = NULL; return node; } @@ -44,15 +49,17 @@ fzl_node_t *fzl_mknode(fzl_nodetype_t type, fzl_token_t *token) // Get token at cursor static fzl_token_t *peek(fzl_parser_t *parser) { - if (!parser->cursor) + if (!parser || !parser->cursor) return NULL; + + last_known_tok = parser->cursor->token; return parser->cursor->token; } // Get token at (cursor + 1) static fzl_token_t *next(fzl_parser_t *parser) { - if (!parser->cursor) + if (!parser || !parser->cursor) return NULL; fzl_reader_ll_t *next = parser->cursor->next; @@ -65,105 +72,934 @@ static fzl_token_t *next(fzl_parser_t *parser) // Consume token at cursor and move to the next static fzl_token_t *consume(fzl_parser_t *parser) { - if (!parser->cursor) + if (!parser || !parser->cursor || !parser->cursor->token) return NULL; + fzl_token_t *tok = parser->cursor->token; - parser->cursor = parser->cursor->next; + parser->cursor = parser->cursor->next; return tok; } +// Check if current token have the same type as specified +static bool is(fzl_parser_t *parser, fzl_toktype_t type) +{ + if (!parser) + return false; + + fzl_token_t *tok = peek(parser); + if (!tok) + return false; + + return tok->type == type; +} + +// Convert arithmetic operator token type to node type +static fzl_nodetype_t math_op_to_type(fzl_toktype_t type) +{ + switch (type) + { + case FZL_TOKTYPE_PLUS: + return FZL_NODETYPE_ADD; + case FZL_TOKTYPE_MINUS: + return FZL_NODETYPE_SUB; + case FZL_TOKTYPE_STAR: + return FZL_NODETYPE_MUL; + case FZL_TOKTYPE_SLASH: + return FZL_NODETYPE_DIV; + case FZL_TOKTYPE_CARET: + return FZL_NODETYPE_POW; + case FZL_TOKTYPE_PERCENT: + return FZL_NODETYPE_MOD; + + default: + return 0; + } +} + +static fzl_nodetype_t unary_op_to_type(fzl_toktype_t type) +{ + switch (type) + { + case FZL_TOKTYPE_PLUS: + return FZL_NODETYPE_UNARY_PLUS; + case FZL_TOKTYPE_MINUS: + return FZL_NODETYPE_UNARY_MINUS; + case FZL_TOKTYPE_NOT: + return FZL_NODETYPE_NOT; + + default: + return 0; + } +} + +fzl_node_t *parse_expression(fzl_parser_t *parser); + // Parse primary fzl_node_t *parse_primary(fzl_parser_t *parser) { - if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_STR_LITERAL) + if (!parser || !peek(parser)) + return NULL; + + if (is(parser, FZL_TOKTYPE_STR_LITERAL)) { fzl_token_t *tok = consume(parser); return fzl_mknode(FZL_NODETYPE_LITERAL, tok); } - - snprintf(plast_err, sizeof(plast_err), "failed to parse primary at %d", - peek(parser)->i0); - return NULL; -} - -fzl_node_t *parse_expression(fzl_parser_t *parser, int precedence); - -// Parse call expression -fzl_node_t *parse_call(fzl_parser_t *parser) -{ - fzl_token_t *tok = consume(parser); // - consume(parser); // ( - - fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CALL, tok); - node->list.entry_count = 0; - - size_t capacity = 4; - node->list.entries = malloc(capacity * sizeof(fzl_node_t *)); - node->list.entry_count = 0; - - if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_RPAIR) + else if (is(parser, FZL_TOKTYPE_NUM_LITERAL)) { + fzl_token_t *tok = consume(parser); + return fzl_mknode(FZL_NODETYPE_LITERAL, tok); + } + else if (is(parser, FZL_TOKTYPE_BOOL_LITERAL)) + { + fzl_token_t *tok = consume(parser); + return fzl_mknode(FZL_NODETYPE_LITERAL, tok); + } + else if (is(parser, FZL_TOKTYPE_LPAREN)) + { + consume(parser); + fzl_node_t *node = parse_expression(parser); + if (!is(parser, FZL_TOKTYPE_RPAREN)) + { + snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' at %ld:%ld", + peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + return NULL; + } consume(parser); return node; } - - while (peek(parser) && peek(parser)->type != FZL_TOKTYPE_RPAIR) + else if (is(parser, FZL_TOKTYPE_NAME)) { - fzl_node_t *arg = parse_expression(parser, 0); - if (!arg) - return NULL; + fzl_token_t *name = consume(parser); + if (!is(parser, FZL_TOKTYPE_NAME)) + return fzl_mknode(FZL_NODETYPE_IDENTIFIER, name); - if (node->list.entry_count == capacity) - { - capacity *= 2; - node->list.entries = - realloc(node->list.entries, capacity * sizeof(fzl_node_t *)); - } - - node->list.entries[node->list.entry_count++] = arg; - - if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_COMMA) - consume(parser); - else if (peek(parser) && peek(parser)->type != FZL_TOKTYPE_RPAIR) + fzl_token_t *realname = consume(parser); // :D + fzl_node_t *decl = fzl_mknode(FZL_NODETYPE_DECLARATION, name); + decl->var.type = name; + decl->var.identifier = fzl_mknode(FZL_NODETYPE_IDENTIFIER, realname); + return decl; + } + else if (is(parser, FZL_TOKTYPE_TYPE)) + { + fzl_token_t *type = consume(parser); + fzl_token_t *name = consume(parser); + if (name->type != FZL_TOKTYPE_NAME) { snprintf(plast_err, sizeof(plast_err), - "expected ',' or ')' after arg list"); + "'%s': expected name after type at %ld:%ld", type->filename, + type->ln, type->cn); return NULL; } + + fzl_node_t *decl = fzl_mknode(FZL_NODETYPE_DECLARATION, type); + decl->var.type = type; + decl->var.identifier = fzl_mknode(FZL_NODETYPE_IDENTIFIER, name); + return decl; + } + + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': failed to parse primary at %ld:%ld", peek(parser)->filename, + peek(parser)->ln, peek(parser)->cn); + return NULL; +} + +// Parse postfix expressions (parens, index access, dots) +fzl_node_t *parse_postfix(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_primary(parser); + fzl_node_t *right = NULL; + + if (!left) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': failed to parse postfix at %ld:%ld", + peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + if (is(parser, FZL_TOKTYPE_LPAREN) || is(parser, FZL_TOKTYPE_DOT)) + { + while (is(parser, FZL_TOKTYPE_LPAREN) || is(parser, FZL_TOKTYPE_DOT)) + { + fzl_token_t *post = consume(parser); + + if (post->type == FZL_TOKTYPE_LPAREN) + { + size_t args_capacity = 2; + size_t args_count = 0; + fzl_node_t **args = malloc(args_capacity * sizeof(fzl_node_t *)); + + while (!is(parser, FZL_TOKTYPE_RPAREN)) + { + fzl_node_t *arg = parse_expression(parser); + if (!arg) + { + free(args); + return NULL; + } + + if (args_capacity <= args_count) + { + args_capacity *= 2; + args = realloc(args, args_capacity * sizeof(fzl_node_t)); + } + + args[args_count] = arg; + args_count++; + + if (is(parser, FZL_TOKTYPE_COMMA)) + consume(parser); + } + + fzl_token_t *rparen = consume(parser); + if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) + { + free(args); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected ')' after argument list at %ld:%ld", + peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CALL, left->token); + node->call.callee = left; + node->call.args = args; + node->call.args_count = args_count; + + left = node; + } + else if (post->type == FZL_TOKTYPE_DOT) + { + if (!is(parser, FZL_TOKTYPE_NAME)) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected name after '.' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + + fzl_token_t *name = consume(parser); + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FIELD, name); + node->memb.parent = left; + node->memb.field = fzl_mknode(FZL_NODETYPE_IDENTIFIER, name); + + left = node; + } + } } - consume(parser); + return left; +} + +// Parse unary operations +fzl_node_t *parse_unary(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + if (!is(parser, FZL_TOKTYPE_MINUS) && !is(parser, FZL_TOKTYPE_PLUS) && + !is(parser, FZL_TOKTYPE_NOT)) + return parse_postfix(parser); + + fzl_token_t *op = consume(parser); + fzl_node_t *operand = parse_unary(parser); + + if (!operand) + return NULL; + + fzl_node_t *result = fzl_mknode(unary_op_to_type(op->type), op); + result->unary_op.operand = operand; + + return result; +} + +fzl_node_t *parse_power(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_unary(parser); + fzl_node_t *right = NULL; + + if (!left) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': failed to parse power at %ld:%ld", peek(parser)->filename, + peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + if (is(parser, FZL_TOKTYPE_CARET)) + { + fzl_token_t *caret = consume(parser); + + right = parse_unary(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': failed to parse right side of power at %ld:%ld", + peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_POW, caret); + node->binary_op.left = left; + node->binary_op.right = right; + + left = node; + } + + return left; +} + +// Parse FACTOR: multiplication and division +fzl_node_t *parse_factor(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_power(parser); + fzl_node_t *right = NULL; + + if (!left) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': invalid expression at %ld:%ld", peek(parser)->filename, + peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + while (is(parser, FZL_TOKTYPE_STAR) || is(parser, FZL_TOKTYPE_SLASH) || + is(parser, FZL_TOKTYPE_PERCENT)) + { + fzl_token_t *op = consume(parser); + + right = parse_factor(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': unfinished factor (mul/div) expression at %ld:%ld", + left->token->filename, left->token->ln, left->token->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(math_op_to_type(op->type), op); + node->binary_op.left = left; + node->binary_op.right = right; + left = node; + } + + return left; +} + +// Parse TERM: addition and subtraction +fzl_node_t *parse_term(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_factor(parser); + fzl_node_t *right = NULL; + + if (!left) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': invalid expression at %ld:%ld", peek(parser)->filename, + peek(parser)->ln, peek(parser)->cn); + return NULL; + } + + while (is(parser, FZL_TOKTYPE_PLUS) || is(parser, FZL_TOKTYPE_MINUS)) + { + fzl_token_t *op = consume(parser); + + right = parse_factor(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': unfinished term (add/sub) expression at %ld:%ld", + left->token->filename, left->token->ln, left->token->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(math_op_to_type(op->type), op); + node->binary_op.left = left; + node->binary_op.right = right; + left = node; + } + + return left; +} + +// Parse conditional operations +fzl_node_t *parse_cond(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_term(parser); + fzl_node_t *right = NULL; + + if (!left) + return NULL; + + while (is(parser, FZL_TOKTYPE_EQEQ) || is(parser, FZL_TOKTYPE_NEQ) || + is(parser, FZL_TOKTYPE_LANGLE) || is(parser, FZL_TOKTYPE_LEQ) || + is(parser, FZL_TOKTYPE_RANGLE) || is(parser, FZL_TOKTYPE_REQ)) + { + fzl_token_t *op = consume(parser); + right = parse_term(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': unfinished expression at %ld:%ld", op->filename, op->ln, + op->cn); + return NULL; + } + + fzl_node_t *result; + switch (op->type) + { + case FZL_TOKTYPE_EQEQ: + result = fzl_mknode(FZL_NODETYPE_EQUAL, op); + break; + case FZL_TOKTYPE_NEQ: + result = fzl_mknode(FZL_NODETYPE_NEQUAL, op); + break; + case FZL_TOKTYPE_LANGLE: + result = fzl_mknode(FZL_NODETYPE_LESS, op); + break; + case FZL_TOKTYPE_LEQ: + result = fzl_mknode(FZL_NODETYPE_LESS_EQUAL, op); + break; + case FZL_TOKTYPE_RANGLE: + result = fzl_mknode(FZL_NODETYPE_GREATER, op); + break; + case FZL_TOKTYPE_REQ: + result = fzl_mknode(FZL_NODETYPE_GREATER_EQUAL, op); + break; + default: + return NULL; + } + + result->binary_op.left = left; + result->binary_op.right = right; + left = result; + } + + return left; +} + +// Parse conditional && +fzl_node_t *parse_and(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_cond(parser); + fzl_node_t *right = NULL; + + if (!left) + return NULL; + + while (is(parser, FZL_TOKTYPE_DAMPERSAND)) + { + fzl_token_t * or = consume(parser); + right = parse_cond(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, + or->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_AND, or); + node->binary_op.left = left; + node->binary_op.right = right; + left = node; + } + + return left; +} + +// Parse conditional || +fzl_node_t *parse_or(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_node_t *left = parse_and(parser); + fzl_node_t *right = NULL; + + if (!left) + return NULL; + + while (is(parser, FZL_TOKTYPE_DVBAR)) + { + fzl_token_t * or = consume(parser); + right = parse_and(parser); + if (!right) + { + if (!plast_err[0]) + snprintf(plast_err, sizeof(plast_err), + "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, + or->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_OR, or); + node->binary_op.left = left; + node->binary_op.right = right; + left = node; + } + + return left; +} + +// Object construct parsing +fzl_node_t *parse_new(fzl_parser_t *parser) +{ + if (!is(parser, FZL_TOKTYPE_NEW)) + return parse_or(parser); + + fzl_token_t *new = consume(parser); + fzl_node_t *objname = parse_or(parser); + if (objname->type != FZL_NODETYPE_IDENTIFIER && + objname->type != FZL_NODETYPE_FIELD) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expect class name after 'new' keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CONSTRUCT, new); + node->construct.name = objname; + return node; } -// Expression parsing -fzl_node_t *parse_expression(fzl_parser_t *parser, int precedence) +// Assignments parsing +fzl_node_t *parse_assignment(fzl_parser_t *parser) { - if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_NAME) - { - if (next(parser) && next(parser)->type == FZL_TOKTYPE_LPAIR) - return parse_call(parser); - } - else if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_STR_LITERAL) - return parse_primary(parser); + fzl_node_t *left = parse_new(parser); + if (!left) + return NULL; - snprintf(plast_err, sizeof(plast_err), "failed to parse expression at %d", - peek(parser)->i0); - return NULL; + if (is(parser, FZL_TOKTYPE_EQ)) + { + fzl_token_t *asn = consume(parser); + if (left->type != FZL_NODETYPE_IDENTIFIER && + left->type != FZL_NODETYPE_DECLARATION && + left->type != FZL_NODETYPE_FIELD) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': cannot assign to '%s' at %ld:%ld", left->token->filename, + (char *)left->token->value, asn->ln, asn->cn); + return NULL; + } + + fzl_node_t *right = parse_assignment(parser); + if (!right) + return NULL; + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_ASSIGN, asn); + node->binary_op.left = left; + node->binary_op.right = right; + + left = node; + } + + return left; +} + +// Expression parsing +fzl_node_t *parse_expression(fzl_parser_t *parser) +{ + return parse_assignment(parser); +} + +fzl_node_t *parse_statement(fzl_parser_t *parser); + +// Parse class definition +fzl_node_t *parse_class(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_token_t *class = consume(parser); + fzl_token_t *name = consume(parser); + if (name->type != FZL_TOKTYPE_NAME) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected class name at %ld:%ld", name->filename, name->ln, + name->cn); + return NULL; + } + + fzl_token_t *lbrace = consume(parser); + if (lbrace->type != FZL_TOKTYPE_LBRACE) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '{' after class name at %ld:%ld", lbrace->filename, + lbrace->ln, lbrace->cn); + return NULL; + } + + size_t fremainder = 2; + size_t fcount = 0; + fzl_node_t **fields = malloc(fremainder * sizeof(fzl_node_t *)); + if (!fields) + { + snprintf(plast_err, sizeof(plast_err), + "failed to allocate space for class fields"); + return NULL; + } + + size_t mremainder = 2; + size_t mcount = 0; + fzl_node_t **methods = malloc(mremainder * sizeof(fzl_node_t *)); + if (!methods) + { + snprintf(plast_err, sizeof(plast_err), + "failed to allocate space for class fields"); + free(fields); + return NULL; + } + + while (!is(parser, FZL_TOKTYPE_RBRACE)) + { + fzl_node_t *node = parse_statement(parser); + if (!node) + return NULL; + + if (node->type == FZL_NODETYPE_DECLARATION) + { + if (fremainder <= fcount) + { + fremainder *= 2; + fields = realloc(fields, fremainder * sizeof(fzl_node_t *)); + if (!fields) + { + snprintf(plast_err, sizeof(plast_err), + "failed to resize fields array"); + free(methods); + return NULL; + } + } + + fields[fcount++] = node; + } + else if (node->type == FZL_NODETYPE_FUNCTION) + { + if (mremainder <= mcount) + { + mremainder *= 2; + methods = realloc(methods, mremainder * sizeof(fzl_node_t *)); + if (!methods) + { + snprintf(plast_err, sizeof(plast_err), + "failed to resize methods array"); + free(fields); + return NULL; + } + } + + fzl_node_t **new_args = realloc(node->fn.args, ++node->fn.args_count); + memcpy(&new_args[1], new_args, + (node->fn.args_count - 1) * sizeof(fzl_node_t *)); + + fzl_token_t *this_tok = + fzl_mktoken(FZL_TOKTYPE_NAME, node->fn.name->ln, node->fn.name->cn, + "this", 4, node->fn.name->filename); + fzl_node_t *this_id = fzl_mknode(FZL_NODETYPE_IDENTIFIER, this_tok); + fzl_node_t *this_dec = fzl_mknode(FZL_NODETYPE_DECLARATION, name); + this_dec->var.type = name; + this_dec->var.identifier = this_id; + + new_args[0] = this_dec; + methods[mcount++] = node; + } + else + { + snprintf(plast_err, sizeof(plast_err), + "'%s': unknown type of class member at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + free(fields); + free(methods); + return NULL; + } + } + fzl_token_t *rbra = consume(parser); + if (rbra->type != FZL_TOKTYPE_RBRACE) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '}' after class definition at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + free(fields); + free(methods); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CLASS, class); + node->class.name = name; + node->class.fields = fields; + node->class.fields_count = fcount; + node->class.methods = methods; + node->class.methods_count = mcount; + + return node; +} + +// Functions parsing +fzl_node_t *parse_function(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_token_t *fn = consume(parser); + fzl_token_t *name = consume(parser); + if (!name || name->type != FZL_TOKTYPE_NAME) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected function name at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_token_t *lpar = consume(parser); + if (!lpar || lpar->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), "'%s': expected '(' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + size_t remainder = 1; + size_t count = 0; + fzl_node_t **args = malloc(remainder * sizeof(fzl_node_t *)); + + while (peek(parser) && !is(parser, FZL_TOKTYPE_RPAREN)) + { + fzl_node_t *arg = parse_expression(parser); + if (!arg) + return NULL; + + if (remainder <= count) + { + remainder *= 2; + args = realloc(args, remainder * sizeof(fzl_node_t *)); + } + + args[count++] = arg; + + if (is(parser, FZL_TOKTYPE_COMMA)) + consume(parser); + } + + fzl_token_t *rpar = consume(parser); + if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected ')' after argument list at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_token_t *type; + if (is(parser, FZL_TOKTYPE_TYPE) || is(parser, FZL_TOKTYPE_NAME)) + type = consume(parser); + + if (!type) + type = fzl_mktoken(FZL_TOKTYPE_TYPE, last_known_tok->ln, last_known_tok->cn, + "null", 4, last_known_tok->filename); + + if (!is(parser, FZL_TOKTYPE_LBRACE)) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '{' after function declaration at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *body = parse_statement(parser); + if (!body) + return NULL; + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FUNCTION, name); + node->fn.args = args; + node->fn.args_count = count; + node->fn.body = body; + node->fn.name = name; + node->fn.type = type; + + return node; +} + +// Return parsing +fzl_node_t *parse_return(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_token_t *ret = consume(parser); + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_RETURN, ret); + + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + fzl_node_t *retval = parse_expression(parser); + if (!retval) + return NULL; + + node->ret.value = retval; + } + + return node; +} + +fzl_node_t *parse_if(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_token_t *if_tok = consume(parser); + fzl_token_t *lparen = consume(parser); + if (lparen->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '(' before condition at %ld:%ld", lparen->filename, + lparen->ln, lparen->cn); + return NULL; + } + + fzl_node_t *condition = parse_expression(parser); + if (!condition) + return NULL; + + fzl_token_t *rparen = consume(parser); + if (rparen->type != FZL_TOKTYPE_RPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected ')' after condition at %ld:%ld", rparen->filename, + rparen->ln, rparen->cn); + return NULL; + } + + fzl_node_t *else_block = NULL; + fzl_node_t *true_block = parse_statement(parser); + if (!true_block) + return NULL; + + if (is(parser, FZL_TOKTYPE_ELSE)) + { + consume(parser); // else + else_block = parse_statement(parser); + if (!else_block) + return NULL; + } + + fzl_node_t *stmt_node = fzl_mknode(FZL_NODETYPE_CONDITIONAL, if_tok); + stmt_node->cond.condition = condition; + stmt_node->cond.true_block = true_block; + stmt_node->cond.else_block = else_block; + + return stmt_node; +} + +// Block statement parsing +fzl_node_t *parse_block(fzl_parser_t *parser) +{ + if (!parser) + return NULL; + + fzl_token_t *block_tok = consume(parser); + + size_t remainder = 2; + size_t count = 0; + fzl_node_t **stmts = malloc(remainder * sizeof(fzl_node_t *)); + + while (peek(parser) && !is(parser, FZL_TOKTYPE_RBRACE)) + { + fzl_node_t *node = parse_statement(parser); + if (!node) + return NULL; + + if (remainder <= count) + { + remainder *= 2; + stmts = realloc(stmts, remainder * sizeof(fzl_node_t *)); + } + + stmts[count++] = node; + } + fzl_token_t *rbrace = consume(parser); + if (rbrace && rbrace->type != FZL_TOKTYPE_RBRACE) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': missing '}' after block at %ld:%ld", rbrace->filename, + rbrace->ln, rbrace->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_BLOCK, block_tok); + node->list.entries = stmts; + node->list.entry_count = count; + return node; } // Statement parsing fzl_node_t *parse_statement(fzl_parser_t *parser) { - return parse_expression(parser, 0); + fzl_node_t *res = NULL; + + if (is(parser, FZL_TOKTYPE_CLASS)) + return parse_class(parser); + else if (is(parser, FZL_TOKTYPE_FUNCTION)) + return parse_function(parser); + else if (is(parser, FZL_TOKTYPE_RETURN)) + res = parse_return(parser); + else if (is(parser, FZL_TOKTYPE_IF)) + return parse_if(parser); + else if (is(parser, FZL_TOKTYPE_LBRACE)) + return parse_block(parser); + + if (!res) + res = parse_expression(parser); + + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + snprintf(plast_err, sizeof(plast_err), "'%s': missing ';' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + while (is(parser, FZL_TOKTYPE_SEMICOLON)) + consume(parser); + + return res; } // Internal parsing entry point fzl_node_t *parse_program(fzl_parser_t *parser) { fzl_node_t *first = NULL; - fzl_node_t *last = NULL; - fzl_node_t *node = NULL; + fzl_node_t *last = NULL; + fzl_node_t *node = NULL; while (peek(parser) != NULL) { node = parse_statement(parser); @@ -173,18 +1009,18 @@ fzl_node_t *parse_program(fzl_parser_t *parser) if (!last) { first = node; - last = node; + last = node; } else { last->next = node; - last = node; + last = node; } } if (!first) { - snprintf(plast_err, sizeof(plast_err), "empty ;("); + snprintf(plast_err, sizeof(plast_err), "nothing to parse >:("); return NULL; } @@ -220,7 +1056,7 @@ void fzl_freeast(fzl_node_t *root) while (root) { - if (root->type == FZL_NODETYPE_BLOCK || root->type == FZL_NODETYPE_CALL) + if (root->type == FZL_NODETYPE_BLOCK) { if (root->list.entries) { @@ -231,9 +1067,69 @@ void fzl_freeast(fzl_node_t *root) free(root->list.entries); } } + else if (root->type == FZL_NODETYPE_CALL) + { + if (root->call.args_count) + { + for (size_t i = 0; i < root->call.args_count; i++) + if (root->call.args[i]) + fzl_freeast(root->call.args[i]); + + free(root->call.args); + } + } + else if (root->type == FZL_NODETYPE_ADD || root->type == FZL_NODETYPE_SUB || + root->type == FZL_NODETYPE_MUL || root->type == FZL_NODETYPE_DIV || + root->type == FZL_NODETYPE_POW || root->type == FZL_NODETYPE_MOD) + { + if (root->binary_op.left) + fzl_freeast(root->binary_op.left); + if (root->binary_op.right) + fzl_freeast(root->binary_op.right); + } + else if (root->type == FZL_NODETYPE_UNARY_PLUS || + root->type == FZL_NODETYPE_UNARY_MINUS) + { + if (root->unary_op.operand) + fzl_freeast(root->unary_op.operand); + } + else if (root->type == FZL_NODETYPE_FUNCTION) + { + if (root->fn.args_count) + for (size_t i = 0; i < root->fn.args_count; i++) + fzl_freenode(root->fn.args[i]); + free(root->fn.args); + fzl_freenode(root->fn.body); + } + else if (root->type == FZL_NODETYPE_RETURN) + { + if (root->ret.value) + fzl_freenode(root->ret.value); + } + else if (root->type == FZL_NODETYPE_CLASS) + { + if (root->class.fields_count) + for (size_t i = 0; i < root->class.fields_count; i++) + fzl_freenode(root->class.fields[i]); + free(root->class.fields); + + if (root->class.methods_count) + for (size_t i = 0; i < root->class.methods_count; i++) + fzl_freenode(root->class.methods[i]); + free(root->class.methods); + } + else if (root->type == FZL_NODETYPE_CONSTRUCT) + { + fzl_freenode(root->construct.name); + } + else if (root->type == FZL_NODETYPE_FIELD) + { + fzl_freenode(root->memb.field); + fzl_freenode(root->memb.parent); + } - fzl_freenode(root); root = root->next; + free(root); } } diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index 31f7437..bc4e3e5 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -1,4 +1,8 @@ +#include "compiler/fzl_token.h" +#include "compiler/utils/fzl_linked.h" #include +#include +#include #include #include #include @@ -6,24 +10,35 @@ // Last error from reader static char last_err[256] = {0}; -fzl_reader_t *fzl_mkreader(char *source) +fzl_reader_t *fzl_mkreader(fzl_linked_t *linked) { fzl_reader_ll_t *first_node = malloc(sizeof(fzl_reader_ll_t)); - first_node->token = NULL; - first_node->next = NULL; + first_node->token = NULL; + first_node->next = NULL; fzl_reader_t *reader = malloc(sizeof(fzl_reader_t)); - reader->first = first_node; - reader->last = first_node; - reader->source = source; - reader->position = 0; - reader->at_end = true; + reader->first = first_node; + reader->last = first_node; + reader->linked = linked; + reader->source = linked->source; + reader->size = linked->total_sz; + reader->position = 0; return reader; } // --- Read zone --- +// Move cursor forward +// @deprecated +static size_t move(fzl_reader_t *reader, size_t len) +{ + if (!reader) + return 0; + + return reader->position += len; +} + // Try parse name fzl_token_t *parse_name(fzl_reader_t *reader) { @@ -35,16 +50,54 @@ fzl_token_t *parse_name(fzl_reader_t *reader) lpos++; size++; } while ((reader->source[lpos] >= 'A' && reader->source[lpos] <= 'Z') || - (reader->source[lpos] >= 'a' && reader->source[lpos] <= 'z')); + (reader->source[lpos] >= 'a' && reader->source[lpos] <= 'z') || + (reader->source[lpos] >= '0' && reader->source[lpos] <= '9') || + (reader->source[lpos] == '_')); - char *value = malloc(size); + char *value = malloc(size + 1); memcpy(value, reader->source + reader->position, size); + value[size] = '\0'; size_t start = reader->position; - reader->position += size; + move(reader, size); - fzl_token_t *tok = - fzl_mktoken(FZL_TOKTYPE_NAME, start, reader->position - 1, value, size); + fzl_token_t *tok; + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, start, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + if (strcmp(value, "class") == 0) + tok = + fzl_mktoken(FZL_TOKTYPE_CLASS, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "new") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_NEW, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "fn") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_FUNCTION, l.line, l.col, value, size, + l.filename); + else if (strcmp(value, "return") == 0) + tok = + fzl_mktoken(FZL_TOKTYPE_RETURN, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "if") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_IF, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "else") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_ELSE, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "true") == 0 || strcmp(value, "false") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_BOOL_LITERAL, l.line, l.col, value, size, + l.filename); + else if (strcmp(value, "str") == 0 || strcmp(value, "char") == 0 || + strcmp(value, "bool") == 0 || strcmp(value, "uint") == 0 || + strcmp(value, "int") == 0 || strcmp(value, "i8") == 0 || + strcmp(value, "i16") == 0 || strcmp(value, "i32") == 0 || + strcmp(value, "i64") == 0 || strcmp(value, "u8") == 0 || + strcmp(value, "u16") == 0 || strcmp(value, "u32") == 0 || + strcmp(value, "u64") == 0 || strcmp(value, "null") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_TYPE, l.line, l.col, value, size, l.filename); + else + tok = fzl_mktoken(FZL_TOKTYPE_NAME, l.line, l.col, value, size, l.filename); free(value); return tok; @@ -59,37 +112,137 @@ fzl_token_t *parse_string_literal(fzl_reader_t *reader) if (reader->source[lpos] != '\'' && reader->source[lpos] != '"') return NULL; - char open_symbol = reader->source[lpos]; + char open_symbol = reader->source[lpos]; + char close_symbol = '\0'; do { lpos++; size++; - } while (reader->source[lpos] != open_symbol); + + if (reader->source[lpos] == open_symbol) + close_symbol = open_symbol; + + } while (reader->source[lpos] != open_symbol && lpos < reader->size); size++; + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, reader->position, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + if (close_symbol != open_symbol) + { + snprintf(last_err, sizeof(last_err), "'%s': unterminated string at %ld:%ld", + l.filename, l.line, l.col); + return NULL; + } + if (size == 2) { - fzl_token_t *tok = fzl_mktoken(FZL_TOKTYPE_STR_LITERAL, reader->position, - reader->position + 1, "", 0); + fzl_token_t *tok = + fzl_mktoken(FZL_TOKTYPE_STR_LITERAL, l.line, l.col, "", 0, l.filename); + move(reader, 2); return tok; } char *value = malloc(size - 2); memcpy(value, reader->source + reader->position + 1, size - 2); - fzl_token_t *tok = fzl_mktoken(FZL_TOKTYPE_STR_LITERAL, reader->position, - lpos, value, size - 2); + fzl_token_t *tok = fzl_mktoken(FZL_TOKTYPE_STR_LITERAL, l.line, l.col, value, + size - 2, l.filename); free(value); - - reader->position += size; + move(reader, size); return tok; } +// Parse numeric literal +fzl_token_t *parse_number(fzl_reader_t *reader) +{ + if (!reader) + return NULL; + + size_t size = 0; + size_t lpos = reader->position; + + do + { + size++; + lpos++; + } while ((reader->source[lpos] >= '0' && reader->source[lpos] <= '9') && + lpos < reader->size); + + char *value = malloc(size * sizeof(char) + 1); + memcpy(value, reader->source + reader->position, size); + value[size] = '\0'; + + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, reader->position, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + fzl_token_t *tok = fzl_mktoken(FZL_TOKTYPE_NUM_LITERAL, l.line, l.col, value, + size, l.filename); + free(value); + + if (!tok) + return NULL; + + move(reader, size); + return tok; +} + +// Add single-char token +fzl_token_t *add_single(fzl_reader_t *reader, fzl_toktype_t type) +{ + size_t p = move(reader, 1); + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, p, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + return fzl_mktoken(type, l.line, l.col, NULL, 0, l.filename); +} + +// Add double-char token +fzl_token_t *add_double(fzl_reader_t *reader, fzl_toktype_t type) +{ + size_t p = move(reader, 2); + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, p, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return NULL; + } + + return fzl_mktoken(type, l.line, l.col, NULL, 0, l.filename); +} + +// Skip symbols until '\n' or EOF +void skip_comment(fzl_reader_t *reader) +{ + move(reader, 2); + + while (reader->position < reader->size) + { + if (reader->source[reader->position++] == '\n') + break; + } +} + int fzl_read(fzl_reader_t *reader) { - const size_t len = strlen(reader->source); - if (reader->position >= len) + if (reader->position >= reader->size) return 0; do @@ -100,58 +253,109 @@ int fzl_read(fzl_reader_t *reader) if (curr == '\n' || curr == ' ' || curr == '\r') { - reader->position++; + move(reader, 1); continue; } if (curr == '\'' || curr == '"') token = parse_string_literal(reader); + else if (curr == '/' && reader->source[p + 1] == '/') + { + skip_comment(reader); + continue; + } + else if (curr == '=' && reader->source[p + 1] == '=') + token = add_double(reader, FZL_TOKTYPE_EQEQ); + else if (curr == '>' && reader->source[p + 1] == '=') + token = add_double(reader, FZL_TOKTYPE_REQ); + else if (curr == '<' && reader->source[p + 1] == '=') + token = add_double(reader, FZL_TOKTYPE_LEQ); + else if (curr == '!' && reader->source[p + 1] == '=') + token = add_double(reader, FZL_TOKTYPE_NEQ); + else if (curr == '|' && reader->source[p + 1] == '|') + token = add_double(reader, FZL_TOKTYPE_DVBAR); + else if (curr == '&' && reader->source[p + 1] == '&') + token = add_double(reader, FZL_TOKTYPE_DAMPERSAND); + else if (curr == '>') + token = add_single(reader, FZL_TOKTYPE_RANGLE); + else if (curr == '<') + token = add_single(reader, FZL_TOKTYPE_LANGLE); + else if (curr == '!') + token = add_single(reader, FZL_TOKTYPE_NOT); + else if (curr == ';') + token = add_single(reader, FZL_TOKTYPE_SEMICOLON); + else if (curr == '{') + token = add_single(reader, FZL_TOKTYPE_LBRACE); + else if (curr == '}') + token = add_single(reader, FZL_TOKTYPE_RBRACE); else if (curr == '(') - { - token = fzl_mktoken(FZL_TOKTYPE_LPAIR, p, p, NULL, 0); - reader->position++; - } + token = add_single(reader, FZL_TOKTYPE_LPAREN); else if (curr == ')') - { - token = fzl_mktoken(FZL_TOKTYPE_RPAIR, p, p, NULL, 0); - reader->position++; - } - else if (curr >= 'A' && curr <= 'Z' || curr >= 'a' && curr <= 'z') + token = add_single(reader, FZL_TOKTYPE_RPAREN); + else if (curr == ',') + token = add_single(reader, FZL_TOKTYPE_COMMA); + else if (curr == '.') + token = add_single(reader, FZL_TOKTYPE_DOT); + else if (curr == '*') + token = add_single(reader, FZL_TOKTYPE_STAR); + else if (curr == '/') + token = add_single(reader, FZL_TOKTYPE_SLASH); + else if (curr == '+') + token = add_single(reader, FZL_TOKTYPE_PLUS); + else if (curr == '-') + token = add_single(reader, FZL_TOKTYPE_MINUS); + else if (curr == '^') + token = add_single(reader, FZL_TOKTYPE_CARET); + else if (curr == '%') + token = add_single(reader, FZL_TOKTYPE_PERCENT); + else if (curr == '=') + token = add_single(reader, FZL_TOKTYPE_EQ); + else if (curr >= 'A' && curr <= 'Z' || curr >= 'a' && curr <= 'z' || + curr == '_') token = parse_name(reader); + else if (curr >= '0' && curr <= '9') + token = parse_number(reader); else { - sprintf(last_err, "undefined token '%c'", curr); + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, p, &l); + if (!ok) + { + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return -1; + } + + sprintf(last_err, "'%s': undefined token '%c' at %ld:%ld", l.filename, + curr, l.line, l.col); return -1; } if (!token) { - size_t line = 1, linepos = 1; - for (size_t i = 0; i < p; i++) + if (!last_err[0]) { - char sym = reader->source[i]; - if (sym == '\n') + fzl_file_loc l; + bool ok = fzl_linked_getpos(reader->linked, p, &l); + if (!ok) { - linepos = 1; - line++; + snprintf(last_err, sizeof(last_err), "failed to locate line:col"); + return -1; } - else - linepos++; + + sprintf(last_err, "'%s': cannot parse token at %ld:%ld", l.filename, + l.line, l.col); } - sprintf(last_err, "error parsing token (position %ld of line %ld)", line, - linepos); return -1; } fzl_reader_ll_t *tok_node = malloc(sizeof(fzl_reader_ll_t)); - tok_node->token = token; + tok_node->token = token; reader->last->next = tok_node; - reader->last = tok_node; - } while (reader->position < len); + reader->last = tok_node; + } while (reader->position < reader->size); - reader->at_end = true; return 0; } diff --git a/src/compiler/fzl_token.c b/src/compiler/fzl_token.c index f11da36..8ba49a7 100644 --- a/src/compiler/fzl_token.c +++ b/src/compiler/fzl_token.c @@ -1,20 +1,23 @@ #include +#include #include #include -fzl_token_t *fzl_mktoken(const fzl_toktype_t type, uint16_t from, uint16_t to, - void *value, size_t value_len) +fzl_token_t *fzl_mktoken(const fzl_toktype_t type, size_t line, size_t col, + void *value, size_t value_len, char *filename) { - void *valholder = malloc(value_len); + char *valholder = malloc(value_len + 1); memcpy(valholder, value, value_len); + valholder[value_len] = '\0'; fzl_token_t *token = malloc(sizeof(fzl_token_t)); - token->type = type; - token->i0 = from; - token->i1 = to; + token->type = type; + token->ln = line; + token->cn = col; token->store_value = value_len > 0; - token->value = valholder; - token->value_sz = value_len; + token->value = valholder; + token->value_sz = value_len; + token->filename = filename; return token; } diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 46ad2cd..05206b8 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -1,8 +1,12 @@ #include #include +#include #include +#include #include #include +#include +#include #include #include #include @@ -34,18 +38,104 @@ fzl_walker_t *fzl_mkwalker(fzl_node_t *root) return NULL; } - walker->root = root; + walker->root = root; walker->consts = pool; - walker->op_count = 0; + walker->op_count = 0; walker->op_remainder = 2; - walker->ops = malloc(walker->op_remainder * sizeof(uint64_t)); + walker->ops = malloc(walker->op_remainder * sizeof(uint64_t)); + + walker->sym_top = 0; + walker->syms[walker->sym_top++] = fzl_mksymt(); + if (!walker->syms[walker->sym_top - 1]) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to create global symtable"); + return NULL; + } + + walker->ctx = NULL; + walker->classes_count = 0; + memset(walker->classes, 0, sizeof(walker->classes)); return walker; } // --- Walking zone --- +// Convert type token to `fzl_type_t` +const fzl_type_t tok_to_type(fzl_token_t *tok) +{ + if (strcmp((char *)tok->value, "i8") == 0) + return FZL_TYPE_INT8; + if (strcmp((char *)tok->value, "i16") == 0) + return FZL_TYPE_INT16; + if (strcmp((char *)tok->value, "i32") == 0) + return FZL_TYPE_INT32; + if (strcmp((char *)tok->value, "i64") == 0) + return FZL_TYPE_INT64; + + if (strcmp((char *)tok->value, "u8") == 0) + return FZL_TYPE_UINT8; + if (strcmp((char *)tok->value, "u16") == 0) + return FZL_TYPE_UINT16; + if (strcmp((char *)tok->value, "u32") == 0) + return FZL_TYPE_UINT32; + if (strcmp((char *)tok->value, "u64") == 0) + return FZL_TYPE_UINT64; + + if (strcmp((char *)tok->value, "int") == 0) + return FZL_TYPE_INT; + if (strcmp((char *)tok->value, "uint") == 0) + return FZL_TYPE_UINT; + + if (strcmp((char *)tok->value, "bool") == 0) + return FZL_TYPE_BOOL; + if (strcmp((char *)tok->value, "str") == 0) + return FZL_TYPE_STRING; + if (strcmp((char *)tok->value, "char") == 0) + return FZL_TYPE_CHAR; + + return FZL_TYPE_NULL; +} + +static bool is_compatible(fzl_type_t t1, fzl_type_t t2) +{ + if (t1 == t2) + return true; + + return t1 >= FZL_TYPE_INT8 && t2 >= FZL_TYPE_INT8; +} + +// Add one layer of ctx +fzl_walk_ctx_t *nest_ctx(fzl_walker_t *walker, fzl_walk_ctx_type_t type) +{ + if (!walker) + return NULL; + + fzl_walk_ctx_t *newctx = malloc(sizeof(fzl_walk_ctx_t)); + if (!newctx) + return NULL; + + newctx->type = type; + newctx->parent = walker->ctx; + walker->ctx = newctx; + + return walker->ctx; +} + +// Go outside current ctx and then clean it +fzl_walk_ctx_t *fin_ctx(fzl_walker_t *walker) +{ + if (!walker) + return NULL; + + fzl_walk_ctx_t *curr = walker->ctx; + walker->ctx = walker->ctx->parent; + + free(curr); + return walker->ctx; +} + // Add const to consts table size_t add_const(fzl_walker_t *walker, fzl_const_t c) { @@ -87,67 +177,952 @@ void push_word(fzl_walker_t *walker, uint64_t word) walker->ops[walker->op_count++] = word; } -bool walk(fzl_walker_t *walker, fzl_node_t *node) +fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { if (!walker || !node) - return false; + return (fzl_walk_res_t){.error = true, .error_msg = "walker is null"}; + fzl_walk_res_t current_res; while (node != NULL) { switch (node->type) { - case FZL_NODETYPE_BLOCK: - // NOTE: create block env here - if (node->list.entry_count > 0) - for (size_t i = 0; i < node->list.entry_count; i++) - walk(walker, node->list.entries[i]); - // NOTE: destroy block env here - break; - - case FZL_NODETYPE_CALL: - if (node->list.entry_count > 0) + case FZL_NODETYPE_CLASS: { - for (size_t i = 0; i < node->list.entry_count; i++) + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_CLASS); + if (!newctx) { - walk(walker, node->list.entries[i]); - push_op(walker, FZL_OPCODE_PUSHARG); + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - } - // NOTE: you need to implement dynamic names call - push_op(walker, FZL_OPCODE_CALL_BUILTIN); - if (strcmp(node->token->value, "printline") == 0) - push_word(walker, FZL_BUILTIN_PRINTLINE); - else + newctx->fn = node; + newctx->initial_env_lvl = walker->sym_top; + + walker->classes[walker->classes_count] = + fzl_mkctable(node->class.name->value); + fzl_ctable_t *class = walker->classes[walker->classes_count++]; + class->name = node->class.name->value; + + for (size_t i = 0; i < node->class.fields_count; i++) + { + if (class->fields_remainder <= class->fields_count) + { + class->fields_remainder *= 2; + class->fields = + realloc(class->fields, + class->fields_remainder * sizeof(fzl_ctable_field_t)); + } + + class->fields[class->fields_count] = (fzl_ctable_field_t){ + .name = node->class.fields[i]->var.identifier->token->value, + .type = tok_to_type(node->class.fields[i]->var.type), + .slot_index = class->fields_count++}; + } + + for (size_t i = 0; i < node->class.methods_count; i++) + { + fzl_node_t *fn = node->class.methods[i]; + + size_t classname_len = strlen(class->name); + size_t fnname_len = strlen(fn->fn.name->value); + char *method_name = malloc(classname_len + fnname_len + 2); + + strcpy(method_name, class->name); + method_name[classname_len] = '_'; + strcpy((method_name + classname_len + 1), fn->fn.name->value); + method_name[classname_len + fnname_len + 1] = 0; + + fn->fn.name->value = method_name; + fzl_walk_res_t fnres = walk(walker, fn); + if (fnres.error) + return fnres; + } + + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + + case FZL_NODETYPE_CONSTRUCT: { - snprintf(wlast_err, sizeof(wlast_err), "call to unknown function '%s'", - (char *)node->token->value); - return false; + fzl_node_t *classname = node->construct.name; + fzl_ctable_t *ct = NULL; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp((char *)classname->token->value, + walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' not found (at %ld:%ld)", + classname->token->filename, (char *)classname->token->value, + classname->token->ln, classname->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_ALLOC_OBJECT); + push_word(walker, ct->fields_count); + + current_res = (fzl_walk_res_t){ + .type = FZL_TYPE_OBJECT, .error = false, .classname = ct->name}; + break; } - break; - case FZL_NODETYPE_LITERAL: - size_t n = add_const( - walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, - .as.string.chars = node->token->value, - .as.string.length = node->token->value_sz}); + case FZL_NODETYPE_FIELD: + { + fzl_sym_t sym; + bool exists = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((exists = fzl_symt_get(walker->syms[i], + node->memb.parent->token->value, &sym))) + break; - push_op(walker, FZL_OPCODE_LOADCONST); - push_word(walker, n); - break; + if (!exists) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot find symbol '%s' at %ld:%ld", + node->token->filename, (char *)node->token->value, + node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (!sym.classname) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': '%s' is not an object (at %ld:%ld)", + node->token->filename, sym.name, node->token->ln, + node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_ctable_t *ct = NULL; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(sym.classname, walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + + fzl_token_t *tok = node->memb.parent->token; + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': access to unknown class '%s' at %ld:%ld", + tok->filename, (char *)tok->value, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_token_t *ftok = node->memb.field->token; + fzl_ctable_field_t ctf; + bool field_exists = false; + for (size_t i = 0; i < ct->fields_count; i++) + { + if (strcmp(ftok->value, ct->fields[i].name) == 0) + { + field_exists = true; + ctf = ct->fields[i]; + break; + } + } + + if (!field_exists) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot find field '%s' in class '%s' at %ld:%ld", + ftok->filename, (char *)ftok->value, (char *)tok->value, + ftok->ln, ftok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_walk_res_t parent_res = walk(walker, node->memb.parent); + if (parent_res.error) + return parent_res; + + push_op(walker, FZL_OPCODE_GET_FIELD); + push_word(walker, ctf.slot_index); + + current_res = (fzl_walk_res_t){ + .type = ctf.type, .error = false, .classname = sym.classname}; + break; + } + + case FZL_NODETYPE_FUNCTION: + { + push_op(walker, FZL_OPCODE_JMP); + size_t skip_ip = walker->op_count; + push_word(walker, (uint64_t)0); + + fzl_type_t fntype = tok_to_type(node->fn.type); + fzl_symtable_res_t putres = + fzl_symt_put(walker->syms[walker->sym_top - 1], + node->fn.name->value, fntype, walker->op_count); + if (putres != FZL_SYMTABLE_RESULT_OK) + { + if (putres == FZL_SYMTABLE_RESULT_EXISTS) + snprintf( + wlast_err, sizeof(wlast_err), + "'%s': cannot define already declared name '%s' at %ld:%ld", + node->token->filename, (char *)node->fn.name->value, + node->token->ln, node->token->cn); + else + snprintf(wlast_err, sizeof(wlast_err), + "'%s': failed to store name '%s'", node->fn.name->filename, + (char *)node->fn.name->value); + + return (fzl_walk_res_t){.error = true}; + } + + // NOTE: add sym_top limit check + size_t lvl = walker->sym_top; + walker->syms[walker->sym_top++] = fzl_mksymt(); + + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_FN); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + newctx->type = FZL_WALK_CTX_TYPE_FN; + newctx->fn = node; + newctx->initial_env_lvl = lvl; + + push_op(walker, FZL_OPCODE_MK_ENVTABLE); + + for (ssize_t i = node->fn.args_count - 1; i >= 0; i--) + { + fzl_walk_res_t res = walk(walker, node->fn.args[i]); + if (res.error) + return res; + push_op(walker, FZL_OPCODE_WRITE); + } + + fzl_walk_res_t bodyres = walk(walker, node->fn.body); + if (bodyres.error) + return bodyres; + + // NOTE: ghost walk (w/o opcodes write) for arg check should be there in + // near future + + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + push_op(walker, FZL_OPCODE_RET); + walker->ops[skip_ip] = walker->op_count; + + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + for (ssize_t i = walker->sym_top - 1; i >= lvl; i--) + fzl_freesymt(walker->syms[--walker->sym_top]); + + current_res = (fzl_walk_res_t){.type = fntype, .error = false}; + break; + } + + case FZL_NODETYPE_RETURN: + { + if (!walker->ctx || walker->ctx->type != FZL_WALK_CTX_TYPE_FN) + { + snprintf( + wlast_err, sizeof(wlast_err), + "'%s': return cannot be used outside of functions (at %ld:%ld)", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (node->ret.value) + { + fzl_walk_res_t retres = walk(walker, node->ret.value); + if (retres.error) + return retres; + + if (!is_compatible(retres.type, + tok_to_type(walker->ctx->fn->fn.type))) + { + snprintf( + wlast_err, sizeof(wlast_err), + "'%s': incompatible types between return and function type at " + "%ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + current_res = (fzl_walk_res_t){.type = retres.type, .error = false}; + } + else + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + + for (size_t i = 0; i < (walker->sym_top - walker->ctx->initial_env_lvl); + i++) + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + push_op(walker, FZL_OPCODE_RET); + break; + } + + case FZL_NODETYPE_NOT: + { + fzl_walk_res_t res = walk(walker, node->unary_op.operand); + if (res.error) + return res; + + push_op(walker, FZL_OPCODE_NOT); + current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false}; + break; + } + + case FZL_NODETYPE_EQUAL: + case FZL_NODETYPE_NEQUAL: + case FZL_NODETYPE_LESS: + case FZL_NODETYPE_LESS_EQUAL: + case FZL_NODETYPE_GREATER: + case FZL_NODETYPE_GREATER_EQUAL: + { + fzl_walk_res_t left = walk(walker, node->binary_op.left); + if (left.error) + return left; + + fzl_walk_res_t right = walk(walker, node->binary_op.right); + if (right.error) + return right; + + if (!is_compatible(left.type, right.type)) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': incompatible types at %ld:%ld", node->token->filename, + node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + switch (node->type) + { + case FZL_NODETYPE_EQUAL: + push_op(walker, FZL_OPCODE_CMP_EQ); + break; + case FZL_NODETYPE_NEQUAL: + push_op(walker, FZL_OPCODE_CMP_NEQ); + break; + case FZL_NODETYPE_LESS: + push_op(walker, FZL_OPCODE_CMP_LT); + break; + case FZL_NODETYPE_LESS_EQUAL: + push_op(walker, FZL_OPCODE_CMP_LTEQ); + break; + case FZL_NODETYPE_GREATER: + push_op(walker, FZL_OPCODE_CMP_GT); + break; + case FZL_NODETYPE_GREATER_EQUAL: + push_op(walker, FZL_OPCODE_CMP_GTEQ); + break; + default: + break; + } + + current_res = (fzl_walk_res_t){.type = left.type, .error = false}; + break; + } + + case FZL_NODETYPE_OR: + case FZL_NODETYPE_AND: + { + fzl_walk_res_t left = walk(walker, node->binary_op.left); + if (left.error) + return left; + + fzl_walk_res_t right = walk(walker, node->binary_op.right); + if (right.error) + return right; + + if (!is_compatible(left.type, right.type)) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': incompatible types at %ld:%ld", node->token->filename, + node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (node->type == FZL_NODETYPE_AND) + push_op(walker, FZL_OPCODE_AND); + else + push_op(walker, FZL_OPCODE_OR); + + current_res = (fzl_walk_res_t){.type = left.type, .error = false}; + break; + } + + case FZL_NODETYPE_CONDITIONAL: + { + fzl_walk_res_t cres = walk(walker, node->cond.condition); + if (cres.error) + return cres; + + push_op(walker, FZL_OPCODE_JZ); + + size_t jz_if = walker->op_count; + push_word(walker, (uint64_t)0); + + fzl_walk_res_t tblock_res = walk(walker, node->cond.true_block); + if (tblock_res.error) + return tblock_res; + + push_op(walker, FZL_OPCODE_JMP); + size_t jmp_else = walker->op_count; + push_word(walker, (uint64_t)0); + + walker->ops[jz_if] = walker->op_count; + + if (node->cond.else_block) + { + fzl_walk_res_t fblock_res = walk(walker, node->cond.else_block); + if (fblock_res.error) + return fblock_res; + } + + walker->ops[jmp_else] = walker->op_count; + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + + case FZL_NODETYPE_UNARY_PLUS: + { + fzl_walk_res_t res = walk(walker, node->unary_op.operand); + if (res.error) + return res; + + current_res = res; + break; + } + + case FZL_NODETYPE_IDENTIFIER: + { + size_t n = add_const( + walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, + .as.string.chars = node->token->value, + .as.string.length = node->token->value_sz}); + + fzl_sym_t sym; + bool exists = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((exists = + fzl_symt_get(walker->syms[i], node->token->value, &sym))) + break; + + if (!exists) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot find symbol '%s' at %ld:%ld", + node->token->filename, (char *)node->token->value, + node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, n); + push_op(walker, FZL_OPCODE_LOOKUP_ENV); + + current_res = (fzl_walk_res_t){ + .type = sym.type, .error = false, .classname = sym.classname}; + break; + } + + case FZL_NODETYPE_BLOCK: + walker->syms[walker->sym_top++] = fzl_mksymt(); + push_op(walker, FZL_OPCODE_MK_ENVTABLE); + if (node->list.entry_count > 0) + for (size_t i = 0; i < node->list.entry_count; i++) + { + fzl_walk_res_t res = walk(walker, node->list.entries[i]); + if (res.error) + return res; + } + fzl_freesymt(walker->syms[--walker->sym_top]); + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + break; + + case FZL_NODETYPE_CALL: + if (strcmp(node->token->value, "printline") == 0) + { + if (node->call.args_count > 0) + { + for (size_t i = 0; i < node->call.args_count; i++) + { + fzl_walk_res_t res = walk(walker, node->call.args[i]); + if (res.error) + return res; + } + } + + push_op(walker, FZL_OPCODE_CALL_BUILTIN); + push_word(walker, FZL_BUILTIN_PRINTLINE); + if (node->call.args_count < 1) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': 'printline' call with zero arguments at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + } + else + { + if (node->call.callee->type == FZL_NODETYPE_IDENTIFIER) + { + if (node->call.args_count > 0) + { + for (size_t i = 0; i < node->call.args_count; i++) + { + fzl_walk_res_t res = walk(walker, node->call.args[i]); + if (res.error) + return res; + } + } + + fzl_sym_t res; + bool found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = + fzl_symt_get(walker->syms[i], node->token->value, &res))) + break; + + if (!found) + { + snprintf(wlast_err, sizeof(wlast_err), + "call to unknown function '%s'", + (char *)node->token->value); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (!res.ip) + { + snprintf(wlast_err, sizeof(wlast_err), + "cannot jump to address 0x0"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_CALL); + push_word(walker, res.ip); + + current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + } + else if (node->call.callee->type == FZL_NODETYPE_FIELD) + { + fzl_node_t *parent_node = node->call.callee->memb.parent; + + fzl_sym_t res; + bool found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = fzl_symt_get(walker->syms[i], + parent_node->token->value, &res))) + break; + + if (!found) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': access to unknown object '%s' at %ld:%ld", + node->token->filename, (char *)parent_node->token->value, + node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (res.type != FZL_TYPE_OBJECT || !res.classname) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': name '%s' is not an object (at %ld:%ld)", + parent_node->token->filename, + (char *)parent_node->token->value, + parent_node->token->ln, parent_node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + found = false; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(walker->classes[i]->name, res.classname) == 0) + { + found = true; + break; + } + } + + if (!found) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' not found at %ld:%ld", + node->token->filename, res.classname, node->token->ln, + node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + char *classname = res.classname; + size_t classlen = strlen(res.classname); + size_t fnlen = strlen(node->call.callee->token->value); + char *methodname = malloc(classlen + fnlen + 2); + strcpy(methodname, res.classname); + strcpy((methodname + classlen + 1), + node->call.callee->token->value); + methodname[classlen] = '_'; + methodname[classlen + fnlen + 1] = 0; + + found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = fzl_symt_get(walker->syms[i], methodname, &res))) + break; + + if (!found) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' has no method '%s' at %ld:%ld", + node->token->filename, classname, + (char *)node->call.callee->token->value, node->token->ln, + node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_walk_res_t parent = walk(walker, parent_node); + if (parent.error) + return parent; + + if (node->call.args_count > 0) + { + for (size_t i = 0; i < node->call.args_count; i++) + { + fzl_walk_res_t res = walk(walker, node->call.args[i]); + if (res.error) + return res; + } + } + + push_op(walker, FZL_OPCODE_CALL); + push_word(walker, res.ip); + + current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + } + } + break; + + case FZL_NODETYPE_LITERAL: + if (node->token->type == FZL_TOKTYPE_STR_LITERAL) + { + size_t n = add_const( + walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, + .as.string.chars = node->token->value, + .as.string.length = node->token->value_sz}); + + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, n); + current_res = + (fzl_walk_res_t){.type = FZL_TYPE_STRING, .error = false}; + } + else if (node->token->type == FZL_TOKTYPE_NUM_LITERAL) + { + push_op(walker, FZL_OPCODE_PUSHNUM); + + uint64_t num = strtol(node->token->value, NULL, 10); + push_word(walker, num); + current_res = (fzl_walk_res_t){.type = FZL_TYPE_INT, .error = false}; + } + else if (node->token->type == FZL_TOKTYPE_BOOL_LITERAL) + { + fzl_token_t *tok = node->token; + uint64_t val = strcmp(tok->value, "true") == 0 ? 1 : 0; + + push_op(walker, FZL_OPCODE_PUSHBOOL); + push_word(walker, val); + current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false}; + } + break; + + case FZL_NODETYPE_ADD: + case FZL_NODETYPE_SUB: + case FZL_NODETYPE_MUL: + case FZL_NODETYPE_DIV: + case FZL_NODETYPE_POW: + case FZL_NODETYPE_MOD: + { + fzl_walk_res_t lres = walk(walker, node->binary_op.left); + if (lres.error) + return lres; + + fzl_walk_res_t rres = walk(walker, node->binary_op.right); + if (rres.error) + return rres; + + if (lres.type < FZL_TYPE_INT8) + { + fzl_token_t *ltok = node->binary_op.left->token; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': left operand at %ld:%ld is not compatible to integer " + "types", + ltok->filename, ltok->ln, ltok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (rres.type < FZL_TYPE_INT8) + { + fzl_token_t *rtok = node->binary_op.right->token; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': right operand at %ld:%ld is not compatible to " + "integer types", + rtok->filename, rtok->ln, rtok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + switch (node->type) + { + case FZL_NODETYPE_ADD: + push_op(walker, FZL_OPCODE_ADD); + break; + case FZL_NODETYPE_SUB: + push_op(walker, FZL_OPCODE_SUB); + break; + case FZL_NODETYPE_MUL: + push_op(walker, FZL_OPCODE_MUL); + break; + case FZL_NODETYPE_DIV: + push_op(walker, FZL_OPCODE_DIV); + break; + case FZL_NODETYPE_POW: + push_op(walker, FZL_OPCODE_POW); + break; + case FZL_NODETYPE_MOD: + push_op(walker, FZL_OPCODE_MOD); + break; + default: + break; + } + current_res = (fzl_walk_res_t){.type = lres.type, .error = false}; + break; + } + + case FZL_NODETYPE_UNARY_MINUS: + { + fzl_walk_res_t res = walk(walker, node->unary_op.operand); + if (res.error) + return res; + + if (res.type < FZL_TYPE_INT8) + { + fzl_token_t *tok = node->unary_op.operand->token; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': incompatible with unary operator type at %ld:%ld", + tok->filename, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_NEG); + current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + break; + } + + case FZL_NODETYPE_DECLARATION: + { + fzl_ctable_t *ct = NULL; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(node->var.type->value, walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + + fzl_symtable_res_t res = fzl_symt_put( + walker->syms[walker->sym_top - 1], + node->var.identifier->token->value, + ct != NULL ? FZL_TYPE_OBJECT : tok_to_type(node->var.type), 0); + + if (ct) + fzl_symt_setobjclass(walker->syms[walker->sym_top - 1], + node->var.identifier->token->value, ct->name); + + if (res != FZL_SYMTABLE_RESULT_OK) + { + fzl_token_t *tok = node->var.identifier->token; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot store '%s' at %ld:%ld", tok->filename, + (char *)tok->value, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_token_t *id_tok = node->var.identifier->token; + size_t const_n = add_const( + walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, + .as.string.chars = id_tok->value, + .as.string.length = id_tok->value_sz}); + + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, const_n); + + push_op(walker, FZL_OPCODE_STORE); + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, const_n); + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + + case FZL_NODETYPE_ASSIGN: + { + fzl_walk_res_t rres = walk(walker, node->binary_op.right); + if (rres.error) + return rres; + + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_ASSIGN); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (node->binary_op.left->type == FZL_NODETYPE_DECLARATION) + { + fzl_walk_res_t lres = walk(walker, node->binary_op.left); + if (lres.error) + return lres; + + push_op(walker, FZL_OPCODE_WRITE); + } + else if (node->binary_op.left->type == FZL_NODETYPE_IDENTIFIER) + { + fzl_sym_t sym; + bool found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = + fzl_symt_get(walker->syms[i], + node->binary_op.left->token->value, &sym))) + break; + + if (!found) + { + fzl_token_t *tok = node->binary_op.left->token->value; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': name '%s' is not exists at %ld:%ld", tok->filename, + (char *)tok->value, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (!is_compatible(sym.type, rres.type)) + { + fzl_token_t *tok = node->binary_op.right->token; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': R-Value is not compatible with L-Value at %ld:%ld", + tok->filename, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + size_t const_n = add_const( + walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, + .as.string.chars = sym.name, + .as.string.length = + node->binary_op.left->token->value_sz}); + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, const_n); + push_op(walker, FZL_OPCODE_WRITE); + } + else if (node->binary_op.left->type == FZL_NODETYPE_FIELD) + { + fzl_node_t *field_node = node->binary_op.left; + fzl_walk_res_t obj_res = walk(walker, field_node->memb.parent); + if (obj_res.error) + return obj_res; + + fzl_ctable_t *ct; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(obj_res.classname, walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot find class '%s' at %ld:%ld", + field_node->token->filename, obj_res.classname, + field_node->token->ln, field_node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_ctable_field_t *ctf; + for (size_t i = 0; i < ct->fields_count; i++) + { + if (strcmp(field_node->memb.field->token->value, + ct->fields[i].name) == 0) + { + ctf = &ct->fields[i]; + break; + } + } + + if (!ctf) + { + fzl_node_t *fn = field_node->memb.field; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' has no field '%s' at %ld:%ld", + fn->token->filename, obj_res.classname, + (char *)fn->token->value, fn->token->ln, fn->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_SET_FIELD); + push_word(walker, ctf->slot_index); + } + + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + current_res = (fzl_walk_res_t){.type = rres.type, .error = false}; + break; + } } node = node->next; } - return true; + return current_res; } // Walk entire AST recursevily int fzl_walk(fzl_walker_t *walker) { - bool result = walk(walker, walker->root); - return !result && wlast_err[0] ? -1 : 0; + if (!walker) + { + snprintf(wlast_err, sizeof(wlast_err), "walker is null"); + return -1; + } + + nest_ctx(walker, FZL_WALK_CTX_TYPE_GLOBAL); + + push_op(walker, FZL_OPCODE_MK_ENVTABLE); + fzl_walk_res_t result = walk(walker, walker->root); + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + + fin_ctx(walker); + + if (result.error && !wlast_err[0]) + snprintf(wlast_err, sizeof(wlast_err), "%s", result.error_msg); + + return result.error ? -1 : 0; } // --- Walking zone end --- @@ -164,5 +1139,11 @@ void fzl_freewalker(fzl_walker_t *walker) if (walker->ops) free(walker->ops); + while (walker->sym_top > 0) + fzl_freesymt(walker->syms[--walker->sym_top]); + + for (size_t i = 0; i < walker->classes_count; i++) + fzl_freectable(walker->classes[i]); + free(walker); } diff --git a/src/compiler/main.c b/src/compiler/main.c index 46c0689..305a83c 100644 --- a/src/compiler/main.c +++ b/src/compiler/main.c @@ -1,143 +1,69 @@ +#include #include #include #include #include #include +#include +#include #include #include #include #include -// Build arguments setup descriptor -typedef struct -{ - char *filepath; - char *outpath; - bool verbose; - bool help; -} build_args_t; - -// Print command usage hint -void print_usage() -{ - printf("Usage: fzc [flags] \n"); - printf("Flags:\n"); - printf("\t-v\t--verbose\tdisplay debug information\n"); - printf("\t-h\t--help\t\tprint usage hint (which you're looking at)\n"); -} - -// Read the arguments and build `build_args_t` struct -build_args_t *getargs(int argc, char **argv) -{ - build_args_t *args = malloc(sizeof(build_args_t)); - if (!args) - return NULL; - - args->filepath = NULL; - args->outpath = NULL; - args->verbose = false; - args->help = false; - - size_t args_total = 0; - if (argc > 2) - { - for (int i = 1; i < argc; i++) - { - if (argv[i][0] != '-') - continue; - - args_total++; - - if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) - { - args->help = true; - } - else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) - { - args->verbose = true; - } - else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--out") == 0) - { - if (!argv[i + 1] || argv[i + 1][0] == '-') - { - printf("Error: file path is not specified.\n"); - return NULL; - } - args->outpath = argv[++i]; - } - else - { - printf("Error: unknown argument '%s'\n", argv[i]); - return NULL; - } - } - } - - if (argc - 1 - args_total < 1) - { - printf("Error: source code file is not specified.\n"); - print_usage(); - return NULL; - } - - if (argv[argc - 1][0] == '-') - { - printf("Error: last argument MUST be a path to file.\n"); - printf("Write 'fzc --help' to read help\n"); - return NULL; - } - - args->filepath = argv[argc - 1]; - return args; -} - int main(int argc, char **argv) { - build_args_t *args = getargs(argc, argv); - if (args == NULL) + fzl_options_t *opts = fzl_mkoptions(argc, argv); + if (!opts) return -1; - else if (args->help) + else if (!opts->should_continue) return 0; - if (args->verbose) - printf("> analyze\n-> reading file..."); + if (opts->verbose) + printf("%s> analyze\n-> reading file...%s", FZL_CDEBUG, FZL_CRST); - char source[8192]; - FILE *fd = fopen(args->filepath, "r"); - if (!fd) + fzl_linked_t *linked = fzl_mklinked(); + if (!linked) { - printf("Error: failed to open file '%s'\n", args->filepath); + char *lasterr = fzl_linked_geterror(); + if (lasterr[0]) + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, lasterr); return -1; } - fseek(fd, 0L, SEEK_END); - size_t fsz = ftell(fd); - rewind(fd); - - fread(source, fsz, 1, fd); - fclose(fd); - if (args->verbose) + bool read_result = + fzl_linked_add_files(linked, opts->files, opts->files_count); + if (!read_result) { - printf("ok!\n"); - printf("-> tokenizing..."); + char *lasterr = fzl_linked_geterror(); + if (lasterr[0]) + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, lasterr); + return -1; } - fzl_reader_t *rd = fzl_mkreader(source); - int result = fzl_read(rd); + if (opts->verbose) + { + printf("%sok%s!\n", FZL_COK, FZL_CRST); + printf("%s--> Red %ld bytes%s\n", FZL_CDEBUG, linked->total_sz, FZL_CRST); + printf("%s-> tokenizing...%s", FZL_CDEBUG, FZL_CRST); + } + + fzl_reader_t *rd = fzl_mkreader(linked); + int result = fzl_read(rd); if (result != 0) { - printf("error: %s\n", fzl_reader_geterror()); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_reader_geterror()); return -1; } - if (args->verbose) - printf("ok!\n"); + if (opts->verbose) + printf("%sok%s!\n", FZL_COK, FZL_CRST); fzl_reader_ll_t *entry = rd->first; - if (args->verbose) + if (opts->verbose) { - printf("--> structure: "); + printf("%s--> structure: %s", FZL_CDEBUG, FZL_CRST); while (entry) { if (entry->token) @@ -153,78 +79,85 @@ int main(int argc, char **argv) } printf("\n"); - printf("-> parsing..."); + printf("%s-> parsing...%s", FZL_CDEBUG, FZL_CRST); } fzl_parser_t *parser = fzl_mkparser(rd->first); - result = fzl_parse(parser); + result = fzl_parse(parser); if (result != 0) { - printf("Error: %s\n", fzl_parser_geterror()); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_parser_geterror()); return -1; } - if (args->verbose) + if (opts->verbose) { - printf("ok!\n"); + printf("%sok%s!\n", FZL_COK, FZL_CRST); fzl_debug_print_ast(parser->root); - printf("> building\n"); - printf("-> walking AST...\n"); + printf("%s> building%s\n", FZL_CDEBUG, FZL_CRST); + printf("%s-> walking AST...%s\n", FZL_CDEBUG, FZL_CRST); } fzl_walker_t *walker = fzl_mkwalker(parser->root); - result = fzl_walk(walker); + result = fzl_walk(walker); if (result != 0) { - printf("Error: %s\n", fzl_walker_geterror()); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_walker_geterror()); return -1; } - if (args->verbose) + if (opts->verbose) { - printf("ok!\n"); + printf("%sok%s!\n", FZL_COK, FZL_CRST); - printf("--> Constants table:\n"); + printf("%s--> Constants table%s:\n", FZL_CDEBUG, FZL_CRST); for (size_t i = 0; i < walker->consts->size; i++) printf("---> %s\n", walker->consts->array[i].as.string.chars); - printf("--> Ops:\n"); + printf("%s--> Ops%s:\n", FZL_CDEBUG, FZL_CRST); + printf("---> "); for (size_t i = 0; i < walker->op_count; i++) - printf("---> %lu\n", walker->ops[i]); + { + if (i > 0 && i < walker->op_count) + printf("->"); + printf("%lu", walker->ops[i]); + } + printf("\n"); - printf("-> preparing final write\n"); + printf("%s-> preparing final write%s\n", FZL_CDEBUG, FZL_CRST); } char outpath[256]; - if (args->outpath) - strcpy(outpath, args->outpath); + if (opts->outname) + strcpy(outpath, opts->outname); else - strcpy(outpath, "a.fzlc"); + strcpy(outpath, "a.fzb"); - if (args->verbose) + if (opts->verbose) { - printf("-> output file path is: %s", outpath); - printf("-> writing file..."); + printf("%s-> output file path is%s: %s\n", FZL_CDEBUG, FZL_CRST, outpath); + printf("%s-> writing file...%s", FZL_CDEBUG, FZL_CRST); } fzl_writer_t *writer = fzl_mkwriter(outpath); - result = fzl_write(writer, walker); + result = fzl_write(writer, walker); if (result != 0) { - printf("Error: %s\n", fzl_writer_geterror()); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_writer_geterror()); return -1; } - if (args->verbose) - printf("ok!\n"); + if (opts->verbose) + printf("%sok%s!\n", FZL_COK, FZL_CRST); - free(args); + fzl_freeoptions(opts); + fzl_freelinked(linked); fzl_freereader(rd); - fzl_freeparser(parser); fzl_freeast(parser->root); + fzl_freeparser(parser); fzl_freewalker(walker); fzl_freewriter(writer); - printf("done.\n"); + printf("%sCompiled successfully%s.\n", FZL_COK, FZL_CRST); return 0; } diff --git a/src/compiler/utils/fzl_const_pool.c b/src/compiler/utils/fzl_const_pool.c index abb0d0b..39c4c28 100644 --- a/src/compiler/utils/fzl_const_pool.c +++ b/src/compiler/utils/fzl_const_pool.c @@ -33,7 +33,7 @@ static uint32_t fzl_hash_const(fzl_const_t c) switch (c.type) { case FZL_CONSTTYPE_STRING: - data = (const uint8_t *)&c.as.string.chars; + data = (const uint8_t *)c.as.string.chars; size = c.as.string.length; break; } diff --git a/src/compiler/utils/fzl_ctable.c b/src/compiler/utils/fzl_ctable.c new file mode 100644 index 0000000..1b7edab --- /dev/null +++ b/src/compiler/utils/fzl_ctable.c @@ -0,0 +1,29 @@ +#include +#include +#include + +fzl_ctable_t *fzl_mkctable(char *name) +{ + fzl_ctable_t *ctable = malloc(sizeof(fzl_ctable_t)); + if (!ctable) + return NULL; + + memset(ctable, 0, sizeof(fzl_ctable_t)); + ctable->name = name; + ctable->fields_remainder = 2; + ctable->fields = + malloc(ctable->fields_remainder * sizeof(fzl_ctable_field_t)); + + return ctable; +} + +void fzl_freectable(fzl_ctable_t *ct) +{ + if (!ct) + return; + + if (ct->fields) + free(ct->fields); + + free(ct); +} diff --git a/src/compiler/utils/fzl_linked.c b/src/compiler/utils/fzl_linked.c new file mode 100644 index 0000000..f7414eb --- /dev/null +++ b/src/compiler/utils/fzl_linked.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include + +char llast_err[256]; + +fzl_linked_t *fzl_mklinked() +{ + fzl_linked_t *l = malloc(sizeof(fzl_linked_t)); + if (!l) + { + snprintf(llast_err, sizeof(llast_err), "potato PC..."); + return NULL; + } + + l->entries = NULL; + l->entries_count = 0; + l->source = NULL; + l->total_sz = 0; + + return l; +} + +bool fzl_linked_add_files(fzl_linked_t *l, char **files, size_t files_count) +{ + if (!l) + return false; + + size_t eremainder = 2; + size_t ecount = 0; + fzl_linked_entry_t *entries = malloc(eremainder * sizeof(fzl_linked_entry_t)); + + size_t current_start = 0; + for (size_t i = 0; i < files_count; i++) + { + char *source; + FILE *fd = fopen(files[i], "r"); + if (!fd) + { + snprintf(llast_err, sizeof(llast_err), "failed to open file '%s'\n", + files[i]); + free(entries); + return false; + } + + fseek(fd, 0L, SEEK_END); + size_t file_sz = ftell(fd); + rewind(fd); + + source = malloc(file_sz + 1); + fread(source, file_sz, 1, fd); + fclose(fd); + source[file_sz] = 0; + + if (eremainder <= ecount) + { + eremainder *= 2; + entries = realloc(entries, eremainder * sizeof(fzl_linked_entry_t)); + if (!entries) + { + snprintf(llast_err, sizeof(llast_err), "potato pc..."); + free(source); + return NULL; + } + } + entries[ecount++] = + (fzl_linked_entry_t){.name = files[i], + .start = current_start, + .end = current_start + file_sz - 1}; + + if (!l->source) + l->source = malloc(file_sz); + else + l->source = realloc(l->source, current_start + file_sz); + + if (!l->source) + { + snprintf(llast_err, sizeof(llast_err), + "failed to allocate space for source code holder"); + free(entries); + free(source); + return NULL; + } + + strcpy(l->source + current_start, source); + current_start += file_sz; + } + + l->source = realloc(l->source, current_start + 1); + if (!l->source) + { + snprintf(llast_err, sizeof(llast_err), + "not enough space for entire source\n"); + free(entries); + return NULL; + } + + l->source[current_start] = 0; + + l->entries = entries; + l->entries_count = ecount; + l->total_sz = current_start; + return true; +} + +char *fzl_linked_geterror() { return llast_err; } + +bool fzl_linked_getpos(fzl_linked_t *l, size_t pos, fzl_file_loc *res) +{ + if (!l) + return false; + + bool found = false; + fzl_linked_entry_t e; + for (size_t i = 0; i < l->entries_count; i++) + { + e = l->entries[i]; + if (pos >= e.start && pos <= e.end) + { + found = true; + break; + } + } + + if (!found) + return NULL; + + size_t line = 1; + size_t line_start = e.start; + for (size_t p = e.start; p < pos; p++) + { + if (l->source[p] == '\n') + { + line++; + line_start = p + 1; + } + } + + *res = + (fzl_file_loc){.filename = e.name, .line = line, .col = pos - line_start}; + return true; +} + +void fzl_freelinked(fzl_linked_t *linked) +{ + if (!linked) + return; + + if (linked->source) + free(linked->source); + + if (linked->entries) + free(linked->entries); + + free(linked); +} diff --git a/src/compiler/utils/fzl_options.c b/src/compiler/utils/fzl_options.c new file mode 100644 index 0000000..4060189 --- /dev/null +++ b/src/compiler/utils/fzl_options.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +const char version[] = "FZL Compiler 1.0"; + +void print_help() +{ + printf("Usage: fzc [flags] \n"); + printf("Available flags:\n"); + printf("\t-h\t--help\t\tPrint help\n"); + printf("\t-v\t--verbose\tDisplay compilation info (tokens, AST, consts " + "table, etc)\n"); + printf("\t-V\t--version\tPrint compiler version\n"); + printf("\t-o\t--out\t\tSet compiled file name\n"); +} + +fzl_options_t *fzl_mkoptions(int argc, char **argv) +{ + fzl_options_t *opts = malloc(sizeof(fzl_options_t)); + if (!opts) + { + printf("%sBruh... Potato PC%s\n", FZL_CERROR, FZL_CRST); + return NULL; + } + + memset(opts, 0, sizeof(fzl_options_t)); + + if (argc < 2) + { + print_help(); + opts->should_continue = false; + return opts; + } + + size_t i; + for (i = 1; i < argc; i++) + { + if (argv[i][0] != '-') + break; + + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + print_help(); + opts->should_continue = false; + break; + } + else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) + { + printf("%s\n", version); + opts->should_continue = false; + break; + } + else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) + { + opts->verbose = true; + } + else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--out") == 0) + { + if (i == argc - 1) + { + printf("%sError%s: missing out file name\n", FZL_CERROR, FZL_CRST); + free(opts); + return NULL; + } + + char *filename = argv[++i]; + opts->outname = filename; + } + else + { + printf("%sError%s: undefined flag '%s'\n", FZL_CERROR, FZL_CRST, argv[i]); + free(opts); + return NULL; + } + } + + size_t fremainder = 2; + size_t fcount = 0; + char **files = malloc(fremainder * sizeof(char *)); + + for (; i < argc; i++) + { + if (argv[i][0] == '-') + { + printf( + "%sWarning%s: unhandled flag '%s' (should be defined before sources " + "list)\n", + FZL_CWARNING, FZL_CRST, argv[i]); + continue; + } + + if (fremainder <= fcount) + { + fremainder *= 2; + files = realloc(files, fremainder * sizeof(char *)); + } + + files[fcount++] = argv[i]; + } + + opts->files = files; + opts->files_count = fcount; + + if (i < argc - 1) + { + printf("%sError%s: flags should go before source files list.\n", FZL_CERROR, + FZL_CRST); + printf("Remain %zu arguments are not handled.\n", argc - 1 - i); + + free(files); + free(opts); + return NULL; + } + + opts->should_continue = true; + return opts; +} + +void fzl_freeoptions(fzl_options_t *options) +{ + if (!options) + return; + + if (options->files) + free(options->files); + + free(options); +} diff --git a/src/compiler/utils/fzl_symtable.c b/src/compiler/utils/fzl_symtable.c new file mode 100644 index 0000000..a1c5f5d --- /dev/null +++ b/src/compiler/utils/fzl_symtable.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include + +fzl_symtable_t *fzl_mksymt() +{ + fzl_symtable_t *tbl = malloc(sizeof(fzl_symtable_t)); + if (!tbl) + return NULL; + + tbl->remainder = 2; + tbl->size = 0; + tbl->symbols = malloc(tbl->remainder * sizeof(fzl_sym_t)); + if (!tbl->symbols) + return NULL; + + return tbl; +} + +fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, char *name, + fzl_type_t type, uint64_t ip) +{ + if (!symt) + return FZL_SYMTABLE_RESULT_ERR; + + for (size_t i = 0; i < symt->size; i++) + if (strcmp(symt->symbols[i].name, name) == 0) + return FZL_SYMTABLE_RESULT_EXISTS; + + if (symt->remainder <= symt->size) + { + symt->remainder *= 2; + symt->symbols = realloc(symt->symbols, symt->remainder * sizeof(fzl_sym_t)); + } + + symt->symbols[symt->size++] = + (fzl_sym_t){.name = name, .type = type, .ip = ip, .classname = NULL}; + return FZL_SYMTABLE_RESULT_OK; +} + +fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name, + char *classname) +{ + if (!symt) + return FZL_SYMTABLE_RESULT_ERR; + + for (size_t i = 0; i < symt->size; i++) + { + if (strcmp(symt->symbols[i].name, name) == 0) + { + if (symt->symbols[i].type != FZL_TYPE_OBJECT) + return FZL_SYMTABLE_RESULT_NOT_OBJECT; + + symt->symbols[i].classname = classname; + return FZL_SYMTABLE_RESULT_OK; + } + } + + return FZL_SYMTABLE_RESULT_ERR; +} + +bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res) +{ + if (!symt) + return false; + + for (size_t i = 0; i < symt->size; i++) + { + if (strcmp(symt->symbols[i].name, name) == 0) + { + *res = symt->symbols[i]; + return true; + } + } + + return false; +} + +void fzl_freesymt(fzl_symtable_t *symt) +{ + if (!symt) + return; + + if (symt->symbols) + free(symt->symbols); + + free(symt); +} diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c new file mode 100644 index 0000000..7c22d8f --- /dev/null +++ b/src/vm/fzl_vm.c @@ -0,0 +1,650 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char last_err[256]; + +static char *expect_hdr = "FZL\0\1"; + +fzl_vm_t *fzl_mkvm(const char *filepath) +{ + fzl_vm_t *vm = malloc(sizeof(fzl_vm_t)); + if (!vm) + { + snprintf(last_err, sizeof(last_err), "failed to allocate vm"); + return NULL; + } + + vm->consts = NULL; + vm->consts_count = 0; + vm->ops = NULL; + vm->ops_count = 0; + vm->ip = 0; + vm->dtop = 0; + vm->ctop = 0; + vm->etop = 0; + vm->objects_count = 0; + + memset(vm->dstack, 0, sizeof(vm->dstack)); + memset(vm->cstack, 0, sizeof(vm->cstack)); + memset(vm->estack, 0, sizeof(vm->estack)); + memset(vm->objects, 0, sizeof(vm->objects)); + + FILE *fd = fopen(filepath, "rb"); + if (!fd) + { + snprintf(last_err, sizeof(last_err), "failed to open file '%s'", filepath); + return NULL; + } + + char hdr[5]; + fread(hdr, sizeof(char), 5, fd); + if (memcmp(hdr, expect_hdr, 5) != 0) + { + snprintf(last_err, sizeof(last_err), "magic check error"); + return NULL; + } + + size_t consts_count, ops_count; + fread(&consts_count, sizeof(size_t), 1, fd); + fread(&ops_count, sizeof(size_t), 1, fd); + + if (ops_count < 1) + { + snprintf(last_err, sizeof(last_err), "there is nothing to do (0 ops)"); + return NULL; + } + + if (consts_count > 0) + { + vm->consts_count = consts_count; + vm->consts = malloc(consts_count * sizeof(fzl_value_t)); + + for (size_t i = 0; i < consts_count; i++) + { + fzl_consttype_t type; + fread(&type, sizeof(fzl_consttype_t), 1, fd); + + if (type == FZL_CONSTTYPE_STRING) + { + size_t length; + char *chars; + + fread(&length, sizeof(size_t), 1, fd); + + chars = malloc(length * sizeof(char) + 1); + fread(chars, sizeof(char), length, fd); + chars[length] = 0; + + vm->consts[i] = (fzl_const_t){ + .type = type, .as.string.length = length, .as.string.chars = chars}; + } + else + { + snprintf(last_err, sizeof(last_err), "unknown const type at index %ld", + i); + return NULL; + } + } + } + + vm->ops_count = ops_count; + vm->ops = malloc(ops_count * sizeof(uint64_t)); + for (size_t i = 0; i < ops_count; i++) + { + uint64_t op; + fread(&op, sizeof(uint64_t), 1, fd); + vm->ops[i] = op; + } + + return vm; +} + +int fzl_vm_run(fzl_vm_t *vm) +{ + if (!vm) + { + snprintf(last_err, sizeof(last_err), "vm is null"); + return -1; + } + + vm->ip = 0; + while (vm->ip < vm->ops_count) + { + fzl_opcode_t opcode = vm->ops[vm->ip]; + + switch (opcode) + { + case FZL_OPCODE_ALLOC_OBJECT: + { + uint64_t values_count = vm->ops[++vm->ip]; + fzl_vm_object_t *obj = malloc(sizeof(fzl_vm_object_t)); + obj->values = malloc(values_count * sizeof(fzl_value_t)); + obj->values_count = values_count; + + vm->objects[vm->objects_count++] = obj; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = obj}; + break; + } + + case FZL_OPCODE_GET_FIELD: + { + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in data stack"); + return -1; + } + + uint64_t field_slot = vm->ops[++vm->ip]; + fzl_value_t val = vm->dstack[--vm->dtop]; + if (val.type != FZL_TYPE_OBJECT) + { + snprintf( + last_err, sizeof(last_err), + "runtime error - cannot access field #%lu of non-object type", + field_slot); + return -1; + } + + fzl_vm_object_t *obj = val.as.obj; + if (obj->values_count <= field_slot) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot access field #%lu", field_slot); + return -1; + } + + vm->dstack[vm->dtop++] = obj->values[field_slot]; + break; + } + + case FZL_OPCODE_SET_FIELD: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + uint64_t field_slot = vm->ops[++vm->ip]; + fzl_value_t objval = vm->dstack[--vm->dtop]; + fzl_value_t wrval = vm->dstack[--vm->dtop]; + if (objval.type != FZL_TYPE_OBJECT) + { + snprintf( + last_err, sizeof(last_err), + "runtime error - cannot access field #%lu of non-object type", + field_slot); + return -1; + } + + fzl_vm_object_t *obj = objval.as.obj; + if (obj->values_count <= field_slot) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot access field #%lu", field_slot); + return -1; + } + + obj->values[field_slot] = wrval; + break; + } + + case FZL_OPCODE_CALL: + { + uint64_t ip = vm->ops[++vm->ip]; + vm->cstack[vm->ctop++] = vm->ip; + vm->ip = ip - 1; + break; + } + + case FZL_OPCODE_RET: + { + if (vm->ctop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - no place to jump"); + return -1; + } + + vm->ip = vm->cstack[--vm->ctop]; + break; + } + + case FZL_OPCODE_CMP_EQ: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t req = vm->dstack[--vm->dtop]; + fzl_value_t leq = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = leq.as.u == req.as.u}; + break; + + case FZL_OPCODE_CMP_NEQ: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t rneq = vm->dstack[--vm->dtop]; + fzl_value_t lneq = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL, + .as.b = lneq.as.u != rneq.as.u}; + break; + + case FZL_OPCODE_CMP_LT: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t rlt = vm->dstack[--vm->dtop]; + fzl_value_t llt = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = llt.as.u < rlt.as.u}; + break; + + case FZL_OPCODE_CMP_LTEQ: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t rlteq = vm->dstack[--vm->dtop]; + fzl_value_t llteq = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = (fzl_value_t){ + .type = FZL_TYPE_BOOL, .as.b = llteq.as.u <= rlteq.as.u}; + break; + + case FZL_OPCODE_CMP_GT: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t rgt = vm->dstack[--vm->dtop]; + fzl_value_t lgt = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = lgt.as.u > rgt.as.u}; + break; + + case FZL_OPCODE_CMP_GTEQ: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t rgteq = vm->dstack[--vm->dtop]; + fzl_value_t lgteq = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = (fzl_value_t){ + .type = FZL_TYPE_BOOL, .as.b = lgteq.as.u >= rgteq.as.u}; + break; + + case FZL_OPCODE_NOT: + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in data stack"); + return -1; + } + + fzl_value_t notval = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = !notval.as.b}; + break; + + case FZL_OPCODE_JZ: + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in data stack"); + return -1; + } + + fzl_value_t jzval = vm->dstack[--vm->dtop]; + uint64_t jzip = vm->ops[++vm->ip] - 1; + if (jzval.as.b == 0) + vm->ip = jzip; + break; + + case FZL_OPCODE_JMP: + vm->ip = vm->ops[++vm->ip] - 1; + break; + + case FZL_OPCODE_AND: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t land = vm->dstack[--vm->dtop]; + fzl_value_t rand = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL, + .as.b = land.as.b && rand.as.b}; + break; + + case FZL_OPCODE_OR: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + fzl_value_t lor = vm->dstack[--vm->dtop]; + fzl_value_t ror = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = lor.as.b || ror.as.b}; + break; + + case FZL_OPCODE_STORE: + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected var name in data stack"); + return -1; + } + + fzl_value_t varname = vm->dstack[--vm->dtop]; + fzl_env_storevalue(vm->estack[vm->etop - 1], (char *)varname.as.s.value, + (fzl_value_t){}); + + break; + + case FZL_OPCODE_WRITE: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in data stack"); + return -1; + } + + fzl_value_t writevar = vm->dstack[--vm->dtop]; + fzl_value_t value = vm->dstack[--vm->dtop]; + + fzl_vm_env_wresult_t wresult = fzl_env_writevalue( + vm->estack[vm->etop - 1], (char *)writevar.as.s.value, value); + if (wresult != FZL_VM_ENV_WRESULT_OK) + { + switch (wresult) + { + case FZL_VM_ENV_WRESULT_ERR: + snprintf(last_err, sizeof(last_err), + "runtime error - failed to write variable"); + return -1; + + case FZL_VM_ENV_WRESULT_ERR_EXISTS: + snprintf(last_err, sizeof(last_err), + "runtime error - redefenition of '%s'", + writevar.as.s.value); + return -1; + + case FZL_VM_ENV_WRESULT_ERR_NOT_FOUND: + snprintf(last_err, sizeof(last_err), + "runtime error - cannot find name '%s'", + writevar.as.s.value); + return -1; + + default: + return -1; + } + } + + // vm->dstack[vm->dtop++] = value; + // NOTE: add POP opcode to prevent + // stack overflow! + break; + + case FZL_OPCODE_MK_ENVTABLE: + vm->estack[vm->etop++] = fzl_mkenv(); + break; + + case FZL_OPCODE_RM_ENVTABLE: + fzl_freeenv(vm->estack[--vm->etop]); + break; + + case FZL_OPCODE_LOOKUP_ENV: + { + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - LOOKUP_ENV: empty data stack"); + return -1; + } + + fzl_value_t val = vm->dstack[--vm->dtop]; + fzl_value_t env_val; + bool result; + for (ssize_t i = vm->etop - 1; i >= 0; i--) + { + if ((result = fzl_env_lookup(vm->estack[i], (char *)val.as.s.value, + &env_val))) + break; + } + vm->dstack[vm->dtop++] = env_val; + + break; + } + + case FZL_OPCODE_CALL_BUILTIN: + { + uint64_t fn_id = vm->ops[++vm->ip]; + if (fn_id == FZL_BUILTIN_PRINTLINE) + { + fzl_value_t arg = vm->dstack[--vm->dtop]; + if (arg.type == FZL_TYPE_STRING) + printf("%s\n", arg.as.s.value); + else if (arg.type == FZL_TYPE_CHAR) + printf("%c\n", arg.as.c); + else if (arg.type == FZL_TYPE_INT) + printf("%ld\n", arg.as.i); + else if (arg.type == FZL_TYPE_UINT) + printf("%lu\n", arg.as.u); + else if (arg.type == FZL_TYPE_BOOL) + printf("%b\n", arg.as.b); + else if (arg.type == FZL_TYPE_NULL) + printf("\n"); + } + else + { + snprintf(last_err, sizeof(last_err), + "runtime error - undefined builtin %lu", fn_id); + return -1; + } + } + break; + + case FZL_OPCODE_LOADCONST: + { + uint64_t cn = vm->ops[++vm->ip]; + if (vm->consts_count <= cn) + { + snprintf(last_err, sizeof(last_err), "runtime error - unknown const"); + return -1; + } + fzl_const_t c = vm->consts[cn]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_STRING, + .as.s.length = c.as.string.length, + .as.s.value = c.as.string.chars}; + } + break; + + case FZL_OPCODE_ADD: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t add1 = vm->dstack[--vm->dtop]; + fzl_value_t add2 = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = add1.type, .as.u = add1.as.u + add2.as.u}; + break; + + case FZL_OPCODE_SUB: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t sub2 = vm->dstack[--vm->dtop]; + fzl_value_t sub1 = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = sub1.type, .as.u = sub1.as.u - sub2.as.u}; + break; + + case FZL_OPCODE_MUL: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t mul1 = vm->dstack[--vm->dtop]; + fzl_value_t mul2 = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = mul1.type, .as.u = mul1.as.u * mul2.as.u}; + break; + + case FZL_OPCODE_DIV: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t div1 = vm->dstack[--vm->dtop]; + fzl_value_t div2 = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = div1.type, .as.u = div2.as.u / div1.as.u}; + break; + + case FZL_OPCODE_POW: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t pow2 = vm->dstack[--vm->dtop]; + fzl_value_t pow1 = vm->dstack[--vm->dtop]; + + uint64_t pow_n = pow1.as.u; + uint64_t pow_res = pow_n; + uint64_t pow_times = pow2.as.u; + for (size_t i = 1; i < pow_times; i++) + pow_res *= pow_n; + + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = pow1.type, .as.u = pow_res}; + break; + + case FZL_OPCODE_MOD: + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected 2 values in stack"); + return -1; + } + fzl_value_t mod1 = vm->dstack[--vm->dtop]; + fzl_value_t mod2 = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = mod1.type, .as.u = mod2.as.u % mod1.as.u}; + break; + + case FZL_OPCODE_NEG: + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in stack"); + return -1; + } + fzl_value_t neg_n = vm->dstack[--vm->dtop]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = neg_n.type, .as.u = -neg_n.as.u}; + break; + + case FZL_OPCODE_PUSHNUM: + { + uint64_t push_n = vm->ops[++vm->ip]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_UINT, .as.u = push_n}; + break; + } + + case FZL_OPCODE_PUSHBOOL: + { + uint64_t push_b = vm->ops[++vm->ip]; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = push_b}; + break; + } + + default: + snprintf(last_err, sizeof(last_err), + "runtime error - unknown opcode %u", opcode); + return -1; + } + + vm->ip++; + } + + return 0; +} + +char *fzl_vm_geterror() { return last_err; } + +void fzl_freevm(fzl_vm_t *vm) +{ + if (!vm) + return; + + for (size_t i = 0; i < vm->consts_count; i++) + { + if (vm->consts[i].type == FZL_CONSTTYPE_STRING) + free((void *)vm->consts[i].as.string.chars); + } + + for (size_t i = 0; i < vm->objects_count; i++) + { + free(vm->objects[i]->values); + free(vm->objects[i]); + } + + free(vm->consts); + free(vm->ops); + free(vm); +} diff --git a/src/vm/fzl_vm_debug.c b/src/vm/fzl_vm_debug.c new file mode 100644 index 0000000..9f3f3ef --- /dev/null +++ b/src/vm/fzl_vm_debug.c @@ -0,0 +1,147 @@ +#include "common/fzl_opcodes.h" +#include +#include +#include + +void fzl_vm_print_ops(fzl_vm_t *vm) +{ + if (!vm) + return; + + if (vm->ops_count < 1) + { + printf("no opcodes\n"); + return; + } + + for (size_t i = 0; i < vm->ops_count; i++) + { + printf("%04ld: ", i); + + switch (vm->ops[i]) + { + case FZL_OPCODE_ALLOC_OBJECT: + printf("ALLOC_OBJECT: %lu fields\n", vm->ops[++i]); + break; + + case FZL_OPCODE_GET_FIELD: + printf("GET_FIELD #%lu\n", vm->ops[++i]); + break; + + case FZL_OPCODE_SET_FIELD: + printf("SET_FIELD #%lu\n", vm->ops[++i]); + break; + + case FZL_OPCODE_CALL: + { + size_t ip = vm->ops[++i]; + printf("CALL IP: %lu\n", ip); + break; + } + + case FZL_OPCODE_RET: + printf("RET\n"); + break; + + case FZL_OPCODE_LOADCONST: + { + size_t const_n = vm->ops[++i]; + fzl_const_t c = vm->consts[const_n]; + printf("LOADCONST #%lu ('%s')\n", const_n, c.as.string.chars); + break; + } + + case FZL_OPCODE_CALL_BUILTIN: + printf("CALL_BUILTIN #%lu\n", vm->ops[++i]); + break; + + case FZL_OPCODE_PUSHNUM: + printf("PUSHNUM %lu\n", vm->ops[++i]); + break; + + case FZL_OPCODE_PUSHBOOL: + printf("PUSHBOOL %s\n", vm->ops[++i] ? "true" : "false"); + break; + + case FZL_OPCODE_MK_ENVTABLE: + printf("MK_ENVTABLE\n"); + break; + + case FZL_OPCODE_RM_ENVTABLE: + printf("RM_ENVTABLE\n"); + break; + + case FZL_OPCODE_STORE: + printf("STORE\n"); + break; + + case FZL_OPCODE_LOOKUP_ENV: + printf("LOOKUP_ENV\n"); + break; + + case FZL_OPCODE_WRITE: + printf("WRITE\n"); + break; + + case FZL_OPCODE_ADD: + case FZL_OPCODE_SUB: + case FZL_OPCODE_MUL: + case FZL_OPCODE_DIV: + case FZL_OPCODE_POW: + case FZL_OPCODE_MOD: + printf("MATH\n"); + break; + + case FZL_OPCODE_AND: + printf("AND\n"); + break; + + case FZL_OPCODE_OR: + printf("OR\n"); + break; + + case FZL_OPCODE_CMP_EQ: + printf("CMP_EQ\n"); + break; + + case FZL_OPCODE_CMP_NEQ: + printf("CMP_NEQ\n"); + break; + + case FZL_OPCODE_CMP_LT: + printf("CMP_LT\n"); + break; + + case FZL_OPCODE_CMP_LTEQ: + printf("CMP_LTEQ\n"); + break; + + case FZL_OPCODE_CMP_GT: + printf("CMP_GT\n"); + break; + + case FZL_OPCODE_CMP_GTEQ: + printf("CMP_GTEQ\n"); + break; + + case FZL_OPCODE_NOT: + printf("CMP_NOT\n"); + break; + + case FZL_OPCODE_NEG: + printf("NEG\n"); + break; + + case FZL_OPCODE_JZ: + printf("JZ IP: %ld\n", vm->ops[++i]); + break; + + case FZL_OPCODE_JMP: + printf("JMP IP: %ld\n", vm->ops[++i]); + break; + + default: + printf("\n"); + } + } +} diff --git a/src/vm/fzl_vm_env.c b/src/vm/fzl_vm_env.c new file mode 100644 index 0000000..2ca4ede --- /dev/null +++ b/src/vm/fzl_vm_env.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include + +static const fzl_value_t null = {.type = FZL_TYPE_NULL}; + +fzl_vm_env_t *fzl_mkenv() +{ + fzl_vm_env_t *env = malloc(sizeof(fzl_vm_env_t)); + env->remainder = 2; + env->size = 0; + env->table = malloc(env->remainder * sizeof(fzl_vm_env_entry_t)); + + return env; +} + +// Resize table if size eq to remainder +static void resize_env_if_needed(fzl_vm_env_t *env) +{ + if (env->remainder > env->size) + return; + + env->remainder *= 2; + env->table = realloc(env->table, env->remainder * sizeof(fzl_vm_env_entry_t)); +} + +bool fzl_env_lookup(fzl_vm_env_t *env, char *name, fzl_value_t *res) +{ + if (!env) + return false; + + for (size_t i = 0; i < env->size; i++) + if (strcmp(env->table[i].name, name) == 0) + { + *res = env->table[i].value; + return true; + } + + return false; +} + +bool fzl_env_contains(fzl_vm_env_t *env, char *name) +{ + if (!env) + return false; + + for (size_t i = 0; i < env->size; i++) + if (strcmp(env->table[i].name, name) == 0) + return true; + + return false; +} + +fzl_vm_env_wresult_t fzl_env_storevalue(fzl_vm_env_t *env, char *name, + fzl_value_t val) +{ + if (!env) + return FZL_VM_ENV_WRESULT_ERR; + + resize_env_if_needed(env); + if (fzl_env_contains(env, name)) + return FZL_VM_ENV_WRESULT_ERR_EXISTS; + + env->table[env->size++] = (fzl_vm_env_entry_t){.name = name, .value = val}; + + return FZL_VM_ENV_WRESULT_OK; +} + +fzl_vm_env_wresult_t fzl_env_writevalue(fzl_vm_env_t *env, char *name, + fzl_value_t newval) +{ + if (!env) + return FZL_VM_ENV_WRESULT_ERR; + + for (size_t i = 0; i < env->size; i++) + { + if (strcmp(env->table[i].name, name) == 0) + { + env->table[i].value = newval; + return FZL_VM_ENV_WRESULT_OK; + } + } + + return FZL_VM_ENV_WRESULT_ERR_NOT_FOUND; +} + +void fzl_freeenv(fzl_vm_env_t *env) +{ + if (!env) + return; + + if (env->table) + free(env->table); + + free(env); +} diff --git a/src/vm/main.c b/src/vm/main.c new file mode 100644 index 0000000..55c85d4 --- /dev/null +++ b/src/vm/main.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include + +typedef struct +{ + char *filepath; + bool help; + bool debug; +} run_args_t; + +void print_hint() +{ + printf("Usage: fz [flags] \n"); + printf("Flags:\n"); + printf("\t-h\t--help\tprint usage information\n"); +} + +// Parse passed arguments and build args setup structure +run_args_t *parse_args(int argc, char **argv) +{ + if (argc < 2) + { + print_hint(); + return NULL; + } + + run_args_t *args = malloc(sizeof(run_args_t)); + args->filepath = NULL; + args->help = false; + args->debug = false; + + for (size_t i = 0; i < argc; i++) + { + if (argv[i][0] != '-') + continue; + + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + args->help = true; + return args; + } + else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--debug") == 0) + args->debug = true; + else + { + printf("Error: unknown flag '%s'.\n", argv[i]); + printf("Use 'fz --help' to read the usage\n"); + return NULL; + } + } + + if (argv[argc - 1][0] == '-') + { + printf("Error: last argument MUST be a file path\n"); + printf("Use 'fz --help' for help\n"); + return NULL; + } + + args->filepath = argv[argc - 1]; + + return args; +} + +int main(int argc, char **argv) +{ + run_args_t *args = parse_args(argc, argv); + if (!args) + return -1; + else if (args->help) + { + print_hint(); + return 0; + } + + fzl_vm_t *vm = fzl_mkvm(args->filepath); + if (!vm) + { + printf("Error: %s\n", fzl_vm_geterror()); + return -1; + } + + if (args->debug) + fzl_vm_print_ops(vm); + else + { + int result = fzl_vm_run(vm); + if (result != 0) + { + printf("Error: %s\n", fzl_vm_geterror()); + return -1; + } + } + + free(args); + fzl_freevm(vm); + + return 0; +}