Compare commits

..

2 Commits

Author SHA1 Message Date
Abner Coimbre f937af9cbe Add main.c for a driver demo 2026-03-15 10:56:38 -07:00
Abner Coimbre 8fefe97975 Add tc_calc library 2026-03-15 10:55:21 -07:00
3 changed files with 483 additions and 0 deletions

72
main.c Normal file
View File

@ -0,0 +1,72 @@
/*
* Simple REPL for the calc evaluator.
* Build: calc main.c -o calc -lm
* @author Abner Coimbre <abner@terminal.click>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <ctype.h>
/* Macros expected by tc_calc.h / tc_calc.c */
#define function static
#define global static
#define cast(T) (T)
#ifndef TC_CALC_FORMAT_BUFFER_SIZE
#define TC_CALC_FORMAT_BUFFER_SIZE 256
#endif
#include "tc_calc.h"
#include "tc_calc.c"
#define INPUT_MAX 1024
int main(void)
{
char buf[INPUT_MAX];
calc_FormatKind fmt = CALC_FORMAT_DECIMAL;
printf("calc> ");
fflush(stdout);
while (fgets(buf, sizeof buf, stdin)) {
/* strip trailing newline */
buf[strcspn(buf, "\n")] = '\0';
/* skip blank lines */
if (buf[0] == '\0') {
printf("calc> ");
fflush(stdout);
continue;
}
/* format switching commands */
if (strcmp(buf, ":dec") == 0) {
fmt = CALC_FORMAT_DECIMAL;
printf(" format: decimal\n");
} else if (strcmp(buf, ":hex") == 0) {
fmt = CALC_FORMAT_HEX;
printf(" format: hex\n");
} else if (strcmp(buf, ":bin") == 0) {
fmt = CALC_FORMAT_BINARY;
printf(" format: binary\n");
} else {
double result = calc_eval(buf);
if (isnan(result))
printf(" error: invalid expression\n");
else
printf(" %s\n", calc_format_result(result, fmt));
}
printf("calc> ");
fflush(stdout);
}
putchar('\n');
return 0;
}

348
tc_calc.c Normal file
View File

@ -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;
}

63
tc_calc.h Normal file
View File

@ -0,0 +1,63 @@
/*
** Copyright (c) Terminal Click - All rights Reserved
**
** @author: Abner Coimbre <abner@terminal.click>
**/
#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 */