add tiny expression library and make it a dice expression parser
Use tiny expression library (from Github) and expand it to provide dice expression parsing. This needs more testing obviously.
This commit is contained in:
		
							parent
							
								
									25227d5c6c
								
							
						
					
					
						commit
						92b909158c
					
				| @ -5,7 +5,8 @@ lib_LTLIBRARIES =	libdice.la | ||||
| libdice_la_SOURCES =	lib/dice_lexer.l \
 | ||||
| 			lib/dice_parse.y \
 | ||||
| 			lib/dice.h \
 | ||||
| 			lib/dice.c | ||||
| 			lib/dice.c \
 | ||||
| 			lib/diceexpr.c | ||||
| 
 | ||||
| AM_CFLAGS =		-Ilib | ||||
| 
 | ||||
|  | ||||
| @ -18,4 +18,8 @@ AC_CHECK_FUNCS([arc4random arc4random_uniform strlcat strlcpy],, | ||||
| 
 | ||||
| AC_CONFIG_FILES([Makefile tests/Makefile]) | ||||
| 
 | ||||
| AC_SEARCH_LIBS([cos], [m], [], [ | ||||
|   AC_MSG_ERROR([unable to find the cos() function]) | ||||
| ]) | ||||
| 
 | ||||
| AC_OUTPUT | ||||
|  | ||||
							
								
								
									
										30
									
								
								lib/dice.c
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								lib/dice.c
									
									
									
									
									
								
							| @ -8,7 +8,7 @@ | ||||
| #include <bsd/stdlib.h> | ||||
| #endif | ||||
| 
 | ||||
| extern int yylex_init(void **state); | ||||
| extern int yylex_init_extra(void *extra, void **state); | ||||
| extern int yylex_destroy(void *state); | ||||
| extern void yylex(void *state); | ||||
| extern void yy_switch_to_buffer(void *buffer, void *scanner); | ||||
| @ -22,7 +22,7 @@ struct dice_ | ||||
|     uint32_t amount; | ||||
|     uint32_t sides; | ||||
| 
 | ||||
|     char *errmsg; | ||||
|     char *error; | ||||
| }; | ||||
| 
 | ||||
| static dice_t dice_new(void) | ||||
| @ -42,6 +42,7 @@ void dice_free(dice_t t) | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     free(t->error); | ||||
|     free(t); | ||||
| } | ||||
| 
 | ||||
| @ -93,7 +94,7 @@ dice_t dice_parse(char const *s) | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     yylex_init(&scanner); | ||||
|     yylex_init_extra(d, &scanner); | ||||
|     buffer = yy_scan_string(s, scanner); | ||||
|     yy_switch_to_buffer(buffer, scanner); | ||||
| 
 | ||||
| @ -118,6 +119,12 @@ bool dice_set(dice_t d, dice_option_t opt, ...) | ||||
|     switch (opt) { | ||||
|     case DICEOPTION_AMOUNT: d->amount = va_arg(lst, uint32_t); break; | ||||
|     case DICEOPTION_SIDES: d->sides = va_arg(lst, uint32_t); break; | ||||
|     case DICEOPTION_ERROR: | ||||
|     { | ||||
|         char const *err = va_arg(lst, char const *); | ||||
|         free(d->error); | ||||
|         d->error = strdup(err); | ||||
|     } break; | ||||
| 
 | ||||
|     default: return false; | ||||
|     } | ||||
| @ -144,6 +151,12 @@ bool dice_get(dice_t d, dice_option_t opt, ...) | ||||
|         *ptr = d->sides; | ||||
|     } break; | ||||
| 
 | ||||
|     case DICEOPTION_ERROR: | ||||
|     { | ||||
|         char **ptr = va_arg(lst, char **); | ||||
|         *ptr = d->error; | ||||
|     } break; | ||||
| 
 | ||||
|     default: return false; | ||||
|     } | ||||
|     va_end(lst); | ||||
| @ -189,7 +202,16 @@ bool dice_evaluate(dice_t d, dice_result_t **res, size_t *reslen) | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void dice_update_consumned(dice_t d, int offset) | ||||
| void dice_update_consumed(dice_t d, int offset) | ||||
| { | ||||
|     d->consumed += offset; | ||||
| } | ||||
| 
 | ||||
| int dice_consumed(dice_t d) | ||||
| { | ||||
|     if (d == NULL) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return d->consumed; | ||||
| } | ||||
|  | ||||
| @ -9,6 +9,9 @@ | ||||
| struct dice_; | ||||
| typedef struct dice_ * dice_t; | ||||
| 
 | ||||
| struct dice_expression_; | ||||
| typedef struct dice_expression_ * dice_expression_t; | ||||
| 
 | ||||
