Compare commits
No commits in common. "f937af9cbe63a91524fbfc6923028987690707a3" and "06011829e656a41e704bb472a4af5473b21da213" have entirely different histories.
f937af9cbe
...
06011829e6
72
main.c
72
main.c
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* 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
348
tc_calc.c
|
|
@ -1,348 +0,0 @@
|
|||
/*
|
||||
* ==============================================================
|
||||
*
|
||||
* 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
63
tc_calc.h
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
** 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 */
|
||||
Loading…
Reference in New Issue