diff --git a/.gitignore b/.gitignore index 8afbcaf1b..e6de101f1 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ dbtools/grepdb dbtools/pwutil pcre2 pcre2-10.* +game/reboot.db +plugins/math.so diff --git a/configure b/configure index 74a8a67fe..28ef38d58 100755 --- a/configure +++ b/configure @@ -10863,7 +10863,7 @@ done CC="${SAVED_CC}" CFLAGS="${SAVED_CFLAGS}" -LIBS="${SAVED_LIBS}" +LIBS="${SAVED_LIBS} -ldl" @@ -11297,7 +11297,7 @@ if test -d pcre2 ; then rm -rf pcre2/ fi CPPFLAGS="${CPPFLAGS} -I../pcre2/include" -LDFLAGS="${LDFLAGS} ../pcre2/lib/libpcre2-8.a" +LDFLAGS="${LDFLAGS} ../pcre2/lib/libpcre2-8.a -rdynamic -Wl,--unresolved-symbols=ignore-in-object-files" $as_echo "#define HAVE_PCRE 1" >>confdefs.h diff --git a/hdrs/conf.h b/hdrs/conf.h index df6c024f9..a1572d618 100644 --- a/hdrs/conf.h +++ b/hdrs/conf.h @@ -373,6 +373,7 @@ struct options_table { connlog_db[FILE_PATH_LEN]; /**< Sqlite3 file to use for connection logs. */ char dict_file[FILE_PATH_LEN]; /**< List of words to load into suggest() db */ char colors_file[FILE_PATH_LEN]; /**< JSON file holding the colors database */ + char plugins_dir[FILE_PATH_LEN]; /**< Directory for where plugins are found based on the penn-root dir */ }; typedef struct mssp MSSP; @@ -548,6 +549,8 @@ int can_view_config_option(dbref player, PENNCONF *opt); #define READ_REMOTE_DESC (options.read_remote_desc) +#define PLUGINS_DIR (options.plugins_dir) + typedef struct globals_table GLOBALTAB; /** State of the MUSH diff --git a/hdrs/plugin.h b/hdrs/plugin.h new file mode 100644 index 000000000..2ace64651 --- /dev/null +++ b/hdrs/plugin.h @@ -0,0 +1,32 @@ +/** + * \file plugin.h + * + * \brief Routines for Penn's plugin system. + */ + +#ifndef __PENNPLUGIN_H +#define __PENNPLUGIN_H + +typedef struct plugin_info { + char *name; + char *author; + char *app_version; + int version_id; + char shortdesc[30]; + char description[BUFFER_LEN]; +} PLUGIN_INFO; + +typedef struct penn_plugin { + void* handle; + char *name; + char file[256]; + int id; + PLUGIN_INFO *info; +} PENN_PLUGIN; + +extern HASHTAB plugins; + +extern void load_plugins(); +extern void unload_plugins(); + +#endif /* __PENN_PLUGIN_H */ diff --git a/hdrs/switches.h b/hdrs/switches.h index 173548d1e..33c9b835d 100644 --- a/hdrs/switches.h +++ b/hdrs/switches.h @@ -2,196 +2,201 @@ #ifndef SWITCHES_H #define SWITCHES_H #define SWITCH_ACCESS 1 -#define SWITCH_ADD 2 -#define SWITCH_AFTER 3 -#define SWITCH_ALIAS 4 -#define SWITCH_ALL 5 -#define SWITCH_ALLOCATIONS 6 -#define SWITCH_ANY 7 -#define SWITCH_ATTRIBS 8 -#define SWITCH_BAN 9 -#define SWITCH_BEFORE 10 -#define SWITCH_BRIEF 11 -#define SWITCH_BUFFER 12 -#define SWITCH_BUILTIN 13 -#define SWITCH_CHECK 14 -#define SWITCH_CHOWN 15 -#define SWITCH_CHUNKS 16 -#define SWITCH_CLEAR 17 -#define SWITCH_CLEARREGS 18 -#define SWITCH_CLONE 19 -#define SWITCH_CMD 20 -#define SWITCH_COLNAMES 21 -#define SWITCH_COMBINE 22 -#define SWITCH_COMMANDS 23 -#define SWITCH_CONN 24 -#define SWITCH_CONNECT 25 -#define SWITCH_CONNECTED 26 -#define SWITCH_CONTENTS 27 -#define SWITCH_COUNT 28 -#define SWITCH_CREATE 29 -#define SWITCH_CSTATS 30 -#define SWITCH_DB 31 -#define SWITCH_DEBUG 32 -#define SWITCH_DECOMPILE 33 -#define SWITCH_DELETE 34 -#define SWITCH_DELIMIT 35 -#define SWITCH_DESCRIBE 36 -#define SWITCH_DESTROY 37 -#define SWITCH_DISABLE 38 -#define SWITCH_DOWN 39 -#define SWITCH_DSTATS 40 -#define SWITCH_EMIT 41 -#define SWITCH_ENABLE 42 -#define SWITCH_ENUM 43 -#define SWITCH_EQSPLIT 44 -#define SWITCH_ERR 45 -#define SWITCH_EXITS 46 -#define SWITCH_EXTEND 47 -#define SWITCH_FILE 48 -#define SWITCH_FIRST 49 -#define SWITCH_FLAGS 50 -#define SWITCH_FOLDERS 51 -#define SWITCH_FORWARD 52 -#define SWITCH_FREESPACE 53 -#define SWITCH_FSTATS 54 -#define SWITCH_FULL 55 -#define SWITCH_FUNCTIONS 56 -#define SWITCH_FWD 57 -#define SWITCH_GAG 58 -#define SWITCH_GENERATE 59 -#define SWITCH_GLOBALS 60 -#define SWITCH_HEADER 61 -#define SWITCH_HERE 62 -#define SWITCH_HIDE 63 -#define SWITCH_IFELSE 64 -#define SWITCH_IGNORE 65 -#define SWITCH_IGSWITCH 66 -#define SWITCH_ILIST 67 -#define SWITCH_INLINE 68 -#define SWITCH_INPLACE 69 -#define SWITCH_INSIDE 70 -#define SWITCH_INVENTORY 71 -#define SWITCH_IPRINT 72 -#define SWITCH_JOIN 73 -#define SWITCH_JSON 74 -#define SWITCH_LEAVE 75 -#define SWITCH_LETTER 76 -#define SWITCH_LIMIT 77 -#define SWITCH_LIST 78 -#define SWITCH_LOCAL 79 -#define SWITCH_LOCALIZE 80 -#define SWITCH_LOCKS 81 -#define SWITCH_LOWERCASE 82 -#define SWITCH_LSARGS 83 -#define SWITCH_MATCH 84 -#define SWITCH_ME 85 -#define SWITCH_MEMBERS 86 -#define SWITCH_MOD 87 -#define SWITCH_MOGRIFIER 88 -#define SWITCH_MORTAL 89 -#define SWITCH_MOTD 90 -#define SWITCH_MUTE 91 -#define SWITCH_NAME 92 -#define SWITCH_NO 93 -#define SWITCH_NOBREAK 94 -#define SWITCH_NOCASE 95 -#define SWITCH_NOEVAL 96 -#define SWITCH_NOFLAGCOPY 97 -#define SWITCH_NOFORK 98 -#define SWITCH_NOISY 99 -#define SWITCH_NOPARSE 100 -#define SWITCH_NOSIG 101 -#define SWITCH_NOSPACE 102 -#define SWITCH_NOSPOOF 103 -#define SWITCH_NOTIFY 104 -#define SWITCH_NUKE 105 -#define SWITCH_OEMIT 106 -#define SWITCH_OFF 107 -#define SWITCH_ON 108 -#define SWITCH_OPAQUE 109 -#define SWITCH_OUTSIDE 110 -#define SWITCH_OVERRIDE 111 -#define SWITCH_PAGING 112 -#define SWITCH_PANIC 113 -#define SWITCH_PARANOID 114 -#define SWITCH_PARENT 115 -#define SWITCH_PLAYER 116 -#define SWITCH_PLAYERS 117 -#define SWITCH_PORT 118 -#define SWITCH_POST 119 -#define SWITCH_POWERS 120 -#define SWITCH_PREFIX 121 -#define SWITCH_PRESERVE 122 -#define SWITCH_PRINT 123 -#define SWITCH_PRIVS 124 -#define SWITCH_PURGE 125 -#define SWITCH_PUT 126 -#define SWITCH_QUERY 127 -#define SWITCH_QUEUED 128 -#define SWITCH_QUICK 129 -#define SWITCH_QUIET 130 -#define SWITCH_READ 131 -#define SWITCH_REBOOT 132 -#define SWITCH_RECALL 133 -#define SWITCH_REGEXP 134 -#define SWITCH_REGIONS 135 -#define SWITCH_REGISTER 136 -#define SWITCH_REMIT 137 -#define SWITCH_REMOVE 138 -#define SWITCH_RENAME 139 -#define SWITCH_RESTART 140 -#define SWITCH_RESTORE 141 -#define SWITCH_RESTRICT 142 -#define SWITCH_RETRACT 143 -#define SWITCH_RETROACTIVE 144 -#define SWITCH_REVIEW 145 -#define SWITCH_ROOM 146 -#define SWITCH_ROOMS 147 -#define SWITCH_ROTATE 148 -#define SWITCH_RSARGS 149 -#define SWITCH_RSNOPARSE 150 -#define SWITCH_SAVE 151 -#define SWITCH_SEARCH 152 -#define SWITCH_SEE 153 -#define SWITCH_SEEFLAG 154 -#define SWITCH_SELF 155 -#define SWITCH_SEND 156 -#define SWITCH_SET 157 -#define SWITCH_SETQ 158 -#define SWITCH_SILENT 159 -#define SWITCH_SKIPDEFAULTS 160 -#define SWITCH_SPEAK 161 -#define SWITCH_SPOOF 162 -#define SWITCH_STATS 163 -#define SWITCH_STATUS 164 -#define SWITCH_SUMMARY 165 -#define SWITCH_TABLES 166 -#define SWITCH_TAG 167 -#define SWITCH_TELEPORT 168 -#define SWITCH_TF 169 -#define SWITCH_THINGS 170 -#define SWITCH_TITLE 171 -#define SWITCH_TRACE 172 -#define SWITCH_TRIM 173 -#define SWITCH_TYPE 174 -#define SWITCH_UNCLEAR 175 -#define SWITCH_UNCOMBINE 176 -#define SWITCH_UNFOLDER 177 -#define SWITCH_UNGAG 178 -#define SWITCH_UNHIDE 179 -#define SWITCH_UNMUTE 180 -#define SWITCH_UNREAD 181 -#define SWITCH_UNTAG 182 -#define SWITCH_UNTIL 183 -#define SWITCH_URGENT 184 -#define SWITCH_USEFLAG 185 -#define SWITCH_WHAT 186 -#define SWITCH_WHO 187 -#define SWITCH_WILD 188 -#define SWITCH_WIPE 189 -#define SWITCH_WIZ 190 -#define SWITCH_WIZARD 191 -#define SWITCH_YES 192 -#define SWITCH_ZONE 193 +#define SWITCH_ACTIVE 2 +#define SWITCH_ADD 3 +#define SWITCH_AFTER 4 +#define SWITCH_ALIAS 5 +#define SWITCH_ALL 6 +#define SWITCH_ALLOCATIONS 7 +#define SWITCH_ANY 8 +#define SWITCH_ATTRIBS 9 +#define SWITCH_BAN 10 +#define SWITCH_BEFORE 11 +#define SWITCH_BRIEF 12 +#define SWITCH_BUFFER 13 +#define SWITCH_BUILTIN 14 +#define SWITCH_CHECK 15 +#define SWITCH_CHOWN 16 +#define SWITCH_CHUNKS 17 +#define SWITCH_CLEAR 18 +#define SWITCH_CLEARREGS 19 +#define SWITCH_CLONE 20 +#define SWITCH_CMD 21 +#define SWITCH_COLNAMES 22 +#define SWITCH_COMBINE 23 +#define SWITCH_COMMANDS 24 +#define SWITCH_CONN 25 +#define SWITCH_CONNECT 26 +#define SWITCH_CONNECTED 27 +#define SWITCH_CONTENTS 28 +#define SWITCH_COUNT 29 +#define SWITCH_CREATE 30 +#define SWITCH_CSTATS 31 +#define SWITCH_DB 32 +#define SWITCH_DEBUG 33 +#define SWITCH_DECOMPILE 34 +#define SWITCH_DELETE 35 +#define SWITCH_DELIMIT 36 +#define SWITCH_DESCRIBE 37 +#define SWITCH_DESTROY 38 +#define SWITCH_DISABLE 39 +#define SWITCH_DOWN 40 +#define SWITCH_DSTATS 41 +#define SWITCH_EMIT 42 +#define SWITCH_ENABLE 43 +#define SWITCH_ENUM 44 +#define SWITCH_EQSPLIT 45 +#define SWITCH_ERR 46 +#define SWITCH_EXITS 47 +#define SWITCH_EXTEND 48 +#define SWITCH_FILE 49 +#define SWITCH_FIRST 50 +#define SWITCH_FLAGS 51 +#define SWITCH_FOLDERS 52 +#define SWITCH_FORWARD 53 +#define SWITCH_FREESPACE 54 +#define SWITCH_FSTATS 55 +#define SWITCH_FULL 56 +#define SWITCH_FUNCTIONS 57 +#define SWITCH_FWD 58 +#define SWITCH_GAG 59 +#define SWITCH_GENERATE 60 +#define SWITCH_GLOBALS 61 +#define SWITCH_HEADER 62 +#define SWITCH_HERE 63 +#define SWITCH_HIDE 64 +#define SWITCH_IFELSE 65 +#define SWITCH_IGNORE 66 +#define SWITCH_IGSWITCH 67 +#define SWITCH_ILIST 68 +#define SWITCH_INFO 69 +#define SWITCH_INLINE 70 +#define SWITCH_INPLACE 71 +#define SWITCH_INSIDE 72 +#define SWITCH_INVENTORY 73 +#define SWITCH_IPRINT 74 +#define SWITCH_JOIN 75 +#define SWITCH_JSON 76 +#define SWITCH_LEAVE 77 +#define SWITCH_LETTER 78 +#define SWITCH_LIMIT 79 +#define SWITCH_LIST 80 +#define SWITCH_LOAD 81 +#define SWITCH_LOCAL 82 +#define SWITCH_LOCALIZE 83 +#define SWITCH_LOCKS 84 +#define SWITCH_LOWERCASE 85 +#define SWITCH_LSARGS 86 +#define SWITCH_MATCH 87 +#define SWITCH_ME 88 +#define SWITCH_MEMBERS 89 +#define SWITCH_MOD 90 +#define SWITCH_MOGRIFIER 91 +#define SWITCH_MORTAL 92 +#define SWITCH_MOTD 93 +#define SWITCH_MUTE 94 +#define SWITCH_NAME 95 +#define SWITCH_NO 96 +#define SWITCH_NOBREAK 97 +#define SWITCH_NOCASE 98 +#define SWITCH_NOEVAL 99 +#define SWITCH_NOFLAGCOPY 100 +#define SWITCH_NOFORK 101 +#define SWITCH_NOISY 102 +#define SWITCH_NOPARSE 103 +#define SWITCH_NOSIG 104 +#define SWITCH_NOSPACE 105 +#define SWITCH_NOSPOOF 106 +#define SWITCH_NOTIFY 107 +#define SWITCH_NUKE 108 +#define SWITCH_OEMIT 109 +#define SWITCH_OFF 110 +#define SWITCH_ON 111 +#define SWITCH_OPAQUE 112 +#define SWITCH_OUTSIDE 113 +#define SWITCH_OVERRIDE 114 +#define SWITCH_PAGING 115 +#define SWITCH_PANIC 116 +#define SWITCH_PARANOID 117 +#define SWITCH_PARENT 118 +#define SWITCH_PLAYER 119 +#define SWITCH_PLAYERS 120 +#define SWITCH_PORT 121 +#define SWITCH_POST 122 +#define SWITCH_POWERS 123 +#define SWITCH_PREFIX 124 +#define SWITCH_PRESERVE 125 +#define SWITCH_PRINT 126 +#define SWITCH_PRIVS 127 +#define SWITCH_PURGE 128 +#define SWITCH_PUT 129 +#define SWITCH_QUERY 130 +#define SWITCH_QUEUED 131 +#define SWITCH_QUICK 132 +#define SWITCH_QUIET 133 +#define SWITCH_READ 134 +#define SWITCH_REBOOT 135 +#define SWITCH_RECALL 136 +#define SWITCH_REGEXP 137 +#define SWITCH_REGIONS 138 +#define SWITCH_REGISTER 139 +#define SWITCH_RELOAD 140 +#define SWITCH_REMIT 141 +#define SWITCH_REMOVE 142 +#define SWITCH_RENAME 143 +#define SWITCH_RESTART 144 +#define SWITCH_RESTORE 145 +#define SWITCH_RESTRICT 146 +#define SWITCH_RETRACT 147 +#define SWITCH_RETROACTIVE 148 +#define SWITCH_REVIEW 149 +#define SWITCH_ROOM 150 +#define SWITCH_ROOMS 151 +#define SWITCH_ROTATE 152 +#define SWITCH_RSARGS 153 +#define SWITCH_RSNOPARSE 154 +#define SWITCH_SAVE 155 +#define SWITCH_SEARCH 156 +#define SWITCH_SEE 157 +#define SWITCH_SEEFLAG 158 +#define SWITCH_SELF 159 +#define SWITCH_SEND 160 +#define SWITCH_SET 161 +#define SWITCH_SETQ 162 +#define SWITCH_SILENT 163 +#define SWITCH_SKIPDEFAULTS 164 +#define SWITCH_SPEAK 165 +#define SWITCH_SPOOF 166 +#define SWITCH_STATS 167 +#define SWITCH_STATUS 168 +#define SWITCH_SUMMARY 169 +#define SWITCH_TABLES 170 +#define SWITCH_TAG 171 +#define SWITCH_TELEPORT 172 +#define SWITCH_TF 173 +#define SWITCH_THINGS 174 +#define SWITCH_TITLE 175 +#define SWITCH_TRACE 176 +#define SWITCH_TRIM 177 +#define SWITCH_TYPE 178 +#define SWITCH_UNCLEAR 179 +#define SWITCH_UNCOMBINE 180 +#define SWITCH_UNFOLDER 181 +#define SWITCH_UNGAG 182 +#define SWITCH_UNHIDE 183 +#define SWITCH_UNLOAD 184 +#define SWITCH_UNMUTE 185 +#define SWITCH_UNREAD 186 +#define SWITCH_UNTAG 187 +#define SWITCH_UNTIL 188 +#define SWITCH_URGENT 189 +#define SWITCH_USEFLAG 190 +#define SWITCH_WHAT 191 +#define SWITCH_WHO 192 +#define SWITCH_WILD 193 +#define SWITCH_WIPE 194 +#define SWITCH_WIZ 195 +#define SWITCH_WIZARD 196 +#define SWITCH_YES 197 +#define SWITCH_ZONE 198 #endif /* SWITCHES_H */ diff --git a/plugins/math/compile.sh b/plugins/math/compile.sh new file mode 100755 index 000000000..82eb855ae --- /dev/null +++ b/plugins/math/compile.sh @@ -0,0 +1,4 @@ +clang -c -Wall -Wshadow -O2 -Wl,-z,notext tinyexpr.c -o tinyexpr.o +clang -c -Wall -Wshadow -O2 -Wl,-z,notext example.c -o example.o +clang -Wall -Wshadow -O2 -Wl,-z,notext -o example example.o tinyexpr.o -lm +clang -shared -fPIC -Wl,-z,notext -O2 -I../../ -I../../hdrs -I../../pcre2/include -o ../math.so math.c tinyexpr.o -lm diff --git a/plugins/math/example b/plugins/math/example new file mode 100755 index 000000000..b2c18164a Binary files /dev/null and b/plugins/math/example differ diff --git a/plugins/math/example.c b/plugins/math/example.c new file mode 100644 index 000000000..a761fc5fb --- /dev/null +++ b/plugins/math/example.c @@ -0,0 +1,11 @@ +#include "tinyexpr.h" +#include + +int main(int argc, char *argv[]) +{ +/* const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)"; */ + const char *c = "2+2"; + double r = te_interp(c, 0); + printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r); + return 0; +} diff --git a/plugins/math/math.c b/plugins/math/math.c new file mode 100644 index 000000000..a7077b28f --- /dev/null +++ b/plugins/math/math.c @@ -0,0 +1,57 @@ +#include "tinyexpr.h" + +#include "config.h" + +#include "conf.h" +#include "externs.h" +#include "parse.h" +#include "function.h" +#include "strutil.h" + +struct plugin_info { + char *name; + char *author; + char *app_version; + int version_id; + char shortdesc[30]; + char description[BUFFER_LEN]; +}; + +FUNCTION(local_fun_tinyexpr) +{ + char *c; + int error; + double r; + + if (!args[0] || !*args[0]) { + safe_str("#-1 NO MATH EXPRESSION GIVEN!", buff, bp); + return; + } + + c = args[0]; + r = te_interp(c, &error); + + if (error != 0) { + safe_format(buff, bp, "Error at character %d for expression %s.", error, c); + return; + } + + safe_str(unparse_number(r), buff, bp); + + return; +} + +void setupMathFunction() { + function_add("TINYEXPR", local_fun_tinyexpr, 1, 1, FN_REG | FN_STRIPANSI | FN_NOPARSE); +} + +struct plugin_info p = { "TinyExpr Math Library", "Ray Herring", "1.0.0", 100000, "Run math related equations", "Run math related equations that aren't as easy to do using Penn's standard math functions" }; + +struct plugin_info *get_plugin() { + return &p; +} + +int plugin_init() { + setupMathFunction(); + return 1; +} diff --git a/plugins/math/tinyexpr.c b/plugins/math/tinyexpr.c new file mode 100755 index 000000000..37a4e7c6f --- /dev/null +++ b/plugins/math/tinyexpr.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: Zlib +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015-2020 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 "tinyexpr.h" +#include +#include +#include +#include +#include +#include + +#ifndef NAN +#define NAN (0.0/0.0) +#endif + +#ifndef INFINITY +#define INFINITY (1.0/0.0) +#endif + + +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 +}; + + +enum {TE_CONSTANT = 1}; + + +typedef struct state { + const char *start; + const char *next; + int type; + union {double value; const double *bound; const void *function;}; + void *context; + + const te_variable *lookup; + int lookup_len; +} state; + + +#define TYPE_MASK(TYPE) ((TYPE)&0x0000001F) + +#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; +} + + +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]); /* Falls through. */ + case TE_FUNCTION6: case TE_CLOSURE6: te_free(n->parameters[5]); /* Falls through. */ + case TE_FUNCTION5: case TE_CLOSURE5: te_free(n->parameters[4]); /* Falls through. */ + case TE_FUNCTION4: case TE_CLOSURE4: te_free(n->parameters[3]); /* Falls through. */ + case TE_FUNCTION3: case TE_CLOSURE3: te_free(n->parameters[2]); /* Falls through. */ + case TE_FUNCTION2: case TE_CLOSURE2: te_free(n->parameters[1]); /* Falls through. */ + case TE_FUNCTION1: case TE_CLOSURE1: te_free(n->parameters[0]); + } +} + + +void te_free(te_expr *n) { + if (!n) return; + te_free_parameters(n); + free(n); +} + + +static double pi(void) {return 3.14159265358979323846;} +static double e(void) {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);} + +#ifdef _MSC_VER +#pragma function (ceil) +#pragma function (floor) +#endif + +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 te_next_token(state *s) { + s->type = TOK_NULL; + + do { + + if (!*s->next){ + s->type = TOK_END; + return; + } + + /* Try reading a number. */ + if ((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') { + s->value = strtod(s->next, (char**)&s->next); + s->type = TOK_NUMBER; + } else { + /* Look for a variable or builtin function call. */ + if (isalpha(s->next[0])) { + const char *start; + start = s->next; + while (isalpha(s->next[0]) || isdigit(s->next[0]) || (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: /* Falls through. */ + case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: /* Falls through. */ + s->context = var->context; /* Falls through. */ + + case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: /* Falls through. */ + case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: /* Falls through. */ + 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) { + /* = | | {"(" ")"} | | "(" {"," } ")" | "(" ")" */ + te_expr *ret; + int arity; + + switch (TYPE_MASK(s->type)) { + case TOK_NUMBER: + ret = new_expr(TE_CONSTANT, 0); + ret->value = s->value; + te_next_token(s); + break; + + case TOK_VARIABLE: + ret = new_expr(TE_VARIABLE, 0); + ret->bound = s->bound; + te_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; + te_next_token(s); + if (s->type == TOK_OPEN) { + te_next_token(s); + if (s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + te_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; + te_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; + te_next_token(s); + + if (s->type != TOK_OPEN) { + s->type = TOK_ERROR; + } else { + int i; + for(i = 0; i < arity; i++) { + te_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 { + te_next_token(s); + } + } + + break; + + case TOK_OPEN: + te_next_token(s); + ret = list(s); + if (s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + te_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) { + /* = {("-" | "+")} */ + int sign = 1; + while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { + if (s->function == sub) sign = -sign; + te_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) { + /* = {"^" } */ + te_expr *ret = power(s); + + int neg = 0; + + if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { + te_expr *se = ret->parameters[0]; + free(ret); + ret = se; + neg = 1; + } + + te_expr *insertion = 0; + + while (s->type == TOK_INFIX && (s->function == pow)) { + te_fun2 t = s->function; + te_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) { + /* = {"^" } */ + te_expr *ret = power(s); + + while (s->type == TOK_INFIX && (s->function == pow)) { + te_fun2 t = s->function; + te_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) { + /* = {("*" | "/" | "%") } */ + te_expr *ret = factor(s); + + while (s->type == TOK_INFIX && (s->function == mul || s->function == divide || s->function == fmod)) { + te_fun2 t = s->function; + te_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) { + /* = {("+" | "-") } */ + te_expr *ret = term(s); + + while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { + te_fun2 t = s->function; + te_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) { + /* = {"," } */ + te_expr *ret = expr(s); + + while (s->type == TOK_SEP) { + te_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_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; + } + } +} + + +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; + + te_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; + } +} + + +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; + } +} + + +void te_print(const te_expr *n) { + pn(n, 0); +} diff --git a/plugins/math/tinyexpr.h b/plugins/math/tinyexpr.h new file mode 100644 index 000000000..c2cbe1a30 --- /dev/null +++ b/plugins/math/tinyexpr.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Zlib +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015-2020 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. + */ + +#ifndef TINYEXPR_H +#define TINYEXPR_H + + +#ifdef __cplusplus +extern "C" { +#endif + + + +typedef struct te_expr { + int type; + union {double value; const double *bound; const void *function;}; + void *parameters[1]; +} te_expr; + + +enum { + TE_VARIABLE = 0, + + 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 = 32 +}; + +typedef struct te_variable { + const char *name; + const void *address; + int type; + void *context; +} te_variable; + + + +/* Parses the input expression, evaluates it, and frees it. */ +/* Returns NaN on error. */ +double te_interp(const char *expression, int *error); + +/* Parses the input expression and binds variables. */ +/* Returns NULL on error. */ +te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error); + +/* Evaluates the expression. */ +double te_eval(const te_expr *n); + +/* Prints debugging information on the syntax tree. */ +void te_print(const te_expr *n); + +/* Frees the expression. */ +/* This is safe to call on NULL pointers. */ +void te_free(te_expr *n); + + +#ifdef __cplusplus +} +#endif + +#endif /*TINYEXPR_H*/ diff --git a/src/Makefile.in b/src/Makefile.in index 844a093be..8112e0c26 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -26,11 +26,11 @@ C_FILES=access.c atr_tab.c attrib.c boolexp.c bsd.c bufferq.c \ help.c htab.c intmap.c local.c lock.c log.c look.c malias.c \ map_file.c markup.c match.c memcheck.c move.c mycrypt.c \ mymalloc.c mysocket.c myrlimit.c myssl.c notify.c parse.c \ - pcg_basic.c player.c plyrlist.c predicat.c privtab.c \ - info_master.c ptab.c rob.c services.c set.c sig.c sort.c \ - speech.c spellfix.c sql.c sqlite3.c ssl_master.c strdup.c \ - strtree.c strutil.c tables.c testframework.c timer.c tz.c \ - uint.c unparse.c utf_impl.c utils.c version.c wait.c \ + pcg_basic.c player.c plugin.c plyrlist.c predicat.c \ + privtab.c info_master.c ptab.c rob.c services.c set.c sig.c \ + sort.c speech.c spellfix.c sql.c sqlite3.c ssl_master.c \ + strdup.c strtree.c strutil.c tables.c testframework.c timer.c \ + tz.c uint.c unparse.c utf_impl.c utils.c version.c wait.c \ warnings.c websock.c wild.c wiz.c @@ -44,7 +44,7 @@ O_FILES=access.o atr_tab.o attrib.o boolexp.o bsd.o bufferq.o \ help.o htab.o intmap.o local.o lock.o log.o look.o malias.o \ map_file.o markup.o match.o memcheck.o move.o mycrypt.o \ mymalloc.o mysocket.o myrlimit.o myssl.o notify.o parse.o \ - pcg_basic.o player.o plyrlist.o predicat.o privtab.o \ + pcg_basic.o player.o plugin.o plyrlist.o predicat.o privtab.o \ info_master.o ptab.o rob.o services.o set.o sig.o sort.o \ speech.o spellfix.o sql.o sqlite3.o ssl_master.o strdup.o \ strtree.o strutil.o tables.o testframework.o timer.o tz.o \ @@ -383,6 +383,7 @@ bsd.o: ../hdrs/lookup.h bsd.o: ../hdrs/ssl_slave.h bsd.o: ../hdrs/websock.h bsd.o: ../hdrs/function.h +bsd.o: ../hdrs/plugin.h bufferq.o: ../config.h bufferq.o: ../confmagic.h bufferq.o: ../options.h @@ -1867,6 +1868,14 @@ player.o: ../hdrs/parse.h player.o: ../hdrs/mushsql.h player.o: ../hdrs/sqlite3.h player.o: ../hdrs/strutil.h +plugin.o: ../hdrs/copyrite.h +plugin.o: ../hdrs/cmds.h +plugin.o: ../hdrs/command.h +plugin.o: ../hdrs/conf.h +plugin.o: ../hdrs/log.h +plugin.o: ../hdrs/mymalloc.h +plugin.o: ../hdrs/notify.h +plugin.o: ../hdrs/plugin.h plyrlist.o: ../config.h plyrlist.o: ../confmagic.h plyrlist.o: ../options.h diff --git a/src/SWITCHES b/src/SWITCHES index c226e346e..2664f8a9b 100644 --- a/src/SWITCHES +++ b/src/SWITCHES @@ -1,4 +1,5 @@ ACCESS +ACTIVE ADD AFTER ALIAS @@ -65,6 +66,7 @@ IFELSE IGNORE IGSWITCH ILIST +INFO INLINE INPLACE INSIDE @@ -76,6 +78,7 @@ LEAVE LETTER LIMIT LIST +LOAD LOCAL LOCALIZE LOCKS @@ -134,6 +137,7 @@ RECALL REGEXP REGIONS REGISTER +RELOAD REMIT REMOVE RENAME @@ -177,6 +181,7 @@ UNCOMBINE UNFOLDER UNGAG UNHIDE +UNLOAD UNMUTE UNREAD UNTAG diff --git a/src/bsd.c b/src/bsd.c index 8d2836da2..71dcc31e8 100644 --- a/src/bsd.c +++ b/src/bsd.c @@ -118,9 +118,12 @@ #include "tests.h" #include "websock.h" #include "log.h" +#include "plugin.h" #ifndef WIN32 #include "wait.h" +#include +#include #ifdef INFO_SLAVE #include "lookup.h" #endif @@ -839,6 +842,8 @@ main(int argc, char **argv) /* start up anything 'external' */ ext_startup(); + load_plugins(); + /* Enter the main game loop */ gameloop(); @@ -884,6 +889,7 @@ main(int argc, char **argv) dump_database(); local_shutdown(); + unload_plugins(); #ifdef HAVE_LIBCURL curl_global_cleanup(); @@ -7705,6 +7711,7 @@ do_reboot(dbref player, int flag) kill_info_slave(); #endif local_shutdown(); + unload_plugins(); shutdown_conndb(1); close_help_files(); end_all_logs(); diff --git a/src/command.c b/src/command.c index 006202af2..e95b14114 100644 --- a/src/command.c +++ b/src/command.c @@ -258,6 +258,8 @@ COMLIST commands[] = { {"@PEMIT", "LIST CONTENTS SILENT NOISY NOEVAL PORT SPOOF", cmd_pemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, + {"@PLUGIN", "ACTIVE INFO LIST LOAD RELOAD UNLOAD", cmd_plugin, CMD_T_PLAYER, "WIZARD", 0}, + {"@PLUGINS", NULL, cmd_plugins, CMD_T_PLAYER, "WIZARD", 0}, {"@POLL", "CLEAR", cmd_poll, CMD_T_ANY, 0, 0}, {"@POOR", NULL, cmd_poor, CMD_T_ANY, 0, 0}, {"@POWER", diff --git a/src/conf.c b/src/conf.c index 793aa9d56..406993949 100644 --- a/src/conf.c +++ b/src/conf.c @@ -349,6 +349,7 @@ PENNCONF conftable[] = { "files"}, {"colors_file", cf_str, options.colors_file, sizeof options.colors_file, 0, "files"}, + {"plugins_dir", cf_str, options.plugins_dir, sizeof options.plugins_dir, 0, "files"}, {NULL, NULL, NULL, 0, 0, NULL}}; @@ -1362,6 +1363,7 @@ conf_default_set(void) strcpy(options.connlog_db, "log/connlog.db"); strcpy(options.dict_file, ""); strcpy(options.colors_file, "txt/colors.json"); + strcpy(options.plugins_dir, "../plugins"); } #undef set_string_option diff --git a/src/local.dst b/src/local.dst index 091a3b3be..66f967906 100644 --- a/src/local.dst +++ b/src/local.dst @@ -10,6 +10,7 @@ #include "copyrite.h" #include +#include #include "command.h" #include "conf.h" @@ -127,7 +128,6 @@ local_dbck(void) bool local_timer(void *data __attribute__((__unused__))) { - /* The callback has to be set back up or it'll only run once. */ return false; } diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 000000000..172417e1d --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,427 @@ +/** + * \file plugin.c + * + * \brief Plugin support for PennMUSH. + * + * Provides plugin support with all associated commands + * and functions necessary for the server and in-game + * + * + */ + +#include "copyrite.h" +#include "cmds.h" +#include "command.h" +#include "conf.h" +#include "log.h" +#include "mymalloc.h" +#include "notify.h" +#include "plugin.h" + +#include +#include +#include +#include +#include + +HASHTAB plugins; + +/** + * Free the memory being used by a plugin when removing + * it from the hashtab + * + * \param ptr pointer to the struct to free + */ +void free_plugin(void *ptr) { + PENN_PLUGIN *p = (PENN_PLUGIN *) ptr; + mush_free(p, "penn_plugin"); +} + +/** + * @brief Resequence all the plugin ids for currently + * loaded plugins so that they go 1...n without having gaps + * + */ +void resequence_plugin_ids() { + PENN_PLUGIN *plugin; + int id = 1; + + for (plugin = hash_firstentry(&plugins); plugin; plugin = hash_nextentry(&plugins)) { + plugin->id = id; + id++; + } +} + +/** + * Helper function for unloading a plugin. + * + * Function gets called by do_unload_plugin, + * do_reload_plugin, and unload_plugins. + * + * \param plugin A pointer to the plugin to be unloaded. + */ +void do_real_unload_plugin(PENN_PLUGIN *plugin) { + dlclose(plugin->handle); + hash_delete(&plugins, plugin->file); +} + +/** + * Helper function for loading a plugin. + * + * Function gets called by do_load_plugin, + * do_reload_plugin, and load_plugins. + * + * \param filename The filename of the plugin you want to load. eg. ../plugins/test.so + * \return int 0 for plugin loaded, -1 for plugin not loaded + */ +char *do_real_load_plugin(char filename[256]) { + typedef int plugin_init(); + typedef void *get_plugin(); + + char plugin_file[256]; + + void *handle; + plugin_init *init_plugin; + get_plugin *info_plugin; + + char *errorVal; + + PENN_PLUGIN *plugin; + + int plugin_name_return = 0; + + errorVal = mush_malloc(sizeof(char *), "plugin_error_string"); + + memset(plugin_file, 0, strlen(plugin_file)); + plugin_name_return = snprintf(plugin_file, sizeof(plugin_file), "%s/%s", options.plugins_dir, filename); + if (plugin_name_return < 0) return NULL; + + do_rawlog(LT_ERR, "Found plugin: %s ", plugin_file); + + handle = dlopen(plugin_file, RTLD_LAZY); + if (handle == NULL) { + strcpy(errorVal, dlerror()); + do_rawlog(LT_ERR, "Could not load plugin: %s", plugin_file); + do_rawlog(LT_ERR, "Reason: %s", errorVal); + return errorVal; + } + + do_rawlog(LT_ERR, "Opened plugin: %s", plugin_file); + + dlerror(); /* Clear any existing error */ + + info_plugin = dlsym(handle, "get_plugin"); + if (info_plugin == NULL) { + strcpy(errorVal, dlerror()); + do_rawlog(LT_ERR, "Missing get_plugin: %s", plugin_file); + dlclose(handle); + return errorVal; + } + + dlerror(); /* Clear any existing error */ + + init_plugin = dlsym(handle, "plugin_init"); + if (init_plugin == NULL) { + strcpy(errorVal, dlerror()); + do_rawlog(LT_ERR, "Missing plugin_init: %s", plugin_file); + dlclose(handle); + return errorVal; + } + + dlerror(); /* Clear any existing error */ + + plugin = mush_malloc(sizeof(PENN_PLUGIN), "penn_plugin"); + plugin->handle = &handle; + + plugin->info = mush_malloc(sizeof(PLUGIN_INFO), "plugin_info"); + plugin->info = info_plugin(); + + plugin->name = mush_malloc(sizeof(char *), "plugin_name"); + plugin->name = plugin->info->name; + strcpy(plugin->file, filename); + + do_rawlog(LT_ERR, "Plugin: %s by %s version %s", plugin->info->name, plugin->info->author, plugin->info->app_version); + + hash_add(&plugins, filename, plugin); + + resequence_plugin_ids(); + + init_plugin(); + + return NULL; +} + +/** + * Loop through all the .so files found in the + * plugins directory, and for each one found + * attempt to open it, check for the plugin information + * and run the plugin. + * + * The first step is to try and open a handle to the plugin, + * if a valid handle can't be created then we ignore this plugin. + * + * Second step is to get a handle to the plugin_info() function + * found in the plugin, if the function can't be found then we + * ignore this plugin, as it doesn't meet the requirements. + * + * Third step is to get a handle to the plugin_init() function, + * if we can't find the function then we ignore the plugin. + * + * Fourth step is to keep track of the plugin we just opened so that + * we can close it later on (or run further functions on it). + * + * Final step is to actually run the plugin_init() function on the + * plugin which will allow the function to set up anything it needs. + * + */ +void load_plugins() { + DIR *pluginsDir; + struct dirent *in_file; + char *errorVal = "\0"; + + hash_init(&plugins, 1, free_plugin); + + if (NULL != (pluginsDir = opendir(options.plugins_dir))) { + while ((in_file = readdir(pluginsDir))) { + if (!strcmp(in_file->d_name, ".")) continue; + if (!strcmp(in_file->d_name, "..")) continue; + if (!strstr(in_file->d_name, ".so")) continue; + + errorVal = do_real_load_plugin(in_file->d_name); + } + + closedir(pluginsDir); + } +} + +/** + * Loop through all currently loaded plugins and close + * their respective handles. + * + * Once we have closed all the plugin handles then we free + * the structure that was used for keeping track of them and + * reset the plugin_count back to 0. + * + * If this is a full shutdown then none of this really matters, + * but if it is an @shutdown/reboot then we need to make sure + * everything is clean for when load_plugins() runs again. + */ +void unload_plugins() +{ + PENN_PLUGIN *plugin; + + for (plugin = hash_firstentry(&plugins); plugin; plugin = hash_nextentry(&plugins)) { + if (plugin->handle) do_real_unload_plugin(plugin); + } +} + + +/** + * Get the plugin by its id. + * + * \param id The id of the plugin + * \return plugin The plugin found or null if it can't be found + */ +PENN_PLUGIN* get_plugin_by_id(int id) { + PENN_PLUGIN *plugin = NULL; + + if (id == 0) return NULL; /* ID of 0 means the plugin hasn't been loaded into penn */ + + for (plugin = hash_firstentry(&plugins); plugin; plugin = hash_nextentry(&plugins)) { + if ( plugin->id == id ) { + break; + } + } + + return plugin; +} + +/** + * List all the plugins found in the plugins directory, + * whether they have been loaded or not. + * + * \param executor Who ran the @plugin command + */ +void do_list_plugins(dbref executor) { + PENN_PLUGIN *plugin; + DIR *pluginsDir; + struct dirent *in_file; + + notify_format(executor, "ID Plugin Name Description "); + + if (NULL != (pluginsDir = opendir(options.plugins_dir))) { + while ((in_file = readdir(pluginsDir))) { + if (!strcmp(in_file->d_name, ".")) continue; + if (!strcmp(in_file->d_name, "..")) continue; + if (!strstr(in_file->d_name, ".so")) continue; + + plugin = hashfind(in_file->d_name, &plugins); + if (!plugin) { + notify_format(executor, "%2d %-33s %-41s", 0, in_file->d_name, "** NOT CURRENTLY LOADED **"); + } else { + notify_format(executor, "%2d %-33s %-41s", plugin->id, plugin->name, plugin->info->shortdesc); + } + } + + closedir(pluginsDir); + } +} + +/** + * @brief Load a plugin into the game using the @plugin/load command + * + * @param executor Dbref of the person executing @plugin/load + * @param filename The filename of the plugin that we want to load, eg. math.so + */ +void do_load_plugin(dbref executor, char filename[256]) { + char fullpath[256]; + char *errorVal = "\0"; + int retval; + + if (!strstr(filename, ".so")) { filename = strcat(filename, ".so"); } + + memset(fullpath, 0, sizeof(fullpath)); + retval = snprintf(fullpath, sizeof(fullpath), "%s/%s", options.plugins_dir, filename); + if ( retval < 0 ) return; + + struct stat buffer; + retval = stat(fullpath, &buffer); + + if ( retval != 0 ) return; + + errorVal = do_real_load_plugin(filename); + + if (errorVal == NULL) { + notify(executor, "Plugin loaded!"); + } else { + notify(executor, errorVal); + } +} + +/** + * Display the information for a particular plugin by id + * + * \param executor Who ran the @plugin command + * \param id The id of the plugin as found in @plugin/list + */ +void show_plugin_info(dbref executor, int id) { + PENN_PLUGIN *plugin = get_plugin_by_id(id); + + if (!plugin) { notify(executor, T("No plugin found!")); return; } + if (!plugin->info) { notify(executor, T("Plugin has no information associated with it!")); return; } + + if (plugin && plugin->info) { + notify_format(executor, "%13s %-65s", "Name:", plugin->info->name); + notify_format(executor, "%13s %-65s", "Version:", plugin->info->app_version); + notify_format(executor, "%13s %-65s", "Author:", plugin->info->author); + notify_format(executor, "%13s %-65s", "Description:", plugin->info->description); + notify_format(executor, "%13s %-65s", "File:", plugin->file); + } +} + +/** + * Reload an already loaded plugin. Can be because of changes + * made to the plugin itself, or because the plugin was unloaded + * in order to disable it. + * + * \param executor Who ran the @plugin command + * \param id The id of the plugin as found in @plugin/list + */ +void do_reload_plugin(dbref executor, int id) { + PENN_PLUGIN *plugin = get_plugin_by_id(id); + char file[256]; + char *errorVal = "\0"; + + if (!plugin) { notify(executor, T("No plugin found!")); return; } + if (!plugin->info) { notify(executor, T("Plugin has no information associated with it!")); return; } + + strcpy(file, plugin->file); + + /* Close the handle to the plugin and delete it from the hashtab */ + do_real_unload_plugin(plugin); + + /* Open a new handle to the plugin and add it to the hashtab */ + errorVal = do_real_load_plugin(file); + + if (errorVal == NULL) { + resequence_plugin_ids(); + notify(executor, "Plugin reloaded!"); + } +} + +/** + * Unload a specific plugin. + * + * \param executor Who ran the @plugin command + * \param id The id of the plugin as found in @plugin/list + */ +void do_unload_plugin(dbref executor, int id) { + PENN_PLUGIN *plugin = get_plugin_by_id(id); + char file[256]; + + if (!plugin) { notify(executor, T("No plugin found!")); return; } + if (!plugin->info) { notify(executor, T("Plugin has no information associated with it!")); return; } + + strcpy(file, plugin->file); + + /* Close the handle to the plugin and delete it from the hashtab */ + do_real_unload_plugin(plugin); + + resequence_plugin_ids(); + notify(executor, "Plugin unloaded!"); +} + +/** + * In-game command for dealing with plugins. + * + * Command will deal with the following things: + * - info - Display information about a plugin that has been loaded + * - list - List all plugins in the plugins directory + * - load - Load a plugin + * - reload - Reload a previously loaded plugin (combines unload and load together) + * - unload - Unload a loaded plugin + * + * Arguments for commands: + * - info requires the plugin name (as found in 'list') + * - list requires no arguments + * - load requires the plugin name (as found in 'list') + * - reload requires the plugin name (as found in 'list') + * - unload requires the plugin name (as found in 'list') + */ +COMMAND(cmd_plugin) { + int plugin_id; + + if (SW_ISSET(sw, SWITCH_INFO)) { + if (sscanf(arg_left, "%d", &plugin_id) != 1) { + notify(executor, T("Invalid plugin id!")); + } else { + show_plugin_info(executor, plugin_id); + } + } else if (SW_ISSET(sw, SWITCH_LIST)) { + do_list_plugins(executor); + } else if (SW_ISSET(sw, SWITCH_LOAD)) { + do_load_plugin(executor, arg_left); + } else if (SW_ISSET(sw, SWITCH_RELOAD)) { + if (sscanf(arg_left, "%d", &plugin_id) != 1) { + notify(executor, T("Invalid plugin id!")); + } else { + do_reload_plugin(executor, plugin_id); + } + } else if (SW_ISSET(sw, SWITCH_UNLOAD)) { + if (sscanf(arg_left, "%d", &plugin_id) != 1) { + notify(executor, T("Invalid plugin id!")); + } else { + do_unload_plugin(executor, plugin_id); + } + } else { + if (sscanf(arg_left, "%d", &plugin_id) != 1) { + notify(executor, T("Invalid plugin id!")); + } else { + show_plugin_info(executor, plugin_id); + } + } +} + +COMMAND(cmd_plugins) { + do_list_plugins(executor); +} diff --git a/src/switchinc.c b/src/switchinc.c index e782d52f2..67b205f05 100644 --- a/src/switchinc.c +++ b/src/switchinc.c @@ -1,7 +1,8 @@ /* AUTOGENERATED FILE. DO NOT EDIT! */ -static const int max_switch = 193; -SWITCH_VALUE switch_list[194] = { +static const int max_switch = 198; +SWITCH_VALUE switch_list[199] = { {"ACCESS", SWITCH_ACCESS, 0}, + {"ACTIVE", SWITCH_ACTIVE, 0}, {"ADD", SWITCH_ADD, 0}, {"AFTER", SWITCH_AFTER, 0}, {"ALIAS", SWITCH_ALIAS, 0}, @@ -68,6 +69,7 @@ SWITCH_VALUE switch_list[194] = { {"IGNORE", SWITCH_IGNORE, 0}, {"IGSWITCH", SWITCH_IGSWITCH, 0}, {"ILIST", SWITCH_ILIST, 0}, + {"INFO", SWITCH_INFO, 0}, {"INLINE", SWITCH_INLINE, 0}, {"INPLACE", SWITCH_INPLACE, 0}, {"INSIDE", SWITCH_INSIDE, 0}, @@ -79,6 +81,7 @@ SWITCH_VALUE switch_list[194] = { {"LETTER", SWITCH_LETTER, 0}, {"LIMIT", SWITCH_LIMIT, 0}, {"LIST", SWITCH_LIST, 0}, + {"LOAD", SWITCH_LOAD, 0}, {"LOCAL", SWITCH_LOCAL, 0}, {"LOCALIZE", SWITCH_LOCALIZE, 0}, {"LOCKS", SWITCH_LOCKS, 0}, @@ -137,6 +140,7 @@ SWITCH_VALUE switch_list[194] = { {"REGEXP", SWITCH_REGEXP, 0}, {"REGIONS", SWITCH_REGIONS, 0}, {"REGISTER", SWITCH_REGISTER, 0}, + {"RELOAD", SWITCH_RELOAD, 0}, {"REMIT", SWITCH_REMIT, 0}, {"REMOVE", SWITCH_REMOVE, 0}, {"RENAME", SWITCH_RENAME, 0}, @@ -180,6 +184,7 @@ SWITCH_VALUE switch_list[194] = { {"UNFOLDER", SWITCH_UNFOLDER, 0}, {"UNGAG", SWITCH_UNGAG, 0}, {"UNHIDE", SWITCH_UNHIDE, 0}, + {"UNLOAD", SWITCH_UNLOAD, 0}, {"UNMUTE", SWITCH_UNMUTE, 0}, {"UNREAD", SWITCH_UNREAD, 0}, {"UNTAG", SWITCH_UNTAG, 0},