Compare commits

..

26 Commits

Author SHA1 Message Date
7ad4eba123 fix/refactor: Modified evaluate and changed it to evaluate_tree
So i did what the last commit said, also fixed parse_expr because it was
still using malloc for allocating new nodes so i made it use arena_alloc
like it should, did the very first tests so it's all good, i think is
readdy to merge.
2026-04-13 08:44:30 -06:00
e4ec102cb9 rework: AST now uses an arena for allocation
For now it works but i dont really like that i use ParseResult, i mean
is necessary but i think i will try to make it cleaner so that i can
just directly use like parse and pass tath into evaluate, that would
require to move the main evaluate funciton into evaluate_tree or
something  and evaluate takes the arena, uses evaluate_tree and frees
the arena, will try that the next commit but for now this version works
perfectly.
2026-04-13 07:57:36 -06:00
fb27e1e34c addition: added arena library to this shit 2026-04-13 06:40:31 -06:00
ef8cf84456 addition: added arena library 2026-04-11 22:41:19 -06:00
a486ed62f4 Rework: moved ASTNodeArray logic to it's own c file 2026-03-26 10:01:17 -06:00
4cddb24405 Changed evaluate function to free the memory it uses, temporary, will be changed to use a sigle arena for the tree 2026-03-26 09:25:40 -06:00
1ce64d8e9e Fixed many minor things, tested it on main and is amazing, i think i need to start adding fractions next so further testing can be done with more powerful operators 2026-03-25 12:25:15 -06:00
92d142b9cf It works, basic but works, need to move out logic to places, like ASTNode array shouldn't be on the lexer or future logic for fractions and error handling in the evaluator. Just the things at the top of my head 2026-03-25 11:30:12 -06:00
845673fb0e FUCK YEAH, it works, it was the is_valid thing fo slices that was inverted, did the test by brute force and damn, is the expected AST 2026-03-25 10:22:54 -06:00
f24671bd19 well, nothing works, at least there is something to debug 2026-03-25 07:43:00 -06:00
f11b6f8c12 print funtion added, not tested 2026-03-25 06:59:52 -06:00
17be815ed0 Done with helpers, need some function to actually print the tree and see for myself if it works wit simple symbols 2026-03-24 21:51:28 -06:00
7d28b69790 added parse, just need a few more helper funtions to start testing some things 2026-03-24 21:44:08 -06:00
0d883ae978 WOW, finally making some sense, i think i get it, finally got the skelleton, gonna keep reading for adding more interesting things and doing things right, for now i think i got the parse_expr 2026-03-24 21:36:14 -06:00
acd5e9781e DAMN, i still don't get it, i really don't want to ask some AI to do it but damn, i'm finding it REALLY difficult, is like the 4th article i read, i hope this is the one and that i can get going for fucks sake 2026-03-24 21:04:36 -06:00
27787308f2 Read a pratt parsing article, i think i can do it, got nud and led the right way i think 2026-03-24 11:19:47 -06:00
6377515558 Starting with the lexer, i'm starting to comprehend better what pratt parsing is and how to apply it. For now just declaring basic structure and functions 2026-03-13 07:58:38 -06:00
903fdbd6ff I think i'm done, lexer works fine and errors work fine too, amazing actually, should be moving on to the parser so that i can construct the expression tree 2026-03-10 07:27:35 -06:00
73451fcca9 Damn, it works, the lexer actually works, that's amazing, need to test the bad cases but at least i'm sure it can detect and process correct math expressions 2026-03-10 07:08:12 -06:00
0de6cf5024 Modified the structure of the lexer, now is more easy to add types of numbers like fractions, like i could enev consider roots, irrationals, complex or imaginary, that would be dope. For now only support for integer, we need to get this shit running 2026-03-09 11:58:55 -06:00
afae8fbe3a Made the arrangements for the mentioned changes in the last commit, for now just integers but IT WILL be capable of handling doubles as fractions 2026-03-09 09:23:06 -06:00
771069455d First version for string_to_number, just one test, is working fine, i'm considering swithching to handling only integers for in the future to manage in special struct that manages doubles as fractions, obviously this will mean changing nodes for general numbers to integers/fractions and shit 2026-03-09 09:06:06 -06:00
194f1dd80f Second test, just pop, almost identical to first put important for asserting pop works because it is very needed 2026-03-05 10:20:44 -06:00
79f7e327ff First test added, changed signature for using pointers beacause i forgot you can't actually change a parameter because c copies everything, stupid from me to forget that 2026-03-05 08:27:36 -06:00
3126be5782 Added functionality for the basic array functionality, i'm going to make tests even thoug they are tedius as fuck, i'm way more interested in making tests for the lexer itself 2026-03-04 19:30:56 -06:00
adaf5c012f Redesigned everithing so that everything is cleaner and not making everything all messy to fix later, still, ther may be redesigns and shit but should be fine 2026-03-04 18:54:46 -06:00
17 changed files with 948 additions and 7 deletions

