Compare commits

..

6 Commits

Author SHA1 Message Date
r4
f9352abf06 add variables/constants + implicit multiplication 2022-07-26 13:29:00 +02:00
r4
fca5950f6c fix overflow + stupid off-by-1 errors 2021-11-23 20:50:40 +01:00
r4
b4ea1650b7 make EVERYTHING static 2021-11-23 17:16:21 +01:00
r4
e3c466e321 cleaner interface for defining functions 2021-11-23 17:13:59 +01:00
r4
dacf01e83e simplification by use of macros (hopefully) 2021-11-22 21:22:07 +01:00
r4
0fcef2a860 apply negative prefix in parser
Previously a negative prefix was applied in the lexer, but that's kind
of hacky and messy so I handed it to the parser.
2021-11-22 20:56:06 +01:00

234
main.c
View File

@@ -9,9 +9,10 @@ typedef double real;
typedef struct Tok { typedef struct Tok {
enum { enum {
TokNull,
TokOp, TokOp,
TokNum, TokNum,
TokFunc, TokIdent,
} kind; } kind;
union { union {
@@ -22,10 +23,10 @@ typedef struct Tok {
} Tok; } Tok;
#define TOKS_CAP 65536 #define TOKS_CAP 65536
Tok toks[TOKS_CAP]; static Tok toks[TOKS_CAP];
size_t toks_size = 0; static size_t toks_size = 0;
uint8_t op_prec[256] = { static uint8_t op_prec[256] = {
['('] = 0, /* A precedence of 0 is reserved for delimiters. */ ['('] = 0, /* A precedence of 0 is reserved for delimiters. */
[')'] = 0, [')'] = 0,
[','] = 0, [','] = 0,
@@ -35,8 +36,9 @@ uint8_t op_prec[256] = {
['/'] = 2, ['/'] = 2,
['^'] = 3, ['^'] = 3,
}; };
#define OP_PREC(tok_char) (op_prec[(size_t)tok_char])
enum { static enum {
OrderLtr, OrderLtr,
OrderRtl, OrderRtl,
} op_order[256] = { } op_order[256] = {
@@ -48,29 +50,78 @@ enum {
['/'] = OrderLtr, ['/'] = OrderLtr,
['^'] = OrderRtl, ['^'] = OrderRtl,
}; };
#define OP_ORDER(tok_char) (op_order[(size_t)tok_char])
#define IS_FLOAT(c) ((c >= '0' && c <= '9') || c == '.') #define IS_FLOAT(c) ((c >= '0' && c <= '9') || c == '.')
#define IS_ALPHA(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) #define IS_ALPHA(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
void push_tok(Tok t) { typedef struct Var {
if (toks_size+1 < TOKS_CAP) const char *name;
real val;
} Var;
#define VARS_CAP 256
static Var vars[VARS_CAP];
static size_t vars_size = 0;
static void set_var(const char *name, real val) {
for (size_t i = 0; i < vars_size; i++) {
if (strcmp(vars[i].name, name) == 0) {
vars[i].val = val;
return;
}
}
vars[vars_size++] = (Var){.name = name, .val = val};
}
static void unset_var(const char *name) {
for (size_t i = 0; i < vars_size; i++) {
if (strcmp(vars[i].name, name) == 0) {
memmove(vars + i, vars + i + 1, sizeof(Var) * (vars_size - (i + 1)));
vars_size--;
return;
}
}
}
typedef struct Function {
const char *name;
real (*func)(real *args);
size_t n_args;
} Function;
#define FUNCTIONS_CAP 256
static Function functions[FUNCTIONS_CAP];
static size_t functions_size = 0;
static void push_tok(Tok t) {
if (toks_size < TOKS_CAP)
toks[toks_size++] = t; toks[toks_size++] = t;
} }
void tokenize(char *expr) { static void add_func(const char *name, real (*func)(real *args), size_t n_args) {
if (functions_size < FUNCTIONS_CAP)
functions[functions_size++] = (Function){.name = name, .func = func, .n_args = n_args};
}
static void tokenize(char *expr) {
push_tok((Tok){.kind = TokOp, .Char = '('}); push_tok((Tok){.kind = TokOp, .Char = '('});
size_t paren_depth = 0; size_t paren_depth = 0;
bool can_be_neg_num = true; Tok last;
char *curr = expr; char *curr = expr;
for (char c = *curr; c != 0; c = *(++curr)) { for (char c = *curr; c != 0; c = *(++curr)) {
if (toks_size > 0)
last = toks[toks_size-1];
else
last = (Tok){.kind = TokNull};
if (c == ' ') if (c == ' ')
continue; continue;
if (IS_FLOAT(c) || if (IS_FLOAT(c)) {
(can_be_neg_num && c == '-' && IS_FLOAT(curr[1]))) {
char buf[16]; char buf[16];
buf[0] = c; buf[0] = c;
size_t i = 1; size_t i = 1;
@@ -83,9 +134,10 @@ void tokenize(char *expr) {
real num = strtod(buf, NULL); real num = strtod(buf, NULL);
push_tok((Tok){.kind = TokNum, .Num = num}); if (last.kind == TokIdent || (last.kind == TokOp && last.Char == ')') || last.kind == TokNum)
push_tok((Tok){.kind = TokOp, .Char = '*'});
can_be_neg_num = false; push_tok((Tok){.kind = TokNum, .Num = num});
continue; continue;
} }
@@ -100,9 +152,10 @@ void tokenize(char *expr) {
curr += i - 1; curr += i - 1;
buf[i++] = 0; buf[i++] = 0;
push_tok((Tok){.kind = TokFunc, .Str = buf}); if (last.kind == TokIdent || (last.kind == TokOp && last.Char == ')') || last.kind == TokNum)
push_tok((Tok){.kind = TokOp, .Char = '*'});
can_be_neg_num = false; push_tok((Tok){.kind = TokIdent, .Str = buf});
continue; continue;
} }
@@ -125,6 +178,8 @@ void tokenize(char *expr) {
case '*': case '*':
case '/': case '/':
case '^': { case '^': {
if (c == '(' && ((last.kind == TokOp && last.Char == ')') || last.kind == TokNum))
push_tok((Tok){.kind = TokOp, .Char = '*'});
push_tok((Tok){.kind = TokOp, .Char = c}); push_tok((Tok){.kind = TokOp, .Char = c});
break; break;
} }
@@ -132,8 +187,6 @@ void tokenize(char *expr) {
fprintf(stderr, "Error: unrecognized token at %zd: '%c'\n", curr - expr, c); fprintf(stderr, "Error: unrecognized token at %zd: '%c'\n", curr - expr, c);
exit(1); exit(1);
} }
can_be_neg_num = c != ')';
} }
if (paren_depth > 0) { if (paren_depth > 0) {
@@ -144,7 +197,7 @@ void tokenize(char *expr) {
push_tok((Tok){.kind = TokOp, .Char = ')'}); push_tok((Tok){.kind = TokOp, .Char = ')'});
} }
void print_toks() { static void print_toks() {
for (size_t i = 0; i < toks_size; i++) { for (size_t i = 0; i < toks_size; i++) {
switch (toks[i].kind) { switch (toks[i].kind) {
case TokOp: case TokOp:
@@ -157,7 +210,7 @@ void print_toks() {
case TokNum: case TokNum:
printf("%.2f ", toks[i].Num); printf("%.2f ", toks[i].Num);
break; break;
case TokFunc: case TokIdent:
printf("%s ", toks[i].Str); printf("%s ", toks[i].Str);
break; break;
default: default:
@@ -168,45 +221,50 @@ void print_toks() {
printf("\n"); printf("\n");
} }
void del_toks(Tok *begin, Tok *end) { /* Delete tokens from begin to end (excluding end itself). */
static void del_toks(Tok *begin, Tok *end) {
memmove(begin, end, (toks_size - (end - toks)) * sizeof(Tok)); memmove(begin, end, (toks_size - (end - toks)) * sizeof(Tok));
toks_size -= end - begin; toks_size -= end - begin;
} }
real eval(Tok *t) { static real eval(Tok *t);
if (!(t[0].kind == TokOp && op_prec[(size_t)t[0].Char] == 0)) {
fprintf(stderr, "Error: expected delimiter at beginning of expression\n"); static void collapse(Tok *t) {
exit(1); /* Collapse factor. */
if (t[1].kind == TokOp && t[1].Char == '-') {
collapse(t + 1);
if (t[2].kind != TokNum) {
fprintf(stderr, "Error: uncollapsable expression after minus factor\n");
exit(1);
}
t[2].Num *= -1.0;
del_toks(t + 1, t + 2);
} }
while (1) { /* Collapse parentheses. */
/* Collapse parentheses. */ if (t[1].kind == TokOp && t[1].Char == '(') {
if (t[1].kind == TokOp && t[1].Char == '(') { real res = eval(t + 1);
real res = eval(t + 1); size_t i;
size_t i; for (i = 2; !(t[i].kind == TokOp && OP_PREC(t[i].Char) == 0); i++);
for (i = 2; !(t[i].kind == TokOp && op_prec[(size_t)t[i].Char] == 0); i++); del_toks(t + 2, t + i + 1);
del_toks(t + 2, t + i + 1); /* Put the newly evaluated value into place. */
/* Put the newly evaluated value into place. */ t[1].kind = TokNum;
t[1].kind = TokNum; t[1].Num = res;
t[1].Num = res; }
}
/* Collapse function. */ if (t[1].kind == TokIdent) {
if (t[1].kind == TokFunc) { if (t + 2 < toks + toks_size && (t[2].kind == TokOp && t[2].Char == '(')) {
if (t + 2 >= toks + toks_size || !(t[2].kind == TokOp && t[2].Char == '(')) { /* Collapse function. */
fprintf(stderr, "Error: expected '(' token after function\n");
exit(1);
}
real arg_results[16]; real arg_results[16];
size_t arg_results_size = 0; size_t arg_results_size = 0;
t += 2; t += 2;
while (1) { while (1) {
arg_results[arg_results_size++] = eval(t); /* TODO: Overflow protection. */ if (arg_results_size < 16)
arg_results[arg_results_size++] = eval(t);
size_t i = 1; size_t i = 1;
for (; !(t[i].kind == TokOp && op_prec[(size_t)t[i].Char] == 0); i++); for (; !(t[i].kind == TokOp && OP_PREC(t[i].Char) == 0); i++);
bool end = t[i].Char == ')'; bool end = t[i].Char == ')';
if (t[i].Char == ',') if (t[i].Char == ',')
del_toks(t, t + i); del_toks(t, t + i);
@@ -218,44 +276,72 @@ real eval(Tok *t) {
t -= 2; t -= 2;
real outer_res; real outer_res;
if (strcmp(t[1].Str, "sqrt") == 0) { bool func_found = false;
if (arg_results_size != 1) { for (size_t i = 0; i < functions_size; i++) {
fprintf(stderr, "Error: function sqrt() requires exactly 1 argument\n"); if (strcmp(t[1].Str, functions[i].name) == 0) {
exit(1); func_found = true;
if (arg_results_size != functions[i].n_args) {
const char *plural = functions[i].n_args == 1 ? "" : "s";
fprintf(stderr, "Error: function %s() requires exactly 1 argument%s\n", functions[i].name, plural);
exit(1);
}
outer_res = functions[i].func(arg_results);
} }
outer_res = sqrt(arg_results[0]); }
} else if (strcmp(t[1].Str, "pow") == 0) { if (!func_found) {
if (arg_results_size != 2) { fprintf(stderr, "Error: unknown function: %s()\n", t[1].Str);
fprintf(stderr, "Error: function pow() requires exactly 2 arguments\n"); exit(1);
exit(1); }
t[1].kind = TokNum;
t[1].Num = outer_res;
} else {
/* Collapse variable. */
real res;
bool found = false;
for (size_t i = 0; i < vars_size; i++) {
if (strcmp(t[1].Str, vars[i].name) == 0) {
found = true;
res = vars[i].val;
} }
outer_res = pow(arg_results[0], arg_results[1]); }
} else { if (!found) {
fprintf(stderr, "Error: unknown function name: %s\n", t[1].Str); fprintf(stderr, "Error: unknown variable: %s\n", t[1].Str);
exit(1); exit(1);
} }
t[1].kind = TokNum; t[1].kind = TokNum;
t[1].Num = outer_res; t[1].Num = res;
} }
}
}
static real eval(Tok *t) {
if (!(t[0].kind == TokOp && OP_PREC(t[0].Char) == 0)) {
fprintf(stderr, "Error: expected delimiter at beginning of expression\n");
exit(1);
}
while (1) {
collapse(t);
if (!(t[0].kind == TokOp && t[1].kind == TokNum && t[2].kind == TokOp)) { if (!(t[0].kind == TokOp && t[1].kind == TokNum && t[2].kind == TokOp)) {
fprintf(stderr, "Error: invalid token order\n"); fprintf(stderr, "Error: invalid token order\n");
exit(1); exit(1);
} }
const char curr_op = t[0].Char; const char curr_op = t[0].Char;
const uint8_t curr_prec = op_prec[(size_t)curr_op]; const uint8_t curr_prec = OP_PREC(curr_op);
const char next_op = t[2].Char; const char next_op = t[2].Char;
const uint8_t next_prec = op_prec[(size_t)next_op]; 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. */ /* Delimiters have a precedence of 0; if we have a number between two delimiters, we're done. */
if (curr_prec == 0 && next_prec == 0) if (curr_prec == 0 && next_prec == 0)
return t[1].Num; return t[1].Num;
if (next_prec > curr_prec || (next_prec == curr_prec && op_order[(size_t)curr_op] == OrderRtl)) { if (next_prec > curr_prec || (next_prec == curr_prec && OP_ORDER(curr_op) == OrderRtl)) {
t += 2; t += 2;
} else if (next_prec < curr_prec || (next_prec == curr_prec && op_order[(size_t)curr_op] == OrderLtr)) { } else if (next_prec < curr_prec || (next_prec == curr_prec && OP_ORDER(curr_op) == OrderLtr)) {
real res; real res;
real lhs = t[-1].Num, rhs = t[1].Num; real lhs = t[-1].Num, rhs = t[1].Num;
switch (curr_op) { switch (curr_op) {
@@ -278,18 +364,38 @@ real eval(Tok *t) {
} }
} }
void cleanup() { static void cleanup() {
for (size_t i = 0; i < toks_size; i++) { for (size_t i = 0; i < toks_size; i++) {
if (toks[i].kind == TokFunc) if (toks[i].kind == TokIdent)
free(toks[i].Str); free(toks[i].Str);
} }
} }
static real fn_sqrt(real *args) { return sqrt(args[0]); }
static real fn_pow(real *args) { return pow(args[0], args[1]); }
static real fn_mod(real *args) { return fmod(args[0], args[1]); }
static real fn_round(real *args) { return round(args[0]); }
static real fn_floor(real *args) { return floor(args[0]); }
static real fn_ceil(real *args) { return ceil(args[0]); }
static real fn_sin(real *args) { return sin(args[0]); }
static real fn_cos(real *args) { return cos(args[0]); }
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc != 2 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { if (argc != 2 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
fprintf(stderr, "Usage: ./exp \"<expression>\"\n"); fprintf(stderr, "Usage: ./exp \"<expression>\"\n");
exit(1); exit(1);
} }
add_func("sqrt", fn_sqrt, 1);
add_func("pow", fn_pow, 2);
add_func("mod", fn_mod, 2);
add_func("round", fn_round, 1);
add_func("floor", fn_floor, 1);
add_func("ceil", fn_ceil, 1);
add_func("sin", fn_sin, 1);
add_func("cos", fn_cos, 1);
unset_var("x");
set_var("pi", M_PI);
set_var("e", M_E);
tokenize(argv[1]); tokenize(argv[1]);
print_toks(); print_toks();
real res = eval(toks); real res = eval(toks);