mirror of
https://github.com/Karaka-Management/cOMS.git
synced 2026-02-07 14:28:40 +00:00
288 lines
7.6 KiB
C
288 lines
7.6 KiB
C
/**
|
|
* Jingga
|
|
*
|
|
* @copyright Jingga
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
#ifndef TOS_MATH_EVALUATOR_H
|
|
#define TOS_MATH_EVALUATOR_H
|
|
|
|
#include "../stdlib/Types.h"
|
|
#include "../utils/StringUtils.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define EVALUATOR_MAX_STACK_SIZE 256
|
|
|
|
// 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[32];
|
|
f32 value;
|
|
};
|
|
|
|
static inline
|
|
const char* evaluator_skip_whitespace(const char* str) {
|
|
while (*str && (*str == ' ' || *str == '\t')) {
|
|
++str;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
// 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;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Apply an operator to 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;
|
|
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 = { .top = -1 };
|
|
EvaluatorValueStack values = { .top = -1 };
|
|
|
|
const char* ptr = expr;
|
|
while (*ptr) {
|
|
ptr = evaluator_skip_whitespace(ptr);
|
|
|
|
if (str_is_num(*ptr) || *ptr == '.') {
|
|
// Parse number
|
|
char* end;
|
|
f32 value = strtof(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);
|
|
f32 a = evaluator_pop_value(&values);
|
|
evaluator_push_value(&values, evaluator_apply_operator(op, a, b));
|
|
}
|
|
|
|
evaluator_pop_operator(&operators); // Remove '('
|
|
++ptr;
|
|
} else if (strchr("+-*/", *ptr)) {
|
|
// Operator
|
|
char op = *ptr;
|
|
while (evaluator_precedence(evaluator_peek_operator(&operators)) >= evaluator_precedence(op)) {
|
|
char top_op = evaluator_pop_operator(&operators);
|
|
f32 b = evaluator_pop_value(&values);
|
|
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);
|
|
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 = strchr(args, ',');
|
|
if (!comma) {
|
|
return 0.0;
|
|
}
|
|
|
|
char arg1[64], arg2[64];
|
|
memcpy(arg1, args, comma - args);
|
|
arg1[comma - args] = '\0';
|
|
str_copy_short(arg2, comma + 1);
|
|
|
|
f32 val1 = evaluator_evaluate_expression(arg1);
|
|
f32 val2 = evaluator_evaluate_expression(arg2);
|
|
|
|
return OMS_MIN(val1, val2);
|
|
} else if (str_compare(name, "max") == 0) {
|
|
const char* comma = strchr(args, ',');
|
|
if (!comma) {
|
|
return 0.0;
|
|
}
|
|
|
|
char arg1[64], arg2[64];
|
|
memcpy(arg1, args, comma - args);
|
|
arg1[comma - args] = '\0';
|
|
str_copy_short(arg2, comma + 1);
|
|
|
|
f32 val1 = evaluator_evaluate_expression(arg1);
|
|
f32 val2 = evaluator_evaluate_expression(arg2);
|
|
|
|
return OMS_MAX(val1, val2);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
f32 evaluator_evaluate(char* expr, int32 variable_count = 0, const EvaluatorVariable* variables = NULL) {
|
|
// Handle variables
|
|
const char* ptr = expr;
|
|
int32 available_variables = variable_count;
|
|
|
|
while (*ptr && available_variables) {
|
|
// Skip none-alpha values
|
|
while (!str_is_alpha(*ptr) && *ptr != '\0') {
|
|
++ptr;
|
|
}
|
|
|
|
if (*ptr == '\0') {
|
|
continue;
|
|
}
|
|
|
|
// Potential variable name
|
|
for (int32 i = 0; i < variable_count; ++i) {
|
|
size_t len = strlen(variables[i].name);
|
|
|
|
// Check if string is variable (must be followed by a whitespace or end of string)
|
|
if (str_compare(ptr, variables[i].name, len) == 0 && (ptr[len] == ' ' || ptr[len] == '\0')) {
|
|
// Remove variable
|
|
str_remove(expr, ptr - expr, len);
|
|
|
|
// Replace variable with value
|
|
char value[25];
|
|
int32 value_length = float_to_str(variables[i].value, value, 4);
|
|
str_insert(expr, ptr - expr, value);
|
|
|
|
--available_variables;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Move past string, this should work regardless of whether we found a variable or not
|
|
while (str_is_alpha(*ptr) && *ptr != '\0') {
|
|
++ptr;
|
|
}
|
|
}
|
|
|
|
// Evaluate math formula
|
|
return evaluator_evaluate_expression(expr);
|
|
}
|
|
|
|
#endif |