Compare commits

57 Commits

Author SHA1 Message Date
964034b203 fix: updating arena and arraylist use 2026-05-14 08:36:21 -06:00
9ea1da549f Merge pull request 'refactor-error-handling' (#12) from refactor-error-handling into main
Reviewed-on: #12
2026-05-13 19:05:54 -06:00
ab791dbc9b fix: tests and main 2026-05-13 18:48:14 -06:00
3ec73559ee refactor: evaluator separated into evaluate bin and un
Is nicer this way, also made it more beautiful to look at and therefor
to understand.
2026-05-13 18:06:01 -06:00
b56a368244 refactor: bp funtions take tokens now
necessary for cleannes
2026-05-13 17:48:03 -06:00
6294121e91 refactor: nud and led have differetn responasblires
So now nud and led do what they were supposed to do i guess, now i
thinks is just adjusting infix and postfix and all bd funcions to act on
operator instead.
2026-05-13 17:35:52 -06:00
70ab06964c addtition: nud and led token to node distinction
I think i should instead have a nud and led function i guess, may do
that next
2026-05-13 16:09:03 -06:00
90c426f3a4 refactor: moved NodeResult to TreeResult 2026-05-13 12:19:17 -06:00
efa0e3bacd refactor: evaluator incomplete.
SO, i forgot to implement nud and led correctly and the parser cant tell
apart from - as unary and - as binary (+ as well), i need to correct
that, move Node * to TreeResult so to use NodeResult with nud and led
2026-05-13 12:13:07 -06:00
542a94ef81 refactor: All of parser.c
DAMN, it wasn't that difficult, just bothers me a bit the part that
checks if both lbp and rbp of the infix are valid, like i do validation
twice but is fine i guess, maybe using an else?, i'll see if i change
it, for now i need to change the evaluator
2026-05-13 11:09:22 -06:00
80e05a9acf refactor: changed parser.h, added Node
So just added node back but now clearly separated by tokens and nodes of
the AST as it should be, now real rework the mess that is the parser
2026-05-13 10:02:55 -06:00
f3373123e1 refactor: adapted lexer to work with new tokens
Now its fine, the code i find it clear if one just sits down to read it
for a moment, next is the parser that REALLY needs reworking and a few
helper structs.
2026-05-13 09:49:28 -06:00
2a73f5f9d6 refactor: delete ASTNode, add Token to lexer
So, total refactor, now we serious. I feel ASTNode was feeling very
bloated so we need to rewrite and adapt everything, by now lets get the
lexer working again, is already well written for me at least.
2026-05-13 09:37:15 -06:00
e3d64596ab Merge pull request 'refactor-lexer' (#11) from refactor-lexer into main
Reviewed-on: #11
2026-05-12 20:08:39 -06:00
56c80fa071 addition: Managing of parenthesis
Its a fucking mess, i was writting straight bullshit but it conceptually
should work, just need to refactor the shit out of it to make it way
more clean than it actually is and also later fix the fucking evaluator
like damn it sucks ASSS now (not that much really is nice but obviously
doesn't work, i like my code a lot :)
2026-05-12 20:04:41 -06:00
7f390a8c6b addition: postfix operator capability, may work 2026-05-12 19:40:42 -06:00
e30b3d7175 addition: proccessing of prefix op 2026-05-12 18:33:52 -06:00
59f99059bb refactor: changes and additions ot parser 2026-05-12 18:15:36 -06:00
c41847e120 refactor: rewrote tokenize and modified ohter funcs
Well i wanted to wildly change a lot of things about the lexer thinking
i could do something better but really all i found was automatic lexers
that at least for me don't really fit the project so a manual one it is,
i guess technically is a automata. Whatever, is good enough.
2026-04-30 21:34:27 -06:00
f2c906c6aa initial-commit 2026-04-30 10:40:17 -06:00
fee33ff1f0 Merge pull request 'refactor-generic-array' (#9) from refactor-generic-array into main
Reviewed-on: #9
all gut
2026-04-30 10:05:21 -06:00
ac2e783ccc fix: tests and implementation of lexer
Just a few details here and there, nothing wrong, everything else is
going well.
2026-04-30 09:58:27 -06:00
630d9f53e1 test: changed lexer tests 2026-04-24 09:36:03 -06:00
b7e1cdf3a6 refactor: made parser work with arrayslices and new result types 2026-04-24 09:06:47 -06:00
cef046f7db refactor: changed string to int adn tokenize number 2026-04-24 08:09:31 -06:00
19c84c382b refactor: changed funtions definitions, modified tokenize 2026-04-24 07:17:35 -06:00
855d683005 addition: Resul structs for rework 2026-04-24 07:02:00 -06:00
576bcd9504 addition: starting to refactor NodeArray into ArrayList 2026-04-23 15:37:16 -06:00
e6420cb1c9 add/fix: Added arena implementation and cmake rework 2026-04-23 12:39:04 -06:00
f50546bd07 Merge pull request 'feature-AST-using-arena' (#8) from feature-AST-using-arena into main
Reviewed-on: #8
2026-04-23 12:34:30 -06:00
c99f307827 Merge pull request 'feature-AST-using-arena' (#5) from feature-AST-using-arena into main
Reviewed-on: #5
2026-04-13 08:58:47 -06:00
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
15 changed files with 990 additions and 12 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ out/Release/
# Cmake files # Cmake files
CMakeCache.txt CMakeCache.txt
cmake
CMakeFiles/ CMakeFiles/
cmake_install.cmake cmake_install.cmake
CTestTestfile.cmake CTestTestfile.cmake

View File

@@ -4,7 +4,7 @@ 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)
# Export compile_commands.json (para clangd) # clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options( add_compile_options(
@@ -13,24 +13,38 @@ add_compile_options(
-Wpedantic -Wpedantic
) )
include(cmake/CPM.cmake)
include_directories(include) 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
)
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
) )
target_include_directories(calculator_lib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(calculator_lib
PUBLIC arena
PUBLIC arraylist
PRIVATE m
)
add_executable(calculator src/main.c) add_executable(calculator src/main.c)
target_link_libraries(calculator calculator_lib) target_link_libraries(calculator calculator_lib)
# ------------------------
# Testing
# ------------------------
enable_testing() enable_testing()
add_subdirectory(test) add_subdirectory(test)

View File

View File

@@ -0,0 +1,29 @@
#ifndef EVALUATOR_H
#define EVALUATOR_H
#include "parser.h"
#include <stdint.h>
typedef enum {
EVALUATOR_OK,
EVALUATOR_MATH_ERR,
EVALUATOR_DIVISION_BY_ZERO,
EVALUATOR_INVALID_PARSING,
EVALUATOR_INVALID_TREE, // just to shut up the compiler with the swithces
} EvaluatorErr;
typedef struct {
bool is_valid;
union {
int64_t val;
EvaluatorErr err;
};
} EvaluatorResult;
EvaluatorResult evaluate_binary(Node *tree);
EvaluatorResult evaluate_unary(Node *tree);
EvaluatorResult evaluate(ParserResult context);
EvaluatorResult evaluate_tree(Node *tree);
#endif // !EVALUATOR_H

View File

@@ -0,0 +1,76 @@
#ifndef LEXER_H
#define LEXER_H
#include "arraylist.h"
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
// For identifing
typedef enum {
TOKEN_INTEGER,
TOKEN_OPERATOR,
} TokenType;
// For classify operators
typedef enum {
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV,
OP_POW,
OP_FACTORIAL,
OP_START_PAR,
OP_END_PAR,
} Operator;
typedef enum {
LEXER_OK = 0,
LEXER_INT_OVERFLOW,
LEXER_FAILED_NUMBER_CONVERSION,
LEXER_NOT_RECOGNIZED_SYMBOL,
LEXER_EMPTY_INPUT,
LEXER_BUF_OVERFLOW,
} LexerErr;
// Can be thought as tokens, they will be used by the parser.
typedef struct {
TokenType type;
union {
int64_t num;
Operator op;
};
} Token;
typedef struct {
bool is_valid;
union {
LexerErr err;
ArrayList *arr;
};
} TokenizeResult;
typedef struct {
bool is_valid;
union {
LexerErr err;
Token token;
};
} TokenResult;
typedef struct {
bool is_valid;
union {
LexerErr err;
int64_t num;
};
} LexerI64Result;
// Lexer funtions as well as few functionality
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);
char operator_to_char(Operator op);
#endif // !LEXER_H

View File

@@ -0,0 +1,89 @@
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h"
#include "arena.h"
#include "arraylist.h"
#include <stdbool.h>
#include <stdint.h>
typedef enum {
NODE_INT,
NODE_BINARY_OP,
NODE_UNARY_OP,
} NodeType;
typedef struct Node {
NodeType type;
union {
int64_t num;
struct {
Operator op;
struct Node *left;
struct Node *right;
}binary;
struct {
Operator op;
struct Node *to;
}unary;
Operator par;
};
} Node;
typedef enum {
PARSER_OK = 0,
PARSER_UNEXPECTED_TOKEN,
PARSER_MISSING_OPERAND,
PARSER_UNMATCHED_PAREN,
PARSER_OUT_OF_MEMORY,
PARSER_INVALID_TOKENIZE,
PARSER_UNEXPECTED_EOF,
} ParserErr;
typedef struct {
bool is_valid;
union {
ParserErr err;
struct {
Arena *arena;
Node *tree;
};
};
} ParserResult;
typedef struct {
bool is_valid;
union {
ParserErr err;
Node *node;
};
} TreeResult;
typedef struct {
bool is_valid;
union {
ParserErr err;
Node node;
};
} NodeResult;
typedef struct {
bool is_valid;
union {
ParserErr err;
uint8_t num;
};
} ParserU8Result;
TreeResult nud(ArraySlice *slice, Arena *arena, Token token); // Null denotation
TreeResult led(ArraySlice *slice, Arena *arena, Node *left, Token token); // Left denotation
ParserU8Result prefix_rbp(Token token);
ParserU8Result postfix_lbp(Token token);
ParserU8Result infix_lbp(Token token);
ParserU8Result infix_rbp(Token token);
ParserResult parse(TokenizeResult tokens);
TreeResult parse_expr(ArraySlice *slice, Arena *arena, uint8_t min_bp);
#endif // !PARSER_H

View File

View File

@@ -0,0 +1,115 @@
#include "evaluator.h"
#include "arena.h"
#include "lexer.h"
#include "parser.h"
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
EvaluatorResult evaluate_tree(Node *tree) {
if (tree->type == NODE_BINARY_OP) {
return evaluate_binary(tree);
} else if (tree->type == NODE_UNARY_OP) {
return evaluate_unary(tree);
}
return (EvaluatorResult) {
.is_valid = true,
.val = tree->num,
};
}
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) {
return left_result;
}
if (!left_result.is_valid) {
return left_result;
}
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,
};
}
}
EvaluatorResult evaluate_unary(Node *tree) {
Operator op = tree->unary.op;
Node *to = tree->unary.to;
EvaluatorResult result = evaluate_tree(to);
if (!result.is_valid) {
return result;
}
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,
};
}
}
EvaluatorResult evaluate(ParserResult context) {
if (!context.is_valid) {
return (EvaluatorResult) {
.is_valid = false,
.err = EVALUATOR_INVALID_PARSING,
};
}
EvaluatorResult result = evaluate_tree(context.tree);
arena_destroy(&context.arena);
return result;
}

