From 8fefe9797505db4df62901cadc0ad0c5923cab3d Mon Sep 17 00:00:00 2001 From: Abner Coimbre Date: Sun, 15 Mar 2026 10:55:21 -0700 Subject: [PATCH] Add tc_calc library --- tc_calc.c | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tc_calc.h | 63 ++++++++++ 2 files changed, 411 insertions(+) create mode 100644 tc_calc.c create mode 100644 tc_calc.h diff --git a/tc_calc.c b/tc_calc.c new file mode 100644 index 0000000..fe7e9d3 --- /dev/null +++ b/tc_calc.c @@ -0,0 +1,348 @@ +/* + * ============================================================== + * + * GLOBALS + * + * =============================================================== + */ + +global char calc_token; +global char *expression; + +/* + * ============================================================== + * + * PUBLIC API IMPLEMENTATION + * + * =============================================================== + */ + +function __attribute__((cold)) char* calc_format_result(double value, calc_FormatKind format) +{ + static char buffer[TC_CALC_FORMAT_BUFFER_SIZE] = {0}; // Enough for 64-bit binary + '0b' prefix + null + long long int_val = cast(long long) value; + + switch (format) { + case CALC_FORMAT_HEX: + snprintf(buffer, sizeof buffer, "0x%llX", int_val); + break; + + case CALC_FORMAT_BINARY: + { + char *ptr = buffer; + *ptr++ = '0'; + *ptr++ = 'b'; + + /* find highest set bit */ + int msb = 63; + while (msb > 0 && !(int_val & (1LL << msb))) + msb--; + + /* convert to binary string */ + for (int i = msb; i >= 0; i--) + *ptr++ = (int_val & (1LL << i)) ? '1' : '0'; + *ptr = '\0'; + } + break; + + case CALC_FORMAT_DECIMAL: + /* handle both integer and floating-point cases */ + if (value == (double)(long long)value) + snprintf(buffer, sizeof(buffer), "%lld", (long long)value); + else + snprintf(buffer, sizeof(buffer), "%g", value); + break; + + default: + assert(0); + break; + } + + return buffer; +} + +function __attribute__((cold)) double calc_eval_formatted(char *expr, calc_FormatKind format, char *result_str) +{ + double result = calc_eval(expr); + + if (!isnan(result) && result_str != NULL) + strcpy(result_str, calc_format_result(result, format)); // @robustness @abner + + return result; +} + +function __attribute__((cold)) double calc_eval(char *expr) +{ + assert(expr); + assert(strlen(expr) > 0); + + expression = expr; + + /* 1st character for look-ahead */ + calc_token = *expression++; + + return calc_bitwise_or(); +} + +function __attribute__((cold)) int calc_match(char expected) +{ + if (calc_token == expected) + calc_token = *expression++; + else + return -1; + + return 0; +} + +function __attribute__((cold)) double calc_expr(void) +{ + double temp; + if (isnan(temp = calc_term())) + return NAN; + + while ((calc_token == '+') || (calc_token == '-')) { + switch (calc_token) + { + case '+': + if (calc_match('+') == -1) + return NAN; + + { + double t; + if (isnan(t = calc_term())) + return NAN; + temp += t; + } + + break; + + case '-': + if (calc_match('-') == -1) + return NAN; + + { + double t; + if (isnan(t = calc_term())) + return NAN; + temp -= t; + } + + break; + } + } + + return temp; +} + +function __attribute__((cold)) double calc_term(void) +{ + double temp; + if (isnan(temp = calc_exponent())) + return NAN; + + while (calc_token == '*' || calc_token == '/' || calc_token == '%') { + char op = calc_token; + + if (calc_match(op) == -1) + return NAN; + + double t; + if (isnan(t = calc_exponent())) + return NAN; + + if (op == '*') + temp *= t; + else if (op == '/') + temp /= t; + else if (op == '%') + temp = cast(int) temp % cast(int)t; + } + + return temp; +} + +function __attribute__((cold)) double calc_exponent(void) +{ + double base = calc_factor(); + if (isnan(base)) + return NAN; + + while (calc_token == '*') { + /* look ahead one char without consuming */ + if (expression[0] != '*') + break; // Not a power operator, just multiplication + + /* consume both '*' characters */ + if (calc_match('*') == -1) + return NAN; + if (calc_match('*') == -1) + return NAN; + + double exponent = calc_exponent(); // right-associative + if (isnan(exponent)) + return NAN; + + base = pow(base, exponent); + } + + return base; +} + +function __attribute__((cold)) double calc_factor(void) +{ + double temp = 0; + + if (calc_token == '-') { + if (calc_match('-') == -1) + return NAN; + double t; + if (isnan(t = calc_factor())) + return NAN; + return -t; + } + + if (calc_token == '(') { + if (calc_match('(') == -1) + return NAN; + + double t; + if (isnan(t = calc_bitwise_or())) + return NAN; + temp = t; + + if (calc_match(')') == -1) + return NAN; + } else if (calc_token == '0') { + /* check for hex or binary prefixes */ + if (expression[0] == 'x' || expression[0] == 'X') { + char x = expression[0]; + + /* handle hex numbers */ + if (calc_match('0') == -1) + return NAN; + if (calc_match(x) == -1) // match 'x' or 'X' + return NAN; + + /* verify we have at least one hex digit */ + if (!isxdigit(calc_token)) + return NAN; + + char *start = expression - 1; + temp = cast(double) strtoll(start, &expression, 16); + calc_token = *expression++; + } else if (expression[0] == 'b' || expression[0] == 'B') { + char b = expression[0]; + + /* handle binary numbers */ + if (calc_match('0') == -1) + return NAN; + if (calc_match(b) == -1) // match 'b' or 'B' + return NAN; + + /* verify we have at least one binary digit */ + if (expression[0] != '0' && expression[0] != '1') + return NAN; + + char *start = expression - 1; + temp = cast(double) strtoll(start, &expression, 2); + calc_token = *expression++; + } else { + return NAN; + } + } else if (isdigit(calc_token) || calc_token == '.') { + char *start = expression - 1; + temp = strtod(start, &expression); + calc_token = *expression++; + } else { + return NAN; + } + + return temp; +} + +function __attribute__((cold)) double calc_bitwise_or(void) +{ + double temp = 0; + + if (isnan(temp = calc_bitwise_xor())) + return NAN; + + while (calc_token == '|') { + if (calc_match('|') == -1) + return NAN; + + double t = 0; + if (isnan(t = calc_bitwise_xor())) + return NAN; + temp = (double)((long long)temp | (long long)t); + } + + return temp; +} + +function __attribute__((cold)) double calc_bitwise_xor(void) +{ + double temp = 0; + if (isnan(temp = calc_bitwise_and())) + return NAN; + + while (calc_token == '^') { + if (calc_match('^') == -1) + return NAN; + + double t = 0; + if (isnan(t = calc_bitwise_and())) + return NAN; + temp = (double)((long long)temp ^ (long long)t); + } + + return temp; +} + +function __attribute__((cold)) double calc_bitwise_and(void) +{ + double temp; + if (isnan(temp = calc_shift())) + return NAN; + + while (calc_token == '&') { + if (calc_match('&') == -1) + return NAN; + + double t; + if (isnan(t = calc_shift())) + return NAN; + temp = (double)((long long)temp & (long long)t); + } + + return temp; +} + +function __attribute__((cold)) double calc_shift(void) +{ + double temp; + if (isnan(temp = calc_expr())) + return NAN; + + while (calc_token == '<' || calc_token == '>') { + char first = calc_token; + if (calc_match(first) == -1) + return NAN; + + // Check for second < or > + if (calc_token != first) + return NAN; + if (calc_match(first) == -1) + return NAN; + + double t; + if (isnan(t = calc_expr())) + return NAN; + + if (first == '<') + temp = (double)((long long)temp << (long long)t); + else + temp = (double)((long long)temp >> (long long)t); + } + return temp; +} diff --git a/tc_calc.h b/tc_calc.h new file mode 100644 index 0000000..5fc0654 --- /dev/null +++ b/tc_calc.h @@ -0,0 +1,63 @@ +/* +** Copyright (c) Terminal Click - All rights Reserved +** +** @author: Abner Coimbre +**/ + +#ifndef CALC_H +#define CALC_H + +/* + * ============================================================== + * + * DATA TYPES + * + * =============================================================== + */ + +typedef enum { + CALC_FORMAT_DECIMAL, + CALC_FORMAT_HEX, + CALC_FORMAT_BINARY, + CALC_FORMAT_MAX, +} calc_FormatKind; + +/* + * ============================================================== + * + * PUBLIC ROUTINES + * + * =============================================================== + */ + +/* + * NOTE: The gcc/clang cold attribute below is specifically designed for functions that: + * + * 1. Are unlikely to be executed during normal program flow + * 2. Should be optimized for size rather than speed + * 3. Handle uncommon conditions or paths in the code + * + * Our calculator routines fit this profile because: + * + * - They're only called when users explicitly invoke the calculator feature + * - In a terminal emulator, most operations would be text display/input rather than calculations + * - The calculations only happen after a specific user action (pressing Enter) + */ + +function __attribute__((cold)) char* calc_format_result(double value, calc_FormatKind format); +function __attribute__((cold)) double calc_eval_formatted(char *expr, calc_FormatKind format, char *result_str); +function __attribute__((cold)) double calc_eval(char *expr); + +function __attribute__((cold)) int calc_match(char expected); + +function __attribute__((cold)) double calc_expr(void); +function __attribute__((cold)) double calc_term(void); +function __attribute__((cold)) double calc_exponent(void); +function __attribute__((cold)) double calc_factor(void); + +function __attribute__((cold)) double calc_bitwise_or(void); +function __attribute__((cold)) double calc_bitwise_xor(void); +function __attribute__((cold)) double calc_bitwise_and(void); +function __attribute__((cold)) double calc_shift(void); + +#endif /* CALC_H */