evalexpr: parse: do not use operator kind directly
This allows for having different tokens mapping to the same mathematical operator, with potentially different semantics. For example, we can add `$` as another notation for factorial, but allowing it to be chained: meaning we can evaluate `3$$` to `720`, and still keep `3!!` as a syntax error. To do so, we simply need to add the following line to our operator table: ```c POSTOP(UNOP_FACT, 5, ASSOC_LEFT, '$', 0) ```
This commit is contained in:
parent
da868be598
commit
77a7bdfddd
|
@ -40,7 +40,7 @@ static void skip_whitespace(const char **input)
|
||||||
eat_char(input);
|
eat_char(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t parse_binop(enum op_kind *op, const char **input)
|
static size_t parse_binop(size_t *op_ind, const char **input)
|
||||||
{
|
{
|
||||||
skip_whitespace(input);
|
skip_whitespace(input);
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ static size_t parse_binop(enum op_kind *op, const char **input)
|
||||||
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
||||||
{
|
{
|
||||||
best_len = ops[i].op_len;
|
best_len = ops[i].op_len;
|
||||||
*op = ops[i].kind;
|
*op_ind = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ static size_t parse_binop(enum op_kind *op, const char **input)
|
||||||
return best_len;
|
return best_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t parse_prefix(enum op_kind *op, const char **input)
|
static size_t parse_prefix(size_t *op_ind, const char **input)
|
||||||
{
|
{
|
||||||
skip_whitespace(input);
|
skip_whitespace(input);
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ static size_t parse_prefix(enum op_kind *op, const char **input)
|
||||||
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
||||||
{
|
{
|
||||||
best_len = ops[i].op_len;
|
best_len = ops[i].op_len;
|
||||||
*op = ops[i].kind;
|
*op_ind = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ static size_t parse_prefix(enum op_kind *op, const char **input)
|
||||||
return best_len;
|
return best_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t parse_postfix(enum op_kind *op, const char **input)
|
static size_t parse_postfix(size_t *op_ind, const char **input)
|
||||||
{
|
{
|
||||||
skip_whitespace(input);
|
skip_whitespace(input);
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ static size_t parse_postfix(enum op_kind *op, const char **input)
|
||||||
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
||||||
{
|
{
|
||||||
best_len = ops[i].op_len;
|
best_len = ops[i].op_len;
|
||||||
*op = ops[i].kind;
|
*op_ind = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,30 +106,24 @@ static size_t parse_postfix(enum op_kind *op, const char **input)
|
||||||
return best_len;
|
return best_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int right_prec(enum op_kind op)
|
static int right_prec(size_t op_ind)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < ARR_SIZE(ops); ++i)
|
if (op_ind >= ARR_SIZE(ops))
|
||||||
if (op == ops[i].kind)
|
return INT_MIN; // Defensive programming
|
||||||
{
|
|
||||||
if (ops[i].assoc == ASSOC_RIGHT)
|
if (ops[op_ind].assoc == ASSOC_RIGHT)
|
||||||
return ops[i].prio;
|
return ops[op_ind].prio;
|
||||||
return ops[i].prio + 1;
|
return ops[op_ind].prio + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return INT_MIN;
|
static int next_prec(size_t op_ind)
|
||||||
}
|
|
||||||
|
|
||||||
static int next_prec(enum op_kind op)
|
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < ARR_SIZE(ops); ++i)
|
if (op_ind >= ARR_SIZE(ops))
|
||||||
if (op == ops[i].kind)
|
return INT_MIN; // Defensive programming
|
||||||
{
|
|
||||||
if (ops[i].assoc != ASSOC_LEFT)
|
|
||||||
return ops[i].prio - 1;
|
|
||||||
return ops[i].prio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return INT_MIN;
|
if (ops[op_ind].assoc != ASSOC_LEFT)
|
||||||
|
return ops[op_ind].prio - 1;
|
||||||
|
return ops[op_ind].prio;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -161,19 +155,19 @@ struct ast_node *climbing_parse(const char *input)
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t update_op(enum op_kind *op, bool *is_binop, const char **input)
|
static size_t update_op(size_t *op_ind, bool *is_binop, const char **input)
|
||||||
{
|
{
|
||||||
skip_whitespace(input); // Unnecessary given that both methods skip it...
|
skip_whitespace(input); // Unnecessary given that both methods skip it...
|
||||||
|
|
||||||
const char *save_input = *input;
|
const char *save_input = *input;
|
||||||
|
|
||||||
enum op_kind op_bin;
|
size_t op_bin;
|
||||||
size_t bin_size = parse_binop(&op_bin, input);
|
size_t bin_size = parse_binop(&op_bin, input);
|
||||||
|
|
||||||
// Reset the parsing
|
// Reset the parsing
|
||||||
*input = save_input;
|
*input = save_input;
|
||||||
|
|
||||||
enum op_kind op_post;
|
size_t op_post;
|
||||||
size_t post_size = parse_postfix(&op_post, input);
|
size_t post_size = parse_postfix(&op_post, input);
|
||||||
|
|
||||||
// Reset the parsing
|
// Reset the parsing
|
||||||
|
@ -181,13 +175,13 @@ static size_t update_op(enum op_kind *op, bool *is_binop, const char **input)
|
||||||
|
|
||||||
if (bin_size > post_size)
|
if (bin_size > post_size)
|
||||||
{
|
{
|
||||||
*op = op_bin;
|
*op_ind = op_bin;
|
||||||
*is_binop = true;
|
*is_binop = true;
|
||||||
return bin_size;
|
return bin_size;
|
||||||
}
|
}
|
||||||
else if (post_size > bin_size)
|
else if (post_size > bin_size)
|
||||||
{
|
{
|
||||||
*op = op_post;
|
*op_ind = op_post;
|
||||||
*is_binop = false;
|
*is_binop = false;
|
||||||
return post_size;
|
return post_size;
|
||||||
}
|
}
|
||||||
|
@ -196,13 +190,12 @@ static size_t update_op(enum op_kind *op, bool *is_binop, const char **input)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool prec_between(enum op_kind op, int min, int max)
|
static bool prec_between(size_t op_ind, int min, int max)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < ARR_SIZE(ops); ++i)
|
if (op_ind >= ARR_SIZE(ops))
|
||||||
if (op == ops[i].kind)
|
return false; // Defensive programming
|
||||||
return min <= ops[i].prio && ops[i].prio <= max;
|
|
||||||
|
|
||||||
return false;
|
return min <= ops[op_ind].prio && ops[op_ind].prio <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_node *climbing_parse_internal(const char **input, int prec)
|
static struct ast_node *climbing_parse_internal(const char **input, int prec)
|
||||||
|
@ -212,23 +205,23 @@ static struct ast_node *climbing_parse_internal(const char **input, int prec)
|
||||||
|
|
||||||
int r = INT_MAX;
|
int r = INT_MAX;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
enum op_kind op; // Used in the next loop
|
size_t op_ind; // Used in the next loop
|
||||||
bool is_binop; // Used in the next loop
|
bool is_binop; // Used in the next loop
|
||||||
while ((len = update_op(&op, &is_binop, input)) // Initialise the operator
|
while ((len = update_op(&op_ind, &is_binop, input)) // Initialise the operator
|
||||||
&& prec_between(op, prec, r) // Use newly initialized operator
|
&& prec_between(op_ind, prec, r) // Use newly initialized operator
|
||||||
&& ast)
|
&& ast)
|
||||||
{
|
{
|
||||||
*input += len; // Skip the parsed operator
|
*input += len; // Skip the parsed operator
|
||||||
if (is_binop) // Given to us by `update_op`
|
if (is_binop) // Given to us by `update_op`
|
||||||
{
|
{
|
||||||
struct ast_node *rhs =
|
struct ast_node *rhs =
|
||||||
climbing_parse_internal(input, right_prec(op));
|
climbing_parse_internal(input, right_prec(op_ind));
|
||||||
if (!rhs)
|
if (!rhs)
|
||||||
{
|
{
|
||||||
destroy_ast(ast);
|
destroy_ast(ast);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
struct ast_node *tree = make_binop(op, ast, rhs);
|
struct ast_node *tree = make_binop(ops[op_ind].kind, ast, rhs);
|
||||||
|
|
||||||
if (!tree)
|
if (!tree)
|
||||||
destroy_ast(ast); // Error case
|
destroy_ast(ast); // Error case
|
||||||
|
@ -236,12 +229,12 @@ static struct ast_node *climbing_parse_internal(const char **input, int prec)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
struct ast_node *tree = make_unop(op, ast);
|
struct ast_node *tree = make_unop(ops[op_ind].kind, ast);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
destroy_ast(ast); // Error case
|
destroy_ast(ast); // Error case
|
||||||
ast = tree;
|
ast = tree;
|
||||||
}
|
}
|
||||||
r = next_prec(op);
|
r = next_prec(op_ind);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast;
|
return ast;
|
||||||
|
@ -269,15 +262,15 @@ static struct ast_node *parse_operand(const char **input)
|
||||||
|
|
||||||
int val = 0;
|
int val = 0;
|
||||||
size_t skip = 0;
|
size_t skip = 0;
|
||||||
enum op_kind op;
|
size_t op_ind;
|
||||||
if ((skip = parse_prefix(&op, input))) // Removes whitespace as side-effect
|
if ((skip = parse_prefix(&op_ind, input))) // Removes whitespace as side-effect
|
||||||
{
|
{
|
||||||
*input += skip; // Skip the parsed operator
|
*input += skip; // Skip the parsed operator
|
||||||
ast = climbing_parse_internal(input, next_prec(op));
|
ast = climbing_parse_internal(input, next_prec(op_ind));
|
||||||
|
|
||||||
if (!ast)
|
if (!ast)
|
||||||
return NULL;
|
return NULL;
|
||||||
struct ast_node *tree = make_unop(op, ast);
|
struct ast_node *tree = make_unop(ops[op_ind].kind, ast);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
destroy_ast(ast);
|
destroy_ast(ast);
|
||||||
ast = tree;
|
ast = tree;
|
||||||
|
|
Loading…
Reference in a new issue