View File

@@ -0,0 +1,195 @@
#include "lexer.h"
#include "arraylist.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
typedef enum {
WAIT_FOR_NUMBER,
WAIT_FOR_OPERATOR
} LexerState;
TokenizeResult tokenize(const char *input) {
ArrayList *arr;
arraylist_init(&arr, 64, sizeof(Token));
size_t offset = 0;
while (input[offset] != '\0') {
if (isdigit(input[offset])) {
TokenResult result = tokenize_number(input, &offset);
if (!result.is_valid) {
arraylist_destroy(&arr);
return (TokenizeResult) {.is_valid = false, .err = result.err};
}
arraylist_push_back(arr, &result.token);
} else if (isoperator(input[offset])) {
Token op_node = {
.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};
}
offset++;
}
if (arraylist_size(arr) < 1) {
arraylist_destroy(&arr);
return (TokenizeResult) {.is_valid = false, .err = LEXER_EMPTY_INPUT};
}
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' };
size_t buf_pos = 0;
bool is_integer = true; // Will later be used to differentiate fractions
// read number
size_t current = *offset;
while (isdigit(input[current])) {
if (buf_pos >= sizeof(buf) - 1) {
return (TokenResult) {
.is_valid = false,
.err = LEXER_BUF_OVERFLOW};
}
buf[buf_pos] = input[current];
current++;
buf_pos++;
}
Token new_token;
if (is_integer) {
new_token.type = TOKEN_INTEGER;
LexerI64Result result = string_to_integer(buf);
if (!result.is_valid) {
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 = false,
.err = LEXER_FAILED_NUMBER_CONVERSION};
}
LexerI64Result string_to_integer(const char *buf) {
int c = 0;
int64_t count = 0;
while (buf[c] != '\0') {
// Extracts number from char
int digit = buf[c] - '0';
if (count > (INT64_MAX - digit) / 10) {
return (LexerI64Result) {
.is_valid = false,
.err = LEXER_INT_OVERFLOW};
}
count = count * 10;
count += digit;
c++;
}
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;
}
}
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;
}
}
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;
}
}

