1 /*************************************************************************
2  *  TinyFugue - programmable mud client
3  *  Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
4  *
5  *  TinyFugue (aka "tf") is protected under the terms of the GNU
6  *  General Public License.  See the file "COPYING" for details.
7  ************************************************************************/
8 static const char RCSid[] = "$Id: macro.c,v 35004.188 2007/01/13 23:12:39 kkeys Exp $";
9 
10 
11 /**********************************************
12  * Fugue macro package                        *
13  *                                            *
14  * Macros, hooks, triggers, hilites and gags  *
15  * are all processed here.                    *
16  **********************************************/
17 
18 #include "tfconfig.h"
19 #include "port.h"
20 #include "tf.h"
21 #include "util.h"
22 #include "pattern.h"
23 #include "search.h"
24 #include "tfio.h"
25 #include "world.h"
26 #include "macro.h"
27 #include "keyboard.h"	/* bind_key()... */
28 #include "expand.h"
29 #include "socket.h"	/* xworld() */
30 #include "output.h"	/* get_keycode() */
31 #include "attr.h"
32 #include "cmdlist.h"
33 #include "command.h"
34 #include "parse.h"	/* valbool() for /def -E */
35 #include "variable.h"	/* set_var_by_id() */
36 
37 typedef struct {
38     cattr_t attr;
39     short subexp;
40 } subattr_t;
41 
42 struct Macro {
43     const char *name;
44     struct ListEntry *numnode;		/* node in maclist */
45     struct ListEntry *hashnode;		/* node in macro_table hash bucket */
46     struct ListEntry *trignode;		/* node in one of the triglists */
47     struct Macro *tnext;		/* temp list ptr for collision/death */
48     conString *body, *expr;
49     Program *prog, *exprprog;		/* compiled body, expr */
50     const char *bind, *keyname;
51     Pattern trig, hargs, wtype;		/* trigger/hook/worldtype patterns */
52     hookvec_t hook;			/* bit vector */
53     struct World *world;		/* only trig on text from world */
54     int pri, num;
55     attr_t attr;
56     int nsubattr;
57     subattr_t *subattr;
58     short prob, shots, invis;
59     short flags;
60     signed char fallthru, quiet;
61     struct BuiltinCmd *builtin;		/* builtin cmd with same name, if any */
62     int used[USED_N];			/* number of calls by each method */
63 };
64 
65 typedef struct {
66     Pattern name, body, bind, keyname, expr;
67 } AuxPat;
68 
69 typedef struct {
70     int shortflag;
71     int usedflag;
72     Cmp *cmp;
73 } ListOpts;
74 
75 int invis_flag = 0;
76 
77 static Macro  *macro_spec(String *args, int offset, int *xmflag, ListOpts *opts);
78 static int     macro_match(Macro *spec, Macro *macro, AuxPat *aux);
79 static int     add_numbered_macro(Macro *macro, unsigned int hash, int num,
80 		ListEntry *numnode);
81 static int     complete_macro(Macro *spec, unsigned int hash, int num,
82 		ListEntry *numnode);
83 static int     init_aux_patterns(Macro *spec, int mflag, AuxPat *aux);
84 static Macro  *match_exact(int hooknum, const char *str, attr_t attrs);
85 static int     list_defs(TFILE *file, Macro *spec, int mflag, ListOpts *opts);
86 static void    apply_attrs_of_match(Macro *macro, String *text, int hooknum,
87 		String *line);
88 static int     run_match(Macro *macro, String *text, int hooknum);
89 static const String *hook_name(const hookvec_t *hook) PURE;
90 static conString *print_def(TFILE *file, String *buffer, Macro *p);
91 static int     rpricmp(const Macro *m1, const Macro *m2);
92 static void    nuke_macro(Macro *macro);
93 
94 
95 #define HASH_SIZE 997	/* prime number */
96 
97 #define MACRO_TEMP	0x01
98 #define MACRO_DEAD	0x02
99 #define MACRO_HOOK	0x08
100 #define MACRO_NOHOOK	0x10	/* -h0 */
101 
102 #define INVALID_SUBEXP	-3
103 
104 static List maclist[1];			/* list of all (live) macros */
105 static List triglist[1];		/* list of macros by trigger */
106 static List hooklist[NUM_HOOKS];	/* lists of macros by hook */
107 static Macro *dead_macros;		/* head of list of dead macros */
108 static HashTable macro_table[1];	/* macros hashed by name */
109 static World NoWorld, AnyWorld;		/* explicit "no" and "any" */
110 static int mnum = 0;			/* macro ID number */
111 
112 typedef enum {
113     HT_TEXT = 0x00,	/* normal text in fg world */
114     HT_ALERT = 0x01,	/* alert */
115     HT_WORLD = 0x02,	/* text in xsock->world, plus alert if xsock != fsock */
116     HT_XSOCK = 0x04	/* alert should be cleared if xsock is foregrounded */
117 } hooktype_t;
118 
119 typedef struct hookrec {
120     const char *name;
121     hooktype_t hooktype;
122 } hookrec_t;
123 
124 static const hookrec_t hook_table[] = {
125 #define gencode(id, type)  { #id, type }
126 #include "hooklist.h"
127 #undef gencode
128 };
129 
130 #define NONNULL(str) ((str) ? (str) : "")
131 
132 /* These macros allow easy sharing of trigger and hook code. */
133 #define MAC(Node)       ((Macro *)((Node)->datum))
134 
135 
init_macros(void)136 void init_macros(void)
137 {
138     int i;
139     init_hashtable(macro_table, HASH_SIZE, cstrstructcmp);
140     init_list(maclist);
141     init_list(triglist);
142     for (i = 0; i < (int)NUM_HOOKS; i++)
143 	init_list(&hooklist[i]);
144 }
145 
146 /***************************************
147  * Routines for parsing macro commands *
148  ***************************************/
149 
hookname2int(const char * name)150 int hookname2int(const char *name)
151 {
152     const hookrec_t *hookrec;
153     hookrec = bsearch((void*)(name), (void *)hook_table,
154 	NUM_HOOKS, sizeof(hookrec_t), cstrstructcmp);
155     if (hookrec)
156 	return hookrec - hook_table;
157     if (cstrcmp(name, "BACKGROUND") == 0) /* backward compatability */
158 	return H_BGTRIG;
159     if (cstrcmp(name, "CONNETFAIL") == 0) /* backward compatability */
160 	eprintf("invalid hook event \"%s\"; see \"/help /connect\"", name);
161     else
162 	eprintf("invalid hook event \"%s\"", name);
163     return -1;
164 }
165 
166 /* Convert hook string to bit vector; return 0 on error, 1 on success. */
parse_hook(char ** argp,hookvec_t * hookvec)167 static int parse_hook(char **argp, hookvec_t *hookvec)
168 {
169     char *in, state;
170 
171     VEC_ZERO(hookvec);
172     if (!**argp) {
173 	memset(hookvec, 0xFF, sizeof(*hookvec));
174 	return 1;
175     }
176     for (state = '|'; state == '|'; *argp = in) {
177         for (in = *argp; *in && !is_space(*in) && *in != '|'; ++in);
178         state = *in;
179         *in++ = '\0';
180         if (strcmp(*argp, "*") == 0) {
181 	    memset(hookvec, 0xFF, sizeof(*hookvec));
182 	} else {
183 	    int i;
184 	    if ((i = hookname2int(*argp)) < 0)
185                 return 0;
186 	    VEC_SET(i, hookvec);
187         }
188     }
189     if (!state) *argp = NULL;
190     return 1;
191 }
192 
193 /* macro_spec
194  * Converts a macro description string to a more useful Macro structure.
195  * Omitted fields are set to a value that means "don't care".
196  * In /def, don't care fields are set to their default values;
197  * in macro_match(), they are not used in the comparison.  Don't care
198  * values for numeric fields are -1 or 0, depending on the field; for
199  * strings, NULL; for hooks, neither MACRO_HOOK nor MACRO_NOHOOK will
200  * be set.
201  */
macro_spec(String * args,int offset,int * xmflag,ListOpts * listopts)202 static Macro *macro_spec(String *args, int offset, int *xmflag, ListOpts *listopts)
203 {
204     Macro *spec;
205     char opt;
206     const char *ptr, *s, *name = NULL, *body = NULL, *nameend, *bodyend;
207     int i, n, mflag = -1, error = 0;
208     ValueUnion uval;
209     attr_t attrs;
210 
211     if (!(spec = (Macro *)MALLOC(sizeof(struct Macro)))) {
212         eprintf("macro_spec: not enough memory");
213         return NULL;
214     }
215     spec->num = 0;
216     spec->body = spec->expr = NULL;
217     spec->prog = spec->exprprog = NULL;
218     spec->name = spec->bind = spec->keyname = NULL;
219     spec->numnode = spec->trignode = spec->hashnode = NULL;
220     init_pattern_str(&spec->trig, NULL);
221     init_pattern_str(&spec->hargs, NULL);
222     init_pattern_str(&spec->wtype, NULL);
223     spec->world = NULL;
224     spec->pri = spec->prob = spec->shots = spec->fallthru = spec->quiet = -1;
225     VEC_ZERO(&spec->hook);
226     spec->invis = 0;
227     spec->attr = 0;
228     spec->nsubattr = 0;
229     spec->subattr = NULL;
230     spec->flags = MACRO_TEMP;
231     spec->builtin = NULL;
232     spec->used[USED_NAME] = spec->used[USED_TRIG] =
233 	spec->used[USED_HOOK] = spec->used[USED_KEY] = 0;
234 
235     startopt(CS(args), "usSp#c#b:B:E:t:w:h:a:f:P:T:FiIn#1m:q" +
236 	(listopts ? 0 : 3));
237     while (!error && (opt = nextopt(&ptr, &uval, NULL, &offset))) {
238         switch (opt) {
239         case 'u':
240             listopts->usedflag = 1;
241             break;
242         case 's':
243             listopts->shortflag = 1;
244             break;
245         case 'S':
246             listopts->cmp = cstrpppcmp;
247             break;
248         case 'm':
249             if (!(error = ((i = enum2int(ptr, 0, enum_match, "-m")) < 0))) {
250 		if ((error = (mflag >= 0 && mflag != i)))
251 		    eprintf("-m option conflicts with earlier -m or -P");
252 		mflag = i;
253 	    }
254             break;
255         case 'p':
256             spec->pri = uval.ival;
257             break;
258         case 'c':
259             spec->prob = uval.ival;
260             break;
261         case 'F':
262             spec->fallthru = 1;
263             break;
264         case 'i':
265             spec->invis = 1;
266             break;
267         case 'I':
268             spec->invis = 2;
269             break;
270         case 'b':
271             if (spec->keyname) FREE(spec->keyname);
272             if (spec->bind) FREE(spec->bind);
273             ptr = print_to_ascii(NULL, ptr)->data;
274             spec->bind = STRDUP(ptr);
275             break;
276         case 'B':
277 	    if (warn_def_B)
278 		wprintf("/def -B is deprecated.  See /help keys.");
279             if (spec->keyname) FREE(spec->keyname);
280             if (spec->bind) FREE(spec->bind);
281             spec->keyname = STRDUP(ptr);
282             break;
283         case 'E':
284             if (spec->expr) conStringfree(spec->expr);
285             (spec->expr = CS(Stringnew(ptr, -1, 0)))->links++;
286             break;
287         case 't':
288             free_pattern(&spec->trig);
289             error += !init_pattern_str(&spec->trig, ptr);
290             break;
291         case 'T':
292             free_pattern(&spec->wtype);
293             error += !init_pattern_str(&spec->wtype, ptr);
294             break;
295         case 'w':
296             if (!*ptr || strcmp(ptr, "+") == 0)
297                 spec->world = &AnyWorld;
298             else if (strcmp(ptr, "-") == 0)
299                 spec->world = &NoWorld;
300             else if ((error = !(spec->world = find_world(ptr))))
301                 eprintf("No world %s", ptr);
302             break;
303         case 'h':
304 	    if (strcmp(ptr, "0") == 0) {
305 		spec->flags |= MACRO_NOHOOK;
306 	    } else {
307 		char *buf, *p;
308 		p = buf = STRDUP(ptr); /* XXX optimize */
309 		if (!(error = !parse_hook(&p, &spec->hook))) {
310 		    free_pattern(&spec->hargs);
311 		    error += !init_pattern_str(&spec->hargs, p);
312 		    spec->flags |= MACRO_HOOK;
313 		}
314 		FREE(buf);
315             }
316             break;
317         case 'a': case 'f':
318             error = !parse_attrs(ptr, &attrs, 0);
319             spec->attr = adj_attr(spec->attr, attrs);
320             break;
321         case 'P':
322             if ((error = (spec->nsubattr > 0))) {
323                 eprintf("-P can be given only once per macro.");
324 		break;
325 	    } else if ((error = (mflag >= 0 && mflag != MATCH_REGEXP))) {
326 		eprintf("\"-P\" requires \"-mregexp -t<pattern>\"");
327 		break;
328 	    }
329 	    for (n = 0, s = ptr; *s; s++) {
330 		if (*s == ';') continue; /* don't count empties */
331 		n++;
332 		s = strchr(s, ';');
333 		if (!s) break;
334 	    }
335 	    spec->subattr = MALLOC(n * sizeof(subattr_t));
336 	    if (!spec->subattr) {
337 		eprintf("-P: out of memory");
338 		error++;
339 		break;
340 	    }
341 	    spec->nsubattr = n;
342 
343 	    {
344 	    char *start, *end, *buf = STRDUP(ptr); /* XXX optimize */
345 	    for (i = 0, start = buf; !error && i < n; i++, start = end+1) {
346 		attr_t attr;
347 		if ((end = strchr(start, ';')))
348 		    *end = '\0';
349 		if (end == start) { /* skip empty */
350 		    i--;
351 		    continue;
352 		}
353 		if (*start == 'L') {
354 		    spec->subattr[i].subexp = -1;
355 		    start++;
356 		} else if (*start == 'R') {
357 		    spec->subattr[i].subexp = -2;
358 		    start++;
359 		} else {
360 		    spec->subattr[i].subexp = strtoint(start, &start);
361 		    if ((error = (spec->subattr[i].subexp < 0))) {
362 			eprintf("-P: number must be non-negative");
363 			break;
364 		    }
365 		}
366 		error = !parse_attrs(start, &attr, 0);
367 		spec->subattr[i].attr = attr;
368 	    }
369 	    FREE(buf);
370 	    }
371 	    mflag = MATCH_REGEXP;
372             break;
373         case 'n':
374             spec->shots = uval.ival;
375             break;
376         case 'q':
377             spec->quiet = TRUE;
378             break;
379         case '1':
380             spec->shots = 1;
381             break;
382         default:
383             error = TRUE;
384         }
385     }
386 
387     if (error) {
388         nuke_macro(spec);
389         return NULL;
390     }
391     if (mflag < 0)
392 	mflag = matching;
393     if (xmflag) *xmflag = mflag;
394     if (!init_pattern_mflag(&spec->trig, mflag, 't') ||
395 	!init_pattern_mflag(&spec->hargs, mflag, 'h') ||
396 	!init_pattern_mflag(&spec->wtype, mflag, 'w'))
397     {
398         nuke_macro(spec);
399         return NULL;
400     }
401 
402     name = args->data + offset;
403     while (is_space(*name)) ++name;
404     if (!*name) return spec;
405     nameend = name + strcspn(name, "=");
406 
407     if (*nameend == '=') {
408 	body = nameend + 1;
409 	while (is_space(*body)) ++body;
410 	bodyend = args->data + args->len;
411 	while (bodyend > body && is_space(bodyend[-1])) bodyend--;
412 	if (bodyend > body)
413 	    (spec->body = CS(Stringodup(CS(args), body - args->data)))->links++;
414     }
415     while (nameend > name && is_space(nameend[-1])) nameend--;
416     if (nameend > name) {
417 	char *buf = strncpy(XMALLOC(nameend - name + 1), name, nameend - name);
418 	buf[nameend - name] = '\0';
419 	spec->name = buf;
420     }
421 
422     return spec;
423 }
424 
425 
426 /* init_aux_patterns
427  * Macro_match() needs to compare some string fields that aren't normally
428  * patterns.  This function initializes patterns for those fields.
429  */
init_aux_patterns(Macro * spec,int mflag,AuxPat * aux)430 static int init_aux_patterns(Macro *spec, int mflag, AuxPat *aux)
431 {
432     init_pattern_str(&aux->name, spec->name);
433     init_pattern_str(&aux->body, spec->body ? spec->body->data : NULL);
434     init_pattern_str(&aux->expr, spec->expr ? spec->expr->data : NULL);
435     init_pattern_str(&aux->bind, spec->bind);
436     init_pattern_str(&aux->keyname, spec->keyname);
437     if (mflag < 0) mflag = matching;
438 
439     return init_pattern_mflag(&aux->name, mflag, 0) &&
440 	init_pattern_mflag(&aux->body, mflag, 0) &&
441 	init_pattern_mflag(&aux->expr, mflag, 'E') &&
442 	init_pattern_mflag(&aux->bind, mflag, 'b') &&
443 	init_pattern_mflag(&aux->keyname, mflag, 'B');
444 }
445 
free_aux_patterns(AuxPat * aux)446 static void free_aux_patterns(AuxPat *aux)
447 {
448     free_pattern(&aux->name);
449     free_pattern(&aux->body);
450     free_pattern(&aux->bind);
451     free_pattern(&aux->keyname);
452     free_pattern(&aux->expr);
453 }
454 
455 /* return 1 if attr contains any of the attributes listed in sattr */
attr_match(attr_t sattr,attr_t attr)456 static int attr_match(attr_t sattr, attr_t attr)
457 {
458     if (sattr == F_NONE) {
459 	return !attr;
460     } else {
461 	if ((sattr & ~F_COLORS & attr) != 0)
462 	    return 1;
463 	if ((sattr & F_FGCOLOR) && (sattr & F_FGCOLORS) == (attr & F_FGCOLORS))
464 	    return 1;
465 	if ((sattr & F_BGCOLOR) && (sattr & F_BGCOLORS) == (attr & F_BGCOLORS))
466 	    return 1;
467 	return 0;
468     }
469 }
470 
471 /* macro_match
472  * Compares spec to macro.  aux contains patterns for string fields that
473  * aren't normally patterns.  Returns 1 for match, 0 for nonmatch.
474  */
macro_match(Macro * spec,Macro * macro,AuxPat * aux)475 static int macro_match(Macro *spec, Macro *macro, AuxPat *aux)
476 {
477     if (!spec->invis && macro->invis) return 0;
478     if (spec->invis == 2 && !macro->invis) return 0;
479     if (spec->shots >= 0 && spec->shots != macro->shots) return 0;
480     if (spec->fallthru >= 0 && spec->fallthru != macro->fallthru) return 0;
481     if (spec->prob >= 0 && spec->prob != macro->prob) return 0;
482     if (spec->pri >= 0 && spec->pri != macro->pri) return 0;
483     if (spec->attr && !attr_match(spec->attr, macro->attr)) return 0;
484     if (spec->subattr) {
485 	int i, j;
486 	for (i = 0; i < spec->nsubattr; i++) {
487 	    for (j = 0; ; j++) {
488 		if (j >= macro->nsubattr) return 0;
489 		if (macro->subattr[j].subexp == spec->subattr[i].subexp) break;
490 	    }
491 	    if (!attr_match(spec->subattr[i].attr, macro->subattr[j].attr))
492 		return 0;
493 	}
494     }
495     if (spec->world) {
496         if (spec->world == &NoWorld) {
497             if (macro->world) return 0;
498         } else if (spec->world == &AnyWorld) {
499             if (!macro->world) return 0;
500         } else if (spec->world != macro->world) return 0;
501     }
502 
503     if (spec->keyname) {
504         if (!*spec->keyname) {
505             if (!*macro->keyname) return 0;
506         } else {
507             if (!patmatch(&aux->keyname, NULL, macro->keyname)) return 0;
508         }
509     }
510 
511     if (spec->bind) {
512         if (!*spec->bind) {
513             if (!*macro->bind) return 0;
514         } else {
515             if (!patmatch(&aux->bind, NULL, macro->bind)) return 0;
516         }
517     }
518 
519     if (spec->expr) {
520         if (!spec->expr->len) {
521             if (!macro->expr) return 0;
522         } else {
523             if (!macro->expr) return 0;
524 	    if (!patmatch(&aux->expr, NULL, macro->expr->data)) return 0;
525         }
526     }
527 
528     if (spec->flags & MACRO_NOHOOK) {
529 	/* -h0 */
530 	if (macro->flags & MACRO_HOOK) return 0;
531     } else if (spec->flags & MACRO_HOOK) {
532 	int i, hit = 0;
533 	if (!(macro->flags & MACRO_HOOK)) return 0;
534 	for (i = 0; i < sizeof(spec->hook)/sizeof(long); i++) {
535 	    if ((hit = (spec->hook.bits[i] & macro->hook.bits[i])))
536 		break;
537 	}
538 	if (!hit) return 0;
539         if (spec->hargs.str && *spec->hargs.str) {
540             if (!patmatch(&spec->hargs, NULL, NONNULL(macro->hargs.str)))
541                 return 0;
542         }
543     }
544 
545     if (spec->trig.str) {
546         if (!*spec->trig.str) {
547             if (!macro->trig.str) return 0;
548         } else {
549             if (!patmatch(&spec->trig, NULL, NONNULL(macro->trig.str)))
550                 return 0;
551         }
552     }
553     if (spec->wtype.str) {
554         if (!*spec->wtype.str) {
555             if (!macro->wtype.str) return 0;
556         } else {
557             if (!patmatch(&spec->wtype, NULL, NONNULL(macro->wtype.str)))
558                 return 0;
559         }
560     }
561     if (spec->num && macro->num != spec->num)
562         return 0;
563     if (spec->name && !patmatch(&aux->name, NULL, macro->name))
564         return 0;
565     if (spec->body && !patmatch(&aux->body, macro->body, NULL))
566         return 0;
567     return 1;
568 }
569 
570 /* macro_equal
571  * Returns true if m1 and m2 are exactly equivalent (except for num).
572  */
macro_equal(Macro * m1,Macro * m2)573 int macro_equal(Macro *m1, Macro *m2)
574 {
575     if (m1->invis != m2->invis) return 0;
576     if (m1->shots != m2->shots) return 0;
577     if (m1->fallthru != m2->fallthru) return 0;
578     if (m1->quiet != m2->quiet) return 0;
579     if (m1->prob != m2->prob) return 0;
580     if (m1->pri != m2->pri) return 0;
581     if (m1->attr != m2->attr) return 0;
582     if (m1->nsubattr != m2->nsubattr) return 0;
583     if (m1->subattr) {
584 	int i, j;
585 	for (i = 0; i < m1->nsubattr; i++) {
586 	    for (j = 0; ; j++) {
587 		if (j >= m2->nsubattr) return 0;
588 		if (m2->subattr[j].subexp == m1->subattr[i].subexp)
589 		    break;
590 	    }
591 	    if (m2->subattr[j].attr != m1->subattr[i].attr)
592 		return 0;
593 	}
594     }
595     if (m1->world != m2->world) return 0;
596 
597     if ((m1->flags ^ m2->flags) &~ MACRO_TEMP) return 0;
598     if (m1->flags & MACRO_HOOK)
599 	if (memcmp(&m1->hook, &m2->hook, sizeof(m1->hook)) != 0) return 0;
600 
601     if (nullstrcmp(m1->keyname, m2->keyname) != 0) return 0;
602     if (nullstrcmp(m1->bind, m2->bind) != 0) return 0;
603     if (nullstrcmp(m1->trig.str, m2->trig.str) != 0) return 0;
604     if (nullstrcmp(m1->hargs.str, m2->hargs.str) != 0) return 0;
605     if (nullstrcmp(m1->wtype.str, m2->wtype.str) != 0) return 0;
606     if (nullstrcmp(m1->name, m2->name) != 0) return 0;
607     if (Stringcmp(m1->body, m2->body) != 0) return 0;
608     if (Stringcmp(m1->expr, m2->expr) != 0) return 0;
609     return 1;
610 }
611 
612 /* find Macro by name */
find_hashed_macro(const char * name,unsigned int hash)613 Macro *find_hashed_macro(const char *name, unsigned int hash)
614 {
615     if (!*name) return NULL;
616     if (*name == '#') return find_num_macro(hash);
617     return (Macro *)hashed_find(name, hash, macro_table);
618 }
619 
620 /* find single exact match */
match_exact(int hooknum,const char * str,attr_t attrs)621 static Macro *match_exact(int hooknum, const char *str, attr_t attrs)
622 {
623     ListEntry *node;
624 
625     if (hooknum < 0 && !*str) return NULL;
626     for (node = hooknum>=0 ? hooklist[hooknum].head : triglist->head; node;
627 	node = node->next)
628     {
629         Macro *macro = MAC(node);
630         if (macro->flags & MACRO_DEAD) continue;
631 	if (hooknum>=0) {
632 	    if (!VEC_ISSET(hooknum, &macro->hook)) continue;
633 	    if (!macro->hargs.str || cstrcmp(macro->hargs.str, str) == 0)
634 		return macro;
635 	} else {
636 	    if (!(macro->attr & attrs)) continue;
637 	    if (!macro->trig.str || cstrcmp(macro->trig.str, str) == 0)
638 		return macro;
639 	}
640     }
641     eprintf("%s on \"%s\" was not defined.", hooknum>=0 ? "Hook" : "Trigger",
642 	str);
643     return NULL;
644 }
645 
646 /**************************
647  * Routines to add macros *
648  **************************/
649 
650 /* create a Macro */
add_new_macro(const char * trig,const char * bind,const hookvec_t * hook,const char * hargs,const char * body,int pri,int prob,attr_t attr,int invis,int mflag)651 int add_new_macro(const char *trig, const char *bind, const hookvec_t *hook,
652     const char *hargs, const char *body, int pri, int prob, attr_t attr,
653     int invis, int mflag)
654 {
655     Macro *new;
656     int error = 0;
657 
658     if (!(new = (Macro *) MALLOC(sizeof(struct Macro)))) {
659         eprintf("add_new_macro: not enough memory");
660         return 0;
661     }
662     new->numnode = new->trignode = new->hashnode = NULL;
663     new->flags = MACRO_TEMP;
664     new->prog = new->exprprog = NULL;
665     new->name = STRDUP("");
666     (new->body = CS(Stringnew(body, -1, 0)))->links++;
667     new->expr = NULL;
668     new->bind = STRDUP(bind);
669     new->keyname = STRDUP("");
670     if (hook) {
671 	new->flags |= MACRO_HOOK;
672 	new->hook = *hook;
673     } else {
674 	VEC_ZERO(&new->hook);
675     }
676     error += !init_pattern(&new->trig, trig, mflag);
677     error += !init_pattern(&new->hargs, hargs, mflag);
678     init_pattern_str(&new->wtype, NULL);
679     new->world = NULL;
680     new->pri = pri;
681     new->prob = prob;
682     new->attr = attr;
683     new->nsubattr = 0;
684     new->subattr = NULL;
685     new->shots = 0;
686     new->invis = invis;
687     new->fallthru = FALSE;
688     new->quiet = FALSE;
689     new->builtin = NULL;
690     new->used[USED_NAME] = new->used[USED_TRIG] =
691 	new->used[USED_HOOK] = new->used[USED_KEY] = 0;
692 
693     if (!error)
694 	return add_numbered_macro(new, 0, 0, NULL);
695     nuke_macro(new);
696     return 0;
697 }
698 
bind_key_macro(Macro * spec)699 static int bind_key_macro(Macro *spec)
700 {
701     Macro *orig;
702 
703     if ((orig = find_key(spec->bind))) {
704 	if (macro_equal(orig, spec)) {
705 	    return -1; /* leave orig in place, don't insert spec */
706         } else if (redef) {
707 	    const char *obody = orig->body->data;
708 	    const char *sbody = spec->body->data;
709 	    if (strncmp(sbody, "/key_", 5) != 0 && orig->invis &&
710 		strncmp(obody, "/key_", 5) == 0 && !strchr(obody, ' '))
711 	    {
712 		conString *buf = print_def(NULL, NULL, orig);
713 		eprintf("%ARecommendation: leave \"%S\" defined and "
714 		    "define a macro named \"%s\" instead.",
715 		    warning_attr, buf, obody+1);
716 		conStringfree(buf);
717 	    }
718             kill_macro(orig); /* this guarantees intrie will succeed */
719 	    do_hook(H_REDEF, "!Redefined %s %S", "%s %S",
720 		"binding", ascii_to_print(spec->bind));
721         } else {
722             eprintf("Binding %S already exists.", ascii_to_print(spec->bind));
723             return 0;
724         }
725     }
726 
727     return bind_key(spec, spec->bind);
728 }
729 
730 /* add_macro
731  * Install a permanent Macro in appropriate structures.
732  * Only the keybinding is checked for conflicts; everything else is assumed
733  * assumed to be error- and conflict-free.  If the bind_key_macro fails, the
734  * macro will be nuked.
735  */
add_numbered_macro(Macro * macro,unsigned int hash,int num,ListEntry * numnode)736 static int add_numbered_macro(Macro *macro, unsigned int hash, int num,
737     ListEntry *numnode)
738 {
739     if (!macro) return 0;
740 
741     if (*macro->bind) {
742 	int result = bind_key_macro(macro);
743 	if (result <= 0) {
744 	    nuke_macro(macro);
745 	    return -result;
746 	}
747     }
748     macro->num = num ? num : ++mnum;
749     macro->numnode = inlist((void *)macro, maclist, numnode);
750 
751     if (*macro->name) {
752         macro->hashnode = hashed_insert((void *)macro, hash, macro_table);
753 	if (macro->builtin) { /* macro->builtin was set in complete_macro() */
754 	    macro->builtin->macro = macro;
755 	}
756     }
757     if (macro->trig.str) {
758         macro->trignode = sinsert((void *)macro,
759 	    macro->world ? macro->world->triglist : triglist, (Cmp *)rpricmp);
760     }
761     if (macro->flags & MACRO_HOOK) {
762 	int i;
763 	for (i = 0; i < (int)NUM_HOOKS; i++) {
764 	    if (VEC_ISSET(i, &macro->hook))
765 		sinsert((void *)macro, &hooklist[i], (Cmp *)rpricmp);
766 	}
767     }
768     macro->flags &= ~MACRO_TEMP;
769     if (!*macro->name && (macro->trig.str || macro->flags & MACRO_HOOK) &&
770 	macro->shots == 0 && pedantic)
771     {
772         wprintf("new macro (#%d) does not have a name.", macro->num);
773     }
774     return macro->num;
775 }
776 
777 /* rebind_key_macros
778  * Unbinds macros with keynames, and attempts to rebind them.
779  */
rebind_key_macros(void)780 void rebind_key_macros(void)
781 {
782     Macro *p;
783     ListEntry *node;
784     const char *code;
785 
786     for (node = maclist->tail; node; node = node->prev) {
787         p = MAC(node);
788         if (!*p->keyname) continue;
789         code = get_keycode(p->keyname);
790         if (strcmp(code, p->bind) == 0) {
791             /* same code, don't need to rebind */
792         } else {
793             if (*p->bind) unbind_key(p->bind);
794             FREE(p->bind);
795             p->bind = STRDUP(code);
796             if (!*code) {
797                 wprintf("no code for key \"%s\"", p->keyname);
798             } else if (bind_key_macro(p)) {
799 		/* bind_key_macro can't return -1 here */
800                 do_hook(H_REDEF, "!Redefined %s %s", "%s %s",
801                     "key", p->keyname);
802             } else {
803                 kill_macro(p);
804 	    }
805         }
806     }
807 }
808 
809 /* compares m1 and m2 based on reverse priority and fallthru */
rpricmp(const Macro * m1,const Macro * m2)810 static int rpricmp(const Macro *m1, const Macro *m2)
811 {
812     if (m2->pri != m1->pri) return m2->pri - m1->pri;
813     else return m2->fallthru - m1->fallthru;
814 }
815 
handle_def_command(String * args,int offset)816 struct Value *handle_def_command(String *args, int offset)
817 {
818     Macro *spec;
819 
820     if (!(args->len - offset) || !(spec = macro_spec(args, offset, NULL, NULL)))
821         return shareval(val_zero);
822     return newint(complete_macro(spec, macro_hash(spec->name), 0, NULL));
823 }
824 
825 /* Fill in "don't care" fields with default values, and add_numbered_macro().
826  * If error checking fails, spec will be nuked.
827  */
complete_macro(Macro * spec,unsigned int hash,int num,ListEntry * numnode)828 static int complete_macro(Macro *spec, unsigned int hash, int num,
829     ListEntry *numnode)
830 {
831     Macro *orig = NULL;
832     int i;
833 
834     if (spec->name && *spec->name) {
835         if (strchr("#@!/", *spec->name) || strchr(spec->name, ' ')) {
836             eprintf("illegal macro name \"%s\".", spec->name);
837             nuke_macro(spec);
838             return 0;
839         }
840         if (keyword(spec->name) ||
841 	    ((spec->builtin = find_builtin_cmd(spec->name)) &&
842 	    (spec->builtin->reserved)))
843 	{
844             eprintf("\"%s\" is a reserved word.", spec->name);
845             nuke_macro(spec);
846             return 0;
847         }
848 
849         if (spec->builtin) {
850             do_hook(H_CONFLICT,
851                 "!warning: macro \"%s\" conflicts with the builtin command.",
852                 "%s", spec->name);
853         }
854     }
855 
856     if (spec->expr) {
857 	/* Compiling expr at /def time allows us to report errors at /def time
858 	 * (and means we don't have to test at runtime) */
859 	extern char current_opt;
860 	current_opt = 'E';
861 	spec->exprprog = compile_tf(spec->expr, 0, -1, 1, 2);
862 	current_opt = '\0';
863 	if (!spec->exprprog) {
864             nuke_macro(spec);
865 	    return 0;
866 	}
867     }
868 
869     if (spec->body && defcompile) {
870 	spec->prog = compile_tf(spec->body, 0, SUB_MACRO, 0,
871 	    !spec->shots ? 2 : spec->shots > 10 ? 1 : 0);
872 	if (!spec->prog) {
873             nuke_macro(spec);
874 	    return 0;
875 	}
876     }
877 
878     if (spec->world == &AnyWorld) spec->world = xworld();
879     if (spec->pri < 0) spec->pri = 1;
880     if (spec->prob < 0) spec->prob = 100;
881     if (spec->shots < 0) spec->shots = 0;
882     if (spec->invis) spec->invis = 1;
883     if (spec->fallthru < 0) spec->fallthru = 0;
884     if (spec->quiet < 0) spec->quiet = 0;
885     if (!spec->name) spec->name = STRNDUP("", 0);
886     if (!spec->body) (spec->body = blankline)->links++;
887     /*if (!spec->expr) (spec->expr = blankline)->links++;*/
888 
889     if (spec->nsubattr > 0 && spec->trig.mflag != MATCH_REGEXP) {
890         eprintf("\"-P\" requires \"-mregexp -t<pattern>\"");
891         nuke_macro(spec);
892         return 0;
893     }
894     spec->attr &= ~F_NONE;
895     if (spec->nsubattr) {
896 	int n;
897 	pcre_fullinfo(spec->trig.ri->re, NULL, PCRE_INFO_CAPTURECOUNT, &n);
898 	for (i = 0; i < spec->nsubattr; i++) {
899 	    spec->subattr[i].attr &= ~F_NONE;
900 	    if (spec->subattr[i].subexp > n) {
901 		eprintf("-P%d: trigger has only %d subexpressions",
902 		    spec->subattr[i].subexp, n);
903 		nuke_macro(spec);
904 		return 0;
905 	    }
906 	}
907     }
908 
909     if (!spec->keyname) spec->keyname = STRNDUP("", 0);
910     else if (*spec->keyname) {
911         if (spec->bind) FREE(spec->bind);
912         spec->bind = get_keycode(spec->keyname);
913         if (!spec->bind) {
914             eprintf("unknown key name \"%s\".", spec->keyname);
915             nuke_macro(spec);
916             return 0;
917         }
918         spec->bind = STRDUP(spec->bind);
919         if (!*spec->bind)
920             wprintf("no code for key \"%s\".", spec->keyname);
921     }
922 
923     if (!spec->bind) spec->bind = STRNDUP("", 0);
924 
925     if (*spec->name &&
926 	(orig = (Macro *)hashed_find(spec->name, hash, macro_table)))
927     {
928 	if (macro_equal(orig, spec)) {
929 	    /* identical redefinition has no effect */
930 	    nuke_macro(spec);
931 	    return orig->num;
932 	} else if (!redef) {
933 	    eprintf("macro %s already exists", spec->name);
934 	    nuke_macro(spec);
935 	    return 0;
936 	}
937     }
938     if (!add_numbered_macro(spec, hash, num, numnode)) return 0;
939     if (orig) {
940         do_hook(H_REDEF, "!Redefined %s %s", "%s %s", "macro", orig->name);
941         kill_macro(orig);
942     }
943     return spec->num;
944 }
945 
946 /* define a new Macro with hook */
add_hook(char * args,const char * body)947 int add_hook(char *args, const char *body)
948 {
949     hookvec_t hook;
950 
951     VEC_ZERO(&hook);
952     if (!parse_hook(&args, &hook)) return 0;
953     if (args && !*args) args = NULL;
954     return add_new_macro(NULL, "", &hook, args, body, 0, 100, 0, 0, matching);
955 }
956 
957 /* /edit: Edit an existing macro.
958  * Actually editing the macro in place is quite hairy, so instead we
959  * remove the old one, create a replacement and add it.  If the replacement
960  * fails, we re-add the original.  Either way, the number and position in
961  * maclist are unchanged.
962  */
handle_edit_command(String * args,int offset)963 struct Value *handle_edit_command(String *args, int offset)
964 {
965     Macro *spec, *macro = NULL;
966     int error = 0;
967     int num;
968     unsigned int hash = 0;
969     ListEntry *numnode;
970 
971     if (!(args->len - offset) || !(spec = macro_spec(args, offset, NULL, NULL))) {
972         return shareval(val_zero);
973     } else if (!spec->name) {
974         eprintf("You must specify a macro.");
975     } else if (spec->name[0] == '$') {
976         macro = match_exact(0, spec->name + 1, F_ATTR);
977 	if (macro) hash = macro_hash(macro->name);
978     } else {
979 	hash = macro_hash(spec->name); /* used for lookup and insertion */
980 	if (!(macro = find_hashed_macro(spec->name, hash)))
981 	    eprintf("macro %s does not exist", spec->name);
982     }
983 
984     if (!macro) {
985         nuke_macro(spec);
986         return shareval(val_zero);
987     }
988 
989     num = macro->num;
990     numnode = macro->numnode->prev;
991     kill_macro(macro);
992 
993     FREE(spec->name);
994     spec->name = STRDUP(macro->name);
995 
996     if (!spec->body && macro->body) (spec->body = macro->body)->links++;
997     if (!spec->expr && macro->expr) (spec->expr = macro->expr)->links++;
998     if (!spec->bind && macro->bind) spec->bind = STRDUP(macro->bind);
999     if (!spec->keyname && macro->keyname) spec->keyname =STRDUP(macro->keyname);
1000     if (!spec->wtype.str && macro->wtype.str)
1001         error += !copy_pattern(&spec->wtype, &macro->wtype);
1002     if (!spec->trig.str && macro->trig.str)
1003         error += !copy_pattern(&spec->trig, &macro->trig);
1004     if (!(spec->flags & (MACRO_HOOK | MACRO_NOHOOK))) {
1005         spec->flags |= MACRO_HOOK;
1006 	spec->hook = macro->hook;
1007 	if (macro->hargs.str)
1008 	    error += !copy_pattern(&spec->hargs, &macro->hargs);
1009     }
1010     if (!spec->world) spec->world = macro->world;
1011     else if (spec->world == &AnyWorld) spec->world = xworld();
1012     if (spec->pri < 0) spec->pri = macro->pri;
1013     if (spec->prob < 0) spec->prob = macro->prob;
1014     if (spec->shots < 0) spec->shots = macro->shots;
1015     if (spec->fallthru < 0) spec->fallthru = macro->fallthru;
1016     if (spec->quiet < 0) spec->quiet = macro->quiet;
1017     if (spec->attr == 0) spec->attr = macro->attr;
1018     if (spec->nsubattr == 0 && macro->nsubattr > 0) {
1019 	spec->nsubattr = macro->nsubattr;
1020 	spec->subattr = MALLOC(spec->nsubattr * sizeof(subattr_t));
1021 	memcpy(spec->subattr, macro->subattr,
1022 	    spec->nsubattr * sizeof(subattr_t));
1023     }
1024 
1025     spec->used[USED_NAME] = macro->used[USED_NAME];
1026     spec->used[USED_TRIG] = macro->used[USED_TRIG];
1027     spec->used[USED_HOOK] = macro->used[USED_HOOK];
1028     spec->used[USED_KEY] = macro->used[USED_KEY];
1029 
1030     if (!error) {
1031         complete_macro(spec, hash, num, numnode);
1032         return newint(spec->num);
1033     }
1034 
1035     /* Edit failed.  Resurrect original macro. */
1036     macro = dead_macros;
1037     macro->flags &= ~MACRO_DEAD;
1038     dead_macros = macro->tnext;
1039     add_numbered_macro(macro, hash, num, numnode);
1040     return shareval(val_zero);
1041 }
1042 
1043 
1044 /********************************
1045  * Routines for removing macros *
1046  ********************************/
1047 
kill_macro(Macro * macro)1048 void kill_macro(Macro *macro)
1049 {
1050    /* Remove macro from maclist, macro_table, and key_trie, and put it on
1051     * the dead_macros list.  When called from find_and_run_matches(), this
1052     * allows a new macro to be defined without conflicting with the name
1053     * or binding of this macro.
1054     * The macro must NOT be removed from triglist and hooklist, so
1055     * find_and_run_matches() can work correctly when a macro kills itself,
1056     * is a one-shot, or defines another macro that is inserted immediately
1057     * after it in triglist/hooklist.  macro will be removed from triglist
1058     * and hooklist in nuke_macro().
1059     */
1060 
1061     if (macro->flags & MACRO_DEAD) return;
1062     macro->flags |= MACRO_DEAD;
1063     macro->tnext = dead_macros;
1064     dead_macros = macro;
1065     unlist(macro->numnode, maclist);
1066     if (*macro->name) hash_remove(macro->hashnode, macro_table);
1067     if (*macro->bind) unbind_key(macro->bind);
1068 }
1069 
nuke_dead_macros(void)1070 void nuke_dead_macros(void)
1071 {
1072     Macro *macro;
1073 
1074     while ((macro = dead_macros)) {
1075         dead_macros = dead_macros->tnext;
1076         nuke_macro(macro);
1077     }
1078 }
1079 
1080 /* free macro structure */
nuke_macro(Macro * m)1081 static void nuke_macro(Macro *m)
1082 {
1083     if (!(m->flags & MACRO_DEAD) && !(m->flags & MACRO_TEMP)) {
1084         kill_macro(m);
1085     }
1086     if (m->trignode)
1087 	unlist(m->trignode, m->world ? m->world->triglist : triglist);
1088     if (m->flags & MACRO_HOOK) {
1089 	int i;
1090 	ListEntry *node;
1091 	for (i = 0; i < (int)NUM_HOOKS; i++) {
1092 	    if (!VEC_ISSET(i, &m->hook)) continue;
1093 	    for (node = hooklist[i].head; node; node = node->next) {
1094 		if (MAC(node) == m) {
1095 		    unlist(node, &hooklist[i]);
1096 		    break; /* macro can only be in hooklist[i] once */
1097 		}
1098 	    }
1099 	}
1100     }
1101 
1102     if (m->body) conStringfree(m->body);
1103     if (m->expr) conStringfree(m->expr);
1104     if (m->bind) FREE(m->bind);
1105     if (m->keyname) FREE(m->keyname);
1106     if (m->subattr) FREE(m->subattr);
1107     if (m->prog) prog_free(m->prog);
1108     if (m->exprprog) prog_free(m->exprprog);
1109     if (m->builtin && m->builtin->macro == m)
1110 	m->builtin->macro = NULL;
1111     free_pattern(&m->trig);
1112     free_pattern(&m->hargs);
1113     free_pattern(&m->wtype);
1114     if (m->name) FREE(m->name);
1115     FREE(m);
1116 }
1117 
1118 /* delete a macro */
remove_macro_by_name(const char * str)1119 int remove_macro_by_name(const char *str)
1120 {
1121     Macro *macro;
1122 
1123     if (!(macro = find_macro(str))) {
1124 	eprintf("Macro \"%s\" was not defined.", str);
1125 	return 0;
1126     }
1127     kill_macro(macro);
1128     return 1;
1129 }
1130 
1131 /* delete specified macros */
handle_purge_command(String * args,int offset)1132 struct Value *handle_purge_command(String *args, int offset)
1133 {
1134     Macro *spec;
1135     ListEntry *node, *next;
1136     int result = 0;
1137     int mflag;
1138     AuxPat aux;
1139 
1140     if (!(spec = macro_spec(args, offset, &mflag, NULL)))
1141         return shareval(val_zero);
1142     if (spec->name && *spec->name == '#') {
1143         spec->num = atoi(spec->name + 1);
1144         FREE(spec->name);
1145         spec->name = NULL;
1146     }
1147     if (!(init_aux_patterns(spec, mflag, &aux)))
1148 	goto error;
1149     for (node = maclist->head; node; node = next) {
1150 	next = node->next;
1151 	if (macro_match(spec, MAC(node), &aux)) {
1152 	    kill_macro(MAC(node));
1153 	    result++;
1154 	}
1155     }
1156     /* regrelease(); */
1157 error:
1158     free_aux_patterns(&aux);
1159     nuke_macro(spec);
1160     return newint(result);
1161 }
1162 
1163 /* delete macro by number */
handle_undefn_command(String * args,int offset)1164 struct Value *handle_undefn_command(String *args, int offset)
1165 {
1166     int num, result = 0;
1167     Macro *macro;
1168     const char *ptr = args->data + offset;
1169 
1170     while (*ptr) {
1171         if ((num = numarg(&ptr)) >= 0 && (macro = find_num_macro(num))) {
1172             kill_macro(macro);
1173             result++;
1174         }
1175     }
1176     return newint(result);
1177 }
1178 
find_num_macro(int num)1179 Macro *find_num_macro(int num)
1180 {
1181     if (maclist->tail && num >= MAC(maclist->tail)->num) {
1182 	/* search from high end, assuming high macros are more likely;
1183 	 * stop when past */
1184 	ListEntry *node = maclist->head;
1185 	for ( ; node && MAC(node)->num >= num; node = node->next)
1186 	    if (MAC(node)->num == num) return MAC(node);
1187     }
1188     eprintf("no macro with number %d", num);
1189     return NULL;
1190 }
1191 
remove_world_macros(World * w)1192 void remove_world_macros(World *w)
1193 {
1194     ListEntry *node, *next;
1195 
1196     /* This could be more efficient by using w->triglist and
1197      * the hooklists, but this is not used often. */
1198     for (node = maclist->head; node; node = next) {
1199         next = node->next;
1200 	if (MAC(node)->world == w)
1201 	    kill_macro(MAC(node));
1202     }
1203 }
1204 
1205 
1206 /**************************
1207  * Routine to list macros *
1208  **************************/
1209 
1210 /* convert hook vector to string */
hook_name(const hookvec_t * hook)1211 static const String *hook_name(const hookvec_t *hook)
1212 {
1213     int i;
1214     STATIC_BUFFER(buf);
1215 
1216     Stringtrunc(buf, 0);
1217     for (i = 0; i < (int)NUM_HOOKS; i++) {
1218                  /* ^^^^^ Some brain dead compilers need that cast */
1219 	if (!VEC_ISSET(i, hook)) continue;
1220         if (buf->len) Stringadd(buf, '|');
1221         Stringcat(buf, hook_table[i].name);
1222     }
1223     return buf;
1224 }
1225 
print_def(TFILE * file,String * buffer,Macro * p)1226 static conString *print_def(TFILE *file, String *buffer, Macro *p)
1227 {
1228     int mflag = -1;
1229 
1230     if (!buffer)
1231 	buffer = Stringnew(NULL, 0, 0);
1232     buffer->links++;
1233 
1234     if (file && file == tfout)
1235 	Sprintf(buffer, "%% %d: /def ", p->num);
1236     else Stringcpy(buffer, "/def ");
1237     if (p->invis) Stringcat(buffer, "-i ");
1238     if (p->trig.str || (p->flags & MACRO_HOOK))
1239 	Sappendf(buffer, "-%sp%d ", p->fallthru ? "F" : "", p->pri);
1240     if (p->prob != 100)
1241 	Sappendf(buffer, "-c%d ", p->prob);
1242     if (p->attr) {
1243 	Stringcat(attr2str(Stringcat(buffer, "-a"), p->attr), " ");
1244     }
1245     if (p->nsubattr > 0) {
1246 	int i;
1247 	mflag = MATCH_REGEXP;
1248 	Stringcat(buffer, "-P");
1249 	for (i = 0; i < p->nsubattr; i++) {
1250 	    if (i > 0) Stringadd(buffer, ';');
1251 	    if (p->subattr[i].subexp == -1)
1252 		Stringadd(buffer, 'L');
1253 	    else if (p->subattr[i].subexp == -2)
1254 		Stringadd(buffer, 'R');
1255 	    else
1256 		Sappendf(buffer, "%d", (int)p->subattr[i].subexp);
1257 	    attr2str(buffer, p->subattr[i].attr);
1258 	}
1259 	Stringadd(buffer, ' ');
1260     }
1261     if (p->shots)
1262 	Sappendf(buffer, "-n%d ", p->shots);
1263     if (p->world)
1264 	Sappendf(buffer, "-w'%q' ", '\'', p->world->name);
1265     if (p->wtype.str) {
1266 	if (p->wtype.mflag != mflag)
1267 	    Sappendf(buffer, "-m%S ", &enum_match[p->wtype.mflag]);
1268 	mflag = p->wtype.mflag;
1269 	Sappendf(buffer, "-T'%q' ", '\'', p->wtype.str);
1270     }
1271 
1272     if (p->expr)
1273 	Sappendf(buffer, "-E'%q' ", '\'', p->expr->data);
1274 
1275     if (p->trig.str) {
1276 	if (p->trig.mflag != mflag)
1277 	    Sappendf(buffer, "-m%S ", &enum_match[p->trig.mflag]);
1278 	mflag = p->trig.mflag;
1279 	Sappendf(buffer, "-t'%q' ", '\'', p->trig.str);
1280     }
1281     if (p->flags & MACRO_HOOK) {
1282 	if (p->hargs.str && *p->hargs.str) {
1283 	    if (p->hargs.mflag != mflag)
1284 		Sappendf(buffer, "-m%S ", &enum_match[mflag = p->hargs.mflag]);
1285 	    Sappendf(buffer, "-h'%S %q' ",
1286 		hook_name(&p->hook), '\'', p->hargs.str);
1287 	} else {
1288 	    Sappendf(buffer, "-h%S ", hook_name(&p->hook));
1289 	}
1290     }
1291 
1292 #if 0 /* obsolete */
1293     if (*p->keyname)
1294 	Sappendf(buffer, "-B'%s' ", p->keyname);
1295     else
1296 #endif
1297     if (*p->bind)
1298 	Sappendf(buffer, "-b'%q' ", '\'', ascii_to_print(p->bind)->data);
1299 
1300     if (p->quiet) Stringcat(buffer, "-q ");
1301     if (*p->name == '-') Stringcat(buffer, "- ");
1302     if (*p->name) Sappendf(buffer, "%s ", p->name);
1303     if (p->body && p->body->len) Sappendf(buffer, "= %S", p->body);
1304 
1305     if (file) {
1306 	tfputline(CS(buffer), file);
1307 	Stringfree(buffer);
1308 	return NULL;
1309     } else {
1310 	return CS(buffer);
1311     }
1312 }
1313 
1314 /* list all specified macros */
list_defs(TFILE * file,Macro * spec,int mflag,ListOpts * listopts)1315 static int list_defs(TFILE *file, Macro *spec, int mflag, ListOpts *listopts)
1316 {
1317     Macro *p;
1318     ListEntry *node;
1319     AuxPat aux;
1320     String *buffer = NULL;
1321     int result = 0, i;
1322     Vector macs = vector_init(1024);
1323 
1324     if (!(init_aux_patterns(spec, mflag, &aux))) goto error;
1325     if (spec->name && *spec->name == '#') {
1326         spec->num = atoi(spec->name + 1);
1327         FREE(spec->name);
1328         spec->name = NULL;
1329     }
1330 
1331     /* maclist is in reverse numeric order, so we start from tail */
1332     for (node = maclist->tail; node; node = node->prev) {
1333         p = MAC(node);
1334         if (!macro_match(spec, p, &aux)) continue;
1335 	vector_add(&macs, p);
1336     }
1337 
1338     if (listopts && listopts->cmp)
1339 	vector_sort(&macs, listopts->cmp);
1340 
1341     for (i = 0; i < macs.size; i++) {
1342         p = macs.ptrs[i];
1343         result = p->num;
1344 
1345         if (!buffer)
1346             (buffer = Stringnew(NULL, 0, 0))->links++;
1347 
1348         if (listopts && listopts->shortflag) {
1349             Sprintf(buffer, "%% %d: ", p->num);
1350             if (p->attr & F_NOHISTORY) Stringcat(buffer, "(nohistory) ");
1351             if (p->attr & F_NOLOG) Stringcat(buffer, "(nolog) ");
1352             if (p->attr & F_GAG) Stringcat(buffer, "(gag) ");
1353             else if (p->attr & (F_HWRITE | F_EXCLUSIVE)) {
1354                 if (p->attr & F_UNDERLINE) Stringcat(buffer, "(underline) ");
1355                 if (p->attr & F_REVERSE)   Stringcat(buffer, "(reverse) ");
1356                 if (p->attr & F_FLASH)     Stringcat(buffer, "(flash) ");
1357                 if (p->attr & F_DIM)       Stringcat(buffer, "(dim) ");
1358                 if (p->attr & F_BOLD)      Stringcat(buffer, "(bold) ");
1359                 if (p->attr & F_BELL)      Stringcat(buffer, "(bell) ");
1360                 if (p->attr & F_HILITE)    Stringcat(buffer, "(hilite) ");
1361                 if (p->attr & F_FGCOLOR)
1362                     Sappendf(buffer, "(%S) ",
1363                         &enum_color[attr2fgcolor(p->attr)]);
1364                 if (p->attr & F_BGCOLOR)
1365                     Sappendf(buffer, "(bg%S) ",
1366                         &enum_color[attr2bgcolor(p->attr)]);
1367             } else if (p->nsubattr > 0) {
1368                 Stringcat(buffer, "(partial) ");
1369             } else if (p->trig.str) {
1370 		Stringcat(buffer, "(trig");
1371 		if (listopts && listopts->usedflag)
1372 		    Sappendf(buffer, " %d", p->used[USED_TRIG]);
1373 		Stringcat(buffer, ") ");
1374             }
1375             if (p->trig.str)
1376                 Sappendf(buffer, "'%q' ", '\'', p->trig.str);
1377 #if 0 /* obsolete */
1378             if (*p->keyname) {
1379 		Stringcat(buffer, "(key");
1380 		if (listopts && listopts->usedflag)
1381 		    Sappendf(buffer, " %d", p->used[USED_KEY]);
1382                 Sappendf(buffer, ") '%s' ", p->keyname);
1383             } else
1384 #endif
1385 	    if (*p->bind) {
1386 		Stringcat(buffer, "(bind");
1387 		if (listopts && listopts->usedflag)
1388 		    Sappendf(buffer, " %d", p->used[USED_KEY]);
1389                 Sappendf(buffer, ") '%q' ", '\'',
1390                     ascii_to_print(p->bind)->data);
1391                 if (p->keyname && *p->keyname)
1392 		    Sappendf(buffer, "(%s) ", p->keyname);
1393             }
1394             if (p->flags & MACRO_HOOK) {
1395 		Stringcat(buffer, "(hook");
1396 		if (listopts && listopts->usedflag)
1397 		    Sappendf(buffer, " %d", p->used[USED_HOOK]);
1398                 Sappendf(buffer, ") %S ", hook_name(&p->hook));
1399 	    }
1400             if (*p->name) {
1401 		Sappendf(buffer, "%s ", p->name);
1402 		if (listopts && listopts->usedflag)
1403 		    Sappendf(buffer, "(%d) ", p->used[USED_NAME]);
1404 	    }
1405 	    tfputline(CS(buffer), file ? file : tfout);
1406 
1407         } else {
1408 	    print_def(file ? file : tfout, buffer, p);
1409         }
1410 
1411         /* If something is sharing buffer, we can't reuse it in next loop. */
1412         if (buffer->links > 1) {
1413             Stringfree(buffer);
1414             buffer = NULL;
1415         }
1416     }
1417     /* regrelease(); */
1418     vector_free(&macs);
1419 error:
1420     free_aux_patterns(&aux);
1421     if (buffer) {
1422         Stringfree(buffer);
1423         buffer = NULL;
1424     }
1425     return result;
1426 }
1427 
1428 /* write specified macros to file */
save_macros(String * args,int offset)1429 int save_macros(String *args, int offset)
1430 {
1431     Macro *spec;
1432     TFILE *file = NULL;
1433     int result = 1;
1434     const char *name, *mode = "w";
1435     char opt, *next;
1436     int mflag;
1437 
1438     startopt(CS(args), "a");
1439     while ((opt = nextopt(NULL, NULL, NULL, &offset))) {
1440         if (opt != 'a') return 0;
1441         mode = "a";
1442     }
1443 
1444     next = args->data + offset;
1445     name = stringarg(&next, NULL);
1446     offset = next - args->data;
1447     if (!(spec = macro_spec(args, offset, &mflag, NULL))) result = 0;
1448     if (result && !(file = tfopen(expand_filename(name), mode))) {
1449         operror(name);
1450         result = 0;
1451     }
1452 
1453     if (result) {
1454         oprintf("%% %sing macros to %s", *mode=='w' ? "Writ" : "Append",
1455             file->name);
1456         result = list_defs(file, spec, mflag, NULL);
1457     }
1458     if (file) tfclose(file);
1459     if (spec) nuke_macro(spec);
1460     return result;
1461 }
1462 
1463 /* list macros on screen */
handle_list_command(String * args,int offset)1464 struct Value *handle_list_command(String *args, int offset)
1465 {
1466     Macro *spec;
1467     int result = 1;
1468     int mflag;
1469     ListOpts opts = { 0, 0, NULL };
1470 
1471     if (!(spec = macro_spec(args, offset, &mflag, &opts))) result = 0;
1472     if (result) result = list_defs(NULL, spec, mflag, &opts);
1473     if (spec) nuke_macro(spec);
1474     return newint(result);
1475 }
1476 
1477 
1478 /**************************
1479  * Routines to use macros *
1480  **************************/
1481 
1482 /* Do a macro! */
do_macro(Macro * macro,String * args,int offset,int used_type,int kbnumlocal)1483 int do_macro(Macro *macro, String *args, int offset, int used_type,
1484     int kbnumlocal)
1485 {
1486     int result, old_invis_flag, oldblock;
1487     const char *command;
1488     char numbuf[16];
1489 
1490     if (*macro->name) {
1491         command = macro->name;
1492     } else {
1493         sprintf(numbuf, "#%d", macro->num);
1494         command = numbuf;
1495     }
1496     if (used_type >= 0) {
1497 	macro->used[used_type]++;
1498     }
1499     old_invis_flag = invis_flag;
1500     invis_flag = macro->invis;
1501     oldblock = block; /* XXX ? */
1502     block = 0; /* XXX ? */
1503     if (!macro->prog) {
1504 	const char *old_command = current_command;
1505 	current_command = command; /* for macro compiler errors */
1506 	macro->prog = compile_tf(macro->body, 0, SUB_MACRO, 0,
1507 	    !macro->shots ? 2 : macro->shots > 10 ? 1 : 0);
1508 	current_command = old_command;
1509     }
1510     if (!macro->prog)
1511 	result = 0;
1512     else
1513 	result = prog_run(macro->prog, args, offset, command, kbnumlocal);
1514     invis_flag = old_invis_flag;
1515     block = oldblock; /* XXX ? */
1516     return result;
1517 }
1518 
1519 /* get body of macro */
macro_body(const char * name)1520 const char *macro_body(const char *name)
1521 {
1522     Macro *m;
1523     const char *body;
1524 
1525     if (!name) return NULL;
1526     if (strncmp("world_", name, 6) == 0 && (body = world_info(NULL, name + 6)))
1527         return body;
1528     if (!(m = find_macro(name))) return NULL;
1529     return m->body ? m->body->data : "";
1530 }
1531 
1532 
1533 /****************************************
1534  * Routines to check triggers and hooks *
1535  ****************************************/
1536 
1537 /* do_hook
1538  * Call macros that match <hooknum> and optionally the filled-in <argfmt>, and
1539  * prints the message in <fmt>.  Returns the number of matches that were run.
1540  * A leading '!' in <fmt> is replaced with "% ", file name, and line number.
1541  * Note that calling do_hook() makes the caller non-atomic; be careful.
1542  */
do_hook(int hooknum,const char * fmt,const char * argfmt,...)1543 int do_hook(int hooknum, const char *fmt, const char *argfmt, ...)
1544 {
1545     va_list ap;
1546     int ran = 0;
1547     String *line = NULL;
1548     String *args = NULL;
1549     /* do_hook is re-entrant, so we can't use static buffer.  macro regexps
1550      * may save a pointer to args, so we can't even use an auto buffer. */
1551 
1552     va_start(ap, argfmt);
1553     if (hookflag || hilite || gag) {
1554         (args = Stringnew(NULL, 96, 0))->links++;
1555         vSprintf(args, 0, argfmt, ap);
1556     }
1557     va_end(ap);
1558 
1559     if (fmt) {
1560         (line = Stringnew(NULL, 96, 0))->links++;
1561         if (*fmt == '!') {
1562             eprefix(line);
1563             fmt++;
1564         }
1565         va_start(ap, argfmt);
1566         vSprintf(line, SP_APPEND, fmt, ap);
1567         va_end(ap);
1568     }
1569 
1570     if (hookflag || hilite || gag) {
1571         ran = find_and_run_matches(args, hooknum, &line, xworld(), TRUE, 0);
1572         Stringfree(args);
1573     }
1574 
1575     if (line) {
1576         Stringfree(line);
1577     }
1578     return ran;
1579 }
1580 
1581 /* Find and run one or more matches for a hook or trig.
1582  * text is text to be matched; if NULL, *linep is used.
1583  * If %Pn subs are to be allowed, text should be NULL.
1584  * If <hooknum> is <0, this looks for a trigger;
1585  * if <hooknum> is >=0 it is a hook number.  If <linep> is non-NULL,
1586  * attributes of matching macros will be applied to *<linep>.
1587  */
find_and_run_matches(String * text,int hooknum,String ** linep,World * world,int globalflag,int exec_list_long)1588 int find_and_run_matches(String *text, int hooknum, String **linep,
1589     World *world, int globalflag, int exec_list_long)
1590 {
1591     Queue runq[1];			    /* queue of macros to run */
1592     Macro *nonfallthru = NULL;		    /* list of non fall-thrus */
1593     int num = 0;                            /* # of non-fall-thrus */
1594     int ran = 0;                            /* # of executed macros */
1595     int lowerlimit = -1;                    /* lowest priority that can match */
1596     int header = 0;			    /* which headers have we printed? */
1597     ListEntry *gnode, *wnode, **nodep;
1598     Pattern *pattern;
1599     Macro *macro;
1600     const char *worldtype = NULL;
1601 
1602     /* Macros are sorted by decreasing priority, with fall-thrus first.  So,
1603      * we search the global and world lists in parallel.  For each matching
1604      * fall-thru, we apply its attributes; if it's a hook, we add it to a
1605      * queue, if it's a trigger, we execute it immediately.  When we find a
1606      * matching non-fall-thru, we collect a list of other non-fall-thru
1607      * matches of the same priority and select one, and apply its attributes;
1608      * again, if it's a hook, we add it to queue, if it's a trigger, we
1609      * execute.
1610      * Then, we print the line.
1611      * Then, if macros were queued (because this is a hook), we run all the
1612      * queued macros.
1613      * The point of the queue is so the line can be printed before any output
1614      * generated by the macros.  We would like to do this for triggers as well
1615      * as hooks, but then /substitute wouldn't work.
1616      */
1617     /* Note: kill_macro() does not remove macros from any lists, so this will
1618      * work correctly when a macro kills itself, or inserts a new macro just
1619      * after itself in a list.
1620      */
1621 
1622     if (world)
1623 	worldtype = world_type(world);
1624     if (!worldtype)
1625 	worldtype = "";
1626     if (!text)
1627         text = *linep;
1628     text->links++; /* in case substitute() frees text */
1629     recur_count++;
1630     if (hooknum>=0) {
1631 	gnode = hooklist[hooknum].head;
1632 	wnode = NULL;
1633     } else {
1634 	gnode = triglist->head;
1635 	wnode = world ? world->triglist->head : NULL;
1636     }
1637 
1638     if (exec_list_long == 0) {
1639 	init_queue(runq);
1640     }
1641 
1642     while (gnode || wnode) {
1643 	nodep = (!wnode) ? &gnode : (!gnode) ? &wnode :
1644 	    (rpricmp(MAC(wnode), MAC(gnode)) > 0) ? &gnode : &wnode;
1645 	macro = MAC(*nodep);
1646 	*nodep = (*nodep)->next;
1647 
1648 	if (macro->pri < lowerlimit && exec_list_long == 0)
1649 	    break;
1650         if (macro->flags & MACRO_DEAD) continue;
1651         if (!(
1652 	    (hooknum<0 && (
1653 		(borg && macro->body && (macro->prob > 0)) ||
1654 		(hilite && ((macro->attr & F_HWRITE) || macro->nsubattr)) ||
1655 		(gag && (macro->attr & F_GAG)))) ||
1656 	    (hooknum>=0 && VEC_ISSET(hooknum, &macro->hook))))
1657 	{
1658 	    continue;
1659 	}
1660 
1661 	/* triggers are listed by world, but hooks are not, so we must check */
1662         if (macro->world && macro->world != world) continue;
1663 
1664         if (!globalflag && !macro->world) continue;
1665         if (macro->wtype.str) {
1666             if (!world) continue;
1667             if (!patmatch(&macro->wtype, NULL, worldtype))
1668                 continue;
1669         }
1670         if (macro->exprprog) {
1671             struct Value *result = NULL;
1672             int expr_condition;
1673 	    result = expr_value_safe(macro->exprprog);
1674             expr_condition = valbool(result);
1675             freeval(result);
1676             if (!expr_condition) continue;
1677         }
1678         pattern = hooknum>=0 ? &macro->hargs : &macro->trig;
1679         if ((hooknum>=0 && !macro->hargs.str) || patmatch(pattern, CS(text), NULL))
1680 	{
1681 	    if (exec_list_long == 0) {
1682 		if (macro->fallthru) {
1683 		    if (linep && *linep)
1684 			apply_attrs_of_match(macro, text, hooknum, *linep);
1685 		    if (hooknum>=0) {
1686 			enqueue(runq, macro);
1687 		    } else {
1688 			ran += run_match(macro, text, hooknum);
1689 			if (linep && hooknum<0) {
1690 			    /* in case of /substitute */ /* XXX */
1691 			    Stringfree(text);
1692 			    text = *linep;
1693 			    text->links++;
1694 			}
1695 		    }
1696 		} else {
1697 		    /* collect list of non-fall-thru matches */
1698 		    lowerlimit = macro->pri;
1699 		    num++;
1700 		    macro->tnext = nonfallthru;
1701 		    nonfallthru = macro;
1702 		}
1703 	    } else {
1704 		ran += (lowerlimit < 0);
1705 		if (header < 3 && macro->pri < lowerlimit) {
1706 		    oputs("% The following matching macros would not be "
1707 			"applied:");
1708 		    header = 3;
1709 		}
1710 		if (header < 2 && !macro->fallthru) {
1711 		    oprintf("%% One of the following macros would %sbe "
1712 			"applied:", ran > 1 ? "also " : "");
1713 		    lowerlimit = macro->pri;
1714 		    header = 2;
1715 		}
1716 		if (header < 1) {
1717 		    oputs("% All of the following macros would be applied:");
1718 		    header = 1;
1719 		}
1720 		if (exec_list_long > 1)
1721 		    print_def(tfout, NULL, macro);
1722 		else if (macro->name)
1723 		    oprintf("%% %c %10d %s", macro->fallthru ? 'F' : ' ',
1724 			macro->pri, macro->name);
1725 		else
1726 		    oprintf("%% %c %10d #%d", macro->fallthru ? 'F' :' ',
1727 			macro->pri, macro->num);
1728 	    }
1729         }
1730     }
1731 
1732     if (exec_list_long == 0) {
1733 	/* select exactly one of the non fall-thrus. */
1734 	if (num > 0) {
1735 	    for (macro = nonfallthru, num = RRAND(0, num-1); num; num--)
1736 		macro = macro->tnext;
1737 	    if (linep && *linep)
1738 		apply_attrs_of_match(macro, text, hooknum, *linep);
1739 	    if (hooknum>=0) {
1740 		enqueue(runq, macro);
1741 	    } else {
1742 		ran += run_match(macro, text, hooknum);
1743 	    }
1744 	}
1745 
1746 	/* print the line! */
1747 	if (hooknum>=0 && linep && *linep) {
1748 	    if (hook_table[hooknum].hooktype & HT_ALERT) {
1749 		alert(CS(*linep));
1750 	    } else if (hook_table[hooknum].hooktype & HT_WORLD) {
1751 		/* Note: world_output() must come before alert(), otherwise
1752 		 * world_output() could cause an activity hook that would
1753 		 * clobber the alert */
1754 		if (xworld())
1755 		    world_output(xworld(), CS(*linep));
1756 		if (!xsock_is_fg()) {
1757 		    alert(CS(*linep));
1758 		}
1759 	    } else {
1760 		tfputline(CS(*linep), tferr); /* XXX conStr bug??? */
1761 	    }
1762 	    if (hook_table[hooknum].hooktype & HT_XSOCK) {
1763 		xsock_alert_id(); /* alert should clear when xsock is fg'd */
1764 	    }
1765 	}
1766 
1767 	/* run all of the queued macros */
1768 	while ((macro = (Macro*)dequeue(runq))) {
1769 	    ran += run_match(macro, text, hooknum);
1770 	}
1771     } else {
1772 	oprintf("%% %s would have %s %d macro%s.",
1773 	    hooknum>=0 ? "Event" : "Text", hooknum>=0 ? "hooked" : "triggered",
1774 	    ran, (ran != 1) ? "s" : "");
1775     }
1776 
1777     recur_count--;
1778     Stringfree(text);
1779     return ran;
1780 }
1781 
1782 
1783 /* apply attributes of a macro that has been selected by a trigger or hook */
apply_attrs_of_match(Macro * macro,String * text,int hooknum,String * line)1784 static void apply_attrs_of_match(
1785     Macro *macro,	/* macro to apply */
1786     String *text,	/* argument text that matched trigger/hook */
1787     int hooknum,	/* hook number */
1788     String *line)	/* line to which attributes are applied */
1789 {
1790     RegInfo *old, *ri;
1791 
1792     if (!hilite && !gag) return;
1793 
1794     /* Apply attributes (full and partial) to line. */
1795     if (!hilite)
1796 	line->attrs = adj_attr(line->attrs, macro->attr & F_GAG);
1797     else if (!gag)
1798 	line->attrs = adj_attr(line->attrs, macro->attr & ~F_GAG);
1799     else
1800 	line->attrs = adj_attr(line->attrs, macro->attr);
1801 
1802     ri = (hooknum>=0 ? macro->hargs : macro->trig).ri;
1803 
1804     if (hooknum<0 && macro->trig.mflag == MATCH_REGEXP && line->len && hilite
1805 	&& macro->nsubattr)
1806     {
1807 	int i, x, offset = 0;
1808 	int start, end;
1809 	int *saved_ovector = NULL;
1810 
1811 	if (text)
1812 	    old = new_reg_scope(ri, text);
1813 
1814 	check_charattrs(line, line->len, 0, __FILE__, __LINE__);
1815 	do {
1816 	    for (x = 0; x < macro->nsubattr; x++) {
1817 		if (macro->subattr[x].subexp == -1) {
1818 		    start = 0;
1819 		    end = ri->ovector[0];
1820 		} else if (macro->subattr[x].subexp == -2) {
1821 		    start = ri->ovector[1];
1822 		    end = line->len;
1823 		} else {
1824 		    start = ri->ovector[macro->subattr[x].subexp * 2];
1825 		    end = ri->ovector[macro->subattr[x].subexp * 2 + 1];
1826 		}
1827 		for (i = start; i < end; ++i)
1828 		    line->charattrs[i] =
1829 			adj_attr(line->charattrs[i], macro->subattr[x].attr);
1830 	    }
1831 	    if (offset == ri->ovector[1]) break; /* offset wouldn't move */
1832 	    offset = ri->ovector[1];
1833 	    if (!saved_ovector) {
1834 		saved_ovector = ri->ovector;
1835 		ri->ovector = NULL;
1836 	    }
1837 	} while (offset < line->len &&
1838 	    tf_reg_exec(ri, CS(text), NULL, offset) > 0);
1839 	/* restore original startp/endp */
1840 	if (saved_ovector) {
1841 	    if (ri->ovector) FREE(ri->ovector);
1842 	    ri->ovector = saved_ovector;
1843 	}
1844 	(ri->Str = CS(line))->links++;
1845 
1846 	if (text)
1847 	    restore_reg_scope(old);
1848     }
1849 }
1850 
1851 typedef struct max_ctr {
1852     int bucket[2];
1853     int count[2];
1854     const int maxid, flagid;
1855     const char *label;
1856 } max_ctr_t;
1857 
1858 static max_ctr_t trig_ctr = {{0,0}, {0,0}, VAR_max_trig, VAR_borg, "Trigger"};
1859 static max_ctr_t hook_ctr = {{0,0}, {0,0}, VAR_max_hook, VAR_hook, "Hook"};
1860 
test_max_counter(max_ctr_t * c)1861 static int test_max_counter(max_ctr_t *c)
1862 {
1863     static int bucketsize = 5;
1864     int now = time(NULL) / bucketsize;
1865     if (now != c->bucket[1]) {
1866 	c->bucket[0] = now - 1;
1867 	c->bucket[1] = now;
1868 	c->count[0] = (now == c->bucket[1] + 1) ? c->count[1] : 1;
1869 	c->count[1] = 0;
1870     }
1871     if (c->count[0] + ++c->count[1] > special_var[c->maxid].val.u.ival) {
1872 	set_var_by_id(c->flagid, 0);
1873 	eprintf("%s rate exceeded %%%s (%d) in less than %ds.  "
1874 	    "Setting %%%s off.", c->label,
1875 	    special_var[c->maxid].val.name, special_var[c->maxid].val.u.ival,
1876 	    2*bucketsize, special_var[c->flagid].val.name);
1877 	return 0;
1878     }
1879     return 1;
1880 }
1881 
1882 /* run a macro that has been selected by a trigger or hook */
run_match(Macro * macro,String * text,int hooknum)1883 static int run_match(
1884     Macro *macro,	/* macro to run */
1885     String *text,	/* argument text that matched trigger/hook */
1886     int hooknum)	/* hook number */
1887 {
1888     int ran = 0;
1889     struct Sock *callingsock = xsock;
1890     RegInfo *old;
1891 
1892     if (hooknum < 0) { /* trigger */
1893 	if (!borg) return 0;
1894 	if (text && max_trig > 0 && !test_max_counter(&trig_ctr)) return 0;
1895     } else { /* hook */
1896 	if (max_hook > 0 && !test_max_counter(&hook_ctr)) return 0;
1897     }
1898 
1899     if (text)
1900         old = new_reg_scope(hooknum>=0 ? macro->hargs.ri : macro->trig.ri, text);
1901 
1902     /* Execute the macro. */
1903     if ((hooknum>=0 && hookflag) || (hooknum<0 && borg)) {
1904 	callingsock = xsock;
1905         if (macro->prob == 100 || RRAND(0, 99) < macro->prob) {
1906             if (macro->shots && !--macro->shots) kill_macro(macro);
1907             if (mecho > macro->invis) {
1908                 char numbuf[16];
1909                 if (!*macro->name) sprintf(numbuf, "#%d", macro->num);
1910                 tfprintf(tferr, "%S%s%s: /%s %S%A", do_mprefix(),
1911                     hooknum>=0 ? hook_table[hooknum].name : "",
1912                     hooknum>=0 ? " HOOK" : "TRIGGER",
1913                     *macro->name ? macro->name : numbuf,
1914 		    text, mecho_attr);
1915             }
1916             if (macro->body && macro->body->len) {
1917                 do_macro(macro, text, 0, hooknum>=0 ? USED_HOOK : USED_TRIG, 0);
1918                 ran += !macro->quiet;
1919             }
1920         }
1921 
1922 	/* Restore xsock, in case macro called fg_sock().  main_loop() will
1923 	 * set xsock=fsock, so any fg_sock() will effect xsock after the
1924 	 * find_and_run_matches() loop is complete.
1925 	 */
1926 	xsock = callingsock;
1927     }
1928 
1929     if (text)
1930         restore_reg_scope(old);
1931     return ran;
1932 }
1933 
1934 #if USE_DMALLOC
free_macros(void)1935 void free_macros(void)
1936 {
1937     while (maclist->head) nuke_macro((Macro *)maclist->head->datum);
1938     free_hash(macro_table);
1939 }
1940 #endif
1941 
1942