| typedef struct { | ||||
|     int64_t result; | ||||
| } dice_result_t; | ||||
| @ -16,6 +19,7 @@ typedef struct { | ||||
| typedef enum { | ||||
|     DICEOPTION_AMOUNT = 0, | ||||
|     DICEOPTION_SIDES, | ||||
|     DICEOPTION_ERROR, | ||||
| } dice_option_t; | ||||
| 
 | ||||
| void dice_result_free(dice_result_t *r); | ||||
| @ -31,4 +35,9 @@ bool dice_get(dice_t d, dice_option_t opt, ...); | ||||
| int64_t dice_roll(dice_t d); | ||||
| bool dice_evaluate(dice_t d, dice_result_t **res, size_t *reslen); | ||||
| 
 | ||||
| dice_expression_t dice_expression_parse(char const *s, int *error); | ||||
| void dice_expression_free(dice_expression_t e); | ||||
| bool dice_expression_evaluate(dice_expression_t e, int64_t *result); | ||||
| bool dice_expression_print(dice_expression_t e); | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -5,15 +5,21 @@ | ||||
| #include "dice.h" | ||||
| #include "dice_parse.h" | ||||
| 
 | ||||
| extern void dice_update_consumed(dice_t d, int off); | ||||
| 
 | ||||
| %} | ||||
| 
 | ||||
| %% | ||||
| [0-9]+ { | ||||
|     dice_t d = yyget_extra(yyscanner); | ||||
|     dice_update_consumed(d, yyleng); | ||||
|     yylval->integer = atoi(yytext); | ||||
|     return TOK_INTEGER; | ||||
| } | ||||
| 
 | ||||
| [d] { | ||||
|     dice_t d = yyget_extra(yyscanner); | ||||
|     dice_update_consumed(d, yyleng); | ||||
|     return TOK_DICESEP; | ||||
| } | ||||
| %% | ||||
|  | ||||
| @ -10,6 +10,7 @@ extern int yylex(void *lval, void *scanner); | ||||
| 
 | ||||
| void yyerror(void *scanner, dice_t dice, char const *err) | ||||
| { | ||||
|     dice_set(dice, DICEOPTION_ERROR, err); | ||||
| } | ||||
| 
 | ||||
| int yywrap(void) | ||||
| @ -33,10 +34,14 @@ dice:           TOK_INTEGER TOK_DICESEP TOK_INTEGER | ||||
|                 { | ||||
|                     dice_set(dice, DICEOPTION_AMOUNT, $1); | ||||
|                     dice_set(dice, DICEOPTION_SIDES, $3); | ||||
| 
 | ||||
|                     YYACCEPT; | ||||
|                 } | ||||
|         |       TOK_DICESEP TOK_INTEGER | ||||
|                 { | ||||
|                     dice_set(dice, DICEOPTION_AMOUNT, 1); | ||||
|                     dice_set(dice, DICEOPTION_SIDES, $2); | ||||
| 
 | ||||
|                     YYACCEPT; | ||||
|                 } | ||||
|         ; | ||||
|  | ||||
							
								
								
									
										771
									
								
								lib/diceexpr.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										771
									
								
								lib/diceexpr.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,771 @@ | ||||
| /*
 | ||||
|  * TINYEXPR - Tiny recursive descent parser and evaluation engine in C | ||||
|  * | ||||
|  * Copyright (c) 2015, 2016 Lewis Van Winkle | ||||
|  * | ||||
|  * http://CodePlea.com
 | ||||
|  * | ||||
|  * This software is provided 'as-is', without any express or implied | ||||
|  * warranty. In no event will the authors be held liable for any damages | ||||
|  * arising from the use of this software. | ||||
|  * | ||||
|  * Permission is granted to anyone to use this software for any purpose, | ||||
|  * including commercial applications, and to alter it and redistribute it | ||||
|  * freely, subject to the following restrictions: | ||||
|  * | ||||
|  * 1. The origin of this software must not be misrepresented; you must not | ||||
|  * claim that you wrote the original software. If you use this software | ||||
|  * in a product, an acknowledgement in the product documentation would be | ||||
|  * appreciated but is not required. | ||||
|  * 2. Altered source versions must be plainly marked as such, and must not be | ||||
|  * misrepresented as being the original software. | ||||
|  * 3. This notice may not be removed or altered from any source distribution. | ||||
|  */ | ||||
| 
 | ||||
| /* COMPILE TIME OPTIONS */ | ||||
| 
 | ||||
| /* Exponentiation associativity:
 | ||||
| For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing. | ||||
| For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/ | ||||
| #define TE_POW_FROM_RIGHT | ||||
| 
 | ||||
| /* Logarithms
 | ||||
| For log = base 10 log do nothing | ||||
| For log = natural log uncomment the next line. */ | ||||
| /* #define TE_NAT_LOG */ | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <math.h> | ||||
| #include <string.h> | ||||
| #include <stdio.h> | ||||
| #include <limits.h> | ||||
| #include <ctype.h> | ||||
| 
 | ||||
| #include "dice.h" | ||||
| 
 | ||||