View File

@@ -1,7 +1,28 @@
#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';
EvaluatorResult result = evaluate(parse(tokenize(buf)));
if (!result.is_valid) {
puts("Error checando expresion");
}
printf("El resultado es: %" PRIi64 "\n", result.val);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -0,0 +1,394 @@
#include "parser.h"
#include "arraylist.h"
#include "lexer.h"
#include "arena.h"
#include <cmocka.h>
#include <stdalign.h>
#include <stdbool.h>
#include <stdint.h>
ParserU8Result prefix_rbp(Token token) {
if (token.type == TOKEN_INTEGER) {
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,
};
}
}
ParserU8Result postfix_lbp(Token token) {
if (token.type != TOKEN_OPERATOR) {
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,
};
}
}
ParserU8Result infix_lbp(Token token) {
if (token.type != TOKEN_OPERATOR) {
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,
};
}
}
ParserU8Result infix_rbp(Token token) {
if (token.type != TOKEN_OPERATOR) {
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,
};
}
}
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)
)
);
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;
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) {
.is_valid = false,
.err = PARSER_UNEXPECTED_TOKEN,
};
}
}
TreeResult nud(ArraySlice *slice, Arena *arena, Token token) {
arena_ensure_capacity(
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) {
.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,
};
}
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) {
.is_valid = false,
.err = PARSER_INVALID_TOKENIZE,
};
}
ArraySlice *context;
arraylist_slice(&context, tokens.arr, 0, arraylist_size(tokens.arr));
Arena *arena;
arena_init(&arena, 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) {
.is_valid = false,
.err = result.err,
};
}
arraylist_destroy(&tokens.arr);
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, &current_token) != ARRLIST_OK) {
return (TreeResult) {
.is_valid = false,
.err = PARSER_UNEXPECTED_EOF,
};
}
TreeResult left_result = nud(slice, arena, current_token);
if (!left_result.is_valid) {
return left_result;
}
Node *left_side = left_result.node;
while (arrayslice_is_valid(slice)) {
Token operator_token;
arrayslice_peek(slice, &operator_token);
if (operator_token.type != TOKEN_OPERATOR) {
break;
}
ParserU8Result postfix_lbp_result = postfix_lbp(operator_token);
if (postfix_lbp_result.is_valid) {
if (postfix_lbp_result.num < min_bp) {
break;
}
arrayslice_next(slice, NULL);
TreeResult result = led(slice, arena, left_side, operator_token);
if (!result.is_valid) {
return result;
}
left_side = result.node;
continue;
}
// Path for infix basically
ParserU8Result lbp_result = infix_lbp(operator_token);
if (!lbp_result.is_valid) {
break;
}
if (lbp_result.num < min_bp) {
break;
}
arrayslice_next(slice, NULL);
TreeResult result = led(slice, arena, left_side, operator_token);
if (!result.is_valid) {
return result;
}
left_side = result.node;
}
// Final: return left side
return (TreeResult){
.is_valid = true,
.node = left_side,
};
}

View File

@@ -1,10 +1,24 @@
find_package(cmocka REQUIRED) find_package(cmocka REQUIRED)
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_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 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)

13
test/test_evaluator.c Normal file
View File

@@ -0,0 +1,13 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
int main(void) {
return EXIT_SUCCESS;
}

11
test/test_lexer.c Normal file
View File

@@ -0,0 +1,11 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
int main(void) {
return EXIT_SUCCESS;
}

View File

@@ -1,5 +1,11 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h> #include <stdlib.h>
int main() { int main(void) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }