init
This commit is contained in:
parent
6475d9c457
commit
824153a9df
16
Makefile
Normal file
16
Makefile
Normal file
@ -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)
|
507
expr.c
Normal file
507
expr.c
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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};
|
||||||
|
}
|
42
expr.h
Normal file
42
expr.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef __EXPR_H__
|
||||||
|
#define __EXPR_H__
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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__ */
|
84
expr_config.h
Normal file
84
expr_config.h
Normal file
@ -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
|
227
main.c
Normal file
227
main.c
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifdef ENABLE_READLINE
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <readline/readline.h>
|
||||||
|
#include <readline/history.h>
|
||||||
|
#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 \"<expression>\" -- 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user