My program takes input from the user in two ways:
- By passing input as an argument whilst calling the command
- By taking any number of inputs after calling the command
The program is a terminal calculator that recognizes parentheses, operators, and errors as soon as you pass an expression. If you happen to come across an (non-internal) error, it will always return a suitable error message. Optionally, the user may pass a number alongside the expression to round the answer to a certain number of digits.
The program mainly centers around the manipulation of strings. As far as I know, there aren't any bugs or memory leaks. However, I've been coding for a few months, and I am in no way an expert. Because of my limited knowledge of C, I'm afraid the methods used in my program are unnecessarily complex. Constructive criticism of any kind is welcome.
Note: I used the
goto
keyword many times, mostly as a way to organize error handling. I know that use of such is frowned upon in most cases, but I thought this would be a good implementation of it.
main.c
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "eval.h"
bool CMD_LINE; // Using command-line interface?
void print_help(void);
int main(int argc, char *argv[]) {
char *expr = NULL, *swap = NULL;
size_t bufsize = 999, // Maximum input size
ndec; // Number of decimal places
double result;
CMD_LINE = true;
if (argc == 1) {
CMD_LINE = false;
goto interactive;
}
command_line:
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { // Interpret help flag if present
if (argc == 2)
print_help();
else
fail("Incorrect flag usage");
}
if ((expr = simplify(argv[1], 0)) == NULL) {
putchar('\n');
exit(EXIT_FAILURE);
}
if (argc == 3) { // Interpret decimal count if present
if (strspn(argv[2], VAL_CHRS + 10) == strlen(argv[2])) /* Decimal count contains only digits */
ndec = atoi(argv[2]); /* VAL_CHRS[11->20] = '1', ..., '9', '0' */
else
goto invdec_err;
if (ndec > DBL_DIG)
goto invdec_err;
}
#ifdef DEBUG
puts("\n\e[4mresult\e[24m");
#endif
result = stod(expr);
free(expr);
puts(expr = dtos(result, argc == 3 ? ndec : 6)); // 6 = default number of decimals shown
free(expr);
return EXIT_SUCCESS;
interactive:
for (;;) {
printf("> ");
expr = calloc(bufsize + 1, sizeof(char));
getline(&expr, &bufsize, stdin);
if (!strcmp(expr, "\n")) {
free(expr);
break;
}
swap = expr; // Swap causes 'still reachable' error in valgrind
expr = simplify(expr, 0);
free(swap);
if (expr != NULL) {
result = stod(expr);
free(expr);
puts(expr = dtos(result, 6));
free(expr);
}
}
return EXIT_SUCCESS;
invdec_err:
fail("Invalid decimal count");
}
void print_help(void) {
printf("Usage: %s [EXPRESSION] [ROUND]\n", PROG_NAME);
puts("High-accuracy terminal calculator");
puts("Encapsulation within apostrophes (') is recommended");
puts("This software falls under the GNU Public License v3.0\n");
puts("++, -- ++x, --x Increment, decrement");
puts("!, !! !x, y!!x Square root, other root ↑ Higher precedence");
puts("^ x^y Exponent");
puts("*, /, % x*y, x/y, x%y Multiply, divide, remainder ↓ Lower Precedence");
puts("+, - x+y, x-y Add, subtract\n");
puts(" (x + y) Control precedence");
puts(" x(y) Multiply terms\n");
puts("GitHub repository: https://github.com/crypticcu/eval");
puts("Report bugs to: [email protected]");
exit(EXIT_SUCCESS);
}
eval.c
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "eval.h"
/* NOTES **********************************************************
- Parameters contain a leading underscore
- Variables shared between files are uppercase
- Variables named "nchr" hold index positions
o Cannot be negative, so use size_t
- An "obstruction" is an invalid character found to be in between
operator and operand
- In terms of operation, a "limit" is the furthest right- or
left-hand index position, relative to an operator in which an
operation would take place
- stod() is used as a replacement to atof()
o Recognizes numbers that cannot computed accurately
o Number of accurate digits determined by __DBL_DIG__ (DBL_DIG)
- dtos() is used as a replacement to gcvt()
o Returns dynamically-allocated string
o Does not require buffer
******************************************************************/
void fail(const char *_desc) {
printf("%s\n", _desc);
if (CMD_LINE) // Exit program if using command-line
exit(EXIT_FAILURE);
}
void printu(const char *_s, size_t _hpos) {
for (int nchr = 0; nchr < strlen(_s); nchr++) {
if (nchr == _hpos)
printf("\e[4m"); // Underline
putchar(_s[nchr]);
if (nchr == _hpos)
printf("\e[24m"); // Reset underline
}
}
char *dtos(double _x, size_t _sig) { // Dynamic memory: numstr
size_t nwhole = nplaces(_x);
if (nwhole > DBL_DIG)
return STR_OVER;
if (nwhole + _sig > DBL_DIG)
_sig = DBL_DIG - nwhole;
if (_sig > DBL_DIG) // Decimal place exceeds accurate number allotted by system
_sig = DBL_DIG;
bool is_negative = _x < 0, is_decimal = _sig, only_decimal = _x < 1 && _x > -1, only_whole = isequal(_x, (int) _x);
size_t reqsize = nwhole + _sig + is_negative + is_decimal + only_decimal;
char *numstr = (char *) calloc(reqsize + 1, sizeof(char));
if (numstr == NULL) // Allocation fails
return NULL;
if (is_negative) { // Negative and decimal requirements
numstr[0] = '-';
if (only_decimal) {
numstr[1] = '0';
numstr[2] = '.';
}
} else if (only_decimal) {
numstr[0] = '0';
numstr[1] = '.';
}
for (int nchr = is_negative + only_decimal * 2, place = nplaces(_x) - !(nwhole == FLT_DIG && only_whole); nchr < reqsize; nchr++, place--) { // Skip characters reserved for negative sign and decimal point, if present
numstr[nchr] = getdigit(_x, place) + 48; // '0' = 48
if (place == 0) {
if (only_whole)
break;
else if (nchr + 1 != reqsize)
numstr[++nchr] = '.';
}
}
return numstr;
}
char *popsub(const char *_s, size_t _low, size_t _high) { // Dynamic memory: sub
char *sub; // Substring to be 'popped' from string
if (_low >= strlen(_s) || _high >= strlen(_s) || _low > _high || // Low/high indices exceed range of string || Low index is greater than high || Allocation fails
(sub = (char *) calloc(_high - _low + 2, sizeof(char))) == NULL)
return NULL;
for (size_t nchr_old = _low, nchr_new = 0; nchr_old <= _high; nchr_old++, nchr_new++)
sub[nchr_new] = _s[nchr_old];
return sub;
}
char *pushsub(char *_s, char *_sub, size_t _low, size_t _high) { // Dynamic memory: newstr
char *newstr = (char *) calloc(
strlen(_s) // Original length
- (_high - _low + 1) // Take away number of characters being removed
+ strlen(_sub) // Add size of substring
+ 1 // Add space for null character
, sizeof(char));
int nchr_new;
if (_low >= strlen(_s) || _high >= strlen(_s) || _low > _high || newstr == NULL) // Low/high indices exceed range of string || Low index is greater than high || Allocation fails
return NULL;
for (nchr_new = 0; nchr_new < _low; nchr_new++)
newstr[nchr_new] = _s[nchr_new]; // Add contents of old string up to point of integration
for (int nchr_sub = 0; nchr_sub < strlen(_sub); nchr_sub++, nchr_new++)
newstr[nchr_new] = _sub[nchr_sub]; // Integrate substring
for (int nchr_old = _high + 1; nchr_old < strlen(_s); nchr_old++, nchr_new++)
newstr[nchr_new] = _s[nchr_old]; // Add rest of old string
free(_s);
free(_sub);
return newstr;
}
char *simplify(const char *_expr, size_t _from) { // Dynamic memory: subA, subB, expr
bool read_parenth = false;
char chr, *subA = NULL, *subB = NULL,
*expr = (char *) calloc(strlen(_expr) + 1, sizeof(char)); // Modifiable expression
size_t par_low, par_high;
int invpos;
double result;
if (expr == NULL)
return NULL;
if (!_from) { // Check syntax and parenthesese only once (_from is always 0 on first call)
if ((invpos = chk_syntax(_expr)) != CHK_PASS) // Check for syntax errors
goto syntax_err;
if ((invpos = chk_parenth(_expr)) != CHK_PASS) // Check for parenthetical errors
goto syntax_err;
}
strcpy(expr, _expr); // Copy constant expression to modifiable one
for (int nchr = _from; (chr = expr[nchr]); nchr++) { // Go straight to evaluate() if '-p' is passed
if (chr == ')') {
par_high = nchr;
read_parenth = false;
if ((subA = popsub(expr, par_low, par_high)) == NULL)
goto popsub_err;
expr[par_low] = (toast(expr, par_low)) ? '*' : ' ';
expr[par_high] = (toast(expr, par_high)) ? '*' : ' ';
result = evaluate(subA); // Value passed to result to increase efficiency and improve debugging mode clarity
if (isequal(result, DBL_FAIL)) // evaluate() does not return heap address, so can be called without assignment
goto evaluate_err;
if ((subB = dtos(result, DBL_DIG)) == NULL)
goto dtos_err;
free(subA);
if ((expr = pushsub(expr, subB, par_low + 1, par_high - 1)) == NULL) // Do not overwite space where parentheses used to be
goto pushsub_err;
if (_from)
return expr;
}
else if (chr == '(') {
if (read_parenth) {
subA = expr; // Swap causes 'still reachable' error in valgrind
if ((expr = simplify(expr, nchr)) == NULL)
goto simplify_err;
free(subA);
} else {
read_parenth = true;
par_low = nchr;
}
}
}
subA = expr;
result = evaluate(expr);
if (isequal(result, DBL_FAIL))
goto evaluate_err;
if ((expr = dtos(result, DBL_DIG)) == NULL)
goto dtos_err;
free(subA);
return expr;
syntax_err:
printf("Syntax error: ");
printu(_expr, invpos);
free(expr);
return NULL;
evaluate_err:
free(subA); // Failure message not required; would have already been handled by evaluate()
return NULL;
dtos_err:
free(subA);
fail("Internal error (simplify.dtos)"); // Exits program if using command-line
return NULL;
popsub_err:
free(expr);
fail("Internal error (simplify.dtos)");
return NULL;
pushsub_err:
fail("Internal error (simplify.pushsub)");
return NULL;
simplify_err:
free(subA);
fail("Internal error (simplify.simplify)");
return NULL;
}
bool isequal(double _x, double _y) {
return fabs(_x - _y) < FLT_EPSILON;
}
bool isin(const char _x, const char *_y) {
for (int nchr = 0; nchr < strlen(_y); nchr++)
if (_x == _y[nchr])
return true;
return false;
}
bool isnumer(char _c) {
return (isdigit(_c) || _c == '-' || _c == '.');
}
bool toast(const char *_expr, size_t _parpos) { // To asterisk?
char chr = _expr[_parpos],
next, // Character after parenthesis
last; // Character before parenthesis
next = _parpos < strlen(_expr) - 1 ? _expr[_parpos + 1] : 0;
last = _parpos ? _expr[_parpos - 1] : 0;
return (isdigit(last) && isnumer(next) ||
chr == '(' && last == ')' ||
chr == ')' && next == '(') ? true : false;
}
size_t getdigit(double _x, int _place) {
size_t digit;
_x = fabs(_x);
if (abs(_place) > DBL_DIG || _x > LLONG_MAX) // Place cannot be over/under place limit; Any 'x' over max llong causes overflow on conversion
return 0; // Digits that cannot be printed
for (int nchr = 0; nchr <= abs(_place); _place > 0 ? (_x /= 10) : (_x *= 10), nchr++)
digit = ((long long int) _x - (long long int) (_x / 10) * 10);
return digit;
}
size_t nplaces(double _x) {
_x = fabs(_x); // log of negative is undefined
if (_x == 0) // log of zero is undefined
return 1;
return log(_x)/log(10) + 1;
}
int chk_parenth(const char *_expr) {
char chr;
int nopen = 0, nclosed = 0, nchr;
for (nchr = 0; (chr = _expr[nchr]); nchr++) // Get number of closed parentheses
if (chr == ')')
nclosed++;
for (nchr = 0; (chr = _expr[nchr]); nchr++) {
if (chr == '(')
nopen++;
else if (chr == ')')
nopen--;
if (nopen > nclosed) { // Extra open parenthesis?
while ((chr = _expr[--nchr]) != '('); // Find last instance of open parenthesis
return nchr;
}
if (nopen < 0) // Extra closed?
return nchr;
}
return CHK_PASS;
}
int chk_syntax(const char *_expr) {
char chr,
lead = 0, // Last non-space
trail = 0, // Next non-space
last = 0, // Immediate last
next = 1; // Immediate next
size_t nsingle = 0, // Single operators
ndouble = 0, // Double operators
npoint = 0, // Decimal points
nchr_err; // Index position of syntax error
#ifdef DEBUG
puts("\e[4mchk_syntax\e[24m");
#endif
for (size_t nchr = 0; (chr = _expr[nchr]); nchr++) {
#ifdef DEBUG
printf("single: %ld\tdouble: %ld\tchr: %c\n", nsingle, ndouble, chr);
#endif
if (nchr)
last = _expr[nchr - 1];
if (nchr != strlen(_expr))
next = _expr[nchr + 1];
if (next != 0)
for (int i = nchr + 1; _expr[i]; i++)
if(!isspace(_expr[i])) {
trail = _expr[i];
break;
}
if (isdigit(chr) || chr == '(' || chr == ')') // CHECK OPERATORS
nsingle = 0, ndouble = 0;
else if (isin(chr, DBLS) && (chr == last && isin(last, DBLS) || chr == next && isin(next, DBLS))) {
if (chr != '!' && isdigit(lead) || chr == '!' && trail == '!' && !isdigit(lead) && lead != '.') // Operator is obstruction
return nchr;
if (chr == '!' && lead == '!' && !isdigit(trail) && trail != '.') { // Operator isn't obstruction; find obstruction
for (nchr_err = nchr; (chr = _expr[nchr_err]) != trail; nchr_err++);
return nchr_err;
}
ndouble++;
}
else if (isin(chr, OPERS) && !(isin(chr, UNRY) && isin(lead, BNRY))) // Extra conditionals needed to prevent 'x + !y' from being a syntax error
nsingle++;
if (!isdigit(chr) && chr != '.') // CHECK DECIMAL POINTS
npoint = 0;
else if (chr == '.')
npoint++;
if (nsingle == 2 || ndouble == 3 || npoint == 2 || /* Extra operator or comma || */ // CHECK ERRORS
!isin(chr, VAL_CHRS) && !isspace(chr) || /* Is not a valid character nor a space || */
isdigit(chr) && isdigit(lead) && lead != last) /* Two numbers side-by-side w/o operator */
return nchr;
if (!isspace(chr))
lead = chr;
}
return CHK_PASS;
}
int getlim(char *_expr, size_t _operpos, char _dir) {
bool reading = false, read_digit;
char chr;
int lim = -1, nchr;
if (_dir != 'l' && _dir != 'r') // Left and right directions only
return INT_FAIL;
for (nchr = _dir == 'r' ? _operpos + 1 : _operpos - 1; (chr = _expr[nchr]) && nchr >= 0; _dir == 'r' ? nchr++ : nchr--) {
if (isnumer(chr) && !reading)
reading = true;
else if (!isnumer(chr) && reading) {
lim = _dir == 'r' ? nchr - 1 : nchr + 1;
break;
}
if (isdigit(chr))
read_digit = true;
}
if (!reading || !read_digit) // No value found
return INT_FAIL;
else {
if (nchr == -1) // Reached beginning of expression
lim = 0;
else if (chr == 0) // Reached end of expression
lim = strlen(_expr) - 1;
}
return lim;
}
size_t fobst(const char *_expr, size_t _operpos, size_t _llim, size_t _rlim) {
bool l_obstr = false, r_obstr = false;
char chr, oper = _expr[_operpos];
int nchr = _operpos,
off = 0; // Offset from operator position
if (_llim == INT_FAIL) // Left limit of unary operation is operator position
_llim = 0;
if (_rlim == INT_FAIL)
_rlim = strlen(_expr) - 1;
if (_llim >= strlen(_expr) || _rlim >= strlen(_expr) || _llim > _rlim)
return INT_FAIL;
while (off < (int) strlen(_expr)) {
if (nchr >= _llim && nchr <= _rlim) {
chr = _expr[nchr];
if (isin(chr, OPERS) && chr != oper && chr != '-')
off < 0 ? (l_obstr = true) : (r_obstr = true);
}
off <= 0 ? (off = -(off - 1)) : (off = -off);
nchr = _operpos + off;
}
if (l_obstr && r_obstr)
return LEFT | RIGHT;
else if (l_obstr && !r_obstr)
return LEFT;
else if (!l_obstr && r_obstr)
return RIGHT;
else
return 0;
}
double evaluate(const char *_expr) { // Dynamic memory: result_str, expr
char chr,
*result_str = NULL, // Operation result
*expr = (char *) calloc(strlen(_expr) + 1, sizeof(char)); // Modifiable expression
size_t llim, rlim; // Left- and right-hand limits of operation
int nchr;
double result, // Operation result; Final return value
lval, rval; // Left and right values of operation
#ifdef DEBUG
printf("\n\e[4mevaluate\e[24m\n%s\n", _expr);
#endif
if (expr == NULL)
return DBL_FAIL;
strcpy(expr, _expr); // Copy constant expression to modifiable one
for (nchr = 0; (chr = expr[nchr]); nchr++) {
if (isin(chr, OPERS))
goto evaluate;
}
goto reevaluate; // Skip main loop if no operators are found
evaluate:
for (nchr = 0; (chr = expr[nchr]); nchr++) { // INCREMENT/DECREMENT
if (chr == '+' && expr[nchr + 1] == '+' || chr == '-' && expr[nchr + 1] == '-') {
INIT_VALS(); // Retrieves rval, lval, rlim, and llim
if (fobst(expr, nchr, llim, rlim) & RIGHT) // Operation cannot continue if another operator is in the way
continue;
CHK_VALS(RIGHT); // Checks for overflow, getval() failure, and missing operand(s)
result = chr == '+' ? rval + 1 // '++x' -> '+(x + 1)'
: -rval - 1; // '--x' -> '-(-x + 1)'
llim = nchr; // Left limit of unary operation is operator position
INIT_EXPR(); // Retrieves new expression
#ifdef DEBUG
puts(expr);
#endif
}
}
for (nchr = 0; (chr = expr[nchr]); nchr++) { // SQUARE ROOT/OTHER ROOT
if (chr == '!') {
INIT_VALS();
if (expr[nchr + 1] == '!') {
if (fobst(expr, nchr, llim, rlim) & (LEFT & RIGHT)) // Needs both sides of operator
continue;
CHK_VALS(LEFT|RIGHT);
if (rval < 0 && (int) lval % 2 == 0)
goto evenroot_err;
if (lval == 0)
goto zeroroot_err;
result = rval < 0 ? -pow(-rval, 1 / lval) : pow(rval, 1 / lval); // Negative root workaround
} else {
if (fobst(expr, nchr, llim, rlim) & RIGHT) // Needs only the right side of operator
continue;
CHK_VALS(RIGHT);
if (rval < 0)
goto evenroot_err;
llim = nchr;
result = sqrt(rval);
}
INIT_EXPR();
#ifdef DEBUG
puts(expr);
#endif
}
}
for (nchr = 0; (chr = expr[nchr]); nchr++) { // EXPONENT
if (chr == '^') {
INIT_VALS();
if (fobst(expr, nchr, llim, rlim) & (LEFT & RIGHT))
continue;
CHK_VALS(LEFT|RIGHT);
result = pow(lval, rval);
INIT_EXPR();
#ifdef DEBUG
puts(expr);
#endif
}
}
for (nchr = 0; (chr = expr[nchr]); nchr++) { // MULTIPLICATION/DIVISION/REMAINDER
if (chr == '*' || chr == '/' || chr == '%') {
INIT_VALS();
if (fobst(expr, nchr, llim, rlim) & (LEFT & RIGHT))
continue;
CHK_VALS(LEFT|RIGHT);
if (rval == 0 && chr != '*')
goto divzero_err;
if (chr == '*')
result = lval * rval;
else if (chr == '/')
result = lval / rval;
else if (chr == '%') {
if (isequal(lval, (int) lval) && isequal(rval, (int) rval))
result = (int) lval % (int) rval;
else
goto modulus_err;
}
INIT_EXPR();
#ifdef DEBUG
puts(expr);
#endif
}
}
for (nchr = 0; (chr = expr[nchr]); nchr++) { // ADDITION/SUBTRACTION/UNARY PLUS/UNARY MINUS
if (chr == '+' || chr == '-') {
if (chr == '+' && expr[nchr + 1] == '+' || chr == '-' && expr[nchr + 1] == '-') // Increment/Decrement found
goto evaluate;
INIT_VALS();
if (fobst(expr, nchr, llim, rlim) & RIGHT)
continue;
CHK_VALS(RIGHT);
if (llim == INT_FAIL)
llim = nchr;
result = chr == '+' ? lval + rval : lval - rval;
INIT_EXPR();
#ifdef DEBUG
puts(expr);
#endif
}
}
reevaluate:
while (strcspn(expr, OPERS + 2) != strlen(expr)) {
if ((result_str = dtos(evaluate(expr), DBL_DIG)) == NULL)
goto dtos_err;
if ((result_str = dtos(evaluate(expr), DBL_DIG)) == STR_OVER)
goto overflow_err;
free(expr);
expr = result_str;
}
if (isequal(result = stod(expr), DBL_OVER))
goto overflow_err;
free(expr);
return result;
opermiss_err:
free(expr);
fail("Missing operand"); // Exits program if using command-line
return DBL_FAIL;
evenroot_err:
free(expr);
fail("Even root of negative number");
return DBL_FAIL;
zeroroot_err:
free(expr);
fail("Root cannot be zero");
return DBL_FAIL;
divzero_err:
free(expr);
fail("Divide by zero");
return DBL_FAIL;
modulus_err:
free(expr);
fail("Remainder takes integers only");
return DBL_FAIL;
getval_err:
free(expr);
fail("Internal error (evaluate.getval)");
return DBL_FAIL;
dtos_err:
free(expr);
fail("Internal error (evaluate.dtos)");
return DBL_FAIL;
overflow_err:
free(expr);
fail("Number too large");
return DBL_FAIL;
}
double getval(char *_expr, size_t _operpos, char _dir) { // Dynamic memory: val_str
bool reading = false;
char chr, *val_str = NULL;
size_t val_low, val_high; // Range of indices in which value is located
int nchr;
double num;
if (_dir != 'l' && _dir != 'r') // Left and right directions only
return DBL_FAIL;
for (nchr = _dir == 'r' ? _operpos + 1 : _operpos - 1; (chr = _expr[nchr]) && nchr >= 0; _dir == 'r' ? nchr++ : nchr--) { // Get size of value string
if (isnumer(chr) && !reading) {
reading = true;
_dir == 'r' ? (val_low = nchr) : (val_high = nchr);
} else if (!isnumer(chr) && reading) {
_dir == 'r' ? (val_high = nchr) : (val_low = nchr);
break;
}
}
if (!reading) // No value found
return 0; // Must return 0 in this case to ensure proper evaluate() functionality (used in unary + and -)
else if (nchr == -1) // Reached beginning of expression
val_low = 0;
else if (chr == 0) // Reached end of expression
val_high = strlen(_expr) - 1;
if ((val_str = popsub(_expr, val_low, val_high)) == NULL) {
free(_expr);
return DBL_FAIL;
}
if ((num = stod(val_str)) == DBL_FAIL) {
free(val_str);
return DBL_OVER;
}
free(val_str);
return num; // Convert value string to value
}
double stod(const char *_s) { // Equivalent to atof(), except that it does not print inaccurate numbers
bool read_decim = false, is_negative = false, reading = false;
char chr;
int nchr;
double num = 0, placeval = 0.1;
for (nchr = 0; (chr = _s[nchr]) && nchr <= DBL_DIG + is_negative + read_decim; nchr++) { // DBL_DIG is accurate digit limit
if (chr == '-' && !is_negative && !reading)
is_negative = true;
else if (chr == '.' && !read_decim && !reading)
read_decim = true;
if (!read_decim && isdigit(chr)) {
num *= 10;
num += chr - 48;
} else if (isdigit(chr)) {
reading = true;
num += placeval * (chr - 48); // '0' = 48
placeval /= 10;
}
}
if (nchr > DBL_DIG && !read_decim)
return DBL_OVER;
if (is_negative)
num *= -1;
return num;
}
eval.h
#include <stdbool.h>
#ifndef PARSE_H
#define PARSE_H
#ifdef __GNU_LIBRARY__
#include <err.h>
#define PROG_NAME\
program_invocation_name[1] == '/' ?\
program_invocation_name + 2 :\
program_invocation_name // Ignore './' if included
extern char *program_invocation_name;
#else
#define PROG_NAME "eval"
#endif /* #ifdef __GNU_LIBRARY__ */
/* Initializes left- and right-hand values in evaluate() */
#define INIT_VALS()\
lval = getval(expr, nchr, 'l');\
rval = getval(expr, nchr, 'r');\
llim = getlim(expr, nchr, 'l');\
rlim = getlim(expr, nchr, 'r')
/* Initializes new expression string in evaluate() */
#define INIT_EXPR()\
if ((result_str = dtos(result, DBL_DIG)) == NULL) {\
free(expr);\
fail("Internal error (evaluate.dtos)");\
}\
if ((expr = pushsub(expr, result_str, llim, rlim)) == NULL) {\
free(result_str);\
fail("Internal error (evaluate.pushsub)");\
}
/* Checks for overflow, getval() failure, and missing operand(s) in evaluate()
* Requires needed values, LEFT or RIGHT */
#define CHK_VALS(reqval)\
if (isequal(rval, DBL_OVER) || isequal(lval, DBL_OVER))\
goto overflow_err;\
if (isequal(rval, DBL_FAIL) || isequal(lval, DBL_FAIL))\
goto getval_err;\
if (reqval & RIGHT && rlim == INT_FAIL)\
goto opermiss_err;\
if (reqval & LEFT && llim == INT_FAIL)\
goto opermiss_err
#define INT_FAIL INT_MAX // Passed by [type]-returning functions on failure
#define DBL_FAIL DBL_MAX
#define DBL_OVER FLT_EPSILON // Passed by [type]-returning functions on overflow
#define STR_OVER "..."
#define CHK_PASS -1 // Used by chk_parenth() and chk_syntax(); Indicates valid syntax
#define RIGHT 1
#define LEFT 2
// #define DEBUG // If defined, prints debug info
extern bool CMD_LINE;
static const char *VAL_CHRS = "+-!^*/%.()\n1234567890'",
*OPERS = "+-!^*/%",
*DBLS = "+-!", // Can be double
*UNRY = "+-!", // Can be unary
*BNRY = "^*/%"; // Can only be binary
/* Prints error message and exits program */
extern void fail(const char *_desc);
/* Prints string with character at given position underlined */
extern void printu(const char *_s, size_t _hpos);
/* Returns string representation of double
* Returns NULL on failure
* Returns OVERFLOW on overflow
* Resulting string must be freed */
extern char *dtos(double _x, size_t _sig);
/* Returns new, allocated substring spanning the given elements
* Returns NULL on failure */
extern char *popsub(const char *_s, size_t _low, size_t _high);
/* Returns new, allocated string where substring replaces given elements in old string
* Frees given string and substring while returning a newly allocated one
* Returns NULL on failure */
extern char *pushsub(char *_s, char *_sub, size_t _low, size_t _high);
/* Evaluates mathemetical expression starting from given index position
* Returns string representation of result depending on index
* Returns NULL on failure */
extern char *simplify(const char *_expr, size_t _from);
/* Returns true if floating-point numbers are equal */
extern bool isequal(double _x, double _y);
/* Returns true if character is in string */
extern bool isin(const char _x, const char *_y);
/* Returns true if character is numerical (digit || '-' || '.') */
extern bool isnumer(char _c);
/* Determines whether a parenthesis indicates multiplication */
extern bool toast(const char *_expr, size_t _parpos);
/* Get digit at given place */
extern size_t getdigit(double _x, int _place);
/* Returns number of whole places */
extern size_t nplaces(double _x);
/* Returns index position of first invalid parenthesis of expression
* Returns CHK_PASS if no invalid parentheses are found */
extern int chk_parenth(const char *_expr);
/* Returns index position of first invalid character of expression
* Returns CHK_PASS if no invalid characters are found */
extern int chk_syntax(const char *_expr);
/* Returns left- or right-hand limit of range of operation at given position in expression
* Returns INT_FAIL if invalid direction or operand is missing */
extern int getlim(char *_expr, size_t _operpos, char _dir);
/* Returns OBS_R or OBS_L depending on type of obstruction(s)
* If none are found, returns 0
* Returns INT_FAIL on failure */
extern size_t fobst(const char *_expr, size_t _operpos, size_t _llim, size_t _rlim);
/* Evaluates mathematical expression from index position 0
* Ignores parentheses and syntax errors
* Returns DBL_FAIL on failure */
extern double evaluate(const char *_expr);
/* Returns left- or right-hand value of the operand at given position in the expression
* Returns DBL_FAIL on failure */
extern double getval(char *_expr, size_t _operpos, char _dir);
/* Returns double representation of string
* Returns DBL_FAIL on overflow */
extern double stod(const char *_s);
#endif /* #ifndef PARSE_H */