| #ifndef NAN | ||||
| #define NAN (0.0/0.0) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef INFINITY | ||||
| #define INFINITY (1.0/0.0) | ||||
| #endif | ||||
| 
 | ||||
| extern int dice_consumed(dice_t d); | ||||
| 
 | ||||
| typedef struct te_expr { | ||||
|     int type; | ||||
|     union { | ||||
|         double value; | ||||
|         const double *bound; | ||||
|         const void *function; | ||||
|         dice_t dice; | ||||
|     }; | ||||
|     void *parameters[1]; | ||||
| } te_expr; | ||||
| 
 | ||||
| enum { | ||||
|     TE_VARIABLE = 0, TE_DICE = 2, | ||||
| 
 | ||||
|     TE_FUNCTION0 = 8, TE_FUNCTION1, TE_FUNCTION2, TE_FUNCTION3, | ||||
|     TE_FUNCTION4, TE_FUNCTION5, TE_FUNCTION6, TE_FUNCTION7, | ||||
| 
 | ||||
|     TE_CLOSURE0 = 16, TE_CLOSURE1, TE_CLOSURE2, TE_CLOSURE3, | ||||
|     TE_CLOSURE4, TE_CLOSURE5, TE_CLOSURE6, TE_CLOSURE7, | ||||
| 
 | ||||
|     TE_FLAG_PURE = 64, | ||||
| }; | ||||
| 
 | ||||
| typedef struct te_variable { | ||||
|     const char *name; | ||||
|     const void *address; | ||||
|     int type; | ||||
|     void *context; | ||||
| } te_variable; | ||||
| 
 | ||||
| 
 | ||||
| typedef double (*te_fun2)(double, double); | ||||
| 
 | ||||
