From 5a67acd796d1ae32d8370cc116042b6bc0582335 Mon Sep 17 00:00:00 2001 From: bog Date: Sat, 10 Feb 2024 16:16:00 +0100 Subject: [PATCH] :sparkles: booleans. --- .gitignore | 4 + CMakeLists.txt | 9 ++ Makefile | 18 +++ bc/CMakeLists.txt | 22 +++ bc/src/compiler.c | 171 +++++++++++++++++++++ bc/src/compiler.h | 20 +++ bc/src/opcodes.c | 3 + bc/src/opcodes.h | 12 ++ bc/src/program.c | 77 ++++++++++ bc/src/program.h | 29 ++++ doc/gux.bnf | 15 ++ guxi/CMakeLists.txt | 24 +++ guxi/src/main.c | 309 ++++++++++++++++++++++++++++++++++++++ lang/CMakeLists.txt | 59 ++++++++ lang/src/lexer.c | 238 +++++++++++++++++++++++++++++ lang/src/lexer.h | 51 +++++++ lang/src/node.c | 83 ++++++++++ lang/src/node.h | 33 ++++ lang/src/parser.c | 291 +++++++++++++++++++++++++++++++++++ lang/src/parser.h | 36 +++++ lang/src/type.c | 91 +++++++++++ lang/src/type.h | 27 ++++ lang/src/type_checker.c | 80 ++++++++++ lang/src/type_checker.h | 25 +++ lang/src/type_resolver.c | 40 +++++ lang/src/type_resolver.h | 19 +++ lang/src/value.c | 81 ++++++++++ lang/src/value.h | 29 ++++ lang/tests/lexer.c | 70 +++++++++ lang/tests/node.c | 17 +++ lang/tests/parser.c | 69 +++++++++ lang/tests/trivial.c | 5 + lang/tests/type.c | 63 ++++++++ lang/tests/type_checker.c | 51 +++++++ lang/tests/value.c | 29 ++++ lib/CMakeLists.txt | 21 +++ lib/src/commons.h | 23 +++ lib/src/vec.c | 65 ++++++++ lib/src/vec.h | 21 +++ tests/bool.gux | 14 ++ tests/run.sh | 32 ++++ vm/CMakeLists.txt | 21 +++ vm/src/vm.c | 163 ++++++++++++++++++++ vm/src/vm.h | 35 +++++ 44 files changed, 2595 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 bc/CMakeLists.txt create mode 100644 bc/src/compiler.c create mode 100644 bc/src/compiler.h create mode 100644 bc/src/opcodes.c create mode 100644 bc/src/opcodes.h create mode 100644 bc/src/program.c create mode 100644 bc/src/program.h create mode 100644 doc/gux.bnf create mode 100644 guxi/CMakeLists.txt create mode 100644 guxi/src/main.c create mode 100644 lang/CMakeLists.txt create mode 100644 lang/src/lexer.c create mode 100644 lang/src/lexer.h create mode 100644 lang/src/node.c create mode 100644 lang/src/node.h create mode 100644 lang/src/parser.c create mode 100644 lang/src/parser.h create mode 100644 lang/src/type.c create mode 100644 lang/src/type.h create mode 100644 lang/src/type_checker.c create mode 100644 lang/src/type_checker.h create mode 100644 lang/src/type_resolver.c create mode 100644 lang/src/type_resolver.h create mode 100644 lang/src/value.c create mode 100644 lang/src/value.h create mode 100644 lang/tests/lexer.c create mode 100644 lang/tests/node.c create mode 100644 lang/tests/parser.c create mode 100644 lang/tests/trivial.c create mode 100644 lang/tests/type.c create mode 100644 lang/tests/type_checker.c create mode 100644 lang/tests/value.c create mode 100644 lib/CMakeLists.txt create mode 100644 lib/src/commons.h create mode 100644 lib/src/vec.c create mode 100644 lib/src/vec.h create mode 100644 tests/bool.gux create mode 100755 tests/run.sh create mode 100644 vm/CMakeLists.txt create mode 100644 vm/src/vm.c create mode 100644 vm/src/vm.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10a7aa1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~* +*\#* +build +.cache \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f879361 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.2) + +project(gux LANGUAGES C) + +add_subdirectory(lib) +add_subdirectory(lang) +add_subdirectory(bc) +add_subdirectory(vm) +add_subdirectory(guxi) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0e71eca --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: build tests install + +build: + cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug + cmake --build build + +tests: build + build/lang/gux-lang-tests + +install: check tests + sudo cmake --install build + +check: + @cppcheck --enable=all lib lang vm -q \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --suppress=missingInclude \ + --suppress=unmatchedSuppression diff --git a/bc/CMakeLists.txt b/bc/CMakeLists.txt new file mode 100644 index 0000000..7679e3f --- /dev/null +++ b/bc/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.2) + +project(gux-bc LANGUAGES C) + +add_library(gux-bc OBJECT + src/compiler.c + src/program.c + src/opcodes.c +) + +set_property(TARGET gux-bc PROPERTY C_STANDARD 99) + +add_dependencies(gux-bc gux-lang) + +target_include_directories(gux-bc + PUBLIC gux-lang + PUBLIC ${CMAKE_SOURCE_DIR}/bc/src +) + +target_link_libraries(gux-bc + PUBLIC gux-lang +) diff --git a/bc/src/compiler.c b/bc/src/compiler.c new file mode 100644 index 0000000..cff97d2 --- /dev/null +++ b/bc/src/compiler.c @@ -0,0 +1,171 @@ +#include "compiler.h" +#include "commons.h" +#include "node.h" +#include "opcodes.h" +#include "value.h" +#include "vec.h" + +void compiler_init(struct compiler* self) +{ + assert(self); +} + +void compiler_free(struct compiler* self) +{ + assert(self); +} + +int compiler_compile(struct compiler* self, + struct node* node, + struct program* program) +{ + assert(self); + assert(node); + assert(program); + + switch (node->type) + { + case NODE_BOOL: { + struct value* val = malloc(sizeof(struct value)); + value_init_bool(val, + strcmp(node->value, "true") == 0 ? 1 : 0, + node->line); + size_t addr = program_push_constant(program, val); + program_push_instr(program, OP_PUSH, addr); + } break; + + case NODE_ASSERT: { + compiler_compile(self, node->children.data[0], program); + size_t brt = program_push_instr(program, OP_BRT, NO_PARAM); + program_push_instr(program, OP_PUSH, GUX_RET_ASSERT); + size_t halt = program_push_instr(program, OP_HALT, node->line); + + struct instruction* instr = program->instructions.data[brt]; + instr->param = halt + 1; + + if (halt + 1 >= program->instructions.size) + { + program_push_instr(program, OP_NOP, NO_PARAM); + } + + struct value* value = malloc(sizeof(struct value)); + value_init_bool(value, VAL_TRUE, node->line); + program_push_instr(program, OP_PUSH, + program_push_constant(program, value)); + + } break; + + case NODE_NOT: { + compiler_compile(self, node->children.data[0], program); + program_push_instr(program, OP_NOT, NO_PARAM); + } break; + + case NODE_AND: { + struct vec to_pivot; + vec_init(&to_pivot, 1); + + for (size_t i=0; ichildren.size; i++) + { + compiler_compile(self, node->children.data[i], program); + + size_t* addr = malloc(sizeof(size_t)); + *addr = program_push_instr(program, OP_BRF, NO_PARAM); // to pivot + vec_push(&to_pivot, addr); + } + + // true case + struct value* true_val = malloc(sizeof(struct value)); + value_init_bool(true_val, VAL_TRUE, node->line); + program_push_instr(program, OP_PUSH, + program_push_constant(program, true_val)); + + size_t to_end = program_push_instr(program, OP_BR, NO_PARAM); // to end + + // pivot + size_t pivot_point = program->instructions.size; + + // false case + struct value* false_val = malloc(sizeof(struct value)); + value_init_bool(false_val, VAL_FALSE, node->line); + program_push_instr(program, OP_PUSH, + program_push_constant(program, false_val)); + + // end + size_t end_point = program->instructions.size; + + for (size_t i=0; iinstructions.data[addr]; + instr->param = pivot_point; + } + + { + struct instruction* instr = program->instructions.data[to_end]; + instr->param = end_point; + } + + vec_free_elements(&to_pivot); + vec_free(&to_pivot); + } break; + + case NODE_OR: { + struct vec to_pivot; + vec_init(&to_pivot, 1); + + for (size_t i=0; ichildren.size; i++) + { + compiler_compile(self, node->children.data[i], program); + + size_t* addr = malloc(sizeof(size_t)); + *addr = program_push_instr(program, OP_BRT, NO_PARAM); // to pivot + vec_push(&to_pivot, addr); + } + + // false case + struct value* false_val = malloc(sizeof(struct value)); + value_init_bool(false_val, VAL_FALSE, node->line); + program_push_instr(program, OP_PUSH, + program_push_constant(program, false_val)); + + size_t to_end = program_push_instr(program, OP_BR, NO_PARAM); // to end + + // pivot + size_t pivot_point = program->instructions.size; + + // false case + struct value* true_val = malloc(sizeof(struct value)); + value_init_bool(true_val, VAL_TRUE, node->line); + program_push_instr(program, OP_PUSH, + program_push_constant(program, true_val)); + + // end + size_t end_point = program->instructions.size; + + for (size_t i=0; iinstructions.data[addr]; + instr->param = pivot_point; + } + + { + struct instruction* instr = program->instructions.data[to_end]; + instr->param = end_point; + } + + vec_free_elements(&to_pivot); + vec_free(&to_pivot); + } break; + + default: { + for (size_t i=0; ichildren.size; i++) + { + compiler_compile(self, node->children.data[i], program); + } + } break; + + } + + return 0; +} diff --git a/bc/src/compiler.h b/bc/src/compiler.h new file mode 100644 index 0000000..0dc531b --- /dev/null +++ b/bc/src/compiler.h @@ -0,0 +1,20 @@ +#ifndef COMPILER_H +#define COMPILER_H + +#include +#include +#include "program.h" + +struct compiler { + char* error_msg; + int error_line; +}; + +void compiler_init(struct compiler* self); +void compiler_free(struct compiler* self); + +int compiler_compile(struct compiler* self, + struct node* node, + struct program* program); + +#endif diff --git a/bc/src/opcodes.c b/bc/src/opcodes.c new file mode 100644 index 0000000..6dc6d4f --- /dev/null +++ b/bc/src/opcodes.c @@ -0,0 +1,3 @@ +#include "opcodes.h" + +GUX_ENUM_C(Opcodes, OPCODES); diff --git a/bc/src/opcodes.h b/bc/src/opcodes.h new file mode 100644 index 0000000..9061717 --- /dev/null +++ b/bc/src/opcodes.h @@ -0,0 +1,12 @@ +#ifndef OPCODES_H +#define OPCODES_H + +#define OPCODES(G) \ + G(OP_PUSH), G(OP_POP), G(OP_BRF), G(OP_BR), G(OP_HALT), \ + G(OP_BRT), G(OP_NOP), G(OP_NOT) + +#include + +GUX_ENUM_H(Opcodes, OPCODES); + +#endif diff --git a/bc/src/program.c b/bc/src/program.c new file mode 100644 index 0000000..f60f783 --- /dev/null +++ b/bc/src/program.c @@ -0,0 +1,77 @@ +#include "program.h" +#include "commons.h" + +void program_init(struct program* self) +{ + assert(self); + vec_init(&self->constant_pool, 1); + vec_init(&self->instructions, 1); +} + +void program_free(struct program* self) +{ + assert(self); + + for (size_t i=0; iconstant_pool.size; i++) + { + value_free(self->constant_pool.data[i]); + } + + vec_free_elements(&self->constant_pool); + vec_free(&self->constant_pool); + + vec_free_elements(&self->instructions); + vec_free(&self->instructions); +} + +size_t program_push_instr(struct program* self, enum Opcodes op, int param) +{ + assert(self); + struct instruction* instr = malloc(sizeof(struct instruction)); + instr->opcode = op; + instr->param = param; + + vec_push(&self->instructions, instr); + return self->instructions.size - 1; +} + +size_t program_push_constant(struct program* self, struct value* value) +{ + assert(self); + assert(value); + + vec_push(&self->constant_pool, value); + return self->constant_pool.size - 1; +} + +size_t program_str(struct program* self, char* buffer, size_t size) +{ + assert(self); + assert(buffer); + + size_t sz = 0; + + sz += snprintf(buffer, size, "[CONSTANTS]\n"); + + for (size_t i=0; iconstant_pool.size; i++) + { + sz += snprintf(buffer + sz, size - sz, "\t%ld: ", i); + sz += value_str(self->constant_pool.data[i], buffer + sz, size - sz); + sz += snprintf(buffer + sz, size - sz, "\n"); + } + + sz += snprintf(buffer + sz, size - sz, "[PROGRAM]\n"); + + for (size_t i=0; iinstructions.size; i++) + { + struct instruction* instr = (struct instruction*) + self->instructions.data[i]; + + sz += snprintf(buffer + sz, size - sz, "\t%ld: %s %d\n", + i, + OpcodesStr[instr->opcode] + strlen("OP_"), + instr->param); + } + + return sz; +} diff --git a/bc/src/program.h b/bc/src/program.h new file mode 100644 index 0000000..da71763 --- /dev/null +++ b/bc/src/program.h @@ -0,0 +1,29 @@ +#ifndef PROGRAM_H +#define PROGRAM_H + +#include +#include +#include +#include "opcodes.h" + +#define NO_PARAM -1 + +struct instruction { + enum Opcodes opcode; + int param; +}; + +struct program { + struct vec instructions; + struct vec constant_pool; +}; + +void program_init(struct program* self); +void program_free(struct program* self); + +size_t program_push_instr(struct program* self, enum Opcodes op, int param); +size_t program_push_constant(struct program* self, struct value* value); + +size_t program_str(struct program* self, char* buffer, size_t size); + +#endif diff --git a/doc/gux.bnf b/doc/gux.bnf new file mode 100644 index 0000000..228efd1 --- /dev/null +++ b/doc/gux.bnf @@ -0,0 +1,15 @@ +ROOT ::= EXPR* + +INSTR ::= +| EXPR semicolon + +EXPR ::= +| OR +| ASSERT EXPR + +OR ::= AND (or AND)* +AND ::= NOT (and NOT)* +NOT ::= not* LITERAL + +LITERAL ::= BUILTIN | opar EXPR cpar +BUILTIN ::= bool diff --git a/guxi/CMakeLists.txt b/guxi/CMakeLists.txt new file mode 100644 index 0000000..3ef423d --- /dev/null +++ b/guxi/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.2) + +project(guxi LANGUAGES C) + +add_executable(guxi + src/main.c +) + +set_property(TARGET guxi PROPERTY C_STANDARD 99) + +add_dependencies(guxi gux-vm) + +target_include_directories(guxi + PUBLIC gux-vm gux-bc +) + +target_link_libraries(guxi + PUBLIC gux-vm + PUBLIC gux-lang + PUBLIC gux-bc + PUBLIC gux-lib +) + +install(TARGETS guxi) diff --git a/guxi/src/main.c b/guxi/src/main.c new file mode 100644 index 0000000..5262938 --- /dev/null +++ b/guxi/src/main.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include + +char* load_new_source(char const* path) +{ + FILE* file = fopen(path, "r+"); + assert(file); + + char* source = malloc(sizeof(char)); + size_t size = 0; + size_t cap = 1; + + char buf; + size_t sz; + + while ( (sz=fread(&buf, sizeof(char), 1, file)) > 0) + { + if (size + 1 >= cap) + { + cap *= 2; + source = realloc(source, sizeof(char) * cap); + } + + source[size] = buf; + size++; + } + + if (size + 1 >= cap) + { + cap *= 2; + source = realloc(source, sizeof(char) * cap); + } + + source[size] = '\0'; + + fclose(file); + + return source; +} + +int main(int argc, char** argv) +{ + static int show_tokens = 0; + static int show_ast = 0; + static int show_bytecodes = 0; + static int show_stack = 0; + + int c; + + while (1) + { + static struct option long_opts[] = { + {"tokens", no_argument, &show_tokens, 1}, + {"ast", no_argument, &show_ast, 1}, + {"bytecodes", no_argument, &show_bytecodes, 1}, + {"stack", no_argument, &show_stack, 1}, + {"help", no_argument, 0, 'h'} + }; + + int opt_index = 0; + + c = getopt_long(argc, argv, "h", long_opts, &opt_index); + + if (c == -1) { break; } + + switch (c) + { + case 'h': { + printf("Usage: guxi [OPTION]... [SOURCE]...\n"); + printf("OPTIONS:\n"); + printf("\t--ast\tshow abstract syntax tree.\n"); + printf("\t--tokens\tshow tokens.\n"); + printf("\t-h, --help\tprint this help message.\n"); + exit(0); + } break; + } + } + + while (optind < argc) + { + char const* path = argv[optind++]; + char* src = load_new_source(path); + + struct lexer lex; + lexer_init(&lex, src); + + struct vec tokens; + vec_init(&tokens, 1); + int err = lexer_extract(&lex, &tokens); + + if (err != 0) + { + fprintf(stderr, "[%s:%d] %s\n", + path, + lex.line, + lex.error_msg); + + for (size_t i=0; isource = strdup(source); + self->cursor = 0; + self->line = 1; + + vec_init(&self->toks, 1); + + lexer_add_tok(self, "&&", NODE_AND, 0); + lexer_add_tok(self, "||", NODE_OR, 0); + lexer_add_tok(self, "!", NODE_NOT, 0); + lexer_add_tok(self, "(", NODE_OPAR, 0); + lexer_add_tok(self, ")", NODE_CPAR, 0); + lexer_add_tok(self, ";", NODE_SEMICOLON, 0); +} + +void lexer_free(struct lexer* self) +{ + assert(self); + free(self->source); + + vec_free_elements(&self->toks); + vec_free(&self->toks); +} + +int lexer_extract(struct lexer* self, struct vec* buffer) +{ + assert(self); + assert(buffer); + + struct node* node; + + while ( (node=lexer_next_new(self)) ) + { + vec_push(buffer, node); + } + + if (self->cursor < strlen(self->source)) + { + snprintf(self->error_msg, GUX_STR_SIZE, + "unexpected symbol '%c'", self->source[self->cursor]); + return 1; + } + + return 0; +} + +struct node* lexer_next_new(struct lexer* self) +{ + assert(self); + + lexer_skip_spaces(self); + + // Comments + while (self->cursor < strlen(self->source) + && self->source[self->cursor] == '#') + { + while (self->cursor < strlen(self->source) + && self->source[self->cursor] != '\n') + { + self->cursor++; + } + + lexer_skip_spaces(self); + } + + struct token_info info; + + struct node* best = NULL; + size_t pos = 0; + + for (size_t i=0; itoks.size; i++) + { + struct tok* tok = self->toks.data[i]; + + if (lexer_scan_text(self, tok->sym, &info)) + { + if (best == NULL || pos > info.position) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, tok->type, "", self->line); + + if (best) + { + node_free(best); + } + + best = node; + pos = info.position; + } + + self->cursor = info.position; + } + } + + if (best) + { + self->cursor = pos; + return best; + } + + if (lexer_scan_keyword(self, "true", &info) + || lexer_scan_keyword(self, "false", &info)) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_BOOL, info.value, self->line); + self->cursor = info.position; + return node; + } + + if (lexer_scan_keyword(self, "assert", &info)) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_ASSERT, "", self->line); + self->cursor = info.position; + return node; + } + + return NULL; +} + +int lexer_is_sep(struct lexer* self, size_t index) +{ + assert(self); + assert(index < strlen(self->source)); + char c = self->source[index]; + + for (size_t i=0; itoks.size; i++) + { + if (c == ((struct tok*)self->toks.data[i])->sym[0]) + { + return 1; + } + } + + return isspace(c); +} + +void lexer_skip_spaces(struct lexer* self) +{ + assert(self); + + while (self->cursor < strlen(self->source) + && isspace(self->source[self->cursor])) + { + if (self->source[self->cursor] == '\n') + { + self->line++; + } + + self->cursor++; + } +} + +int lexer_scan_keyword(struct lexer* self, + char* keyword, + struct token_info* info) +{ + assert(self); + assert(keyword); + assert(info); + + size_t cursor = self->cursor; + + for (size_t i=0; i= strlen(self->source) + || keyword[i] != self->source[self->cursor + i]) + { + return 0; + } + + cursor++; + } + + if (!(self->cursor == 0 + || lexer_is_sep(self, self->cursor - 1))) + { + return 0; + } + + if (!(cursor + 1 >= strlen(self->source) + || lexer_is_sep(self, cursor))) + { + return 0; + } + + info->position = self->cursor + strlen(keyword); + info->value = keyword; + + return 1; +} + +int lexer_scan_text(struct lexer* self, + char* text, + struct token_info* info) +{ + assert(self); + assert(text); + assert(info); + + size_t cursor = self->cursor; + + for (size_t i=0; i= strlen(self->source) + || text[i] != self->source[self->cursor + i]) + { + return 0; + } + + cursor++; + } + + info->position = self->cursor + strlen(text); + info->value = text; + + return 1; +} + +void lexer_add_tok(struct lexer* self, + char* sym, + enum NodeType type, + int is_keyword) +{ + (void) self; + struct tok* tok = malloc(sizeof(struct tok)); + tok->sym = sym; + tok->type = type; + tok->is_keyword = is_keyword; + + vec_push(&self->toks, tok); +} diff --git a/lang/src/lexer.h b/lang/src/lexer.h new file mode 100644 index 0000000..aff632c --- /dev/null +++ b/lang/src/lexer.h @@ -0,0 +1,51 @@ +#ifndef LEXER_H +#define LEXER_H + +#include +#include +#include + +struct tok { + char* sym; + enum NodeType type; + int is_keyword; +}; + +struct lexer { + char* source; + size_t cursor; + int line; + char error_msg[GUX_STR_SIZE]; + struct vec toks; +}; + +struct token_info { + enum NodeType type; + char* value; + size_t position; +}; + +void lexer_init(struct lexer* self, char const* source); +void lexer_free(struct lexer* self); + +int lexer_extract(struct lexer* self, struct vec* buffer); +struct node* lexer_next_new(struct lexer* self); + +int lexer_is_sep(struct lexer* self, size_t index); + +void lexer_skip_spaces(struct lexer* self); + +int lexer_scan_keyword(struct lexer* self, + char* keyword, + struct token_info* info); + +int lexer_scan_text(struct lexer* self, + char* text, + struct token_info* info); + +void lexer_add_tok(struct lexer* self, + char* sym, + enum NodeType type, + int is_keyword); + +#endif diff --git a/lang/src/node.c b/lang/src/node.c new file mode 100644 index 0000000..5c14946 --- /dev/null +++ b/lang/src/node.c @@ -0,0 +1,83 @@ +#include "node.h" +#include "vec.h" + +GUX_ENUM_C(NodeType, NODE_TYPE); + +void node_init(struct node* self, enum NodeType type, + char const* value, int line) +{ + assert(self); + self->type = type; + self->value = strdup(value); + vec_init(&self->children, 1); + self->line = line; +} + +void node_free(struct node* self) +{ + assert(self); + free(self->value); + + for (size_t i=0; ichildren.size; i++) + { + node_free(self->children.data[i]); + } + + vec_free_elements(&self->children); + vec_free(&self->children); +} + +struct node* node_add_new_child(struct node* self, + enum NodeType type, char const* value) +{ + assert(self); + struct node* child = malloc(sizeof(struct node)); + node_init(child, type, value, self->line); + + vec_push(&self->children, child); + + return child; +} + +struct node* node_add_child(struct node* self, struct node* child) +{ + assert(self); + vec_push(&self->children, child); + return child; +} + + +size_t node_str(struct node* self, char* buffer, size_t size) +{ + assert(self); + assert(buffer); + + size_t sz = 0; + + sz += snprintf(buffer + sz, size - sz, "%s", + NodeTypeStr[self->type] + strlen("NODE_")); + + if (strcmp(self->value, "") != 0) + { + sz += snprintf(buffer + sz, size - sz, "[%s]", self->value); + } + + if (self->children.size > 0) + { + sz += snprintf(buffer + sz, size - sz, "("); + + for (size_t i=0; ichildren.size; i++) + { + if (i > 0) + { + sz += snprintf(buffer + sz, size - sz, ","); + } + + sz += node_str(self->children.data[i], buffer + sz, size - sz); + } + + sz += snprintf(buffer + sz, size - sz, ")"); + } + + return sz; +} diff --git a/lang/src/node.h b/lang/src/node.h new file mode 100644 index 0000000..d805e2b --- /dev/null +++ b/lang/src/node.h @@ -0,0 +1,33 @@ +#ifndef NODE_H +#define NODE_H + +#include +#include + +#define NODE_TYPE(G) \ + G(NODE_ROOT), \ + G(NODE_SEMICOLON), \ + G(NODE_BOOL), G(NODE_ASSERT), G(NODE_OPAR), \ + G(NODE_CPAR), G(NODE_AND), G(NODE_OR), G(NODE_NOT) + +GUX_ENUM_H(NodeType, NODE_TYPE); + +struct node { + enum NodeType type; + char* value; + struct vec children; + int line; +}; + +void node_init(struct node* self, enum NodeType type, + char const* value, int line); +void node_free(struct node* self); + +struct node* node_add_new_child(struct node* self, + enum NodeType type, char const* value); + +struct node* node_add_child(struct node* self, struct node* child); + +size_t node_str(struct node* self, char* buffer, size_t size); + +#endif diff --git a/lang/src/parser.c b/lang/src/parser.c new file mode 100644 index 0000000..a8f90b8 --- /dev/null +++ b/lang/src/parser.c @@ -0,0 +1,291 @@ +#include "parser.h" +#include "commons.h" +#include "lexer.h" +#include "vec.h" + +void parser_init(struct parser* self) +{ + assert(self); + vec_init(&self->tokens, 1); + self->cursor = 0; + self->error_line = 0; +} + +void parser_free(struct parser* self) +{ + for (size_t i=0; itokens.size; i++) + { + node_free(self->tokens.data[i]); + } + + vec_free_elements(&self->tokens); + vec_free(&self->tokens); + + assert(self); +} + +int parser_current_line(struct parser* self) +{ + assert(self); + + if (self->cursor >= self->tokens.size) + { + struct node* node = (struct node*) + self->tokens.data[self->tokens.size - 1]; + return node->line; + } + + struct node* node = (struct node*) self->tokens.data[self->cursor]; + return node->line; +} + +int parser_type_is(struct parser* self, + enum NodeType type, + size_t lookahead) +{ + assert(self); + + if (self->cursor + lookahead >= self->tokens.size) + { + return 0; + } + + return ((struct node*) + self->tokens.data[self->cursor + lookahead])->type == type; +} + +struct node* parser_try_consume_new(struct parser* self, enum NodeType type) +{ + assert(self); + + if (!parser_type_is(self, type, 0)) + { + struct node* token = ((struct node*)self->tokens.data[self->cursor]); + + self->error_line = parser_current_line(self); + snprintf(self->error_msg, GUX_STR_SIZE, "expected <%s>, got <%s>", + NodeTypeStr[type] + strlen("NODE_"), + NodeTypeStr[token->type] + + strlen("NODE_")); + return NULL; + } + + struct node* token = self->tokens.data[self->cursor]; + + struct node* node = malloc(sizeof(struct node)); + node_init(node, token->type, token->value, token->line); + + self->cursor++; + + return node; +} + +int parser_ensure(struct parser* self, enum NodeType type) +{ + assert(self); + + if (self->cursor >= self->tokens.size) + { + self->error_line = parser_current_line(self); + + snprintf(self->error_msg, GUX_STR_SIZE, + "expected <%s>, got nothing", + NodeTypeStr[type] + strlen("NODE_")); + + return 1; + } + + struct node* current = self->tokens.data[self->cursor]; + + if (current->type != type) + { + self->error_line = current->line; + snprintf(self->error_msg, GUX_STR_SIZE, + "expected <%s>, got <%s>", + NodeTypeStr[type] + strlen("NODE_"), + NodeTypeStr[current->type] + strlen("NODE_")); + return 2; + } + + self->cursor++; + + return 0; +} + +int parser_try_consume(struct parser* self, enum NodeType type) +{ + assert(self); + + if (!parser_type_is(self, type, 0)) + { + return 1; + } + + self->cursor++; + + return 0; +} + +struct node* parser_try_new_root(struct parser* self, char const* source) +{ + assert(self); + struct lexer lex; + lexer_init(&lex, source); + lexer_extract(&lex, &self->tokens); + lexer_free(&lex); + + struct node* root = malloc(sizeof(struct node)); + node_init(root, NODE_ROOT, "", 0); + + while (self->cursor < self->tokens.size) + { + struct node* node = parser_try_new_instr(self); + + if (!node) + { + node_free(root); + free(root); + return NULL; + } + + if (root->line == 0) + { + root->line = node->line; + } + + node_add_child(root, node); + } + + return root; +} + +struct node* parser_try_new_instr(struct parser* self) +{ + assert(self); + struct node* node = parser_try_new_expr(self); + + if (parser_ensure(self, NODE_SEMICOLON) != 0) + { + node_free(node); + free(node); + return NULL; + } + + return node; +} + +struct node* parser_try_new_expr(struct parser* self) +{ + assert(self); + + if (parser_type_is(self, NODE_ASSERT, 0)) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_ASSERT, "", parser_current_line(self)); + self->cursor++; + + struct node* expr = parser_try_new_expr(self); + + if (!expr) + { + node_free(node); + free(node); + return NULL; + } + + node_add_child(node, expr); + + return node; + } + + struct node* node = parser_try_new_or(self); + + if (node == NULL) + { + return NULL; + } + + return node; +} + +struct node* parser_try_new_or(struct parser* self) +{ + struct node* lhs = parser_try_new_and(self); + + while (parser_try_consume(self, NODE_OR) == 0) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_OR, "", parser_current_line(self)); + + struct node* rhs = parser_try_new_and(self); + + node_add_child(node, lhs); + node_add_child(node, rhs); + + lhs = node; + } + + return lhs; +} + +struct node* parser_try_new_and(struct parser* self) +{ + struct node* lhs = parser_try_new_not(self); + + while (parser_try_consume(self, NODE_AND) == 0) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_AND, "", parser_current_line(self)); + + struct node* rhs = parser_try_new_not(self); + + node_add_child(node, lhs); + node_add_child(node, rhs); + + lhs = node; + } + + return lhs; +} + +struct node* parser_try_new_not(struct parser* self) +{ + if (parser_try_consume(self, NODE_NOT) == 0) + { + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_NOT, "", parser_current_line(self)); + + node_add_child(node, parser_try_new_not(self)); + return node; + } + + return parser_try_new_literal(self); +} + +struct node* parser_try_new_literal(struct parser* self) +{ + if (parser_try_consume(self, NODE_OPAR) == 0) + { + struct node* node = parser_try_new_expr(self); + + if (parser_try_consume(self, NODE_CPAR) != 0) + { + node_free(node); + free(node); + return NULL; + } + + return node; + } + + return parser_try_new_builtin(self); +} + +struct node* parser_try_new_builtin(struct parser* self) +{ + assert(self); + + struct node* value = parser_try_consume_new(self, NODE_BOOL); + + return value; +} diff --git a/lang/src/parser.h b/lang/src/parser.h new file mode 100644 index 0000000..11ff842 --- /dev/null +++ b/lang/src/parser.h @@ -0,0 +1,36 @@ +#ifndef PARSER_H +#define PARSER_H + +#include +#include +#include + +struct parser { + struct vec tokens; + size_t cursor; + char error_msg[GUX_STR_SIZE]; + int error_line; +}; + +void parser_init(struct parser* self); +void parser_free(struct parser* self); + +int parser_current_line(struct parser* self); +int parser_type_is(struct parser* self, + enum NodeType type, + size_t lookahead); + +struct node* parser_try_consume_new(struct parser* self, enum NodeType type); +int parser_try_consume(struct parser* self, enum NodeType type); +int parser_ensure(struct parser* self, enum NodeType type); + +struct node* parser_try_new_root(struct parser* self, char const* source); +struct node* parser_try_new_instr(struct parser* self); +struct node* parser_try_new_expr(struct parser* self); +struct node* parser_try_new_or(struct parser* self); +struct node* parser_try_new_and(struct parser* self); +struct node* parser_try_new_not(struct parser* self); +struct node* parser_try_new_literal(struct parser* self); +struct node* parser_try_new_builtin(struct parser* self); + +#endif diff --git a/lang/src/type.c b/lang/src/type.c new file mode 100644 index 0000000..457912b --- /dev/null +++ b/lang/src/type.c @@ -0,0 +1,91 @@ +#include "type.h" +#include "vec.h" + +GUX_ENUM_C(TypeKind, TYPE_KIND); + +void type_init(struct type* self, enum TypeKind kind) +{ + assert(self); + self->kind = kind; + vec_init(&self->subtypes, 1); +} + +void type_free(struct type* self) +{ + for (size_t i=0; isubtypes.size; i++) + { + struct type* sty = self->subtypes.data[i]; + type_free(sty); + } + + vec_free_elements(&self->subtypes); + vec_free(&self->subtypes); + + assert(self); +} + +void type_clear(struct type* self) +{ + assert(self); + vec_clear(&self->subtypes); +} + +int type_equals(struct type* self, struct type* rhs) +{ + assert(self); + assert(rhs); + + if (self->kind != rhs->kind + || self->subtypes.size != rhs->subtypes.size) + { + return 0; + } + + for (size_t i=0; isubtypes.size; i++) + { + if (!type_equals(self->subtypes.data[i], rhs->subtypes.data[i])) + { + return 0; + } + } + + return 1; +} + +void type_add_subtype(struct type* self, enum TypeKind kind) +{ + assert(self); + struct type* ty = malloc(sizeof(struct type)); + type_init(ty, kind); + + vec_push(&self->subtypes, ty); +} + +size_t type_str(struct type* self, char* buffer, size_t size) +{ + assert(self); + assert(buffer); + size_t sz = 0; + + sz += snprintf(buffer + sz, size - sz, "%s", + TypeKindStr[self->kind] + strlen("TYPE_")); + + if (self->subtypes.size > 0) + { + sz += snprintf(buffer + sz, size - sz, "("); + + for (size_t i=0; isubtypes.size; i++) + { + if (i > 0) + { + sz += snprintf(buffer + sz, size - sz, ","); + } + + sz += type_str(self->subtypes.data[i], buffer + sz, size - sz); + } + + sz += snprintf(buffer + sz, size - sz, ")"); + } + + return sz; +} diff --git a/lang/src/type.h b/lang/src/type.h new file mode 100644 index 0000000..4ea2eda --- /dev/null +++ b/lang/src/type.h @@ -0,0 +1,27 @@ +#ifndef TYPE_H +#define TYPE_H + +#include +#include + +#define TYPE_KIND(G) \ + G(TYPE_VOID), G(TYPE_BOOL) + +GUX_ENUM_H(TypeKind, TYPE_KIND); + +struct type { + enum TypeKind kind; + struct vec subtypes; +}; + +void type_init(struct type* self, enum TypeKind kind); +void type_free(struct type* self); + +void type_clear(struct type* self); + +int type_equals(struct type* self, struct type* rhs); +void type_add_subtype(struct type* self, enum TypeKind kind); +size_t type_str(struct type* self, char* buffer, size_t size); + + +#endif diff --git a/lang/src/type_checker.c b/lang/src/type_checker.c new file mode 100644 index 0000000..ae0ff12 --- /dev/null +++ b/lang/src/type_checker.c @@ -0,0 +1,80 @@ +#include "type_checker.h" +#include "commons.h" +#include "node.h" +#include "type.h" + +void type_checker_init(struct type_checker* self) +{ + assert(self); + type_resolver_init(&self->resolver); +} + +void type_checker_free(struct type_checker* self) +{ + assert(self); + type_resolver_free(&self->resolver); +} + +int type_checker_check(struct type_checker* self, + struct node* node) +{ + assert(self); + assert(node); + + switch (node->type) + { + case NODE_ASSERT: + case NODE_NOT: + case NODE_AND: + case NODE_OR:{ + for (size_t i=0; ichildren.size; i++) + { + if (type_checker_ensure_kind(self, + node->children.data[i], + TYPE_BOOL) != 0) + { + return 1; + } + } + } return 0; + + default: { + for (size_t i=0; ichildren.size; i++) + { + if (type_checker_check(self, node->children.data[i]) != 0) + { + return 1; + } + } + } return 0; + } + + return 2; +} + +int type_checker_ensure_kind(struct type_checker* self, + struct node* node, + enum TypeKind kind) +{ + assert(self); + assert(node); + + struct type type; + type_init(&type, TYPE_VOID); + type_resolver_resolve(&self->resolver, node, &type); + + if (type.kind != kind || type.subtypes.size != 0) + { + self->error_line = node->line; + snprintf(self->error_msg, GUX_STR_SIZE, + "type mismatch: expected <%s>, got <%s>", + TypeKindStr[kind] + strlen("TYPE_"), + TypeKindStr[type.kind] + strlen("TYPE_")); + + type_free(&type); + return 1; + } + + type_free(&type); + return 0; +} diff --git a/lang/src/type_checker.h b/lang/src/type_checker.h new file mode 100644 index 0000000..e76d381 --- /dev/null +++ b/lang/src/type_checker.h @@ -0,0 +1,25 @@ +#ifndef TYPE_CHECKER_H +#define TYPE_CHECKER_H + +#include +#include +#include +#include + +struct type_checker { + struct type_resolver resolver; + char* error_msg; + int error_line; +}; + +void type_checker_init(struct type_checker* self); +void type_checker_free(struct type_checker* self); + +int type_checker_check(struct type_checker* self, + struct node* node); + +int type_checker_ensure_kind(struct type_checker* self, + struct node* node, + enum TypeKind kind); + +#endif diff --git a/lang/src/type_resolver.c b/lang/src/type_resolver.c new file mode 100644 index 0000000..e17b3a1 --- /dev/null +++ b/lang/src/type_resolver.c @@ -0,0 +1,40 @@ +#include "type_resolver.h" +#include "node.h" +#include "type.h" + +void type_resolver_init(struct type_resolver* self) +{ + assert(self); +} + +void type_resolver_free(struct type_resolver* self) +{ + assert(self); +} + +int type_resolver_resolve(struct type_resolver* self, + struct node* node, + struct type* type) +{ + assert(self); + assert(node); + assert(type); + + type_clear(type); + + switch (node->type) + { + case NODE_ASSERT: + case NODE_BOOL: + case NODE_AND: + case NODE_OR: + case NODE_NOT:{ + type->kind = TYPE_BOOL; + } return 0; + + default: { + } return 1; + } + + return 1; +} diff --git a/lang/src/type_resolver.h b/lang/src/type_resolver.h new file mode 100644 index 0000000..c048c4d --- /dev/null +++ b/lang/src/type_resolver.h @@ -0,0 +1,19 @@ +#ifndef TYPE_RESOLVER_H +#define TYPE_RESOLVER_H + +#include +#include +#include + +struct type_resolver { + +}; + +void type_resolver_init(struct type_resolver* self); +void type_resolver_free(struct type_resolver* self); + +int type_resolver_resolve(struct type_resolver* self, + struct node* node, + struct type* type); + +#endif diff --git a/lang/src/value.c b/lang/src/value.c new file mode 100644 index 0000000..258d23f --- /dev/null +++ b/lang/src/value.c @@ -0,0 +1,81 @@ +#include "value.h" + +void value_init_bool(struct value* self, int value, int line) +{ + assert(self); + + type_init(&self->type, TYPE_BOOL); + self->data.b = value; + self->line = line; +} + +void value_free(struct value* self) +{ + assert(self); + type_free(&self->type); +} + +size_t value_str(struct value* self, char* buffer, size_t size) +{ + assert(self); + assert(buffer); + + size_t sz = 0; + + if (self->type.subtypes.size == 0) + { + if (self->type.kind == TYPE_VOID) + { + sz += snprintf(buffer + sz, size - sz, "void"); + } + else if (self->type.kind == TYPE_BOOL) + { + sz += snprintf(buffer + sz, size - sz, "%s", + self->data.b ? "true" : "false"); + } + } + + return sz; +} + +struct value* value_try_new_and(struct value* self, struct value* rhs) +{ + if (self->type.kind != TYPE_BOOL + || self->type.subtypes.size != 0 + || !type_equals(&self->type, &rhs->type)) + { + return NULL; + } + + struct value* res = malloc(sizeof(struct value)); + value_init_bool(res, self->data.b && rhs->data.b, self->line); + return res; +} + +struct value* value_try_new_or(struct value* self, struct value* rhs) +{ + if (self->type.kind != TYPE_BOOL + || self->type.subtypes.size != 0 + || !type_equals(&self->type, &rhs->type)) + { + return NULL; + } + + struct value* res = malloc(sizeof(struct value)); + value_init_bool(res, self->data.b || rhs->data.b, self->line); + return res; +} + +struct value* value_try_new_not(struct value* self) +{ + if (self->type.kind != TYPE_BOOL + || self->type.subtypes.size != 0) + { + return NULL; + } + + struct value* res = malloc(sizeof(struct value)); + value_init_bool(res, !self->data.b, self->line); + + return res; +} diff --git a/lang/src/value.h b/lang/src/value.h new file mode 100644 index 0000000..f7dbd4e --- /dev/null +++ b/lang/src/value.h @@ -0,0 +1,29 @@ +#ifndef VALUE_H +#define VALUE_H + +#include +#include + +#define VAL_TRUE 1 +#define VAL_FALSE 0 + +union value_data { + int b; +}; + +struct value { + struct type type; + int line; + union value_data data; +}; + +void value_init_bool(struct value* self, int value, int line); +void value_free(struct value* self); + +size_t value_str(struct value* self, char* buffer, size_t size); + +struct value* value_try_new_and(struct value* self, struct value* rhs); +struct value* value_try_new_or(struct value* self, struct value* rhs); +struct value* value_try_new_not(struct value* self); + +#endif diff --git a/lang/tests/lexer.c b/lang/tests/lexer.c new file mode 100644 index 0000000..c1ee2bf --- /dev/null +++ b/lang/tests/lexer.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +static void test_lexer(char const* source, size_t size, ...) +{ + va_list lst; + va_start(lst, size); + + struct vec tokens; + vec_init(&tokens, 1); + + struct lexer lex; + lexer_init(&lex, source); + int err = lexer_extract(&lex, &tokens); + cr_assert_eq(0, err); + cr_assert_eq(size, tokens.size, + "size (%zu) != tokens.size (%zu)", + size, tokens.size); + + for (size_t i=0; i +#include + +Test(node, string) { + size_t const SZ = 256; + char str[SZ]; + + struct node node; + node_init(&node, NODE_ROOT, "", 1); + node_add_new_child(&node, NODE_ROOT, "hello"); + node_add_new_child(&node, NODE_ROOT, ""); + + + node_str(&node, str, SZ); + cr_assert_str_eq(str, "ROOT(ROOT[hello],ROOT)"); + node_free(&node); +} diff --git a/lang/tests/parser.c b/lang/tests/parser.c new file mode 100644 index 0000000..22b4246 --- /dev/null +++ b/lang/tests/parser.c @@ -0,0 +1,69 @@ +#include "commons.h" +#include +#include +#include + +static void test_parser(char const* oracle, char const* source) +{ + struct parser parser; + parser_init(&parser); + + struct node* ast = parser_try_new_root(&parser, source); + cr_assert_neq(NULL, ast, "%s", parser.error_msg); + + { + char buf[GUX_STR_SIZE]; + node_str(ast, buf, GUX_STR_SIZE); + cr_assert_str_eq(oracle, buf); + } + + parser_free(&parser); +} + +static void test_parser_fail(char const* source) +{ + struct parser parser; + parser_init(&parser); + + struct node* ast = parser_try_new_root(&parser, source); + + if (ast) + { + char msg[GUX_STR_SIZE]; + node_str(ast, msg, GUX_STR_SIZE); + cr_assert_eq(NULL, ast, "ast = %s", msg); + } + + cr_assert_eq(NULL, ast); +} + +Test(parser, boolean) { + test_parser_fail("true"); + test_parser_fail(";"); + test_parser("ROOT(BOOL[true])", "true;"); +} + +Test(parser, assert) { + test_parser("ROOT(ASSERT(BOOL[false]))", + "assert false;"); + + test_parser_fail("assert;"); +} + +Test(parser, bool_arith) { + test_parser("ROOT(NOT(NOT(BOOL[true])))", + "!!true;"); + + test_parser("ROOT(OR(OR(BOOL[true],BOOL[false]),BOOL[false]))", + "true || false || false;"); + + test_parser("ROOT(OR(AND(BOOL[true],BOOL[false]),BOOL[true]))", + "true && false || true;"); + + test_parser("ROOT(AND(BOOL[true],OR(BOOL[false],BOOL[true])))", + "true && (false || true);"); + + test_parser("ROOT(OR(AND(BOOL[true],NOT(BOOL[false])),BOOL[true]))", + "true && !false || true;"); + +} diff --git a/lang/tests/trivial.c b/lang/tests/trivial.c new file mode 100644 index 0000000..01c5d7b --- /dev/null +++ b/lang/tests/trivial.c @@ -0,0 +1,5 @@ +#include + +Test(trivial, test) { + cr_assert(1 + 1 == 2); +} diff --git a/lang/tests/type.c b/lang/tests/type.c new file mode 100644 index 0000000..ccd6d87 --- /dev/null +++ b/lang/tests/type.c @@ -0,0 +1,63 @@ +#include "commons.h" +#include +#include + +Test(type, atomic_type) { + struct type ty; + type_init(&ty, TYPE_BOOL); + + char buf[GUX_STR_SIZE]; + type_str(&ty, buf, GUX_STR_SIZE); + + cr_assert_str_eq(buf, "BOOL"); + + type_free(&ty); +} + +Test(type, compound_type) { + struct type ty; + type_init(&ty, TYPE_BOOL); + type_add_subtype(&ty, TYPE_VOID); + type_add_subtype(&ty, TYPE_BOOL); + + char buf[GUX_STR_SIZE]; + type_str(&ty, buf, GUX_STR_SIZE); + + cr_assert_str_eq(buf, "BOOL(VOID,BOOL)"); + + type_free(&ty); +} + +Test(type, equals) { + struct type lhs; + type_init(&lhs, TYPE_BOOL); + type_add_subtype(&lhs, TYPE_VOID); + type_add_subtype(&lhs, TYPE_BOOL); + + struct type rhs; + type_init(&rhs, TYPE_BOOL); + type_add_subtype(&rhs, TYPE_VOID); + type_add_subtype(&rhs, TYPE_BOOL); + + cr_assert(type_equals(&lhs, &rhs)); + + type_free(&rhs); + type_free(&lhs); +} + +Test(type, no_equals) { + struct type lhs; + type_init(&lhs, TYPE_BOOL); + type_add_subtype(&lhs, TYPE_VOID); + type_add_subtype(&lhs, TYPE_BOOL); + + struct type rhs; + type_init(&rhs, TYPE_BOOL); + type_add_subtype(&rhs, TYPE_BOOL); + type_add_subtype(&rhs, TYPE_BOOL); + + cr_assert(!type_equals(&lhs, &rhs)); + + type_free(&rhs); + type_free(&lhs); +} diff --git a/lang/tests/type_checker.c b/lang/tests/type_checker.c new file mode 100644 index 0000000..cd07058 --- /dev/null +++ b/lang/tests/type_checker.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +static void test_check_ok(char const* source) +{ + struct parser parser; + parser_init(&parser); + + struct node* ast = parser_try_new_root(&parser, source); + cr_assert_neq(ast, NULL); + + struct type_checker tc; + type_checker_init(&tc); + + cr_assert_eq(type_checker_check(&tc, ast), 0); + + type_checker_free(&tc); + + node_free(ast); + free(ast); + parser_free(&parser); +} + +static void test_check_ko(char const* source) +{ + struct parser parser; + parser_init(&parser); + + struct node* ast = parser_try_new_root(&parser, source); + cr_assert_neq(ast, NULL); + + struct type_checker tc; + type_checker_init(&tc); + + cr_assert_neq(type_checker_check(&tc, ast), 0); + + type_checker_free(&tc); + + node_free(ast); + free(ast); + parser_free(&parser); +} + +Test(type_checker, booleans) { + test_check_ok("true;"); + test_check_ok("true && false;"); + test_check_ok("true || false;"); + test_check_ok("true || !false && false;"); +} diff --git a/lang/tests/value.c b/lang/tests/value.c new file mode 100644 index 0000000..b9ac351 --- /dev/null +++ b/lang/tests/value.c @@ -0,0 +1,29 @@ +#include +#include + +static void test_bool_ops(int lhs_val, int rhs_val, + struct value* (*op)(struct value*, struct value*), + int oracle) +{ + struct value lhs; + value_init_bool(&lhs, lhs_val, 0); + + struct value rhs; + value_init_bool(&rhs, rhs_val, 0); + + struct value* res = op(&lhs, &rhs); + cr_assert_neq(res, NULL); + cr_assert(type_equals(&lhs.type, &res->type)); + cr_assert_eq(res->data.b, oracle); + + value_free(&rhs); + value_free(&lhs); + value_free(res); +} + +Test(value, booleans_ops) { + test_bool_ops(VAL_TRUE, VAL_TRUE, &value_try_new_and, VAL_TRUE); + test_bool_ops(VAL_TRUE, VAL_FALSE, &value_try_new_and, VAL_FALSE); + test_bool_ops(VAL_FALSE, VAL_TRUE, &value_try_new_and, VAL_FALSE); + test_bool_ops(VAL_FALSE, VAL_FALSE, &value_try_new_and, VAL_FALSE); +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..46f08eb --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.2) + +project(gux-lib LANGUAGES C) + +add_library(gux-lib OBJECT + src/commons.h + src/vec.h + src/vec.c +) + +target_include_directories(gux-lib + PUBLIC src +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + + target_compile_options(gux-lib + PUBLIC -Wall -Wextra -g + ) + +endif() diff --git a/lib/src/commons.h b/lib/src/commons.h new file mode 100644 index 0000000..576c6a7 --- /dev/null +++ b/lib/src/commons.h @@ -0,0 +1,23 @@ +#ifndef COMMONS_H +#define COMMONS_H + +#include +#include +#include +#include +#include + +#define GUX_STR_SIZE 256 +#define GUX_ENUM_IDENT(X) X +#define GUX_ENUM_STR(X) #X +#define GUX_ENUM_H(Prefix, Types) \ + enum Prefix { Types(GUX_ENUM_IDENT) }; \ + extern char* Prefix ## Str[]; + +#define GUX_ENUM_C(Prefix, Types) \ + char* Prefix ## Str[] = { Types(GUX_ENUM_STR) }; + +#define GUX_RET_ERROR 1 +#define GUX_RET_ASSERT 2 + +#endif diff --git a/lib/src/vec.c b/lib/src/vec.c new file mode 100644 index 0000000..4b31548 --- /dev/null +++ b/lib/src/vec.c @@ -0,0 +1,65 @@ +#include "vec.h" + +void vec_init(struct vec* self, size_t capacity) +{ + assert(self); + assert(capacity > 0); + + self->capacity = capacity; + self->size = 0; + + self->data = malloc(self->capacity * sizeof(void*)); +} + +void vec_free(struct vec* self) +{ + assert(self); + + free(self->data); +} + +void vec_push(struct vec* self, void* element) +{ + assert(self); + assert(element); + + if (self->size >= self->capacity) + { + self->capacity *= 2; + self->data = realloc(self->data, self->capacity * sizeof(void*)); + } + + self->data[self->size] = element; + self->size++; +} + +void* vec_pop(struct vec* self) +{ + assert(self); + assert(self->size > 0); + + void* element = self->data[self->size - 1]; + self->size--; + + return element; +} + +void vec_clear(struct vec* self) +{ + assert(self); + + while (self->size > 0) + { + vec_pop(self); + } +} + +void vec_free_elements(struct vec* self) +{ + assert(self); + + for (size_t i=0; isize; i++) + { + free(self->data[i]); + } +} diff --git a/lib/src/vec.h b/lib/src/vec.h new file mode 100644 index 0000000..2737e84 --- /dev/null +++ b/lib/src/vec.h @@ -0,0 +1,21 @@ +#ifndef VEC_H +#define VEC_H + +#include + +struct vec { + void** data; + size_t capacity; + size_t size; +}; + +void vec_init(struct vec* self, size_t capacity); +void vec_free(struct vec* self); + +void vec_push(struct vec* self, void* element); +void* vec_pop(struct vec* self); +void vec_clear(struct vec* self); + +void vec_free_elements(struct vec* self); + +#endif diff --git a/tests/bool.gux b/tests/bool.gux new file mode 100644 index 0000000..ee93100 --- /dev/null +++ b/tests/bool.gux @@ -0,0 +1,14 @@ +assert true; + +assert !false; +assert !!true; + +assert true && true; +assert !(true && false); +assert !(false && true); +assert !(false && false); + +assert true || true; +assert true || false; +assert false || true; +assert !(false || false); diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..c73de16 --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,32 @@ +#!/bin/env bash + +SUCCESS=0 +TOTAL=0 + +for file in $(find . -name "*.gux" | sort) +do + MSG=$(guxi $file 2>&1) + RET=$? + + echo -en "$file...\t" + if [ $RET -eq 0 ] + then + echo -e "\e[32mpass\e[0m" + SUCCESS=$(($SUCCESS + 1)) + else + echo -e "\e[31mfail\e[0m" + echo -e "\e[33m$MSG\e[0m" + fi + + TOTAL=$(($TOTAL + 1)) +done + +echo + +if [ $SUCCESS -eq $TOTAL ] +then + echo -e "\e[32m--- All tests passed ---\e[0m" +else + FAIL=$(($TOTAL - $SUCCESS)) + echo -e "\e[31m... $FAIL tests failed ...\e[0m" +fi diff --git a/vm/CMakeLists.txt b/vm/CMakeLists.txt new file mode 100644 index 0000000..72a4ff7 --- /dev/null +++ b/vm/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.2) + +project(gux-vm LANGUAGES C) + +add_library(gux-vm OBJECT + src/vm.h + src/vm.c +) + +set_property(TARGET gux-vm PROPERTY C_STANDARD 99) + +add_dependencies(gux-vm gux-bc) + +target_include_directories(gux-vm + PUBLIC gux-bc + PUBLIC ${CMAKE_SOURCE_DIR}/vm/src +) + +target_link_libraries(gux-vm + PUBLIC gux-bc +) diff --git a/vm/src/vm.c b/vm/src/vm.c new file mode 100644 index 0000000..51242e0 --- /dev/null +++ b/vm/src/vm.c @@ -0,0 +1,163 @@ +#include "vm.h" +#include "commons.h" + +void vm_init(struct vm* self) +{ + assert(self); + self->pc = 0; + self->fp = 0; + + vm_add_frame(self); +} + +void vm_free(struct vm* self) +{ + assert(self); + + size_t fp = self->fp; + + for (size_t i=0; ifp = 0; +} + +void vm_add_frame(struct vm* self) +{ + assert(self); + self->stack[self->fp] = malloc(sizeof(struct frame)); + self->stack[self->fp]->sp = 0; + self->fp++; +} + +void vm_remove_frame(struct vm* self) +{ + assert(self); + assert(self->fp > 0); + + free(self->stack[self->fp - 1]); + self->fp--; +} + +void vm_push(struct vm* self, int param) +{ + assert(self); + struct frame* frame = self->stack[self->fp - 1]; + frame->stack[frame->sp] = param; + frame->sp++; +} + +int vm_pop(struct vm* self) +{ + assert(self); + struct frame* frame = self->stack[self->fp - 1]; + assert(frame->sp > 0); + int param = frame->stack[frame->sp - 1]; + frame->sp--; + return param; +} + +int vm_exec(struct vm* self, struct program* program) +{ + assert(self); + assert(program); + + while (self->pc < program->instructions.size) + { + struct instruction* instr = program->instructions.data[self->pc]; + enum Opcodes opcode = instr->opcode; + int param = instr->param; + struct frame* frame = self->stack[self->fp - 1]; + + switch (opcode) + { + case OP_PUSH: { + vm_push(self, param); + self->pc++; + } break; + + case OP_POP: { + frame->sp--; + self->pc++; + } break; + + case OP_BRF: { + int val_id = vm_pop(self); + struct value* value = program->constant_pool.data[val_id]; + + if (value->data.b == 0) + { + self->pc = param; + } + else + { + self->pc++; + } + } break; + + case OP_BRT: { + int val_id = vm_pop(self); + struct value* value = program->constant_pool.data[val_id]; + + if (value->data.b != 0) + { + self->pc = param; + } + else + { + self->pc++; + } + } break; + + case OP_BR: { + self->pc = param; + } break; + + case OP_HALT: { + int code = vm_pop(self); + snprintf(self->error_msg, GUX_STR_SIZE, "program halt"); + self->error_line = param; + + return code; + } break; + + case OP_NOP: { + self->pc++; + } break; + + case OP_NOT: { + int addr = vm_pop(self); + struct value* val = program->constant_pool.data[addr]; + struct value* not = value_try_new_not(val); + vm_push(self, program_push_constant(program, not)); + self->pc++; + } break; + } + } + + return 0; +} + +size_t vm_str(struct vm* self, char* buffer, size_t size) +{ + assert(self); + assert(buffer); + + size_t sz = 0; + + for (size_t i=0; ifp; i++) + { + struct frame* frame = self->stack[i]; + sz += snprintf(buffer + sz, size - sz, "[FRAME %zu]\n", i); + + for (size_t j=0; jsp; j++) + { + sz += snprintf(buffer + sz, size - sz, "\t%zu: %d\n", + j, frame->stack[j]); + } + } + + return sz; +} diff --git a/vm/src/vm.h b/vm/src/vm.h new file mode 100644 index 0000000..c70b0eb --- /dev/null +++ b/vm/src/vm.h @@ -0,0 +1,35 @@ +#ifndef VM_H +#define VM_H +#include +#include +#include + +#define FRAME_DEPTH 256 +#define STACK_DEPTH 256 + +struct frame { + int stack[STACK_DEPTH]; + size_t sp; +}; + +struct vm { + char error_msg[GUX_STR_SIZE]; + int error_line; + size_t pc; + size_t fp; + struct frame* stack[FRAME_DEPTH]; +}; + +void vm_init(struct vm* self); +void vm_free(struct vm* self); +void vm_add_frame(struct vm* self); +void vm_remove_frame(struct vm* self); + +void vm_push(struct vm* self, int param); +int vm_pop(struct vm* self); + +int vm_exec(struct vm* self, struct program* program); + +size_t vm_str(struct vm* self, char* buffer, size_t size); + +#endif