init
This commit is contained in:
		
							
								
								
									
										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;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user