# # Patch name: Dynamic Scope # Patch version: 0a # Author's name: Codebot Unit 5 # Author's email: anonycow@yahoo.com # Version of PennMUSH: 1.7.4 patchlevel 16 # Date patch made: Mon Apr 22 14:04:53 PDT 2002 # Author is willing to support (yes/no): no # Patch format: diff -c # # # This is a contributed PennMUSH patch. Its use is subject to the # same restrictions found in PennMUSH's hdrs/copyrite.h file. # # No warranty is given for this patch. It is not necessarily going # to work on your system, with any version of PennMUSH other than # the one above, etc. # # If the author given above was willing to support the patch, you # should write to the author if you have any questions or problems. Do # *NOT* send email messages to Javelin or any PennMUSH mailing list about # this patch! # # Below this line is the author's description of the patch, # followed by the patch itself. If the patch is in context diff # format, you'll probably apply it by typing: patch < patchfile # in your top-level MUSH directory, unless instructed otherwise # below. # # # PURPOSE # # This patch adds a rudimentary dynamic scope capability, as found in Lisp-like # languages, to user functions. Dynamic scope is similar to ulocal(), in that # variables defined at the level of the calling function are preserved from # changes made by called functions, yet called functions can still access the # values of these variables, and make local changes. # # Dynamic scope is transparent in that you don't need to call ulocal(), and that # you can define a much larger set of variables than is preserved by ulocal(), # because this patch supports named variables. # # # INSTALLATION # # Type (in your top level PennMUSH directory, i.e. pennmush): # # patch -p0 < DynamicScope.174p16 # # Then either add -DDYNAMIC_ENV_VARS to CCFLAGS in the top level Makefile, or # add #define DYNAMIC_ENV_VARS to your options.h. # # # USING DYNAMIC SCOPE # # The following two new functions are defined in funlocal.c: # # define(, ) # # Binds to in the current environment frame. If the binding # already exists in the current environment frame, the value is overwritten # rather than defining a new binding. # # # w() # # Returns the value previously bound to . All names are considered to # be initially bound to the empty string, so using this function with a name # which has not yet been defined will simply return nothing. # # # CONFIGURATION # # This patch supports a single configuration file option 'dynamic_binding_limit' # which specifies the maximum number of bindings per command. This defaults to # 256, which should be more than enough for all reasonable uses, so you'll # probably never need to touch this. # # # CAVEATS # # Fiddling with the MUSH code evaluator at such a fundamental level is bound to # be a complex affair, and I can't guarantee that this patch won't introduce # horrendous new memory leaks or segmentation faults. I've done some testing # with MEM_CHECK defined, and everything seems to work well enough, but your # mileage may vary. Report bugs to the given e-mail address, and I'll try to # fix them as I find time. # # # SHAMELESS PLUG # # Visit feem.dyndns.org 1978! My character there is CU5. # Index: src/bsd.c diff -c -r1.1.1.2 -r1.1.1.2.2.3 *** src/bsd.c 2002/04/03 03:28:13 1.1.1.2 --- src/bsd.c 2002/04/22 23:22:50 1.1.1.2.2.3 *************** *** 3552,3559 **** --- 3552,3565 ---- for (j = 0; j < NUMQ; j++) renv[j][0] = '\0'; + #ifdef DYNAMIC_ENV_VARS + make_dynamic_env(&dynamic_env); + #endif /* DYNAMIC_ENV_VARS */ process_command(d->player, command, d->player, 1); send_suffix(d); + #ifdef DYNAMIC_ENV_VARS + free_dynamic_env(&dynamic_env); + #endif /* DYNAMIC_ENV_VARS */ } else { if (!check_connect(d, command)) return 0; *************** *** 3649,3655 **** --- 3655,3667 ---- check_mail(player, 0, 0); set_player_folder(player, 0); #endif + #ifdef DYNAMIC_ENV_VARS + make_dynamic_env(&dynamic_env); + #endif /* DYNAMIC_ENV_VARS */ do_look_around(player); + #ifdef DYNAMIC_ENV_VARS + free_dynamic_env(&dynamic_env); + #endif /* DYNAMIC_ENV_VARS */ if (Haven(player)) notify(player, T("Your HAVEN flag is set. You cannot receive pages.")); #ifdef VACATION_FLAG Index: src/conf.c diff -c -r1.1.1.2 -r1.1.1.2.2.1 *** src/conf.c 2002/04/03 03:28:13 1.1.1.2 --- src/conf.c 2002/04/22 20:37:03 1.1.1.2.2.1 *************** *** 181,186 **** --- 181,190 ---- "limits"}, {"function_invocation_limit", cf_int, &options.func_invk_lim, 100000, 0, "limits"}, + #ifdef DYNAMIC_ENV_VARS + {"dynamic_binding_limit", cf_int, &options.dynamic_bind_lim, 100000, 0, + "limits"}, + #endif /* DYNAMIC_ENV_VARS */ #ifdef CHAT_SYSTEM {"max_channels", cf_int, &options.max_channels, 1000, 0, "chat"}, *************** *** 631,636 **** --- 635,643 ---- options.active_q_chunk = 0; options.func_nest_lim = 50; options.func_invk_lim = 2500; + #ifdef DYNAMIC_ENV_VARS + options.dynamic_bind_lim = 256; + #endif /* DYNAMIC_ENV_VARS */ strcpy(options.money_singular, "Penny"); strcpy(options.money_plural, "Pennies"); strcpy(options.log_wipe_passwd, "zap!"); Index: src/cque.c diff -c -r1.1.1.2 -r1.1.1.2.2.6 *** src/cque.c 2002/04/03 03:28:13 1.1.1.2 --- src/cque.c 2002/04/22 23:22:50 1.1.1.2.2.6 *************** *** 39,44 **** --- 39,88 ---- extern char ccom[]; extern dbref cplr; + #ifdef DYNAMIC_ENV_VARS + DEnv *dynamic_env; /* working dynamic environment */ + + int + make_dynamic_env(DEnv **denvp) + { + DEnv *denv = *denvp; + + if (denv) { + denv->refcount++; + return 0; + } + + denv = mush_malloc(sizeof(DEnv), "DEnv"); + if (!denv) return -1; + + denv->refcount = 1; + denv->nvars = 0; + denv->end = denv->vars = (DE_Var *) NULL; + + *denvp = denv; + return 1; + } + + int + free_dynamic_env(DEnv **denvp) + { + DE_Var *devtmp; + DEnv *denv = *denvp; + + if (--denv->refcount != 0) return 0; + + /* Free dynamic environment. */ + while (denv->vars) { + devtmp = denv->vars; + denv->vars = devtmp->next; + mush_free((Malloc_t) devtmp, "DE_Var"); + } + mush_free((Malloc_t) denv, "DEnv"); + + *denvp = (DEnv *) NULL; + return 1; + } + #endif /* DYNAMIC_ENV_VARS */ char *wenv[10]; /* working environment (wptr/rptr equiv) */ char renv[NUMQ][BUFFER_LEN]; char *wnxt[10], *rnxt[NUMQ]; /* stuff to be shoved into the queue */ *************** *** 55,60 **** --- 99,107 ---- int left; /* seconds left until execution */ char *env[10]; /* environment, from wild match */ char *rval[NUMQ]; /* environment, from setq() */ + #ifdef DYNAMIC_ENV_VARS + DEnv *denv; /* dynamic environment, from define() */ + #endif /* DYNAMIC_ENV_VARS */ char *comm; /* command to be executed */ }; *************** *** 179,184 **** --- 226,234 ---- if (point->rval[a]) { mush_free((Malloc_t) point->rval[a], "bqueue_rval"); } + #ifdef DYNAMIC_ENV_VARS + free_dynamic_env(&dynamic_env); + #endif /* DYNAMIC_ENV_VARS */ if (point->semattr) mush_free((Malloc_t) point->semattr, "bqueue_semattr"); if (point->comm) *************** *** 246,251 **** --- 296,305 ---- else { tmp->rval[a] = mush_strdup(rnxt[a], "bqueue_rval"); } + #ifdef DYNAMIC_ENV_VARS + make_dynamic_env(&dynamic_env); + tmp->denv = dynamic_env; + #endif /* DYNAMIC_ENV_VARS */ if (IsPlayer(cause)) { if (qlast) { qlast->next = tmp; *************** *** 340,345 **** --- 394,403 ---- tmp->rval[a] = mush_strdup(rnxt[a], "bqueue_rval"); } } + #ifdef DYNAMIC_ENV_VARS + make_dynamic_env(&dynamic_env); + tmp->denv = dynamic_env; + #endif /* DYNAMIC_ENV_VARS */ if (until) { tmp->left = wait; *************** *** 487,492 **** --- 545,553 ---- else renv[a][0] = '\0'; } + #ifdef DYNAMIC_ENV_VARS + dynamic_env = entry->denv; + #endif /* DYNAMIC_ENV_VARS */ s = entry->comm; break_called = 0; while (!break_called && *s) { Index: src/funufun.c diff -c -r1.1.1.1 -r1.1.1.1.2.1 *** src/funufun.c 2002/03/11 21:54:06 1.1.1.1 --- src/funufun.c 2002/04/22 09:12:09 1.1.1.1.2.1 *************** *** 96,102 **** --- 96,109 ---- char *tptr[10]; char *tbuf; char const *tp; + #ifdef DYNAMIC_ENV_VARS + DE_Var *tde_end; + /* Save a pointer to the current top of the dynamic environment stack. */ + tde_end = dynamic_env->end; + dynamic_env->end = dynamic_env->vars; + #endif /* DYNAMIC_ENV_VARS */ + /* save our stack */ for (j = 0; j < 10; j++) tptr[j] = wenv[j]; *************** *** 117,122 **** --- 124,140 ---- /* restore the stack */ for (j = 0; j < 10; j++) wenv[j] = tptr[j]; + + #ifdef DYNAMIC_ENV_VARS + /* Free current environment frame, which is no longer in scope. */ + while (dynamic_env->vars != dynamic_env->end) { + DE_Var *devtmp = dynamic_env->vars; + dynamic_env->vars = devtmp->next; + mush_free((Malloc_t) devtmp, "DE_Var"); + dynamic_env->nvars--; + } + dynamic_env->end = tde_end; + #endif /* DYNAMIC_ENV_VARS */ } /* ARGSUSED */ Index: src/game.c diff -c -r1.1.1.2 -r1.1.1.2.2.1 *** src/game.c 2002/04/03 03:28:13 1.1.1.2 --- src/game.c 2002/04/22 23:22:50 1.1.1.2.2.1 *************** *** 632,637 **** --- 632,640 ---- renv[a][0] = '\0'; rnxt[a] = NULL; } + #ifdef DYNAMIC_ENV_VARS + dynamic_env = (DEnv *) NULL; + #endif /* DYNAMIC_ENV_VARS */ /* set MUSH start time */ start_time = time((time_t *) 0); Index: hdrs/conf.h diff -c -r1.1.1.1 -r1.1.1.1.2.1 *** hdrs/conf.h 2002/03/11 21:54:06 1.1.1.1 --- hdrs/conf.h 2002/04/22 20:37:01 1.1.1.1.2.1 *************** *** 120,125 **** --- 120,128 ---- int active_q_chunk; int func_nest_lim; int func_invk_lim; + #ifdef DYNAMIC_ENV_VARS + int dynamic_bind_lim; + #endif /* DYNAMIC_ENV_VARS */ char log_wipe_passwd[256]; char money_singular[32]; char money_plural[32]; Index: hdrs/externs.h diff -c -r1.1.1.2 -r1.1.1.2.2.3 *** hdrs/externs.h 2002/04/03 03:28:13 1.1.1.2 --- hdrs/externs.h 2002/04/22 23:22:49 1.1.1.2.2.3 *************** *** 140,145 **** --- 140,150 ---- #endif /* From cque.c */ + #ifdef DYNAMIC_ENV_VARS + extern DEnv *dynamic_env; + extern int make_dynamic_env(DEnv **denvp); + extern int free_dynamic_env(DEnv **denvp); + #endif /* DYNAMIC_ENV_VARS */ extern char *wenv[10], renv[NUMQ][BUFFER_LEN]; extern char *wnxt[10], *rnxt[NUMQ]; extern void do_second _((void)); Index: hdrs/mushtype.h diff -c -r1.1.1.1 -r1.1.1.1.2.2 *** hdrs/mushtype.h 2002/03/11 21:54:06 1.1.1.1 --- hdrs/mushtype.h 2002/04/22 09:12:07 1.1.1.1.2.2 *************** *** 60,63 **** --- 60,85 ---- typedef struct channel CHAN; #endif + #ifdef DYNAMIC_ENV_VARS + /* Version 0 (quick and hacked) implements environments as a stack. */ + + typedef struct dynamic_env_var DE_Var; + struct dynamic_env_var { + /* Following in the great tradition of BUFFER_LEN'ing everything... */ + char name[BUFFER_LEN]; + char value[BUFFER_LEN]; + + DE_Var *next; + }; + + typedef struct dynamic_env DEnv; + struct dynamic_env { + int refcount; + + int nvars; + DE_Var *vars; + DE_Var *end; + }; + #endif /* DYNAMIC_ENV_VARS */ + #endif Index: src/funlocal.c diff -c -r1.1.1.1 -r1.1.1.1.2.2 *** src/funlocal.c 2002/03/11 21:54:06 1.1.1.1 --- src/funlocal.c 2002/04/22 09:12:07 1.1.1.1.2.2 *************** *** 38,47 **** --- 38,123 ---- #endif + #ifdef DYNAMIC_ENV_VARS + /* Implements the softcode function DEFINE(), which binds a value to a symbol + * in the current environment frame. The idea is to add named variables with + * dynamic scope. The jury is still out whether this really works right. */ + FUNCTION(dynamic_env_vars_fun_define) + { + DE_Var *newdev; + + /* Try to mutate an existing variable in the current frame first. */ + newdev = dynamic_env->vars; + while (newdev != dynamic_env->end) { + if (strcasecmp(newdev->name, args[0]) == 0) { + strcpy(newdev->value, args[1]); + free_dynamic_env(&dynamic_env); + return; + } + newdev = newdev->next; + } + + /* Check to make sure we haven't exceeded MAX_DE_VARS. */ + if (dynamic_env->nvars >= options.dynamic_bind_lim) { + safe_str("#-1 BINDING LIMIT EXCEEDED", buff, bp); + free_dynamic_env(&dynamic_env); + return; + } + dynamic_env->nvars++; + + /* Allocate a new DE_Var. */ + newdev = (DE_Var *) mush_malloc(sizeof(DE_Var), "DE_Var"); + if (!newdev) { + /* I've always wondered why most of the PennMUSH code doesn't bother + * checking for a NULL pointer. I guess they panic and go crazy at that + * point. I mean, how many recovery strategies do you have? */ + safe_str("#-1 OUT OF MEMORY", buff, bp); + free_dynamic_env(&dynamic_env); + return; + } + + /* Great. Now we initialize and push on to the stack. + * + * The strcpy's are kinda safe, as long as args[0]/args[1] are both strings + * which are less than BUFFER_LEN long (not including terminator), which they + * should be, or else all sorts of things would go wrong elsewhere. */ + strcpy(newdev->name, args[0]); + strcpy(newdev->value, args[1]); + newdev->next = dynamic_env->vars; + dynamic_env->vars = newdev; + } + + /* Implements the softcode function W(), which looks up a symbol in the + * environment. Why W(), you ask? Well, it wasn't already taken. */ + FUNCTION(dynamic_env_vars_fun_lookup) + { + DE_Var *devp; + + /* All hail linear search, running in O(n) time, not counting the string + * comparison time! We anticipate the environment stack will stay small. */ + devp = dynamic_env->vars; + while (devp) { + if (strcasecmp(devp->name, args[0]) == 0) { + safe_str(devp->value, buff, bp); + return; + } + devp = devp->next; + } + + /* I debated putting in an error message, but couldn't decide how to + * differentiate an error message from a value that happened to look like an + * error message. So there's no error message, at least for now. */ + } + #endif /* DYNAMIC_ENV_VARS */ + void local_functions() { #ifdef EXAMPLE function_add("SILLY", local_fun_silly, 1, 1, FN_REG); #endif + #ifdef DYNAMIC_ENV_VARS + function_add("DEFINE", dynamic_env_vars_fun_define, 2, 2, FN_REG); + function_add("W", dynamic_env_vars_fun_lookup, 1, 1, FN_REG); + #endif /* DYNAMIC_ENV_VARS */ }