From 824153a9df79445663384dade60f3aef3bde2697 Mon Sep 17 00:00:00 2001 From: r4 Date: Mon, 22 Aug 2022 23:43:21 -0400 Subject: [PATCH] init --- Makefile | 16 ++ expr.c | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++ expr.h | 42 +++++ expr_config.h | 84 +++++++++ main.c | 227 ++++++++++++++++++++++ 5 files changed, 876 insertions(+) create mode 100644 Makefile create mode 100644 expr.c create mode 100644 expr.h create mode 100644 expr_config.h create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa41a7d --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +READLINEFLAGS = -lreadline -DENABLE_READLINE +LDFLAGS = +CFLAGS = -Ofast -march=native -Wall -pedantic -Werror -lm $(READLINEFLAGS) +#CFLAGS = -ggdb -Wall -pedantic -Werror -lm $(READLINEFLAGS) +CC = cc +EXE = qc + +all: $(EXE) + +$(EXE): main.c expr.c expr.h expr_config.h + $(CC) -o $@ main.c expr.c $(LDFLAGS) $(CFLAGS) + +.PHONY: clean + +clean: + rm -f $(EXE) diff --git a/expr.c b/expr.c new file mode 100644 index 0000000..065d6a1 --- /dev/null +++ b/expr.c @@ -0,0 +1,507 @@ +#include +#include +#include +#include + +#include "expr.h" + +#define EXPR_INCLUDE_CONFIG +#include "expr_config.h" + +/* Builtin funcs array */ +const size_t expr_n_builtin_funcs = sizeof(_builtin_funcs) / sizeof(_builtin_funcs[0]); +ExprBuiltinFunc *expr_builtin_funcs = _builtin_funcs; + +/* Builtin vars array */ +const size_t expr_n_builtin_vars = sizeof(_builtin_vars) / sizeof(_builtin_vars[0]); +ExprBuiltinVar *expr_builtin_vars = _builtin_vars; + +#define TRY(x) {ExprError _err = x; if (_err.err != NULL) return _err;} + +typedef struct { + size_t start, end; + + enum { + TokNull, + TokOp, + TokNum, + TokIdent, + } kind; + + union { + double Num; + char Char; + char *Str; + }; +} Tok; + +typedef struct Var { + char *name; + double val; +} Var; + +typedef struct Func { + char *name; + double (*func)(double *args); + size_t n_args; +} Func; + +struct _Expr { + Tok *toks_working; + size_t toks_working_len; + Tok *toks; + size_t toks_len; + size_t toks_cap; + + Var *vars; + size_t vars_len; + size_t vars_cap; + + Func *funcs; + size_t funcs_len; + size_t funcs_cap; +}; + +static size_t smap_get_idx(void *smap, const char *key, size_t type_size, size_t cap); +static void *smap_get_for_setting(void **smap, const char *key, size_t type_size, size_t *len, size_t *cap); +static void del_toks(Expr *e, Tok *start, Tok *end); +static ExprError collapse(Expr *e, Tok *t) __attribute__((warn_unused_result)); +static ExprError eval(Expr *e, Tok *t, double *out_res) __attribute__((warn_unused_result)); +static uint32_t fnv1a32(const void *data, size_t n); +static Func get_func(Expr *e, const char *name); +static void push_tok(Expr *e, Tok t); +static ExprError tokenize(Expr *e, const char *expr) __attribute__((warn_unused_result)); + +const static uint8_t op_prec[256] = { + ['('] = 0, /* A precedence of 0 is reserved for delimiters. */ + [')'] = 0, + [','] = 0, + ['+'] = 1, + ['-'] = 1, + ['*'] = 2, + ['/'] = 2, + ['^'] = 3, +}; +#define OP_PREC(tok_char) (op_prec[(size_t)tok_char]) + +const static enum { + OrderLtr, + OrderRtl, +} op_order[256] = { + ['('] = OrderLtr, + [')'] = OrderLtr, + ['+'] = OrderLtr, + ['-'] = OrderLtr, + ['*'] = OrderLtr, + ['/'] = OrderLtr, + ['^'] = OrderRtl, +}; +#define OP_ORDER(tok_char) (op_order[(size_t)tok_char]) + +#define IS_NUM(c) (c >= '0' && c <= '9') +#define IS_ALPHA(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) +#define IS_SYMBOL(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + +Expr *expr_new() { + Expr *res = malloc(sizeof(Expr)); + *res = (Expr){0}; + for (size_t i = 0; i < expr_n_builtin_funcs; i++) { + expr_set_func(res, expr_builtin_funcs[i].name, expr_builtin_funcs[i].func, expr_builtin_funcs[i].n_args); + } + for (size_t i = 0; i < expr_n_builtin_vars; i++) { + expr_set_var(res, expr_builtin_vars[i].name, expr_builtin_vars[i].val); + } + return res; +} + +void expr_destroy(Expr *e) { + for (size_t i = 0; i < e->toks_len; i++) { + if (e->toks[i].kind == TokIdent) + free(e->toks[i].Str); + } + free(e->toks); + free(e->toks_working); + for (size_t i = 0; i < e->vars_cap; i++) + free(e->vars[i].name); + free(e->vars); + for (size_t i = 0; i < e->funcs_cap; i++) + free(e->funcs[i].name); + free(e->funcs); + free(e); +} + +ExprError expr_set(Expr *e, const char *expr) { + for (size_t i = 0; i < e->toks_len; i++) { + if (e->toks[i].kind == TokIdent) + free(e->toks[i].Str); + } + + free(e->toks_working); e->toks_working = NULL; + e->toks_working_len = 0; + + free(e->toks); e->toks = NULL; + e->toks_len = 0; + e->toks_cap = 0; + + TRY(tokenize(e, expr)); + return (ExprError){0}; +} + +ExprError expr_eval(Expr *e, double *out_res) { + e->toks_working = realloc(e->toks_working, sizeof(Tok) * e->toks_len); + memcpy(e->toks_working, e->toks, sizeof(Tok) * e->toks_len); + e->toks_working_len = e->toks_len; + TRY(eval(e, e->toks_working, out_res)); + return (ExprError){0}; +} + +static size_t smap_get_idx(void *smap, const char *key, size_t type_size, size_t cap) { + size_t i = fnv1a32(key, strlen(key)) & (cap - 1); + while (1) { + void *i_ptr = (uint8_t*)smap + type_size * i; + char *i_key = *((char**)i_ptr); + if (i_key == NULL || strcmp(i_key, key) == 0) { + return i; + } + i = (i + 1) % cap; + } +} + +static void *smap_get_for_setting(void **smap, const char *key, size_t type_size, size_t *len, size_t *cap) { + if (*cap == 0 || (double)*len / (double)*cap >= 0.7) { + size_t new_cap = *cap == 0 ? 16 : *cap * 2; + void *new = malloc(type_size * new_cap); + for (size_t i = 0; i < new_cap; i++) + *((char**)((uint8_t*)new + type_size * i)) = NULL; + for (size_t i = 0; i < *cap; i++) { + void *i_ptr = (uint8_t*)*smap + type_size * i; + char *i_key = *((char**)i_ptr); + if (i_key != NULL) { + void *ptr = (uint8_t*)new + type_size * smap_get_idx(new, i_key, type_size, new_cap); + memcpy(ptr, i_ptr, type_size); + } + } + free(*smap); + *cap = new_cap; + *smap = new; + } + + void *ptr = (uint8_t*)*smap + type_size * smap_get_idx(*smap, key, type_size, *cap); + char **keyptr = (char**)ptr; + if (*keyptr == NULL) { + *keyptr = strdup(key); + (*len)++; + } + return ptr; +} + +void expr_set_var(Expr *e, const char *name, double val) { + Var *v = smap_get_for_setting((void**)&e->vars, name, sizeof(Var), &e->vars_len, &e->vars_cap); + v->val = val; +} + +bool expr_get_var(Expr *e, const char *name, double *out) { + Var v = e->vars[smap_get_idx(e->vars, name, sizeof(Var), e->vars_cap)]; + *out = v.name == NULL ? NAN : v.val; + return v.name != NULL; +} + +void expr_set_func(Expr *e, const char *name, double (*func)(double *args), size_t n_args) { + Func *v = smap_get_for_setting((void**)&e->funcs, name, sizeof(Func), &e->funcs_len, &e->funcs_cap); + v->func = func; + v->n_args = n_args; +} + +static void del_toks(Expr *e, Tok *start, Tok *end) { + memmove(start, end, (e->toks_working_len - (end - e->toks_working)) * sizeof(Tok)); + e->toks_working_len -= end - start; +} + +static ExprError collapse(Expr *e, Tok *t) { + /* Collapse factor. */ + if (t[1].kind == TokOp && t[1].Char == '-') { + TRY(collapse(e, t + 1)); + if (t[2].kind != TokNum) { + return (ExprError){.start = t[2].start, .end = t[2].end, .err = "invalid expression after minus factor"}; + } + t[2].Num *= -1.0; + del_toks(e, t + 1, t + 2); + } + + /* Collapse parentheses. */ + if (t[1].kind == TokOp && t[1].Char == '(') { + double res; + TRY(eval(e, t + 1, &res)); + size_t i; + for (i = 2; !(t[i].kind == TokOp && OP_PREC(t[i].Char) == 0); i++); + del_toks(e, t + 2, t + i + 1); + /* Put the newly evaluated value into place. */ + t[1].kind = TokNum; + t[1].Num = res; + } + + + if (t[1].kind == TokIdent) { + if (t + 2 < e->toks_working + e->toks_working_len && (t[2].kind == TokOp && t[2].Char == '(')) { + /* Collapse function. */ + double arg_results[16]; + size_t arg_results_size = 0; + + t += 2; + while (1) { + if (arg_results_size < 16) { + double res; + TRY(eval(e, t, &res)); + arg_results[arg_results_size++] = res; + } + size_t i = 1; + for (; !(t[i].kind == TokOp && OP_PREC(t[i].Char) == 0); i++); + bool end = t[i].Char == ')'; + if (t[i].Char == ',') + del_toks(e, t, t + i); + else if (t[i].Char == ')') + del_toks(e, t, t + i + 1); + if (end) + break; + } + t -= 2; + + Func func = get_func(e, t[1].Str); + if (func.name == NULL) + return (ExprError){.start = t[1].start, .end = t[1].end, .err = "unknown function"}; + if (arg_results_size != func.n_args) + return (ExprError){.start = t[1].start, .end = t[1].end, .err = "invalid number of arguments to function"}; + + t[1].kind = TokNum; + t[1].Num = func.func(arg_results); + } else { + /* Collapse variable. */ + t[1].kind = TokNum; + if (!expr_get_var(e, t[1].Str, &t[1].Num)) + return (ExprError){.start = t[1].start, .end = t[1].end, .err = "unknown variable"}; + } + } + return (ExprError){0}; +} + +static ExprError eval(Expr *e, Tok *t, double *out_res) { + if (!(t[0].kind == TokOp && OP_PREC(t[0].Char) == 0)) { + return (ExprError){.start = t[0].start, .end = t[0].end, .err = "expected delimiter at beginning of expression"}; + } + + while (1) { + TRY(collapse(e, t)); + + if (!(t[0].kind == TokOp && t[1].kind == TokNum && t[2].kind == TokOp)) { + return (ExprError){.start = t[0].start, .end = t[1].end, .err = "invalid token order"}; + } + + const char curr_op = t[0].Char; + const uint8_t curr_prec = OP_PREC(curr_op); + + const char next_op = t[2].Char; + const uint8_t next_prec = OP_PREC(next_op); + + /* Delimiters have a precedence of 0; if we have a number between two delimiters, we're done. */ + if (curr_prec == 0 && next_prec == 0) { + *out_res = t[1].Num; + return (ExprError){0}; + } + + if (next_prec > curr_prec || (next_prec == curr_prec && OP_ORDER(curr_op) == OrderRtl)) { + t += 2; + } else if (next_prec < curr_prec || (next_prec == curr_prec && OP_ORDER(curr_op) == OrderLtr)) { + double res; + double lhs = t[-1].Num, rhs = t[1].Num; + switch (curr_op) { + case '+': res = lhs + rhs; break; + case '-': res = lhs - rhs; break; + case '*': res = lhs * rhs; break; + case '/': res = lhs / rhs; break; + case '^': res = pow(lhs, rhs); break; + default: + return (ExprError){.start = t[0].start, .end = t[0].end, .err = "invalid operator"}; + } + + t[1].Num = res; + + del_toks(e, t - 1, t + 1); + + t -= 2; + } + } +} + +static uint32_t fnv1a32(const void *data, size_t n) { + uint32_t res = 2166136261u; + for (size_t i = 0; i < n; i++) { + res ^= ((uint8_t*)data)[i]; + res *= 16777619u; + } + return res; +} + +static Func get_func(Expr *e, const char *name) { + return e->funcs[smap_get_idx(e->funcs, name, sizeof(Func), e->funcs_cap)]; +} + +static void push_tok(Expr *e, Tok t) { + if (e->toks_len >= e->toks_cap) { + size_t new_cap = e->toks_cap == 0 ? 16 : e->toks_cap * 2; + e->toks = realloc(e->toks, sizeof(Tok) * new_cap); + e->toks_cap = new_cap; + } + e->toks[e->toks_len++] = t; +} + +static ExprError tokenize(Expr *e, const char *expr) { + size_t start; + + push_tok(e, (Tok){.start = 0, .end = 1, .kind = TokOp, .Char = '('}); + + /* We try to use the stack first for parenthesis tracking and only resort + * to heap allocation if the depth exceeds 16. */ + size_t paren_depth = 0; + size_t parens_cap = 16; + size_t *h_parens = NULL; + size_t s_parens[16]; + size_t *parens = s_parens; + + Tok last; + const char *curr = expr; + for (char c = *curr; c != 0; c = *(++curr)) { + if (e->toks_len > 0) + last = e->toks[e->toks_len-1]; + else + last = (Tok){.kind = TokNull}; + + if (c == ' ') + continue; + + if (IS_NUM(c) || c == '.') { + bool dot_seen = c == '.'; + bool e_seen = false; + char add_e_as_var = 0; + char buf[32]; + start = curr - expr; + buf[0] = c; + size_t i = 1; + while (i < 31 && (IS_NUM(curr[i]) || curr[i] == '.' || curr[i] == 'e' || curr[i] == 'E' || ((curr[i-1] == 'e' || curr[i-1] == 'E') && (curr[i] == '-' || curr[i] == '+')))) { + if (curr[i] == '.') { + if (dot_seen) { + free(h_parens); + return (ExprError){.start = start + i, .end = start + i, .err = "more than one dot in decimal number"}; + } else if (e_seen) { + return (ExprError){.start = start + i, .end = start + i, .err = "decimal dot not allowed in exponent"}; + } else + dot_seen = true; + } + if (curr[i] == 'e' || curr[i] == 'E') { + if (e_seen) { + free(h_parens); + return (ExprError){.start = start + i, .end = start + i, .err = "more than one 'e' or 'E' in decimal number"}; + } else + e_seen = true; + } + buf[i] = curr[i]; + i++; + } + + if (i > 0 && (curr[i-1] == 'e' || curr[i-1] == 'E')) + add_e_as_var = curr[i-1]; + + curr += i - 1; + + if (add_e_as_var) + buf[--i] = 0; + else + buf[i] = 0; + + char *endptr; + double num = strtod(buf, &endptr); + size_t endpos = endptr - buf; + if (endpos != i) { + free(h_parens); + return (ExprError){.start = start + endpos, .end = start + endpos, .err = "error parsing number"}; + } + + if (last.kind == TokIdent || (last.kind == TokOp && last.Char == ')') || last.kind == TokNum) + push_tok(e, (Tok){.start = last.end + 1, .end = last.end + 1, .kind = TokOp, .Char = '*'}); + + push_tok(e, (Tok){.start = start, .end = curr - expr, .kind = TokNum, .Num = num}); + + if (add_e_as_var) { + char b[2] = { add_e_as_var, 0 }; + push_tok(e, (Tok){.start = curr - expr + 1, .end = curr - expr + 1, .kind = TokOp, .Char = '*'}); + push_tok(e, (Tok){.start = curr - expr - 1, .end = curr - expr - 1, .kind = TokIdent, .Str = strdup(b)}); + } + continue; + } + + if (IS_SYMBOL(c)) { + start = curr - expr; + char *buf = malloc(32); + buf[0] = c; + size_t i = 1; + while (i < 31 && IS_SYMBOL(curr[i])) { + buf[i] = curr[i]; + i++; + } + curr += i - 1; + buf[i++] = 0; + + if (last.kind == TokIdent || (last.kind == TokOp && last.Char == ')') || last.kind == TokNum) + push_tok(e, (Tok){.start = last.end + 1, .end = last.end + 1, .kind = TokOp, .Char = '*'}); + + push_tok(e, (Tok){.start = start, .end = curr - expr, .kind = TokIdent, .Str = buf}); + continue; + } + + if (c == '(') { + if (paren_depth+1 > parens_cap) { + bool h_n = h_parens == NULL; + parens = h_parens = realloc(h_parens, sizeof(size_t) * (parens_cap *= 2)); + if (h_n) { + memcpy(h_parens, s_parens, sizeof(size_t) * 16); + } + } + parens[paren_depth++] = curr - expr; + } else if (c == ')') { + if (paren_depth == 0) { + free(h_parens); + return (ExprError){.start = curr - expr, .end = curr - expr, .err = "unmatched ')'"}; + } + paren_depth--; + } + + switch (c) { + case '(': + case ')': + case ',': + case '+': + case '-': + case '*': + case '/': + case '^': { + if (c == '(' && ((last.kind == TokOp && last.Char == ')') || last.kind == TokNum)) + push_tok(e, (Tok){.start = last.end + 1, .end = last.end + 1, .kind = TokOp, .Char = '*'}); + push_tok(e, (Tok){.start = curr - expr, .end = curr - expr, .kind = TokOp, .Char = c}); + break; + } + default: + free(h_parens); + return (ExprError){.start = curr - expr, .end = curr - expr, .err = "unrecognized symbol"}; + } + } + + if (paren_depth > 0) { + size_t p = parens[paren_depth-1]; + free(h_parens); + return (ExprError){.start = p, .end = p, .err = "unmatched '('"}; + } + + push_tok(e, (Tok){.start = curr - expr, .end = curr - expr, .kind = TokOp, .Char = ')'}); + + free(h_parens); + return (ExprError){0}; +} diff --git a/expr.h b/expr.h new file mode 100644 index 0000000..9a9a7e5 --- /dev/null +++ b/expr.h @@ -0,0 +1,42 @@ +#ifndef __EXPR_H__ +#define __EXPR_H__ + +#include +#include +#include + +typedef struct { + size_t start, end; + const char *err; +} ExprError; + +typedef struct { + const char *name; + const char *description; + double (*func)(double *args); + const char **arg_names; + size_t n_args; +} ExprBuiltinFunc; + +typedef struct { + const char *name; + const char *description; + double val; +} ExprBuiltinVar; + +typedef struct _Expr Expr; + +extern ExprBuiltinFunc *expr_builtin_funcs; +extern const size_t expr_n_builtin_funcs; +extern ExprBuiltinVar *expr_builtin_vars; +extern const size_t expr_n_builtin_vars; + +Expr *expr_new(); +void expr_destroy(Expr *e); +ExprError expr_set(Expr *e, const char *expr) __attribute__((warn_unused_result)); +ExprError expr_eval(Expr *e, double *out_res) __attribute__((warn_unused_result)); +void expr_set_var(Expr *e, const char *name, double val); +bool expr_get_var(Expr *e, const char *name, double *out); /* Returns false if not present */ +void expr_set_func(Expr *e, const char *name, double (*func)(double *args), size_t n_args); + +#endif /* __EXPR_H__ */ diff --git a/expr_config.h b/expr_config.h new file mode 100644 index 0000000..340b816 --- /dev/null +++ b/expr_config.h @@ -0,0 +1,84 @@ +#ifndef EXPR_INCLUDE_CONFIG +#error expr_config.h should not be imported by any files other than expr.c +#endif + +static double fn_sqrt(double *args) {return sqrt(args[0]); } +static double fn_cbrt(double *args) {return cbrt(args[0]); } +static double fn_pow(double *args) {return pow(args[0], args[1]); } +static double fn_exp(double *args) {return exp(args[0]); } +static double fn_ln(double *args) {return log(args[0]); } +static double fn_log(double *args) {return log(args[1]) / log(args[0]);} +static double fn_mod(double *args) {return fmod(args[0], args[1]); } +static double fn_round(double *args) {return round(args[0]); } +static double fn_floor(double *args) {return floor(args[0]); } +static double fn_ceil(double *args) {return ceil(args[0]); } +static double fn_sin(double *args) {return sin(args[0]); } +static double fn_cos(double *args) {return cos(args[0]); } +static double fn_tan(double *args) {return tan(args[0]); } +static double fn_asin(double *args) {return asin(args[0]); } +static double fn_acos(double *args) {return acos(args[0]); } +static double fn_atan(double *args) {return atan(args[0]); } +static double fn_sinh(double *args) {return sinh(args[0]); } +static double fn_cosh(double *args) {return cosh(args[0]); } +static double fn_tanh(double *args) {return tanh(args[0]); } +static double fn_asinh(double *args) {return asinh(args[0]); } +static double fn_acosh(double *args) {return acosh(args[0]); } +static double fn_atanh(double *args) {return atanh(args[0]); } +static double fn_abs(double *args) {return fabs(args[0]); } +static double fn_hypot(double *args) {return hypot(args[0], args[1]); } +static double fn_polar(double *args) {return atan2(args[1], args[0]); } +static double fn_max(double *args) {return fmax(args[0], args[1]); } +static double fn_min(double *args) {return fmin(args[0], args[1]); } +static double fn_rad(double *args) {return args[0] / M_PI * 180.0; } +static double fn_deg(double *args) {return args[0] / 180.0 * M_PI; } + +static const char *arg_names_x[] = {"x"}; +static const char *arg_names_xy[] = {"x", "y"}; +static const char *arg_names_nx[] = {"n", "x"}; + +static ExprBuiltinFunc _builtin_funcs[] = { + {"sqrt", "square root of x", fn_sqrt, arg_names_x, 1}, + {"cbrt", "cube root of x", fn_cbrt, arg_names_x, 1}, + {"pow", "x^y", fn_pow, arg_names_xy, 2}, + {"exp", "e^x", fn_exp, arg_names_x, 1}, + {"ln", "natural log (base e) of x", fn_ln, arg_names_x, 1}, + {"log", "log (base n) of x", fn_log, arg_names_nx, 2}, + {"mod", "x%y", fn_mod, arg_names_xy, 2}, + {"round", "closest integer to x", fn_round, arg_names_x, 1}, + {"floor", "greatest integer less than x", fn_floor, arg_names_x, 1}, + {"ceil", "smallest integer grater than x", fn_ceil, arg_names_x, 1}, + {"sin", "sine of x", fn_sin, arg_names_x, 1}, + {"cos", "cosine of x", fn_cos, arg_names_x, 1}, + {"tan", "tangent of x", fn_tan, arg_names_x, 1}, + {"asin", "inverse sine of x", fn_asin, arg_names_x, 1}, + {"acos", "inverse cosine of x", fn_acos, arg_names_x, 1}, + {"atan", "inverse tangent of x", fn_atan, arg_names_x, 1}, + {"sinh", "hyperbolic sine of x", fn_sinh, arg_names_x, 1}, + {"cosh", "hyperbolic cosine of x", fn_cosh, arg_names_x, 1}, + {"tanh", "hyperbolic tangent of x", fn_tanh, arg_names_x, 1}, + {"asinh", "inverse hyperbolic sine of x", fn_asinh, arg_names_x, 1}, + {"acosh", "inverse hyperbolic cosine of x", fn_acosh, arg_names_x, 1}, + {"atanh", "inverse hyperbolic tangent of x", fn_atanh, arg_names_x, 1}, + {"abs", "absolute value of x", fn_abs, arg_names_x, 1}, + {"hypot", "sqrt(x^2+y^2)", fn_hypot, arg_names_xy, 2}, + {"polar", "polar coordinates to radians", fn_polar, arg_names_xy, 2}, + {"max", "the greater value of x and y", fn_max, arg_names_xy, 2}, + {"min", "the smaller value of x and y", fn_min, arg_names_xy, 2}, + {"rad", "x (radians) to degrees", fn_rad, arg_names_x, 1}, + {"deg", "x (degrees) to radians", fn_deg, arg_names_x, 1}, +}; + +static ExprBuiltinVar _builtin_vars[] = { + {"pi", "π", M_PI }, + {"tau", "τ = 2π", 2.0 * M_PI }, + {"e", "Euler's number", M_E }, + {"phi", "golden ratio", 1.61803398874989484820}, + {"h", "Planck constant (Js)", 6.62607015e-34 }, + {"NA", "Avogadro constant (1/mol)", 6.02214076e23 }, + {"c", "speed of light (m/s)", 299792458 }, + {"g", "gravitational acceleration (m/s^2)", 9.80665 }, + {"G", "gravitational constant (N(m/kg)^2)", 6.673889e-11 }, + {"k", "Boltzmann constant (J/K)", 1.380649e-23 }, +}; + +#undef EXPR_INCLUDE_CONFIG diff --git a/main.c b/main.c new file mode 100644 index 0000000..0391d86 --- /dev/null +++ b/main.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_READLINE +#include +#include +#include +#endif + +#include "expr.h" + +#define ssub(_x, _y) ((_x) >= (_y) ? (_x) - (_y) : 0) +#define bufprint(_buf, _n, ...) _n += snprintf(_buf + _n, ssub(sizeof(buf), _n), __VA_ARGS__) + +static Expr *e; +static bool running = true; +static bool last_status_ok = true; +#ifdef ENABLE_READLINE +static bool sigwinch_received = false; +static size_t completer_idx, completer_len; +static bool completer_funcs; +#endif + +static void print_help() { + fprintf(stderr, + "Usage:\n" + " qc \"\" -- evaluate expression\n" + " qc -- run in REPL mode\n" + " qc --help -- show this page\n" + "Syntax:\n" + " Numbers: 123.45 or 1.2345e2 or 1.2345E2\n" + " Precedence | Operations\n" + " -----------+------------\n" + " 1 (LtR) | +, -\n" + " 2 (LtR) | *, /\n" + " 3 (RtL) | ^\n" + " Other symbols: (, ), - (prefix)\n"); + char buf[32][128]; + size_t maxw[2]; + maxw[0] = 0; + fprintf(stderr, "Builtin functions:\n"); + for (size_t i = 0; i < expr_n_builtin_funcs; i++) { + assert(i < 32); + size_t n = 0; + bufprint(buf[i], n, "%s(", expr_builtin_funcs[i].name); + for (size_t j = 0; j < expr_builtin_funcs[i].n_args; j++) { + if (j != 0) + bufprint(buf[i], n, ", "); + bufprint(buf[i], n, "%s", expr_builtin_funcs[i].arg_names[j]); + } + bufprint(buf[i], n, ")"); + if (n > maxw[0]) + maxw[0] = n; + } + for (size_t i = 0; i < expr_n_builtin_funcs; i++) { + fprintf(stderr, " %s%*s - %s\n", buf[i], (int)(maxw[0] - strlen(buf[i])), "", expr_builtin_funcs[i].description); + } + maxw[0] = maxw[1] = 0; + fprintf(stderr, "Builtin variables:\n"); + for (size_t i = 0; i < expr_n_builtin_vars; i++) { + assert(i < 32); + size_t n = strlen(expr_builtin_vars[i].name); + if (n > maxw[0]) + maxw[0] = n; + n = snprintf(buf[i], 128, "%.*g", 15, expr_builtin_vars[i].val); + if (n > maxw[1]) + maxw[1] = n; + } + for (size_t i = 0; i < expr_n_builtin_vars; i++) { + fprintf(stderr, " %s%*s = %s%*s - %s\n", expr_builtin_vars[i].name, (int)(maxw[0] - strlen(expr_builtin_vars[i].name)), "", buf[i], (int)(maxw[1] - strlen(buf[i])), "", expr_builtin_vars[i].description); + } +} + +static void sig_handler(int signum) { + running = false; + fprintf(stderr, "\nExiting\n"); +} + +static bool run(const char *line) { + if (line == NULL || line[0] == 0) + return line != NULL; + if (strcmp(line, "help") == 0) { + print_help(); + return true; + } + double res; + ExprError err; + err = expr_set(e, line); + if (err.err == NULL) + err = expr_eval(e, &res); + if (err.err == NULL) { + printf("%.*g\n", 15, res); + } else { + fprintf(stderr, "Error parsing expression:\n"); + fprintf(stderr, "%s\n", line); + fprintf(stderr, "%*s", (int)err.start, ""); + for (size_t i = err.start; i <= err.end; i++) + fprintf(stderr, "^"); + fprintf(stderr, "\n%s\n", err.err); + } + return err.err == NULL; +} + +#if ENABLE_READLINE +static void winch_handler(int signum) { + sigwinch_received = true; +} + +static void line_handler(char *line) { + if (line == NULL) { + rl_callback_handler_remove(); + running = false; + } else { + add_history(line); + + last_status_ok = run(line); + + free(line); + } +} + +static char *completer(const char *text, int state) { + if (!state) { + completer_idx = 0; + completer_len = strlen(text); + completer_funcs = false; + } + + while (1) { + if (completer_funcs) { + if (completer_idx >= expr_n_builtin_funcs) { + break; + } + } else { + if (completer_idx >= expr_n_builtin_vars) { + completer_funcs = true; + completer_idx = 0; + } + } + + const char *name; + if (completer_funcs) + name = expr_builtin_funcs[completer_idx].name; + else + name = expr_builtin_vars[completer_idx].name; + completer_idx++; + if (strncmp(name, text, completer_len) == 0) { + rl_completion_append_character = completer_funcs ? '(' : 0; + return strdup(name); + } + } + + return NULL; +} +#endif + +int main(int argc, const char **argv) { +#ifdef ENABLE_READLINE + rl_catch_signals = false; + rl_readline_name = "qc"; + rl_completion_entry_function = completer; + rl_basic_word_break_characters = "+-*/^() "; + + signal (SIGWINCH, winch_handler); +#else + char buf[2048]; +#endif + + struct sigaction action = {0}; + action.sa_handler = sig_handler; + if (sigaction(SIGINT, &action, NULL) == -1) { + fprintf(stderr, "Error setting up signal handler: %s\n", strerror(errno)); + return 1; + } + + e = expr_new(); + + if (argc == 1) { + printf("Running in REPL (read-evaluate-print loop) mode. Type `help` for more information.\n"); + printf("Hit Ctrl+C to exit.\n"); + } else if (argc == 2 && strcmp(argv[1], "-h") != 0 && strcmp(argv[1], "--help") != 0) { + return !run(argv[1]); + } else { + print_help(); + return 1; + } + +#ifdef ENABLE_READLINE + rl_callback_handler_install("> ", line_handler); + fd_set fds; +#endif + while (running) { +#ifdef ENABLE_READLINE + FD_ZERO(&fds); + FD_SET(fileno(rl_instream), &fds); + int r = select(FD_SETSIZE, &fds, NULL, NULL, NULL); + if (r < 0 && errno != EINTR) { + rl_callback_handler_remove(); + break; + } + if (sigwinch_received) { + rl_resize_terminal(); + sigwinch_received = false; + } + if (r < 0) + continue; + if (FD_ISSET(fileno(rl_instream), &fds)) + rl_callback_read_char(); +#else + printf("> "); + const char *line = fgets(buf, 2048, stdin); + if (line == NULL) + break; + size_t len = strlen(buf); + if (len == 0) + continue; + buf[len-1] = 0; + last_status_ok = run(line); +#endif + } + + expr_destroy(e); + return !last_status_ok; +}