cOMS/math/Evaluator.h
Dennis Eichhorn eb9a135ca7
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (autobuild, c-cpp) (push) Has been cancelled
Microsoft C++ Code Analysis / Analyze (push) Has been cancelled
fixing bugs and adding some test scripts
2025-04-27 20:10:58 +00:00

333 lines
9.7 KiB
C
Executable File

/**
* Jingga
*
* @copyright Jingga
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
#ifndef COMS_MATH_EVALUATOR_H
#define COMS_MATH_EVALUATOR_H
#include "../stdlib/Types.h"
#include "../utils/StringUtils.h"
#include "../utils/TestUtils.h"
#include "../compiler/CompilerUtils.h"
#include <string.h>
#include <math.h>
#define EVALUATOR_MAX_STACK_SIZE 16
// Stack for operators
struct EvaluatorOperatorStack {
char items[EVALUATOR_MAX_STACK_SIZE];
int32 top;
};
// Stack for values
struct EvaluatorValueStack {
f32 items[EVALUATOR_MAX_STACK_SIZE];
int32 top;
};
struct EvaluatorVariable {
char name[8];
f32 value;
};
// Stack operations
void evaluator_push_operator(EvaluatorOperatorStack* stack, char op) {
if (stack->top >= EVALUATOR_MAX_STACK_SIZE - 1) {
return;
}
stack->items[++stack->top] = op;
}
char evaluator_pop_operator(EvaluatorOperatorStack* stack) {
ASSERT_SIMPLE(stack->top >= 0);
return stack->items[stack->top--];
}
char evaluator_peek_operator(EvaluatorOperatorStack* stack) {
if (stack->top < 0) {
return '\0';
}
return stack->items[stack->top];
}
void evaluator_push_value(EvaluatorValueStack* stack, f32 value) {
if (stack->top >= EVALUATOR_MAX_STACK_SIZE - 1) {
return;
}
stack->items[++stack->top] = value;
}
f32 evaluator_pop_value(EvaluatorValueStack* stack) {
ASSERT_SIMPLE(stack->top >= 0);
return stack->items[stack->top--];
}
// Precedence of operators
int32 evaluator_precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case 'u': // Unary minus
return 3;
default:
return 0;
}
}
// Apply an operator to one or two values
f32 evaluator_apply_operator(char op, f32 a, f32 b) {
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
case 'u': // Unary minus
return -a;
default: {
UNREACHABLE();
}
}
}
f32 evaluator_evaluate_function(const char* name, const char* args);
// Shunting-yard algorithm to evaluate the expression
f32 evaluator_evaluate_expression(const char* expr) {
EvaluatorOperatorStack operators = { .items = {}, .top = -1 };
EvaluatorValueStack values = { .items = {}, .top = -1 };
const char* ptr = expr;
while (*ptr) {
if (str_is_num(*ptr) || *ptr == '.') {
// Parse number
const char* end;
f32 value = str_to_float(ptr, &end);
evaluator_push_value(&values, value);
ptr = end;
} else if (str_is_alpha(*ptr)) {
// Parse function
const char* start = ptr;
while (str_is_alphanum(*ptr)) {
++ptr;
}
ASSERT_SIMPLE(*ptr == '(');
++ptr;
const char* args_start = ptr;
int32 depth = 1;
while (*ptr && depth > 0) {
if (*ptr == '(') {
++depth;
} else if (*ptr == ')') {
--depth;
}
++ptr;
}
ASSERT_SIMPLE(depth == 0);
char name[32];
memcpy(name, start, ptr - start);
name[ptr - start] = '\0';
char args[128];
memcpy(args, args_start, ptr - args_start - 1);
args[ptr - args_start - 1] = '\0';
f32 result = evaluator_evaluate_function(name, args);
evaluator_push_value(&values, result);
} else if (*ptr == '(') {
// Open parenthesis
evaluator_push_operator(&operators, *ptr);
++ptr;
} else if (*ptr == ')') {
// Close parenthesis
while (evaluator_peek_operator(&operators) != '(') {
char op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
if (op == 'u') {
evaluator_push_value(&values, evaluator_apply_operator(op, b, 0));
} else {
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(op, a, b));
}
}
evaluator_pop_operator(&operators); // Remove '('
++ptr;
} else if (str_find("+-*/", *ptr)) {
// Operator
char op = *ptr;
// Check if the operator is unary
if (op == '-' && (ptr == expr || *(ptr - 1) == '(' || str_find("+-*/", *(ptr - 1)))) {
op = 'u'; // Unary minus
}
while (evaluator_precedence(evaluator_peek_operator(&operators)) >= evaluator_precedence(op)) {
char top_op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
if (top_op == 'u') {
evaluator_push_value(&values, evaluator_apply_operator(top_op, b, 0));
} else {
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(top_op, a, b));
}
}
evaluator_push_operator(&operators, op);
++ptr;
} else {
UNREACHABLE();
}
}
// Process remaining operators
while (operators.top >= 0) {
char op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
if (op == 'u') {
evaluator_push_value(&values, evaluator_apply_operator(op, b, 0));
} else {
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(op, a, b));
}
}
return evaluator_pop_value(&values);
}
// Evaluate built-in functions
f32 evaluator_evaluate_function(const char* name, const char* args) {
if (str_compare(name, "min") == 0) {
const char* comma = str_find(args, ',');
if (!comma) {
return 0.0; // Invalid function call (min requires at least two arguments)
}
// Split the arguments into two expressions
char arg1[64], arg2[64];
memcpy(arg1, args, comma - args);
arg1[comma - args] = '\0';
str_copy_short(arg2, comma + 1);
// Recursively evaluate the arguments
f32 val1 = evaluator_evaluate_expression(arg1);
f32 val2 = evaluator_evaluate_expression(arg2);
return (val1 < val2) ? val1 : val2; // Return the minimum value
} else if (str_compare(name, "max") == 0) {
const char* comma = str_find(args, ',');
if (!comma) {
return 0.0; // Invalid function call (max requires at least two arguments)
}
// Split the arguments into two expressions
char arg1[64], arg2[64];
memcpy(arg1, args, comma - args);
arg1[comma - args] = '\0';
str_copy_short(arg2, comma + 1);
// Recursively evaluate the arguments
f32 val1 = evaluator_evaluate_expression(arg1);
f32 val2 = evaluator_evaluate_expression(arg2);
return (val1 > val2) ? val1 : val2; // Return the maximum value
} else if (str_compare(name, "sqrt") == 0) {
// Evaluate the single argument
f32 val = evaluator_evaluate_expression(args);
return sqrtf(val); // Return the square root
} else if (str_compare(name, "abs") == 0) {
// Evaluate the single argument
f32 val = evaluator_evaluate_expression(args);
return OMS_ABS(val); // Return the absolute value
}
// Handle unknown functions (you can add more functions as needed)
return 0.0;
}
f32 evaluator_evaluate(const char* expr, int32 variable_count = 0, const EvaluatorVariable* variables = NULL) {
ASSERT_SIMPLE(str_length(expr) > 0);
// Handle variables
char expr_internal[1024];
char* dest = expr_internal;
if (variable_count) {
while (*expr != '\0') {
// Skip none-alpha values
while (!str_is_alpha(*expr) && *expr != '\0') {
if (*expr != ' ') {
*dest++ = *expr;
}
++expr;
}
if (*expr == '\0') {
break;
}
// Potential variable name
bool found_variable = false;
for (int32 i = 0; i < variable_count; ++i) {
size_t len = str_length(variables[i].name);
// Check if string is variable (must be followed by none-alpha value = string mustn't continue)
if (str_compare(expr, variables[i].name, len) == 0
&& !str_is_alphanum(expr[len]) && expr[len] != '('
) {
// Replace variable with value
dest += float_to_str(variables[i].value, dest, 4);
expr += len;
found_variable = true;
break;
}
}
if (!found_variable) {
// If no variable is found we must skip the entire alpha string,
// otherwise a substring may match a variable name later on
while (str_is_alphanum(*expr)) {
*dest++ = *expr++;
}
}
}
}
// Copy remaining / or entire string over to internal expression
// We also ignore all whitespaces here for simplified handling later on
while (*expr != '\0') {
if (*expr != ' ') {
*dest++ = *expr;
}
++expr;
}
*dest = '\0';
return evaluator_evaluate_expression(expr_internal);
}
#endif