From 9a2e084143875e41ecb53c20099a4bb95cc1bfdb Mon Sep 17 00:00:00 2001 From: laentropia Date: Wed, 10 Jun 2026 16:03:21 -0600 Subject: [PATCH] refactor: xmake in use, allocator sintaxis --- .gitignore | 6 + CMakeLists.txt | 100 ----------- include/lexer.h | 8 +- include/parser.h | 16 +- src/evaluator.c | 105 ++++++------ src/lexer.c | 161 +++++++++--------- src/parser.c | 405 +++++++++++++++++++++----------------------- test/CMakeLists.txt | 114 ------------- xmake.lua | 107 ++++++++++++ 9 files changed, 444 insertions(+), 578 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 test/CMakeLists.txt create mode 100644 xmake.lua diff --git a/.gitignore b/.gitignore index e920eb7..83835c9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,12 @@ out/ out/Debug/ out/Release/ +# xmake +.xmake +xmake +xmake/ +.xmake/ + # Cmake files CMakeCache.txt cmake diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index b46160d..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,100 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project( - calculator - VERSION 1.0 - LANGUAGES C) - -# ------------------------------------------------ -# C standard — no compiler extensions -# ------------------------------------------------ -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -# Export compile_commands.json (clangd / IDEs) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# ------------------------------------------------ -# Options -# ------------------------------------------------ -option(CALCULATOR_BUILD_TESTS "Build calculator tests" ON) -option(CALCULATOR_ENABLE_SANITIZERS "Enable Address Sanitizer in tests" ON) - -# ------------------------------------------------ -# Portable warning flags -# ------------------------------------------------ -if(MSVC) - add_compile_options(/W4) -else() - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -# ================================================================ -# CPM.cmake bootstrap Descarga CPM solo si no existe; usa caché para builds -# offline. -# ================================================================ -set(CPM_DOWNLOAD_VERSION 0.40.2) -set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM.cmake") - -if(NOT EXISTS "${CPM_DOWNLOAD_LOCATION}") - message( - STATUS "[calculator] Downloading CPM.cmake v${CPM_DOWNLOAD_VERSION}...") - file( - DOWNLOAD - "https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake" - "${CPM_DOWNLOAD_LOCATION}" - STATUS download_status - TLS_VERIFY ON) - list(GET download_status 0 status_code) - list(GET download_status 1 status_msg) - if(NOT status_code EQUAL 0) - message( - FATAL_ERROR "[calculator] Failed to download CPM.cmake: ${status_msg}") - endif() -endif() - -include("${CPM_DOWNLOAD_LOCATION}") - -# ================================================================ -# Dependencies via CPM -# ================================================================ -cpmaddpackage( - NAME arena GIT_REPOSITORY - https://laentropia-homelab.tail7368da.ts.net/laentropia/Arena.git GIT_TAG - main) - -cpmaddpackage( - NAME arraylist GIT_REPOSITORY - https://laentropia-homelab.tail7368da.ts.net/laentropia/ArrayList.git GIT_TAG - main) - -# ================================================================ -# LIBRARY (lógica reutilizable, separada del ejecutable) -# ================================================================ -add_library(calculator_lib src/lexer.c src/parser.c src/evaluator.c) - -target_include_directories( - calculator_lib PUBLIC $ - $) - -target_compile_features(calculator_lib PUBLIC c_std_11) - -target_link_libraries( - calculator_lib - PUBLIC arena - PUBLIC arraylist - PRIVATE m) # libm — solo en el linker interno, no se propaga - -# ================================================================ -# EXECUTABLE -# ================================================================ -add_executable(calculator src/main.c) -target_link_libraries(calculator PRIVATE calculator_lib) - -# ================================================================ -# TESTS -# ================================================================ -if(CALCULATOR_BUILD_TESTS) - enable_testing() - add_subdirectory(test) -endif() diff --git a/include/lexer.h b/include/lexer.h index a8d0cd8..19fd075 100644 --- a/include/lexer.h +++ b/include/lexer.h @@ -1,9 +1,9 @@ #ifndef LEXER_H #define LEXER_H -#include "arraylist.h" -#include +#include "lae_arraylist.h" #include +#include #include // For identifing @@ -66,8 +66,8 @@ typedef struct { } LexerI64Result; // Lexer funtions as well as few functionality -TokenizeResult tokenize(const char* input); -TokenResult tokenize_number(const char* input, size_t *offset); +TokenizeResult tokenize(const char *input); +TokenResult tokenize_number(const char *input, size_t *offset); LexerI64Result string_to_integer(const char buf[]); bool isoperator(int c); Operator char_to_operator(int c); diff --git a/include/parser.h b/include/parser.h index f4b4219..2b49ee3 100644 --- a/include/parser.h +++ b/include/parser.h @@ -1,9 +1,9 @@ #ifndef PARSER_H #define PARSER_H +#include "lae_arena.h" +#include "lae_arraylist.h" #include "lexer.h" -#include "arena.h" -#include "arraylist.h" #include #include @@ -21,11 +21,11 @@ typedef struct Node { Operator op; struct Node *left; struct Node *right; - }binary; + } binary; struct { Operator op; struct Node *to; - }unary; + } unary; Operator par; }; } Node; @@ -51,7 +51,7 @@ typedef struct { }; } ParserResult; -typedef struct { +typedef struct { bool is_valid; union { ParserErr err; @@ -76,7 +76,11 @@ typedef struct { } ParserU8Result; TreeResult nud(ArraySlice *slice, Arena *arena, Token token); // Null denotation -TreeResult led(ArraySlice *slice, Arena *arena, Node *left, Token token); // Left denotation +TreeResult +led(ArraySlice *slice, + Arena *arena, + Node *left, + Token token); // Left denotation ParserU8Result prefix_rbp(Token token); ParserU8Result postfix_lbp(Token token); diff --git a/src/evaluator.c b/src/evaluator.c index f34aa02..b8c94bb 100644 --- a/src/evaluator.c +++ b/src/evaluator.c @@ -1,11 +1,10 @@ #include "evaluator.h" -#include "arena.h" +#include "lae_arena.h" #include "lexer.h" #include "parser.h" +#include #include #include -#include - EvaluatorResult evaluate_tree(Node *tree) { if (tree->type == NODE_BINARY_OP) { @@ -14,7 +13,7 @@ EvaluatorResult evaluate_tree(Node *tree) { return evaluate_unary(tree); } - return (EvaluatorResult) { + return (EvaluatorResult){ .is_valid = true, .val = tree->num, }; @@ -24,7 +23,7 @@ EvaluatorResult evaluate_binary(Node *tree) { Operator op = tree->binary.op; Node *left = tree->binary.left; Node *right = tree->binary.right; - + EvaluatorResult left_result = evaluate_tree(left); EvaluatorResult right_result = evaluate_tree(right); if (!left_result.is_valid) { @@ -36,36 +35,36 @@ EvaluatorResult evaluate_binary(Node *tree) { } switch (op) { - case OP_ADD: - return (EvaluatorResult) { - .is_valid = true, - .val = left_result.val + right_result.val, - }; - case OP_SUB: - return (EvaluatorResult) { - .is_valid = true, - .val = left_result.val - right_result.val, - }; - case OP_MUL: - return (EvaluatorResult) { - .is_valid = true, - .val = left_result.val * right_result.val, - }; - case OP_DIV: - return (EvaluatorResult) { - .is_valid = true, - .val = left_result.val / right_result.val, - }; - case OP_POW: - return (EvaluatorResult) { - .is_valid = true, - .val = pow(left_result.val, right_result.val), - }; - default: - return (EvaluatorResult) { - .is_valid = false, - .err = EVALUATOR_INVALID_TREE, - }; + case OP_ADD: + return (EvaluatorResult){ + .is_valid = true, + .val = left_result.val + right_result.val, + }; + case OP_SUB: + return (EvaluatorResult){ + .is_valid = true, + .val = left_result.val - right_result.val, + }; + case OP_MUL: + return (EvaluatorResult){ + .is_valid = true, + .val = left_result.val * right_result.val, + }; + case OP_DIV: + return (EvaluatorResult){ + .is_valid = true, + .val = left_result.val / right_result.val, + }; + case OP_POW: + return (EvaluatorResult){ + .is_valid = true, + .val = pow(left_result.val, right_result.val), + }; + default: + return (EvaluatorResult){ + .is_valid = false, + .err = EVALUATOR_INVALID_TREE, + }; } } @@ -79,29 +78,29 @@ EvaluatorResult evaluate_unary(Node *tree) { } switch (op) { - case OP_ADD: - return result; - case OP_SUB: - return (EvaluatorResult) { - .is_valid = true, - .val = -result.val, - }; - case OP_FACTORIAL: - return (EvaluatorResult) { - .is_valid = true, - .val = tgamma(result.val + 1), - }; - default: - return (EvaluatorResult) { - .is_valid = false, - .err = EVALUATOR_INVALID_TREE, - }; + case OP_ADD: + return result; + case OP_SUB: + return (EvaluatorResult){ + .is_valid = true, + .val = -result.val, + }; + case OP_FACTORIAL: + return (EvaluatorResult){ + .is_valid = true, + .val = tgamma(result.val + 1), + }; + default: + return (EvaluatorResult){ + .is_valid = false, + .err = EVALUATOR_INVALID_TREE, + }; } } EvaluatorResult evaluate(ParserResult context) { if (!context.is_valid) { - return (EvaluatorResult) { + return (EvaluatorResult){ .is_valid = false, .err = EVALUATOR_INVALID_PARSING, }; diff --git a/src/lexer.c b/src/lexer.c index 367c4c2..3268f4f 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1,22 +1,19 @@ #include "lexer.h" -#include "arraylist.h" +#include "lae_allocator.h" +#include "lae_arraylist.h" #include +#include #include #include #include #include #include -#include - -typedef enum { - WAIT_FOR_NUMBER, - WAIT_FOR_OPERATOR -} LexerState; +typedef enum { WAIT_FOR_NUMBER, WAIT_FOR_OPERATOR } LexerState; TokenizeResult tokenize(const char *input) { ArrayList *arr; - arraylist_init(&arr, 64, sizeof(Token)); + arraylist_init(&arr, allocator_default(), 64, sizeof(Token)); size_t offset = 0; while (input[offset] != '\0') { @@ -26,7 +23,7 @@ TokenizeResult tokenize(const char *input) { if (!result.is_valid) { arraylist_destroy(&arr); - return (TokenizeResult) {.is_valid = false, .err = result.err}; + return (TokenizeResult){.is_valid = false, .err = result.err}; } arraylist_push_back(arr, &result.token); @@ -35,15 +32,14 @@ TokenizeResult tokenize(const char *input) { .type = TOKEN_OPERATOR, .op = char_to_operator(input[offset]), }; - + arraylist_push_back(arr, &op_node); } else if (isspace(input[offset])) { // Nothing... } else { arraylist_destroy(&arr); - return (TokenizeResult) { - .is_valid = false, - .err = LEXER_NOT_RECOGNIZED_SYMBOL}; + return (TokenizeResult){.is_valid = false, + .err = LEXER_NOT_RECOGNIZED_SYMBOL}; } offset++; @@ -51,16 +47,16 @@ TokenizeResult tokenize(const char *input) { if (arraylist_size(arr) < 1) { arraylist_destroy(&arr); - return (TokenizeResult) {.is_valid = false, .err = LEXER_EMPTY_INPUT}; + return (TokenizeResult){.is_valid = false, .err = LEXER_EMPTY_INPUT}; } - return (TokenizeResult) {.is_valid = true, .arr = arr}; + return (TokenizeResult){.is_valid = true, .arr = arr}; } // CURRENTLY, it only supports ints, not clear how floating // point is implemented but i'll figure it out TokenResult tokenize_number(const char *input, size_t *offset) { - char buf[64] = { '\0' }; + char buf[64] = {'\0'}; size_t buf_pos = 0; bool is_integer = true; // Will later be used to differentiate fractions @@ -68,9 +64,7 @@ TokenResult tokenize_number(const char *input, size_t *offset) { size_t current = *offset; while (isdigit(input[current])) { if (buf_pos >= sizeof(buf) - 1) { - return (TokenResult) { - .is_valid = false, - .err = LEXER_BUF_OVERFLOW}; + return (TokenResult){.is_valid = false, .err = LEXER_BUF_OVERFLOW}; } buf[buf_pos] = input[current]; @@ -84,20 +78,18 @@ TokenResult tokenize_number(const char *input, size_t *offset) { new_token.type = TOKEN_INTEGER; LexerI64Result result = string_to_integer(buf); - if (!result.is_valid) { - return (TokenResult) {.is_valid = false, .err = result.err}; + return (TokenResult){.is_valid = false, .err = result.err}; } new_token.num = result.num; *offset = current - 1; - return (TokenResult) {.is_valid = true, .token = new_token}; + return (TokenResult){.is_valid = true, .token = new_token}; } - return (TokenResult) { - .is_valid = false, - .err = LEXER_FAILED_NUMBER_CONVERSION}; + return (TokenResult){.is_valid = false, + .err = LEXER_FAILED_NUMBER_CONVERSION}; } LexerI64Result string_to_integer(const char *buf) { @@ -110,86 +102,85 @@ LexerI64Result string_to_integer(const char *buf) { int digit = buf[c] - '0'; if (count > (INT64_MAX - digit) / 10) { - return (LexerI64Result) { - .is_valid = false, - .err = LEXER_INT_OVERFLOW}; + return (LexerI64Result){.is_valid = false, + .err = LEXER_INT_OVERFLOW}; } count = count * 10; count += digit; - + c++; } - return (LexerI64Result) {.is_valid = true, .num = count}; + return (LexerI64Result){.is_valid = true, .num = count}; } bool isoperator(int c) { switch (c) { - case '+': - case '-': - case '/': - case '*': - case '^': - case '!': - case '(': - case ')': - return true; - default: - return false; + case '+': + case '-': + case '/': + case '*': + case '^': + case '!': + case '(': + case ')': + return true; + default: + return false; } } Operator char_to_operator(int c) { switch (c) { - case '+': - return OP_ADD; - break; - case '-': - return OP_SUB; - break; - case '*': - return OP_MUL; - break; - case '/': - return OP_DIV; - break; - case '^': - return OP_POW; - break; - case '!': - return OP_FACTORIAL; - break; - case '(': - return OP_START_PAR; - break; - case ')': - return OP_END_PAR; - break; - default: // I mean shouldn't be used, we assume - return -1; + case '+': + return OP_ADD; + break; + case '-': + return OP_SUB; + break; + case '*': + return OP_MUL; + break; + case '/': + return OP_DIV; + break; + case '^': + return OP_POW; + break; + case '!': + return OP_FACTORIAL; + break; + case '(': + return OP_START_PAR; + break; + case ')': + return OP_END_PAR; + break; + default: // I mean shouldn't be used, we assume + return -1; } } char operator_to_char(Operator op) { switch (op) { - case OP_ADD: - return '+'; - case OP_SUB: - return '-'; - case OP_MUL: - return '*'; - case OP_DIV: - return '/'; - case OP_POW: - return '^'; - case OP_FACTORIAL: - return '!'; - case OP_START_PAR: - return '('; - case OP_END_PAR: - return ')'; - default: - return EOF; + case OP_ADD: + return '+'; + case OP_SUB: + return '-'; + case OP_MUL: + return '*'; + case OP_DIV: + return '/'; + case OP_POW: + return '^'; + case OP_FACTORIAL: + return '!'; + case OP_START_PAR: + return '('; + case OP_END_PAR: + return ')'; + default: + return EOF; } } diff --git a/src/parser.c b/src/parser.c index 5722a10..73a3fd5 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1,7 +1,8 @@ #include "parser.h" -#include "arraylist.h" +#include "lae_allocator.h" +#include "lae_arena.h" +#include "lae_arraylist.h" #include "lexer.h" -#include "arena.h" #include #include #include @@ -9,320 +10,292 @@ ParserU8Result prefix_rbp(Token token) { if (token.type == TOKEN_INTEGER) { - return (ParserU8Result) { + return (ParserU8Result){ .is_valid = false, .err = PARSER_UNEXPECTED_TOKEN, }; } switch (token.op) { - case OP_SUB: - case OP_ADD: - return (ParserU8Result) { - .is_valid = true, - .num = 30, - }; - default: - return (ParserU8Result) { - .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, - }; + case OP_SUB: + case OP_ADD: + return (ParserU8Result){ + .is_valid = true, + .num = 30, + }; + default: + return (ParserU8Result){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, + }; } } ParserU8Result postfix_lbp(Token token) { if (token.type != TOKEN_OPERATOR) { - return (ParserU8Result) { + return (ParserU8Result){ .is_valid = false, .err = PARSER_UNEXPECTED_TOKEN, }; } switch (token.op) { - case OP_FACTORIAL: - return (ParserU8Result) { - .is_valid = true, - .num = 40, - }; - default: - return (ParserU8Result) { - .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, - }; + case OP_FACTORIAL: + return (ParserU8Result){ + .is_valid = true, + .num = 40, + }; + default: + return (ParserU8Result){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, + }; } } ParserU8Result infix_lbp(Token token) { if (token.type != TOKEN_OPERATOR) { - return (ParserU8Result) { + return (ParserU8Result){ .is_valid = false, .err = PARSER_UNEXPECTED_TOKEN, }; } switch (token.op) { - case OP_ADD: - case OP_SUB: - return (ParserU8Result) { - .is_valid = true, - .num = 10, - }; - case OP_DIV: - case OP_MUL: - return (ParserU8Result) { - .is_valid = true, - .num = 20, - }; - case OP_POW: - return (ParserU8Result) { - .is_valid = true, - .num = 51, - }; - default: - return (ParserU8Result) { - .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, - }; + case OP_ADD: + case OP_SUB: + return (ParserU8Result){ + .is_valid = true, + .num = 10, + }; + case OP_DIV: + case OP_MUL: + return (ParserU8Result){ + .is_valid = true, + .num = 20, + }; + case OP_POW: + return (ParserU8Result){ + .is_valid = true, + .num = 51, + }; + default: + return (ParserU8Result){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, + }; } } ParserU8Result infix_rbp(Token token) { if (token.type != TOKEN_OPERATOR) { - return (ParserU8Result) { + return (ParserU8Result){ .is_valid = false, .err = PARSER_UNEXPECTED_TOKEN, }; } switch (token.op) { - case OP_ADD: - case OP_SUB: - return (ParserU8Result) { - .is_valid = true, - .num = 11, - }; - case OP_DIV: - case OP_MUL: - return (ParserU8Result) { - .is_valid = true, - .num = 21, - }; - case OP_POW: - return (ParserU8Result) { - .is_valid = true, - .num = 50, - }; - default: - return (ParserU8Result) { - .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, - }; + case OP_ADD: + case OP_SUB: + return (ParserU8Result){ + .is_valid = true, + .num = 11, + }; + case OP_DIV: + case OP_MUL: + return (ParserU8Result){ + .is_valid = true, + .num = 21, + }; + case OP_POW: + return (ParserU8Result){ + .is_valid = true, + .num = 50, + }; + default: + return (ParserU8Result){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, + }; } } -TreeResult led( - ArraySlice *slice, - Arena *arena, - Node *left, - Token token -) { - arena_ensure_capacity( - arena, - sizeof(Node), - alignof(Node) - ); +TreeResult led(ArraySlice *slice, Arena *arena, Node *left, Token token) { + arena_ensure_capacity(arena, sizeof(Node), alignof(Node)); - Node *node = arena_unwrap_pointer( - arena_alloc( - arena, - sizeof(Node), - alignof(Node) - ) - ); + Node *node = + arena_unwrap_pointer(arena_alloc(arena, sizeof(Node), alignof(Node))); switch (token.op) { - // Binary operators - case OP_ADD: - case OP_SUB: - case OP_MUL: - case OP_DIV: - case OP_POW: { - node->type = NODE_BINARY_OP; - node->binary.op = token.op; + // Binary operators + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_POW: { + node->type = NODE_BINARY_OP; + node->binary.op = token.op; - ParserU8Result rbp_result = infix_rbp(token); - if (!rbp_result.is_valid) { - return (TreeResult) { - .is_valid = false, - .err = rbp_result.err, - }; - } - - TreeResult right = parse_expr( - slice, - arena, - rbp_result.num - ); - - if (!right.is_valid) { - return right; - } - - node->binary.left = left; - node->binary.right = right.node; - - return (TreeResult) { - .is_valid = true, - .node = node, - }; - } - - // Postfix operators - case OP_FACTORIAL: { - node->type = NODE_UNARY_OP; - node->unary.op = token.op; - node->unary.to = left; - - return (TreeResult) { - .is_valid = true, - .node = node, - }; - } - - default: - return (TreeResult) { + ParserU8Result rbp_result = infix_rbp(token); + if (!rbp_result.is_valid) { + return (TreeResult){ .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, + .err = rbp_result.err, }; + } + + TreeResult right = parse_expr(slice, arena, rbp_result.num); + + if (!right.is_valid) { + return right; + } + + node->binary.left = left; + node->binary.right = right.node; + + return (TreeResult){ + .is_valid = true, + .node = node, + }; + } + + // Postfix operators + case OP_FACTORIAL: { + node->type = NODE_UNARY_OP; + node->unary.op = token.op; + node->unary.to = left; + + return (TreeResult){ + .is_valid = true, + .node = node, + }; + } + + default: + return (TreeResult){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, + }; } } TreeResult nud(ArraySlice *slice, Arena *arena, Token token) { - arena_ensure_capacity( - arena, - sizeof(Node), - alignof(Node) - ); + arena_ensure_capacity(arena, sizeof(Node), alignof(Node)); - Node *node = arena_unwrap_pointer( - arena_alloc( - arena, - sizeof(Node), - alignof(Node) - ) - ); + Node *node = + arena_unwrap_pointer(arena_alloc(arena, sizeof(Node), alignof(Node))); if (token.type == TOKEN_INTEGER) { node->type = NODE_INT; node->num = token.num; - return (TreeResult) { + return (TreeResult){ .is_valid = true, .node = node, }; } switch (token.op) { - case OP_START_PAR: { - TreeResult expr = parse_expr(slice, arena, 0); - if (!expr.is_valid) { - return expr; - } - - Token end_par; - if (arrayslice_next(slice, &end_par) != ARRLIST_OK) { - return (TreeResult) { - .is_valid = false, - .err = PARSER_UNMATCHED_PAREN, - }; - } - - if (end_par.type != TOKEN_OPERATOR || - end_par.op != OP_END_PAR) { - return (TreeResult) { - .is_valid = false, - .err = PARSER_UNMATCHED_PAREN, - }; - } - + case OP_START_PAR: { + TreeResult expr = parse_expr(slice, arena, 0); + if (!expr.is_valid) { return expr; } - case OP_ADD: - case OP_SUB: { - node->type = NODE_UNARY_OP; - node->unary.op = token.op; - - ParserU8Result rbp_result = prefix_rbp(token); - if (!rbp_result.is_valid) { - return (TreeResult) { - .is_valid = false, - .err = rbp_result.err, - }; - } - - TreeResult right = parse_expr( - slice, - arena, - rbp_result.num - ); - - if (!right.is_valid) { - return right; - } - - node->unary.to = right.node; - - return (TreeResult) { - .is_valid = true, - .node = node, + Token end_par; + if (arrayslice_next(slice, &end_par) != ARRLIST_OK) { + return (TreeResult){ + .is_valid = false, + .err = PARSER_UNMATCHED_PAREN, }; } - default: - return (TreeResult) { + + if (end_par.type != TOKEN_OPERATOR || end_par.op != OP_END_PAR) { + return (TreeResult){ .is_valid = false, - .err = PARSER_UNEXPECTED_TOKEN, + .err = PARSER_UNMATCHED_PAREN, + }; + } + + return expr; + } + case OP_ADD: + + case OP_SUB: { + node->type = NODE_UNARY_OP; + node->unary.op = token.op; + + ParserU8Result rbp_result = prefix_rbp(token); + if (!rbp_result.is_valid) { + return (TreeResult){ + .is_valid = false, + .err = rbp_result.err, + }; + } + + TreeResult right = parse_expr(slice, arena, rbp_result.num); + + if (!right.is_valid) { + return right; + } + + node->unary.to = right.node; + + return (TreeResult){ + .is_valid = true, + .node = node, + }; + } + default: + return (TreeResult){ + .is_valid = false, + .err = PARSER_UNEXPECTED_TOKEN, }; } } - - ParserResult parse(TokenizeResult tokens) { if (!tokens.is_valid) { - return (ParserResult) { + return (ParserResult){ .is_valid = false, .err = PARSER_INVALID_TOKENIZE, }; } ArraySlice *context; - arraylist_slice(&context, tokens.arr, 0, arraylist_size(tokens.arr)); + arraylist_slice( + &context, + allocator_default(), + tokens.arr, + 0, + arraylist_size(tokens.arr)); Arena *arena; - arena_init(&arena, sizeof(Node) * arraylist_size(tokens.arr)); + arena_init( + &arena, + allocator_default(), + sizeof(Node) * arraylist_size(tokens.arr)); TreeResult result = parse_expr(context, arena, 0); if (!result.is_valid) { arena_destroy(&arena); arraylist_destroy(&tokens.arr); - return (ParserResult) { + return (ParserResult){ .is_valid = false, .err = result.err, }; } arraylist_destroy(&tokens.arr); - return (ParserResult) { - .is_valid = true, - .arena = arena, - .tree = result.node}; + return ( + ParserResult){.is_valid = true, .arena = arena, .tree = result.node}; } TreeResult parse_expr(ArraySlice *slice, Arena *arena, uint8_t min_bp) { Token current_token; if (arrayslice_next(slice, ¤t_token) != ARRLIST_OK) { - return (TreeResult) { + return (TreeResult){ .is_valid = false, .err = PARSER_UNEXPECTED_EOF, }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index fad601a..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,114 +0,0 @@ -# ================================================================ -# FIND CMOCKA (system → FetchContent as fallback) -# ================================================================ -find_package(cmocka QUIET) - -if(NOT cmocka_FOUND) - message( - STATUS - "[calculator] cmocka not found on system — fetching with FetchContent...") - - include(FetchContent) - FetchContent_Declare( - cmocka - GIT_REPOSITORY https://git.cryptomilk.org/projects/cmocka.git - GIT_TAG cmocka-1.1.7 - GIT_SHALLOW TRUE) - - # Static lib only, no cmocka examples or self-tests - set(WITH_STATIC_LIB - ON - CACHE BOOL "" FORCE) - set(WITH_SHARED_LIB - OFF - CACHE BOOL "" FORCE) - set(WITH_CMOCKERY_SUPPORT - OFF - CACHE BOOL "" FORCE) - set(WITH_EXAMPLES - OFF - CACHE BOOL "" FORCE) - set(UNIT_TESTING - OFF - CACHE BOOL "" FORCE) - set(PICKY_DEVELOPER - OFF - CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(cmocka) - - # Normalize the target name (varies across cmocka versions) - if(NOT TARGET cmocka::cmocka) - if(TARGET cmocka-static) - add_library(cmocka::cmocka ALIAS cmocka-static) - elseif(TARGET cmocka) - add_library(cmocka::cmocka ALIAS cmocka) - else() - message( - WARNING "[calculator] Could not create cmocka::cmocka — skipping tests." - ) - return() - endif() - endif() -endif() - -# ================================================================ -# DETECT ADDRESS SANITIZER SUPPORT -# ================================================================ -set(CALCULATOR_USE_ASAN OFF) - -if(CALCULATOR_ENABLE_SANITIZERS) - if(MSVC) - if(MSVC_VERSION GREATER_EQUAL 1928) - set(CALCULATOR_USE_ASAN ON) - else() - message(STATUS "[calculator] MSVC < 16.9: ASAN not available.") - endif() - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32) - include(CheckCCompilerFlag) - check_c_compiler_flag(-fsanitize=address CALCULATOR_COMPILER_HAS_ASAN) - if(CALCULATOR_COMPILER_HAS_ASAN) - set(CALCULATOR_USE_ASAN ON) - else() - message( - STATUS "[calculator] Compiler does not support -fsanitize=address.") - endif() - else() - message(STATUS "[calculator] Unknown platform/compiler — ASAN skipped.") - endif() - - if(CALCULATOR_USE_ASAN) - message(STATUS "[calculator] Address Sanitizer enabled.") - endif() -endif() - -# ================================================================ -# Helper: apply common settings to every test target -# ================================================================ -function(calculator_add_test target source) - add_executable(${target} ${source}) - - target_link_libraries(${target} PRIVATE calculator_lib cmocka::cmocka) - - target_compile_features(${target} PRIVATE c_std_11) - - if(CALCULATOR_USE_ASAN) - if(MSVC) - target_compile_options(${target} PRIVATE /fsanitize=address) - else() - target_compile_options(${target} PRIVATE -fsanitize=address - -fno-omit-frame-pointer) - target_link_options(${target} PRIVATE -fsanitize=address) - endif() - endif() - - add_test(NAME ${target} COMMAND ${target}) - set_tests_properties(${target} PROPERTIES TIMEOUT 30) -endfunction() - -# ================================================================ -# TEST TARGETS -# ================================================================ -calculator_add_test(test_lexer test_lexer.c) -calculator_add_test(test_parser test_parser.c) -calculator_add_test(test_evaluator test_evaluator.c) diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..590e36a --- /dev/null +++ b/xmake.lua @@ -0,0 +1,107 @@ +set_project("calculator") +set_version("1.0.0") + +set_languages("c11") + +add_rules("mode.debug", "mode.release") + +-- clang compile commands +add_rules("plugin.compile_commands.autoupdate") + +-- ========================================================= +-- Options +-- ========================================================= + +option("tests") +set_default(false) +set_showmenu(true) +option_end() + +option("asan") +set_default(false) +set_showmenu(true) +option_end() + +-- ========================================================= +-- Packages +-- ========================================================= + +-- Run xmake repo --add lae-repo https://laentropia-homelab.tail7368da.ts.net/laentropia/xmake-repo.git +-- to add my repo :) + +add_requires("lae_arena") +add_requires("lae_arraylist") +add_requires("cmocka", { optional = true }) + +-- ========================================================= +-- Library +-- ========================================================= + +target("calculator_lib") +set_kind("static") + +add_packages("lae_arena", "lae_arraylist", { + public = true, +}) + +add_files("src/lexer.c", "src/parser.c", "src/evaluator.c") + +add_headerfiles("include/*.h") + +add_includedirs("include", { + public = true, +}) + +if is_mode("debug") then + add_cflags("-Wall", "-Wextra", "-Wpedantic") +end + +if not is_plat("windows") then + add_syslinks("m") +end + +-- ========================================================= +-- Executable +-- ========================================================= + +target("calculator") +set_kind("binary") + +add_deps("calculator_lib") + +add_files("src/main.c") + +-- ========================================================= +-- Tests +-- ========================================================= + +if has_config("tests") then + local test_files = { + "test_lexer", + "test_parser", + "test_evaluator", + } + + for _, name in ipairs(test_files) do + target(name) + set_kind("binary") + + add_files("test/" .. name .. ".c") + + add_deps("calculator_lib") + + add_packages("cmocka") + + add_tests("default") + + if has_config("asan") then + add_cflags("-fsanitize=address", "-fno-omit-frame-pointer", { + force = true, + }) + + add_ldflags("-fsanitize=address", { + force = true, + }) + end + end +end -- 2.51.0