View File

@@ -3,6 +3,14 @@ project(calculator VERSION 1.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
arena
GIT_REPOSITORY https://laentropia-homelab.tail7368da.ts.net/laentropia/Arena.git
GIT_TAG main
SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/arena
)
# Export compile_commands.json (para clangd) # Export compile_commands.json (para clangd)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -14,13 +22,29 @@ add_compile_options(
) )
include_directories(include) FetchContent_MakeAvailable(arena)
add_library(arena STATIC
external/arena/src/arena.c
)
target_include_directories(arena
PUBLIC ${CMAKE_SOURCE_DIR}/external/arena/include
)
add_library(calculator_lib add_library(calculator_lib
src/lexer.c src/lexer.c
src/parser.c src/parser.c
src/ast.c
src/evaluator.c src/evaluator.c
src/ASTNodeArray.c
)
target_include_directories(calculator_lib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(calculator_lib
PUBLIC arena
) )
add_executable(calculator src/main.c) add_executable(calculator src/main.c)

1
external/arena vendored Submodule

Submodule external/arena added at 3d3b8596cc

View File

View File

@@ -0,0 +1,11 @@
#ifndef EVALUATOR_H
#define EVALUATOR_H
#include "lexer.h"
#include "parser.h"
#include <stdint.h>
int64_t evaluate(ParseResult context);
int64_t evaluate_tree(ASTNode *tree);
#endif // !EVALUATOR_H

View File

@@ -0,0 +1,78 @@
#ifndef LEXER_H
#define LEXER_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
// For identifing
typedef enum {
NODE_INTEGER,
NODE_BINARY_OP,
} ASTNodeType;
// For classify operators
typedef enum {
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV
} Operator;
typedef enum {
ARRAY_OK = 0,
ARRAY_NULL,
ARRAY_EMPTY,
ARRAY_OUT_OF_BOUNDS,
ARRAY_NULL_ARG,
ARRAY_ALLOC,
} ASTNodeArrayErr;
typedef enum {
LEXER_OK = 0,
LEXER_INT_OVERFLOW,
LEXER_FAILED_NUMBER_CONVERSION,
LEXER_NOT_RECOGNIZED_SYMBOL,
LEXER_EMPTY_INPUT,
LEXER_NULL_ARG,
LEXER_WRONG_SYNTAX,
LEXER_BUF_OVERFLOW,
} LexerErr;
// Can be thought as tokens, they will be used by the parser.
typedef struct ASTNode {
ASTNodeType type;
union {
int64_t integer;
struct {
struct ASTNode *left;
struct ASTNode *right;
Operator op;
} binary;
} data;
} ASTNode;
// I prefer ot have a dynamic array for storing the "tokens"
typedef struct {
size_t len;
size_t cap;
ASTNode *data;
} ASTNodeArray;
ASTNodeArray ASTNodeArray_init(size_t size);
void ASTNodeArray_free(ASTNodeArray *arr);
ASTNodeArrayErr ASTNodeArray_push(ASTNodeArray *arr, ASTNode node);
ASTNodeArrayErr ASTNodeArray_get(const ASTNodeArray *arr, size_t index, ASTNode *out);
// Out in pop can be NULL so it doesn't return anything
ASTNodeArrayErr ASTNodeArray_pop(ASTNodeArray *arr, size_t index, ASTNode *out);
size_t ASTNodeArray_len(ASTNodeArray *arr);
// Lexer funtions as well as few functionality
LexerErr tokenize(const char* input, ASTNodeArray *out);
LexerErr tokenize_number(const char* input, size_t *offset, ASTNode *out);
LexerErr string_to_integer(const char buf[], int64_t *number);
bool isoperator(int c);
Operator char_to_operator(int c);
char operator_to_char(Operator op);
#endif // !LEXER_H

View File

@@ -0,0 +1,35 @@
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h"
#include "arena.h"
#include <stdint.h>
typedef struct {
ASTNode *head;
} AST;
typedef struct {
ASTNodeArray *arr;
size_t pos;
} ASTNodeSlice;
typedef struct {
Arena arena;
ASTNode *tree;
} ParseResult;
ASTNode ASTNodeSlice_peek(ASTNodeSlice *slice);
ASTNode ASTNodeSlice_next(ASTNodeSlice *slice);
bool ASTNodeSlice_is_valid(ASTNodeSlice *slice);
ASTNode *nud(ASTNodeSlice *slice);
ASTNode *led(ASTNodeSlice *slice, size_t right_precedence);
uint8_t node_lbp(ASTNode node);
uint8_t node_rbp(ASTNode node);
ParseResult parse(ASTNodeArray *arr);
ASTNode *parse_expr(ASTNodeSlice *slice, Arena *arena, uint8_t min_bp);
#endif // !PARSER_H

108
src/ASTNodeArray.c Normal file
View File

@@ -0,0 +1,108 @@
#include "lexer.h"
#include <stdlib.h>
#define NODE_ARRAY_DEFAULT_SIZE 64
// Helps state machine for the lexer :)
typedef enum {
WAIT_FOR_NUMBER,
WAIT_FOR_OPERATOR,
} LexerState;
ASTNodeArray ASTNodeArray_init(size_t size) {
ASTNodeArray new;
new.len = 0; // if 0 then use default
new.cap = size == 0 ? NODE_ARRAY_DEFAULT_SIZE : size;
new.data = malloc(new.cap * sizeof(ASTNode));
return new;
}
void ASTNodeArray_free(ASTNodeArray *arr) {
free(arr->data);
arr->cap = 0;
arr->len = 0;
}
ASTNodeArrayErr ASTNodeArray_get(const ASTNodeArray *arr, size_t index, ASTNode *out) {
if (arr == NULL) {
return ARRAY_NULL;
}
if (out == NULL) {
return ARRAY_NULL_ARG;
}
if (arr->len == 0) {
return ARRAY_EMPTY;
}
if (index >= arr->len) {
return ARRAY_OUT_OF_BOUNDS;
}
*out = arr->data[index];
return ARRAY_OK;
}
ASTNodeArrayErr ASTNodeArray_push(ASTNodeArray *arr, ASTNode node) {
if (arr == NULL) {
return ARRAY_NULL;
}
if (arr->len >= arr->cap) {
size_t new_cap = arr->cap * 2;
ASTNode *tmp = realloc(arr->data, new_cap * sizeof(ASTNode));
if (tmp == NULL) {
return ARRAY_ALLOC;
}
arr->data = tmp;
arr->cap = new_cap;
}
arr->data[arr->len] = node;
arr->len = arr->len + 1;
return ARRAY_OK;
}
ASTNodeArrayErr ASTNodeArray_pop(ASTNodeArray *arr, size_t index, ASTNode *out) {
if (arr == NULL) {
return ARRAY_NULL;
}
if (arr->len == 0) {
return ARRAY_EMPTY;
}
if (index >= arr->len) {
return ARRAY_OUT_OF_BOUNDS;
}
if (arr->cap / 4 > arr->len) {
size_t new_cap = arr->cap / 2;
ASTNode *tmp = realloc(arr->data, new_cap * sizeof(ASTNode));
if (tmp == NULL) {
return ARRAY_ALLOC;
}
arr->data = tmp;
arr->cap = new_cap;
}
if (out != NULL) {
ASTNode node_to_delete = arr->data[index];
*out = node_to_delete;
}
for (size_t i = index; i < arr->len - 1; i++) {
arr->data[index] = arr->data[index + 1];
}
return ARRAY_OK;
}
size_t ASTNodeArray_len(ASTNodeArray *arr) {
if (arr == NULL) {
return 0;
}
return arr->len;
}

View File

View File

@@ -0,0 +1,36 @@
#include "evaluator.h"
#include "arena.h"
#include "lexer.h"
#include "parser.h"
#include <stdint.h>
int64_t evaluate_tree(ASTNode *tree) {
if (tree->type == NODE_BINARY_OP) {
Operator op = tree->data.binary.op;
ASTNode *left = tree->data.binary.left;
ASTNode *right = tree->data.binary.right;
switch (op) {
case OP_ADD:
return evaluate_tree(left) + evaluate_tree(right);
case OP_SUB:
return evaluate_tree(left) - evaluate_tree(right);
case OP_MUL:
return evaluate_tree(left) * evaluate_tree(right);
case OP_DIV:
return evaluate_tree(left) / evaluate_tree(right);
}
}
int64_t return_val = tree->data.integer;
return return_val;
}
int64_t evaluate(ParseResult context) {
int64_t result = evaluate_tree(context.tree);
arena_destroy(&context.arena);
return result;
}

View File

@@ -0,0 +1,163 @@
#include "lexer.h"
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <strings.h>
#include <limits.h>
typedef enum {
WAIT_FOR_NUMBER,
WAIT_FOR_OPERATOR
} LexerState;
LexerErr tokenize(const char *input, ASTNodeArray *out) {
size_t offset = 0;
LexerState state = WAIT_FOR_NUMBER;
ASTNodeArray arr = ASTNodeArray_init(0); // 0 defaults to 64
while (input[offset] != '\n' && input[offset] != '\0') {
int current = input[offset];
if (isdigit(current)) {
if (state != WAIT_FOR_NUMBER) {
ASTNodeArray_free(&arr);
return LEXER_WRONG_SYNTAX;
}
ASTNode new_node;
LexerErr result = tokenize_number(input, &offset, &new_node);
if (result != LEXER_OK) {
ASTNodeArray_free(&arr);
return result;
}
ASTNodeArray_push(&arr, new_node);
state = WAIT_FOR_OPERATOR;
} else if (isoperator(current)) {
if (state != WAIT_FOR_OPERATOR) {
return LEXER_WRONG_SYNTAX;
}
ASTNode new_node = {
.type = NODE_BINARY_OP,
.data.binary.op = char_to_operator(current),
.data.binary.right = NULL,
.data.binary.left = NULL,
};
ASTNodeArray_push(&arr, new_node);
state = WAIT_FOR_NUMBER;
} else if (isspace(current)) {
// Nothing...
} else {
ASTNodeArray_free(&arr);
return LEXER_NOT_RECOGNIZED_SYMBOL;
}
offset++;
}
if (arr.len < 1) {
return LEXER_EMPTY_INPUT;
}
*out = arr;
return LEXER_OK;
}
// CURRENTLY, it only supports ints, not clear how floating
// point is implemented but i'll figure it out
LexerErr tokenize_number(const char *input, size_t *offset, ASTNode *out) {
char buf[128] = { '\0' };
size_t buf_pos = 0;
bool is_integer = true; // Will later be used to differentiate fractions
size_t current = *offset;
while (isdigit(input[current])) {
buf[buf_pos] = input[current];
if (buf_pos >= sizeof(buf)) {
return LEXER_BUF_OVERFLOW;
}
current++;
buf_pos++;
}
ASTNode new_node;
if (is_integer) {
new_node.type = NODE_INTEGER;
LexerErr status = string_to_integer(buf, &new_node.data.integer);
if (status == LEXER_OK) {
*out = new_node;
}
*offset = current;
return status;
}
return LEXER_FAILED_NUMBER_CONVERSION;
}
LexerErr string_to_integer(const char *buf, int64_t *number) {
int c = 0;
int64_t count = 0;
while (buf[c] != '\0') {
int digit = buf[c] - '0';
if (count > (INT64_MAX - digit) / 10) {
return LEXER_INT_OVERFLOW;
}
count = count * 10;
count += digit;
c++;
}
*number = count;
return LEXER_OK;
}
bool isoperator(int c) {
switch (c) {
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;
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 '/';
}
}

View File

@@ -1,7 +1,31 @@
#include "arena.h"
#include "evaluator.h"
#include "lexer.h"
#include "parser.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
int main(int argc, char *argv[]) { int main(void) {
puts("Hello"); char buf[256];
printf("Insert a valid mathematical expression: ");
int c;
int pos = 0;
while ((c = getc(stdin)) != '\n' && c != EOF) {
buf[pos] = c;
pos++;
}
buf[pos] = '\0';
ASTNodeArray context;
tokenize(buf, &context);
ParseResult par = parse(&context);
int64_t result = evaluate(par);
printf("El resultado es: %" PRIi64 "\n", result);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -0,0 +1,134 @@
#include "parser.h"
#include "lexer.h"
#include "arena.h"
#include <stdalign.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
uint8_t node_lbp(ASTNode node) {
if (node.type == NODE_INTEGER) {
return 0;
}
switch (node.data.binary.op) {
case OP_ADD:
case OP_SUB:
return 10;
break;
case OP_DIV:
case OP_MUL:
return 20;
default:
return 0;
}
}
uint8_t node_rbp(ASTNode node) {
if (node.type == NODE_INTEGER) {
return 0;
}
switch (node.data.binary.op) {
case OP_ADD:
case OP_SUB:
return 11;
break;
case OP_DIV:
case OP_MUL:
return 21;
default:
return 0;
}
}
ASTNode ASTNodeSlice_next(ASTNodeSlice *slice) {
return slice->arr->data[slice->pos++];
}
ASTNode ASTNodeSlice_peek(ASTNodeSlice *slice) {
return slice->arr->data[slice->pos];
}
bool ASTNodeSlice_is_valid(ASTNodeSlice *slice) {
if (slice->arr->len < 1) {
return false;
}
if (slice->pos >= slice->arr->len) {
return false;
}
return true;
}
ParseResult parse(ASTNodeArray *arr) {
ASTNodeSlice context = {
.arr = arr,
.pos = 0,
};
Arena arena = arena_init(sizeof(ASTNode) * arr->len).arena;
return (ParseResult) {
.arena = arena,
.tree = parse_expr(&context, &arena, 0)};
}
ASTNode *parse_expr(ASTNodeSlice *slice, Arena *arena, uint8_t min_bp) {
arena_ensure_capacity(
arena,
sizeof(ASTNode),
alignof(ASTNode)
);
ASTNode *left_side = arena_unwrap_pointer(
arena_alloc(
arena,
sizeof(ASTNode),
alignof(ASTNode)
)
);
*left_side = ASTNodeSlice_next(slice);
while (true) {
if (!ASTNodeSlice_is_valid(slice)) {
break;
}
ASTNode operator = ASTNodeSlice_peek(slice);
uint8_t rbp = node_rbp(operator);
uint8_t lbp = node_lbp(operator);
if (lbp < min_bp) {
break;
}
ASTNodeSlice_next(slice);
ASTNode *right_side = parse_expr(slice, arena, rbp);
arena_ensure_capacity(
arena,
sizeof(ASTNode),
alignof(ASTNode));
ASTNode *new_node = arena_unwrap_pointer(
arena_alloc(
arena,
sizeof(ASTNode),
alignof(ASTNode)
)
);
*new_node = operator;
new_node->data.binary.left = left_side;
new_node->data.binary.right = right_side;
left_side = new_node;
}
return left_side;
}

View File

@@ -1,10 +1,31 @@
find_package(cmocka REQUIRED) find_package(cmocka REQUIRED)
add_executable(test_nodeArray test_ASTNodeArray.c)
add_executable(test_lexer test_lexer.c)
add_executable(test_parser test_parser.c) add_executable(test_parser test_parser.c)
add_executable(test_evaluator test_evaluator.c)
target_link_libraries(test_nodeArray
calculator_lib
cmocka::cmocka
)
target_link_libraries(test_lexer
calculator_lib
cmocka::cmocka
)
target_link_libraries(test_parser target_link_libraries(test_parser
calculator_lib calculator_lib
cmocka::cmocka cmocka::cmocka
) )
target_link_libraries(test_evaluator
calculator_lib
cmocka::cmocka
)
add_test(NAME nodeArray_tests COMMAND test_nodeArray)
add_test(NAME lexer_tests COMMAND test_lexer)
add_test(NAME parser_tests COMMAND test_parser) add_test(NAME parser_tests COMMAND test_parser)
add_test(NAME evaluator_tests COMMAND test_evaluator)

86
test/test_ASTNodeArray.c Normal file
View File

@@ -0,0 +1,86 @@
#include "lexer.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
static void test_array_push(void **state) {
(void) state;
// We use 2 to force resize and checking anything wrong with malloc
ASTNodeArray arr = ASTNodeArray_init(2);
ASTNode node1 = {
.type = NODE_INTEGER,
.data = { .integer = 90 }
};
ASTNode node2 = {
.type = NODE_INTEGER,
.data = { .integer = 80 }
};
ASTNode node3 = {
.type = NODE_INTEGER,
.data = { .integer = 70 }
};
assert_int_equal(ASTNodeArray_push(&arr, node1), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 1);
assert_int_equal(ASTNodeArray_push(&arr, node2), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 2);
assert_int_equal(ASTNodeArray_push(&arr, node3), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 3);
ASTNodeArray_free(&arr);
}
static void test_array_pop(void **state) {
(void) state;
// Set to force desize
ASTNodeArray arr = ASTNodeArray_init(16);
ASTNode node1 = {
.type = NODE_INTEGER,
.data = { .integer = 90 }
};
ASTNode node2 = {
.type = NODE_INTEGER,
.data = { .integer = 80 }
};
ASTNode node3 = {
.type = NODE_INTEGER,
.data = { .integer = 70 }
};
assert_int_equal(ASTNodeArray_push(&arr, node1), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 1);
assert_int_equal(ASTNodeArray_push(&arr, node2), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 2);
assert_int_equal(ASTNodeArray_push(&arr, node3), ARRAY_OK);
assert_int_equal(ASTNodeArray_len(&arr), 3);
ASTNode node4;
assert_int_equal(ASTNodeArray_pop(&arr, 1, &node4), ARRAY_OK);
assert_int_equal(node4.type, NODE_INTEGER);
assert_int_equal(node4.data.integer, 80);
ASTNodeArray_free(&arr);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_array_push),
cmocka_unit_test(test_array_pop),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

32
test/test_evaluator.c Normal file
View File

@@ -0,0 +1,32 @@
#include "lexer.h"
#include "parser.h"
#include "evaluator.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
static void test_basic_evaluation(void** state) {
(void) state;
char expr[256] = "2 + 4 * 40 / 2";
ASTNodeArray context;
tokenize(expr, &context);
ParseResult result = parse(&context);
int64_t value = evaluate(result);
assert_int_equal(value, 82);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_basic_evaluation),
};
cmocka_run_group_tests(tests, NULL, NULL);
return EXIT_SUCCESS;
}

112
test/test_lexer.c Normal file
View File

@@ -0,0 +1,112 @@
#include "lexer.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
static void test_tokenize_normal_expresion(void **state) {
(void) state;
char expr[256] = "2 + 3 / 66 * 789";
ASTNodeArray tokens;
ASTNode node;
assert_int_equal(tokenize(expr, &tokens), LEXER_OK);
assert_int_equal(tokens.len, 7);
ASTNodeArray_get(&tokens, 0, &node);
assert_int_equal(node.type, NODE_INTEGER);
assert_int_equal(node.data.integer, 2);
ASTNodeArray_get(&tokens, 1, &node);
assert_int_equal(node.type, NODE_BINARY_OP);
assert_int_equal(node.data.binary.op, OP_ADD);
ASTNodeArray_get(&tokens, 2, &node);
assert_int_equal(node.type, NODE_INTEGER);
assert_int_equal(node.data.integer, 3);
ASTNodeArray_get(&tokens, 3, &node);
assert_int_equal(node.type, NODE_BINARY_OP);
assert_int_equal(node.data.binary.op, OP_DIV);
ASTNodeArray_get(&tokens, 4, &node);
assert_int_equal(node.type, NODE_INTEGER);
assert_int_equal(node.data.integer, 66);
ASTNodeArray_get(&tokens, 5, &node);
assert_int_equal(node.type, NODE_BINARY_OP);
assert_int_equal(node.data.binary.op, OP_MUL);
ASTNodeArray_get(&tokens, 6, &node);
assert_int_equal(node.type, NODE_INTEGER);
assert_int_equal(node.data.integer, 789);
}
static void test_tokenize_unrecognized_symbol(void **state) {
(void) state;
char expr[256] = " 2 j 3 / 66 } 789";
ASTNodeArray tokens = {
.len = 0,
.cap = 0,
};
assert_int_equal(tokenize(expr, &tokens), LEXER_NOT_RECOGNIZED_SYMBOL);
assert_int_equal(tokens.len, 0);
assert_int_equal(tokens.cap, 0);
}
static void test_tokenize_wrong_sintax(void **state) {
(void) state;
char expr[256] = "2 3 / 66 789";
ASTNodeArray tokens = {
.len = 0,
.cap = 0,
};
assert_int_equal(tokenize(expr, &tokens), LEXER_WRONG_SYNTAX);
assert_int_equal(tokens.len, 0);
assert_int_equal(tokens.cap, 0);
}
static void test_string_to_number_normal(void **state) {
(void) state;
char num[16] = "2333t55";
size_t offset = 0;
ASTNode result;
assert_int_equal(tokenize_number(num, &offset, &result), LEXER_OK);
assert_int_equal(offset, 4); // equal to t position in string
assert_int_equal(result.type, NODE_INTEGER);
assert_int_equal(result.data.integer, 2333);
}
static void test_string_to_number_overflow(void **state) {
(void) state;
// Number is INT64_MAX but with a extra 8 at the end
char num[32] = "92233720368547758078yy7";
size_t offset = 0;
ASTNode result;
assert_int_equal(tokenize_number(num, &offset, &result), LEXER_INT_OVERFLOW);
// Technically it can trigger a buf overflow error but obvioulsy
// it will trigger int overflow error first
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_string_to_number_normal),
cmocka_unit_test(test_string_to_number_overflow),
cmocka_unit_test(test_tokenize_normal_expresion),
cmocka_unit_test(test_tokenize_unrecognized_symbol),
cmocka_unit_test(test_tokenize_wrong_sintax),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View File

@@ -1,5 +1,81 @@
#include <stdlib.h> #include "arena.h"
#include "lexer.h"
#include "parser.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
int main() { static void test_parsing_basic_expression(void **state) {
return EXIT_SUCCESS; (void) state;
char expr[256] = "2 + 3 / 66 * 789";
ASTNodeArray tokens;
ASTNode node;
assert_int_equal(tokenize(expr, &tokens), LEXER_OK);
assert_int_equal(tokens.len, 7);
ParseResult result = parse(&tokens);
// Assert head is +
assert_int_equal(result.tree->type, NODE_BINARY_OP);
assert_int_equal(result.tree->data.binary.op, OP_ADD);
assert_int_equal(result.tree->data.binary.left->type, NODE_INTEGER);
assert_int_equal(result.tree->data.binary.left->data.integer, 2);
assert_int_equal(
result.tree->data.binary.right->type,
NODE_BINARY_OP
);
assert_int_equal(
result.tree->data.binary.right->data.binary.op,
OP_MUL
);
assert_int_equal(
result.tree->data.binary.right->data.binary.right->type,
NODE_INTEGER);
assert_int_equal(
result.tree->data.binary.right->data.binary.right->data.integer,
789);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->type,
NODE_BINARY_OP
);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->data.binary.op,
OP_DIV
);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->data.binary.right->type,
NODE_INTEGER
);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->data.binary.right->data.integer,
66
);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->data.binary.left->type,
NODE_INTEGER
);
assert_int_equal(
result.tree->data.binary.right->data.binary.left->data.binary.left->data.integer,
3
);
arena_destroy(&result.arena);
}
int main(void) {
const struct CMUnitTest tests [] = {
cmocka_unit_test(test_parsing_basic_expression),
};
return cmocka_run_group_tests(tests, NULL, NULL);
} }