| enum { | ||||
|     TOK_NULL = TE_CLOSURE7+1, TOK_ERROR, TOK_END, TOK_SEP, | ||||
|     TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_VARIABLE, TOK_INFIX, TOK_DICE, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| enum {TE_CONSTANT = 1}; | ||||
| 
 | ||||
| 
 | ||||
| typedef struct state { | ||||
|     const char *start; | ||||
|     const char *next; | ||||
|     int type; | ||||
|     union { | ||||
|         double value; | ||||
|         const double *bound; | ||||
|         const void *function; | ||||
|         dice_t dice; | ||||
|     }; | ||||
|     void *context; | ||||
| 
 | ||||
|     const te_variable *lookup; | ||||
|     int lookup_len; | ||||
| } state; | ||||
| 
 | ||||
| static void te_free(te_expr *); | ||||
| 
 | ||||
| #define TYPE_MASK(TYPE) ((TYPE)&0x0000003F) | ||||
| 
 | ||||
| #define IS_PURE(TYPE) (((TYPE) & TE_FLAG_PURE) != 0) | ||||
| #define IS_FUNCTION(TYPE) (((TYPE) & TE_FUNCTION0) != 0) | ||||
| #define IS_CLOSURE(TYPE) (((TYPE) & TE_CLOSURE0) != 0) | ||||
| #define ARITY(TYPE) ( ((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE) & 0x00000007) : 0 ) | ||||
| #define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__}) | ||||
| 
 | ||||
| static te_expr *new_expr(const int type, const te_expr *parameters[]) { | ||||
|     const int arity = ARITY(type); | ||||
|     const int psize = sizeof(void*) * arity; | ||||
|     const int size = (sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0); | ||||
|     te_expr *ret = malloc(size); | ||||
|     memset(ret, 0, size); | ||||
|     if (arity && parameters) { | ||||
|         memcpy(ret->parameters, parameters, psize); | ||||
|     } | ||||
|     ret->type = type; | ||||
|     ret->bound = 0; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void te_free_parameters(te_expr *n) { | ||||
|     if (!n) return; | ||||
|     switch (TYPE_MASK(n->type)) { | ||||
|         case TE_FUNCTION7: case TE_CLOSURE7: te_free(n->parameters[6]); | ||||
|         case TE_FUNCTION6: case TE_CLOSURE6: te_free(n->parameters[5]); | ||||
|         case TE_FUNCTION5: case TE_CLOSURE5: te_free(n->parameters[4]); | ||||
|         case TE_FUNCTION4: case TE_CLOSURE4: te_free(n->parameters[3]); | ||||
|         case TE_FUNCTION3: case TE_CLOSURE3: te_free(n->parameters[2]); | ||||
|         case TE_FUNCTION2: case TE_CLOSURE2: te_free(n->parameters[1]); | ||||
|         case TE_FUNCTION1: case TE_CLOSURE1: te_free(n->parameters[0]); | ||||
|     } | ||||
|     if (TYPE_MASK(n->type) == TE_DICE) { | ||||
|         dice_free(n->dice); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void te_free(te_expr *n) { | ||||
|     if (!n) return; | ||||
|     te_free_parameters(n); | ||||
|     free(n); | ||||
| } | ||||
| 
 | ||||
| static double pi() {return 3.14159265358979323846;} | ||||
| static double e() {return 2.71828182845904523536;} | ||||
| static double fac(double a) {/* simplest version of fac */ | ||||
|     if (a < 0.0) | ||||
|         return NAN; | ||||
|     if (a > UINT_MAX) | ||||
|         return INFINITY; | ||||
|     unsigned int ua = (unsigned int)(a); | ||||
|     unsigned long int result = 1, i; | ||||
|     for (i = 1; i <= ua; i++) { | ||||
|         if (i > ULONG_MAX / result) | ||||
|             return INFINITY; | ||||
|         result *= i; | ||||
|     } | ||||
|     return (double)result; | ||||
| } | ||||
| static double ncr(double n, double r) { | ||||
|     if (n < 0.0 || r < 0.0 || n < r) return NAN; | ||||
|     if (n > UINT_MAX || r > UINT_MAX) return INFINITY; | ||||
|     unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i; | ||||
|     unsigned long int result = 1; | ||||
|     if (ur > un / 2) ur = un - ur; | ||||
|     for (i = 1; i <= ur; i++) { | ||||
|         if (result > ULONG_MAX / (un - ur + i)) | ||||
|             return INFINITY; | ||||
|         result *= un - ur + i; | ||||
|         result /= i; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| static double npr(double n, double r) {return ncr(n, r) * fac(r);} | ||||
| 
 | ||||
| static const te_variable functions[] = { | ||||
|     /* must be in alphabetical order */ | ||||
|     {"abs", fabs,     TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"acos", acos,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"asin", asin,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"atan", atan,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"atan2", atan2,  TE_FUNCTION2 | TE_FLAG_PURE, 0}, | ||||
|     {"ceil", ceil,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"cos", cos,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"cosh", cosh,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"e", e,          TE_FUNCTION0 | TE_FLAG_PURE, 0}, | ||||
|     {"exp", exp,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"fac", fac,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"floor", floor,  TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"ln", log,       TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
| #ifdef TE_NAT_LOG | ||||
|     {"log", log,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
| #else | ||||
|     {"log", log10,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
| #endif | ||||
|     {"log10", log10,  TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"ncr", ncr,      TE_FUNCTION2 | TE_FLAG_PURE, 0}, | ||||
|     {"npr", npr,      TE_FUNCTION2 | TE_FLAG_PURE, 0}, | ||||
|     {"pi", pi,        TE_FUNCTION0 | TE_FLAG_PURE, 0}, | ||||
|     {"pow", pow,      TE_FUNCTION2 | TE_FLAG_PURE, 0}, | ||||
|     {"sin", sin,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"sinh", sinh,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"sqrt", sqrt,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"tan", tan,      TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {"tanh", tanh,    TE_FUNCTION1 | TE_FLAG_PURE, 0}, | ||||
|     {0, 0, 0, 0} | ||||
| }; | ||||
| 
 | ||||
| static const te_variable *find_builtin(const char *name, int len) { | ||||
|     int imin = 0; | ||||
|     int imax = sizeof(functions) / sizeof(te_variable) - 2; | ||||
| 
 | ||||
|     /*Binary search.*/ | ||||
|     while (imax >= imin) { | ||||
|         const int i = (imin + ((imax-imin)/2)); | ||||
|         int c = strncmp(name, functions[i].name, len); | ||||
|         if (!c) c = '\0' - functions[i].name[len]; | ||||
|         if (c == 0) { | ||||
|             return functions + i; | ||||
|         } else if (c > 0) { | ||||
|             imin = i + 1; | ||||
|         } else { | ||||
|             imax = i - 1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static const te_variable *find_lookup(const state *s, const char *name, int len) { | ||||
|     int iters; | ||||
|     const te_variable *var; | ||||
|     if (!s->lookup) return 0; | ||||
| 
 | ||||
|     for (var = s->lookup, iters = s->lookup_len; iters; ++var, --iters) { | ||||
|         if (strncmp(name, var->name, len) == 0 && var->name[len] == '\0') { | ||||
|             return var; | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| static double add(double a, double b) {return a + b;} | ||||
| static double sub(double a, double b) {return a - b;} | ||||
| static double mul(double a, double b) {return a * b;} | ||||
| static double divide(double a, double b) {return a / b;} | ||||
| static double negate(double a) {return -a;} | ||||
| static double comma(double a, double b) {(void)a; return b;} | ||||
| 
 | ||||
| 
 | ||||
| void next_token(state *s) { | ||||
|     s->type = TOK_NULL; | ||||
| 
 | ||||
|     do { | ||||
| 
 | ||||
|         if (!*s->next){ | ||||
|             s->type = TOK_END; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (isdigit(s->next[0]) || s->next[0] == '.' || s->next[0] == 'd') { | ||||
|             /* first try reading a dice expression, if that fails, we go
 | ||||
|                back to trying a number instead */ | ||||
|             dice_t d = dice_parse(s->next); | ||||
|             if (d != NULL) { | ||||
|                 int consumed = dice_consumed(d); | ||||
|                 s->type = TOK_DICE; | ||||
|                 s->dice = d; | ||||
|                 s->next += consumed; | ||||
|             } else if (d == NULL) { | ||||
|                 /* Try reading a number. */ | ||||
|                 s->value = strtod(s->next, (char**)&s->next); | ||||
|                 s->type = TOK_NUMBER; | ||||
|             } | ||||
|         } else { | ||||
|             /* Look for a variable or builtin function call. */ | ||||
|             if (s->next[0] >= 'a' && s->next[0] <= 'z') { | ||||
|                 const char *start; | ||||
|                 start = s->next; | ||||
|                 while ((s->next[0] >= 'a' && s->next[0] <= 'z') || (s->next[0] >= '0' && s->next[0] <= '9') || (s->next[0] == '_')) s->next++; | ||||
| 
 | ||||
|                 const te_variable *var = find_lookup(s, start, s->next - start); | ||||
|                 if (!var) var = find_builtin(start, s->next - start); | ||||
| 
 | ||||
|                 if (!var) { | ||||
|                     s->type = TOK_ERROR; | ||||
|                 } else { | ||||
|                     switch(TYPE_MASK(var->type)) | ||||
|                     { | ||||
|                         case TE_VARIABLE: | ||||
|                             s->type = TOK_VARIABLE; | ||||
|                             s->bound = var->address; | ||||
|                             break; | ||||
| 
 | ||||
|                         case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: | ||||
|                         case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: | ||||
|                             s->context = var->context; | ||||
| 
 | ||||
|                         case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: | ||||
|                         case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: | ||||
|                             s->type = var->type; | ||||
|                             s->function = var->address; | ||||
|                             break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } else { | ||||
|                 /* Look for an operator or special character. */ | ||||
|                 switch (s->next++[0]) { | ||||
|                     case '+': s->type = TOK_INFIX; s->function = add; break; | ||||
|                     case '-': s->type = TOK_INFIX; s->function = sub; break; | ||||
|                     case '*': s->type = TOK_INFIX; s->function = mul; break; | ||||
|                     case '/': s->type = TOK_INFIX; s->function = divide; break; | ||||
|                     case '^': s->type = TOK_INFIX; s->function = pow; break; | ||||
|                     case '%': s->type = TOK_INFIX; s->function = fmod; break; | ||||
|                     case '(': s->type = TOK_OPEN; break; | ||||
|                     case ')': s->type = TOK_CLOSE; break; | ||||
|                     case ',': s->type = TOK_SEP; break; | ||||
|                     case ' ': case '\t': case '\n': case '\r': break; | ||||
|                     default: s->type = TOK_ERROR; break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } while (s->type == TOK_NULL); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *list(state *s); | ||||
| static te_expr *expr(state *s); | ||||
| static te_expr *power(state *s); | ||||
| 
 | ||||
| static te_expr *base(state *s) { | ||||
|     /* <base>      =    <constant> | <variable> | <function-0> {"(" ")"} | <function-1> <power> | <function-X> "(" <expr> {"," <expr>} ")" | "(" <list> ")" */ | ||||
|     te_expr *ret; | ||||
|     int arity; | ||||
| 
 | ||||
|     switch (TYPE_MASK(s->type)) { | ||||
|         case TOK_NUMBER: | ||||
|             ret = new_expr(TE_CONSTANT, 0); | ||||
|             ret->value = s->value; | ||||
|             next_token(s); | ||||
|             break; | ||||
| 
 | ||||
|         case TOK_VARIABLE: | ||||
|             ret = new_expr(TE_VARIABLE, 0); | ||||
|             ret->bound = s->bound; | ||||
|             next_token(s); | ||||
|             break; | ||||
| 
 | ||||
|         case TOK_DICE: | ||||
|             ret = new_expr(TE_DICE, 0); | ||||
|             ret->dice = s->dice; | ||||
|             next_token(s); | ||||
|             break; | ||||
| 
 | ||||
|         case TE_FUNCTION0: | ||||
|         case TE_CLOSURE0: | ||||
|             ret = new_expr(s->type, 0); | ||||
|             ret->function = s->function; | ||||
|             if (IS_CLOSURE(s->type)) ret->parameters[0] = s->context; | ||||
|             next_token(s); | ||||
|             if (s->type == TOK_OPEN) { | ||||
|                 next_token(s); | ||||
|                 if (s->type != TOK_CLOSE) { | ||||
|                     s->type = TOK_ERROR; | ||||
|                 } else { | ||||
|                     next_token(s); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case TE_FUNCTION1: | ||||
|         case TE_CLOSURE1: | ||||
|             ret = new_expr(s->type, 0); | ||||
|             ret->function = s->function; | ||||
|             if (IS_CLOSURE(s->type)) ret->parameters[1] = s->context; | ||||
|             next_token(s); | ||||
|             ret->parameters[0] = power(s); | ||||
|             break; | ||||
| 
 | ||||
|         case TE_FUNCTION2: case TE_FUNCTION3: case TE_FUNCTION4: | ||||
|         case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: | ||||
|         case TE_CLOSURE2: case TE_CLOSURE3: case TE_CLOSURE4: | ||||
|         case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: | ||||
|             arity = ARITY(s->type); | ||||
| 
 | ||||
|             ret = new_expr(s->type, 0); | ||||
|             ret->function = s->function; | ||||
|             if (IS_CLOSURE(s->type)) ret->parameters[arity] = s->context; | ||||
|             next_token(s); | ||||
| 
 | ||||
|             if (s->type != TOK_OPEN) { | ||||
|                 s->type = TOK_ERROR; | ||||
|             } else { | ||||
|                 int i; | ||||
|                 for(i = 0; i < arity; i++) { | ||||
|                     next_token(s); | ||||
|                     ret->parameters[i] = expr(s); | ||||
|                     if(s->type != TOK_SEP) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if(s->type != TOK_CLOSE || i != arity - 1) { | ||||
|                     s->type = TOK_ERROR; | ||||
|                 } else { | ||||
|                     next_token(s); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             break; | ||||
| 
 | ||||
|         case TOK_OPEN: | ||||
|             next_token(s); | ||||
|             ret = list(s); | ||||
|             if (s->type != TOK_CLOSE) { | ||||
|                 s->type = TOK_ERROR; | ||||
|             } else { | ||||
|                 next_token(s); | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             ret = new_expr(0, 0); | ||||
|             s->type = TOK_ERROR; | ||||
|             ret->value = NAN; | ||||
|             break; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *power(state *s) { | ||||
|     /* <power>     =    {("-" | "+")} <base> */ | ||||
|     int sign = 1; | ||||
|     while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { | ||||
|         if (s->function == sub) sign = -sign; | ||||
|         next_token(s); | ||||
|     } | ||||
| 
 | ||||
|     te_expr *ret; | ||||
| 
 | ||||
|     if (sign == 1) { | ||||
|         ret = base(s); | ||||
|     } else { | ||||
|         ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s)); | ||||
|         ret->function = negate; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| #ifdef TE_POW_FROM_RIGHT | ||||
| static te_expr *factor(state *s) { | ||||
|     /* <factor>    =    <power> {"^" <power>} */ | ||||
|     te_expr *ret = power(s); | ||||
| 
 | ||||
|     int neg = 0; | ||||
|     te_expr *insertion = 0; | ||||
| 
 | ||||
|     if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { | ||||
|         te_expr *se = ret->parameters[0]; | ||||
|         free(ret); | ||||
|         ret = se; | ||||
|         neg = 1; | ||||
|     } | ||||
| 
 | ||||
|     while (s->type == TOK_INFIX && (s->function == pow)) { | ||||
|         te_fun2 t = s->function; | ||||
|         next_token(s); | ||||
| 
 | ||||
|         if (insertion) { | ||||
|             /* Make exponentiation go right-to-left. */ | ||||
|             te_expr *insert = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s)); | ||||
|             insert->function = t; | ||||
|             insertion->parameters[1] = insert; | ||||
|             insertion = insert; | ||||
|         } else { | ||||
|             ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); | ||||
|             ret->function = t; | ||||
|             insertion = ret; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (neg) { | ||||
|         ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret); | ||||
|         ret->function = negate; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| #else | ||||
| static te_expr *factor(state *s) { | ||||
|     /* <factor>    =    <power> {"^" <power>} */ | ||||
|     te_expr *ret = power(s); | ||||
| 
 | ||||
|     while (s->type == TOK_INFIX && (s->function == pow)) { | ||||
|         te_fun2 t = s->function; | ||||
|         next_token(s); | ||||
|         ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); | ||||
|         ret->function = t; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *term(state *s) { | ||||
|     /* <term>      =    <factor> {("*" | "/" | "%") <factor>} */ | ||||
|     te_expr *ret = factor(s); | ||||
| 
 | ||||
|     while (s->type == TOK_INFIX && (s->function == mul || s->function == divide || s->function == fmod)) { | ||||
|         te_fun2 t = s->function; | ||||
|         next_token(s); | ||||
|         ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s)); | ||||
|         ret->function = t; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *expr(state *s) { | ||||
|     /* <expr>      =    <term> {("+" | "-") <term>} */ | ||||
|     te_expr *ret = term(s); | ||||
| 
 | ||||
|     while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { | ||||
|         te_fun2 t = s->function; | ||||
|         next_token(s); | ||||
|         ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s)); | ||||
|         ret->function = t; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *list(state *s) { | ||||
|     /* <list>      =    <expr> {"," <expr>} */ | ||||
|     te_expr *ret = expr(s); | ||||
| 
 | ||||
|     while (s->type == TOK_SEP) { | ||||
|         next_token(s); | ||||
|         ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s)); | ||||
|         ret->function = comma; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #define TE_FUN(...) ((double(*)(__VA_ARGS__))n->function) | ||||
| #define M(e) te_eval(n->parameters[e]) | ||||
| 
 | ||||
| 
 | ||||
| double te_eval(const te_expr *n) { | ||||
|     if (!n) return NAN; | ||||
| 
 | ||||
|     switch(TYPE_MASK(n->type)) { | ||||
|         case TE_CONSTANT: return n->value; | ||||
|         case TE_VARIABLE: return *n->bound; | ||||
|         case TE_DICE: return (double)(dice_roll(n->dice) * 1.0); | ||||
| 
 | ||||
|         case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: | ||||
|         case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: | ||||
|             switch(ARITY(n->type)) { | ||||
|                 case 0: return TE_FUN(void)(); | ||||
|                 case 1: return TE_FUN(double)(M(0)); | ||||
|                 case 2: return TE_FUN(double, double)(M(0), M(1)); | ||||
|                 case 3: return TE_FUN(double, double, double)(M(0), M(1), M(2)); | ||||
|                 case 4: return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3)); | ||||
|                 case 5: return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4)); | ||||
|                 case 6: return TE_FUN(double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5)); | ||||
|                 case 7: return TE_FUN(double, double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5), M(6)); | ||||
|                 default: return NAN; | ||||
|             } | ||||
| 
 | ||||
|         case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: | ||||
|         case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: | ||||
|             switch(ARITY(n->type)) { | ||||
|                 case 0: return TE_FUN(void*)(n->parameters[0]); | ||||
|                 case 1: return TE_FUN(void*, double)(n->parameters[1], M(0)); | ||||
|                 case 2: return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1)); | ||||
|                 case 3: return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2)); | ||||
|                 case 4: return TE_FUN(void*, double, double, double, double)(n->parameters[4], M(0), M(1), M(2), M(3)); | ||||
|                 case 5: return TE_FUN(void*, double, double, double, double, double)(n->parameters[5], M(0), M(1), M(2), M(3), M(4)); | ||||
|                 case 6: return TE_FUN(void*, double, double, double, double, double, double)(n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5)); | ||||
|                 case 7: return TE_FUN(void*, double, double, double, double, double, double, double)(n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6)); | ||||
|                 default: return NAN; | ||||
|             } | ||||
| 
 | ||||
|         default: return NAN; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #undef TE_FUN | ||||
| #undef M | ||||
| 
 | ||||
| static void optimize(te_expr *n) { | ||||
|     /* Evaluates as much as possible. */ | ||||
|     if (n->type == TE_CONSTANT) return; | ||||
|     if (n->type == TE_VARIABLE) return; | ||||
| 
 | ||||
|     /* Only optimize out functions flagged as pure. */ | ||||
|     if (IS_PURE(n->type)) { | ||||
|         const int arity = ARITY(n->type); | ||||
|         int known = 1; | ||||
|         int i; | ||||
|         for (i = 0; i < arity; ++i) { | ||||
|             optimize(n->parameters[i]); | ||||
|             if (((te_expr*)(n->parameters[i]))->type != TE_CONSTANT) { | ||||
|                 known = 0; | ||||
|             } | ||||
|         } | ||||
|         if (known) { | ||||
|             const double value = te_eval(n); | ||||
|             te_free_parameters(n); | ||||
|             n->type = TE_CONSTANT; | ||||
|             n->value = value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static te_expr *te_compile(const char *expression, const te_variable *variables, | ||||
|                            int var_count, int *error) { | ||||
|     state s; | ||||
|     s.start = s.next = expression; | ||||
|     s.lookup = variables; | ||||
|     s.lookup_len = var_count; | ||||
| 
 | ||||
|     next_token(&s); | ||||
|     te_expr *root = list(&s); | ||||
| 
 | ||||
|     if (s.type != TOK_END) { | ||||
|         te_free(root); | ||||
|         if (error) { | ||||
|             *error = (s.next - s.start); | ||||
|             if (*error == 0) *error = 1; | ||||
|         } | ||||
|         return 0; | ||||
|     } else { | ||||
|         optimize(root); | ||||
|         if (error) *error = 0; | ||||
|         return root; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static double te_interp(const char *expression, int *error) { | ||||
|     te_expr *n = te_compile(expression, 0, 0, error); | ||||
|     double ret; | ||||
|     if (n) { | ||||
|         ret = te_eval(n); | ||||
|         te_free(n); | ||||
|     } else { | ||||
|         ret = NAN; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static void pn (const te_expr *n, int depth) { | ||||
|     int i, arity; | ||||
|     printf("%*s", depth, ""); | ||||
| 
 | ||||
|     switch(TYPE_MASK(n->type)) { | ||||
|     case TE_CONSTANT: printf("%f\n", n->value); break; | ||||
|     case TE_VARIABLE: printf("bound %p\n", n->bound); break; | ||||
| 
 | ||||
|     case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: | ||||
|     case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: | ||||
|     case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: | ||||
|     case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: | ||||
|          arity = ARITY(n->type); | ||||
|          printf("f%d", arity); | ||||
|          for(i = 0; i < arity; i++) { | ||||
|              printf(" %p", n->parameters[i]); | ||||
|          } | ||||
|          printf("\n"); | ||||
|          for(i = 0; i < arity; i++) { | ||||
|              pn(n->parameters[i], depth + 1); | ||||
|          } | ||||
|          break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void te_print(const te_expr *n) { | ||||
|     pn(n, 0); | ||||
| } | ||||
| 
 | ||||
| struct dice_expression_ | ||||
| { | ||||
|     te_expr *expr; | ||||
| }; | ||||
| 
 | ||||
| void dice_expression_free(dice_expression_t e) | ||||
| { | ||||
|     if (e == NULL) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     te_free(e->expr); | ||||
|     free(e); | ||||
| } | ||||
| 
 | ||||
| dice_expression_t dice_expression_parse(char const *n, int *error) | ||||
| { | ||||
|     dice_expression_t e = NULL; | ||||
| 
 | ||||
|     e = calloc(1, sizeof(struct dice_expression_)); | ||||
|     if (e == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     e->expr = te_compile(n, 0, 0, error); | ||||
|     if (e->expr == NULL) { | ||||
|         free(e); | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     return e; | ||||
| } | ||||
| 
 | ||||
| bool dice_expression_evaluate(dice_expression_t e, int64_t *result) | ||||
| { | ||||
|     double val; | ||||
|     if (e == NULL || e->expr == NULL) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     val = te_eval(e->expr); | ||||
|     if (val == NAN) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     *result = (int64_t)trunc(val); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool dice_expression_print(dice_expression_t e) | ||||
| { | ||||
|     if (e == NULL || e->expr == NULL) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     te_print(e->expr); | ||||
|     return true; | ||||
| } | ||||
| @ -1,6 +1,7 @@ | ||||
| bin_PROGRAMS =	test_dice_simple_roll \
 | ||||
| 		test_dice_parse \
 | ||||
| 		test_dice_evaluate | ||||
| 		test_dice_evaluate \
 | ||||
| 		test_expr_parse | ||||
| 
 | ||||
| AM_CFLAGS =	-I../lib ${CMOCKA_CFLAGS} | ||||
| AM_LDFLAGS =	${CMOCKA_LIBS} ../libdice.la | ||||
|  | ||||
| @ -6,6 +6,10 @@ | ||||
| #include <string.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| /* private library function
 | ||||
|  */ | ||||
| int dice_consumed(dice_t d); | ||||
| 
 | ||||
| static void test_dice_parse_none(void **data) | ||||
| { | ||||
|     dice_t d = dice_parse(""); | ||||
| @ -31,6 +35,8 @@ static void test_dice_parse_amount_sides(void **data) | ||||
|     assert_true(dice_get(d, DICEOPTION_SIDES, &i)); | ||||
|     assert_int_equal(i, 10); | ||||
| 
 | ||||
|     assert_int_equal(dice_consumed(d), 4); | ||||
| 
 | ||||
|     dice_free(d); | ||||
| } | ||||
| 
 | ||||
| @ -44,6 +50,27 @@ static void test_dice_parse_sides(void **data) | ||||
|     assert_true(dice_get(d, DICEOPTION_SIDES, &i)); | ||||
|     assert_int_equal(i, 12); | ||||
| 
 | ||||
|     assert_int_equal(dice_consumed(d), 3); | ||||
| 
 | ||||
|     dice_free(d); | ||||
| } | ||||
| 
 | ||||
| static void test_dice_parse_big(void **data) | ||||
| { | ||||
|     char const *dice_str = "1000d120000"; | ||||
|     dice_t d = dice_parse(dice_str); | ||||
|     int i = 0; | ||||
| 
 | ||||
|     assert_non_null(d); | ||||
| 
 | ||||
|     assert_true(dice_get(d, DICEOPTION_SIDES, &i)); | ||||
|     assert_int_equal(i, 120000); | ||||
| 
 | ||||
|     assert_true(dice_get(d, DICEOPTION_AMOUNT, &i)); | ||||
|     assert_int_equal(i, 1000); | ||||
| 
 | ||||
|     assert_int_equal(dice_consumed(d), strlen(dice_str)); | ||||
| 
 | ||||
|     dice_free(d); | ||||
| } | ||||
| 
 | ||||
| @ -54,6 +81,7 @@ int main(int ac, char **av) | ||||
|         cmocka_unit_test(test_dice_parse_amount), | ||||
|         cmocka_unit_test(test_dice_parse_amount_sides), | ||||
|         cmocka_unit_test(test_dice_parse_sides), | ||||
|         cmocka_unit_test(test_dice_parse_big), | ||||
|     }; | ||||
| 
 | ||||
|     return cmocka_run_group_tests(tests, NULL, NULL); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user