/*************************************************************************
* TinyFugue - programmable mud client
* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
*
* TinyFugue (aka "tf") is protected under the terms of the GNU
* General Public License. See the file "COPYING" for details.
************************************************************************/
static const char RCSid[] = "$Id: macro.c,v 35004.188 2007/01/13 23:12:39 kkeys Exp $";
/**********************************************
* Fugue macro package *
* *
* Macros, hooks, triggers, hilites and gags *
* are all processed here. *
**********************************************/
#include "tfconfig.h"
#include "port.h"
#include "tf.h"
#include "util.h"
#include "pattern.h"
#include "search.h"
#include "tfio.h"
#include "world.h"
#include "macro.h"
#include "keyboard.h" /* bind_key()... */
#include "expand.h"
#include "socket.h" /* xworld() */
#include "output.h" /* get_keycode() */
#include "attr.h"
#include "cmdlist.h"
#include "command.h"
#include "parse.h" /* valbool() for /def -E */
#include "variable.h" /* set_var_by_id() */
typedef struct {
cattr_t attr;
short subexp;
} subattr_t;
struct Macro {
const char *name;
struct ListEntry *numnode; /* node in maclist */
struct ListEntry *hashnode; /* node in macro_table hash bucket */
struct ListEntry *trignode; /* node in one of the triglists */
struct Macro *tnext; /* temp list ptr for collision/death */
conString *body, *expr;
Program *prog, *exprprog; /* compiled body, expr */
const char *bind, *keyname;
Pattern trig, hargs, wtype; /* trigger/hook/worldtype patterns */
hookvec_t hook; /* bit vector */
struct World *world; /* only trig on text from world */
int pri, num;
attr_t attr;
int nsubattr;
subattr_t *subattr;
short prob, shots, invis;
short flags;
signed char fallthru, quiet;
struct BuiltinCmd *builtin; /* builtin cmd with same name, if any */
int used[USED_N]; /* number of calls by each method */
};
typedef struct {
Pattern name, body, bind, keyname, expr;
} AuxPat;
typedef struct {
int shortflag;
int usedflag;
Cmp *cmp;
} ListOpts;
int invis_flag = 0;
static Macro *macro_spec(String *args, int offset, int *xmflag, ListOpts *opts);
static int macro_match(Macro *spec, Macro *macro, AuxPat *aux);
static int add_numbered_macro(Macro *macro, unsigned int hash, int num,
ListEntry *numnode);
static int complete_macro(Macro *spec, unsigned int hash, int num,
ListEntry *numnode);
static int init_aux_patterns(Macro *spec, int mflag, AuxPat *aux);
static Macro *match_exact(int hooknum, const char *str, attr_t attrs);
static int list_defs(TFILE *file, Macro *spec, int mflag, ListOpts *opts);
static void apply_attrs_of_match(Macro *macro, String *text, int hooknum,
String *line);
static int run_match(Macro *macro, String *text, int hooknum);
static const String *hook_name(const hookvec_t *hook) PURE;
static conString *print_def(TFILE *file, String *buffer, Macro *p);
static int rpricmp(const Macro *m1, const Macro *m2);
static void nuke_macro(Macro *macro);
#define HASH_SIZE 997 /* prime number */
#define MACRO_TEMP 0x01
#define MACRO_DEAD 0x02
#define MACRO_HOOK 0x08
#define MACRO_NOHOOK 0x10 /* -h0 */
#define INVALID_SUBEXP -3
static List maclist[1]; /* list of all (live) macros */
static List triglist[1]; /* list of macros by trigger */
static List hooklist[NUM_HOOKS]; /* lists of macros by hook */
static Macro *dead_macros; /* head of list of dead macros */
static HashTable macro_table[1]; /* macros hashed by name */
static World NoWorld, AnyWorld; /* explicit "no" and "any" */
static int mnum = 0; /* macro ID number */
typedef enum {
HT_TEXT = 0x00, /* normal text in fg world */
HT_ALERT = 0x01, /* alert */
HT_WORLD = 0x02, /* text in xsock->world, plus alert if xsock != fsock */
HT_XSOCK = 0x04 /* alert should be cleared if xsock is foregrounded */
} hooktype_t;
typedef struct hookrec {
const char *name;
hooktype_t hooktype;
} hookrec_t;
static const hookrec_t hook_table[] = {
#define gencode(id, type) { #id, type }
#include "hooklist.h"
#undef gencode
};
#define NONNULL(str) ((str) ? (str) : "")
/* These macros allow easy sharing of trigger and hook code. */
#define MAC(Node) ((Macro *)((Node)->datum))
void init_macros(void)
{
int i;
init_hashtable(macro_table, HASH_SIZE, cstrstructcmp);
init_list(maclist);
init_list(triglist);
for (i = 0; i < (int)NUM_HOOKS; i++)
init_list(&hooklist[i]);
}
/***************************************
* Routines for parsing macro commands *
***************************************/
int hookname2int(const char *name)
{
const hookrec_t *hookrec;
hookrec = bsearch((void*)(name), (void *)hook_table,
NUM_HOOKS, sizeof(hookrec_t), cstrstructcmp);
if (hookrec)
return hookrec - hook_table;
if (cstrcmp(name, "BACKGROUND") == 0) /* backward compatability */
return H_BGTRIG;
if (cstrcmp(name, "CONNETFAIL") == 0) /* backward compatability */
eprintf("invalid hook event \"%s\"; see \"/help /connect\"", name);
else
eprintf("invalid hook event \"%s\"", name);
return -1;
}
/* Convert hook string to bit vector; return 0 on error, 1 on success. */
static int parse_hook(char **argp, hookvec_t *hookvec)
{
char *in, state;
VEC_ZERO(hookvec);
if (!**argp) {
memset(hookvec, 0xFF, sizeof(*hookvec));
return 1;
}
for (state = '|'; state == '|'; *argp = in) {
for (in = *argp; *in && !is_space(*in) && *in != '|'; ++in);
state = *in;
*in++ = '\0';
if (strcmp(*argp, "*") == 0) {
memset(hookvec, 0xFF, sizeof(*hookvec));
} else {
int i;
if ((i = hookname2int(*argp)) < 0)
return 0;
VEC_SET(i, hookvec);
}
}
if (!state) *argp = NULL;
return 1;
}
/* macro_spec
* Converts a macro description string to a more useful Macro structure.
* Omitted fields are set to a value that means "don't care".
* In /def, don't care fields are set to their default values;
* in macro_match(), they are not used in the comparison. Don't care
* values for numeric fields are -1 or 0, depending on the field; for
* strings, NULL; for hooks, neither MACRO_HOOK nor MACRO_NOHOOK will
* be set.
*/
static Macro *macro_spec(String *args, int offset, int *xmflag, ListOpts *listopts)
{
Macro *spec;
char opt;
const char *ptr, *s, *name = NULL, *body = NULL, *nameend, *bodyend;
int i, n, mflag = -1, error = 0;
ValueUnion uval;
attr_t attrs;
if (!(spec = (Macro *)MALLOC(sizeof(struct Macro)))) {
eprintf("macro_spec: not enough memory");
return NULL;
}
spec->num = 0;
spec->body = spec->expr = NULL;
spec->prog = spec->exprprog = NULL;
spec->name = spec->bind = spec->keyname = NULL;
spec->numnode = spec->trignode = spec->hashnode = NULL;
init_pattern_str(&spec->trig, NULL);
init_pattern_str(&spec->hargs, NULL);
init_pattern_str(&spec->wtype, NULL);
spec->world = NULL;
spec->pri = spec->prob = spec->shots = spec->fallthru = spec->quiet = -1;
VEC_ZERO(&spec->hook);
spec->invis = 0;
spec->attr = 0;
spec->nsubattr = 0;
spec->subattr = NULL;
spec->flags = MACRO_TEMP;
spec->builtin = NULL;
spec->used[USED_NAME] = spec->used[USED_TRIG] =
spec->used[USED_HOOK] = spec->used[USED_KEY] = 0;
startopt(CS(args), "usSp#c#b:B:E:t:w:h:a:f:P:T:FiIn#1m:q" +
(listopts ? 0 : 3));
while (!error && (opt = nextopt(&ptr, &uval, NULL, &offset))) {
switch (opt) {
case 'u':
listopts->usedflag = 1;
break;
case 's':
listopts->shortflag = 1;
break;
case 'S':
listopts->cmp = cstrpppcmp;
break;
case 'm':
if (!(error = ((i = enum2int(ptr, 0, enum_match, "-m")) < 0))) {
if ((error = (mflag >= 0 && mflag != i)))
eprintf("-m option conflicts with earlier -m or -P");
mflag = i;
}
break;
case 'p':
spec->pri = uval.ival;
break;
case 'c':
spec->prob = uval.ival;
break;
case 'F':
spec->fallthru = 1;
break;
case 'i':
spec->invis = 1;
break;
case 'I':
spec->invis = 2;
break;
case 'b':
if (spec->keyname) FREE(spec->keyname);
if (spec->bind) FREE(spec->bind);
ptr = print_to_ascii(NULL, ptr)->data;
spec->bind = STRDUP(ptr);
break;
case 'B':
if (warn_def_B)
wprintf("/def -B is deprecated. See /help keys.");
if (spec->keyname) FREE(spec->keyname);
if (spec->bind) FREE(spec->bind);
spec->keyname = STRDUP(ptr);
break;
case 'E':
if (spec->expr) conStringfree(spec->expr);
(spec->expr = CS(Stringnew(ptr, -1, 0)))->links++;
break;
case 't':
free_pattern(&spec->trig);
error += !init_pattern_str(&spec->trig, ptr);
break;
case 'T':
free_pattern(&spec->wtype);
error += !init_pattern_str(&spec->wtype, ptr);
break;
case 'w':
if (!*ptr || strcmp(ptr, "+") == 0)
spec->world = &AnyWorld;
else if (strcmp(ptr, "-") == 0)
spec->world = &NoWorld;
else if ((error = !(spec->world = find_world(ptr))))
eprintf("No world %s", ptr);
break;
case 'h':
if (strcmp(ptr, "0") == 0) {
spec->flags |= MACRO_NOHOOK;
} else {
char *buf, *p;
p = buf = STRDUP(ptr); /* XXX optimize */
if (!(error = !parse_hook(&p, &spec->hook))) {
free_pattern(&spec->hargs);
error += !init_pattern_str(&spec->hargs, p);
spec->flags |= MACRO_HOOK;
}
FREE(buf);
}
break;
case 'a': case 'f':
error = !parse_attrs(ptr, &attrs, 0);
spec->attr = adj_attr(spec->attr, attrs);
break;
case 'P':
if ((error = (spec->nsubattr > 0))) {
eprintf("-P can be given only once per macro.");
break;
} else if ((error = (mflag >= 0 && mflag != MATCH_REGEXP))) {
eprintf("\"-P\" requires \"-mregexp -t<pattern>\"");
break;
}
for (n = 0, s = ptr; *s; s++) {
if (*s == ';') continue; /* don't count empties */
n++;
s = strchr(s, ';');
if (!s) break;
}
spec->subattr = MALLOC(n * sizeof(subattr_t));
if (!spec->subattr) {
eprintf("-P: out of memory");
error++;
break;
}
spec->nsubattr = n;
{
char *start, *end, *buf = STRDUP(ptr); /* XXX optimize */
for (i = 0, start = buf; !error && i < n; i++, start = end+1) {
attr_t attr;
if ((end = strchr(start, ';')))
*end = '\0';
if (end == start) { /* skip empty */
i--;
continue;
}
if (*start == 'L') {
spec->subattr[i].subexp = -1;
start++;
} else if (*start == 'R') {
spec->subattr[i].subexp = -2;
start++;
} else {
spec->subattr[i].subexp = strtoint(start, &start);
if ((error = (spec->subattr[i].subexp < 0))) {
eprintf("-P: number must be non-negative");
break;
}
}
error = !parse_attrs(start, &attr, 0);
spec->subattr[i].attr = attr;
}
FREE(buf);
}
mflag = MATCH_REGEXP;
break;
case 'n':
spec->shots = uval.ival;
break;
case 'q':
spec->quiet = TRUE;
break;
case '1':
spec->shots = 1;
break;
default:
error = TRUE;
}
}
if (error) {
nuke_macro(spec);
return NULL;
}
if (mflag < 0)
mflag = matching;
if (xmflag) *xmflag = mflag;
if (!init_pattern_mflag(&spec->trig, mflag, 't') ||
!init_pattern_mflag(&spec->hargs, mflag, 'h') ||
!init_pattern_mflag(&spec->wtype, mflag, 'w'))
{
nuke_macro(spec);
return NULL;
}
name = args->data + offset;
while (is_space(*name)) ++name;
if (!*name) return spec;
nameend = name + strcspn(name, "=");
if (*nameend == '=') {
body = nameend + 1;
while (is_space(*body)) ++body;
bodyend = args->data + args->len;
while (bodyend > body && is_space(bodyend[-1])) bodyend--;
if (bodyend > body)
(spec->body = CS(Stringodup(CS(args), body - args->data)))->links++;
}
while (nameend > name && is_space(nameend[-1])) nameend--;
if (nameend > name) {
char *buf = strncpy(XMALLOC(nameend - name + 1), name, nameend - name);
buf[nameend - name] = '\0';
spec->name = buf;
}
return spec;
}
/* init_aux_patterns
* Macro_match() needs to compare some string fields that aren't normally
* patterns. This function initializes patterns for those fields.
*/
static int init_aux_patterns(Macro *spec, int mflag, AuxPat *aux)
{
init_pattern_str(&aux->name, spec->name);
init_pattern_str(&aux->body, spec->body ? spec->body->data : NULL);
init_pattern_str(&aux->expr, spec->expr ? spec->expr->data : NULL);
init_pattern_str(&aux->bind, spec->bind);
init_pattern_str(&aux->keyname, spec->keyname);
if (mflag < 0) mflag = matching;
return init_pattern_mflag(&aux->name, mflag, 0) &&
init_pattern_mflag(&aux->body, mflag, 0) &&
init_pattern_mflag(&aux->expr, mflag, 'E') &&
init_pattern_mflag(&aux->bind, mflag, 'b') &&
init_pattern_mflag(&aux->keyname, mflag, 'B');
}
static void free_aux_patterns(AuxPat *aux)
{
free_pattern(&aux->name);
free_pattern(&aux->body);
free_pattern(&aux->bind);
free_pattern(&aux->keyname);
free_pattern(&aux->expr);
}
/* return 1 if attr contains any of the attributes listed in sattr */
static int attr_match(attr_t sattr, attr_t attr)
{
if (sattr == F_NONE) {
return !attr;
} else {
if ((sattr & ~F_COLORS & attr) != 0)
return 1;
if ((sattr & F_FGCOLOR) && (sattr & F_FGCOLORS) == (attr & F_FGCOLORS))
return 1;
if ((sattr & F_BGCOLOR) && (sattr & F_BGCOLORS) == (attr & F_BGCOLORS))
return 1;
return 0;
}
}
/* macro_match
* Compares spec to macro. aux contains patterns for string fields that
* aren't normally patterns. Returns 1 for match, 0 for nonmatch.
*/
static int macro_match(Macro *spec, Macro *macro, AuxPat *aux)
{
if (!spec->invis && macro->invis) return 0;
if (spec->invis == 2 && !macro->invis) return 0;
if (spec->shots >= 0 && spec->shots != macro->shots) return 0;
if (spec->fallthru >= 0 && spec->fallthru != macro->fallthru) return 0;
if (spec->prob >= 0 && spec->prob != macro->prob) return 0;
if (spec->pri >= 0 && spec->pri != macro->pri) return 0;
if (spec->attr && !attr_match(spec->attr, macro->attr)) return 0;
if (spec->subattr) {
int i, j;
for (i = 0; i < spec->nsubattr; i++) {
for (j = 0; ; j++) {
if (j >= macro->nsubattr) return 0;
if (macro->subattr[j].subexp == spec->subattr[i].subexp) break;
}
if (!attr_match(spec->subattr[i].attr, macro->subattr[j].attr))
return 0;
}
}
if (spec->world) {
if (spec->world == &NoWorld) {
if (macro->world) return 0;
} else if (spec->world == &AnyWorld) {
if (!macro->world) return 0;
} else if (spec->world != macro->world) return 0;
}
if (spec->keyname) {
if (!*spec->keyname) {
if (!*macro->keyname) return 0;
} else {
if (!patmatch(&aux->keyname, NULL, macro->keyname)) return 0;
}
}
if (spec->bind) {
if (!*spec->bind) {
if (!*macro->bind) return 0;
} else {
if (!patmatch(&aux->bind, NULL, macro->bind)) return 0;
}
}
if (spec->expr) {
if (!spec->expr->len) {
if (!macro->expr) return 0;
} else {
if (!macro->expr) return 0;
if (!patmatch(&aux->expr, NULL, macro->expr->data)) return 0;
}
}
if (spec->flags & MACRO_NOHOOK) {
/* -h0 */
if (macro->flags & MACRO_HOOK) return 0;
} else if (spec->flags & MACRO_HOOK) {
int i, hit = 0;
if (!(macro->flags & MACRO_HOOK)) return 0;
for (i = 0; i < sizeof(spec->hook)/sizeof(long); i++) {
if ((hit = (spec->hook.bits[i] & macro->hook.bits[i])))
break;
}
if (!hit) return 0;
if (spec->hargs.str && *spec->hargs.str) {
if (!patmatch(&spec->hargs, NULL, NONNULL(macro->hargs.str)))
return 0;
}
}
if (spec->trig.str) {
if (!*spec->trig.str) {
if (!macro->trig.str) return 0;
} else {
if (!patmatch(&spec->trig, NULL, NONNULL(macro->trig.str)))
return 0;
}
}
if (spec->wtype.str) {
if (!*spec->wtype.str) {
if (!macro->wtype.str) return 0;
} else {
if (!patmatch(&spec->wtype, NULL, NONNULL(macro->wtype.str)))
return 0;
}
}
if (spec->num && macro->num != spec->num)
return 0;
if (spec->name && !patmatch(&aux->name, NULL, macro->name))
return 0;
if (spec->body && !patmatch(&aux->body, macro->body, NULL))
return 0;
return 1;
}
/* macro_equal
* Returns true if m1 and m2 are exactly equivalent (except for num).
*/
int macro_equal(Macro *m1, Macro *m2)
{
if (m1->invis != m2->invis) return 0;
if (m1->shots != m2->shots) return 0;
if (m1->fallthru != m2->fallthru) return 0;
if (m1->quiet != m2->quiet) return 0;
if (m1->prob != m2->prob) return 0;
if (m1->pri != m2->pri) return 0;
if (m1->attr != m2->attr) return 0;
if (m1->nsubattr != m2->nsubattr) return 0;
if (m1->subattr) {
int i, j;
for (i = 0; i < m1->nsubattr; i++) {
for (j = 0; ; j++) {
if (j >= m2->nsubattr) return 0;
if (m2->subattr[j].subexp == m1->subattr[i].subexp)
break;
}
if (m2->subattr[j].attr != m1->subattr[i].attr)
return 0;
}
}
if (m1->world != m2->world) return 0;
if ((m1->flags ^ m2->flags) &~ MACRO_TEMP) return 0;
if (m1->flags & MACRO_HOOK)
if (memcmp(&m1->hook, &m2->hook, sizeof(m1->hook)) != 0) return 0;
if (nullstrcmp(m1->keyname, m2->keyname) != 0) return 0;
if (nullstrcmp(m1->bind, m2->bind) != 0) return 0;
if (nullstrcmp(m1->trig.str, m2->trig.str) != 0) return 0;
if (nullstrcmp(m1->hargs.str, m2->hargs.str) != 0) return 0;
if (nullstrcmp(m1->wtype.str, m2->wtype.str) != 0) return 0;
if (nullstrcmp(m1->name, m2->name) != 0) return 0;
if (Stringcmp(m1->body, m2->body) != 0) return 0;
if (Stringcmp(m1->expr, m2->expr) != 0) return 0;
return 1;
}
/* find Macro by name */
Macro *find_hashed_macro(const char *name, unsigned int hash)
{
if (!*name) return NULL;
if (*name == '#') return find_num_macro(hash);
return (Macro *)hashed_find(name, hash, macro_table);
}
/* find single exact match */
static Macro *match_exact(int hooknum, const char *str, attr_t attrs)
{
ListEntry *node;
if (hooknum < 0 && !*str) return NULL;
for (node = hooknum>=0 ? hooklist[hooknum].head : triglist->head; node;
node = node->next)
{
Macro *macro = MAC(node);
if (macro->flags & MACRO_DEAD) continue;
if (hooknum>=0) {
if (!VEC_ISSET(hooknum, ¯o->hook)) continue;
if (!macro->hargs.str || cstrcmp(macro->hargs.str, str) == 0)
return macro;
} else {
if (!(macro->attr & attrs)) continue;
if (!macro->trig.str || cstrcmp(macro->trig.str, str) == 0)
return macro;
}
}
eprintf("%s on \"%s\" was not defined.", hooknum>=0 ? "Hook" : "Trigger",
str);
return NULL;
}
/**************************
* Routines to add macros *
**************************/
/* create a Macro */
int 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)
{
Macro *new;
int error = 0;
if (!(new = (Macro *) MALLOC(sizeof(struct Macro)))) {
eprintf("add_new_macro: not enough memory");
return 0;
}
new->numnode = new->trignode = new->hashnode = NULL;
new->flags = MACRO_TEMP;
new->prog = new->exprprog = NULL;
new->name = STRDUP("");
(new->body = CS(Stringnew(body, -1, 0)))->links++;
new->expr = NULL;
new->bind = STRDUP(bind);
new->keyname = STRDUP("");
if (hook) {
new->flags |= MACRO_HOOK;
new->hook = *hook;
} else {
VEC_ZERO(&new->hook);
}
error += !init_pattern(&new->trig, trig, mflag);
error += !init_pattern(&new->hargs, hargs, mflag);
init_pattern_str(&new->wtype, NULL);
new->world = NULL;
new->pri = pri;
new->prob = prob;
new->attr = attr;
new->nsubattr = 0;
new->subattr = NULL;
new->shots = 0;
new->invis = invis;
new->fallthru = FALSE;
new->quiet = FALSE;
new->builtin = NULL;
new->used[USED_NAME] = new->used[USED_TRIG] =
new->used[USED_HOOK] = new->used[USED_KEY] = 0;
if (!error)
return add_numbered_macro(new, 0, 0, NULL);
nuke_macro(new);
return 0;
}
static int bind_key_macro(Macro *spec)
{
Macro *orig;
if ((orig = find_key(spec->bind))) {
if (macro_equal(orig, spec)) {
return -1; /* leave orig in place, don't insert spec */
} else if (redef) {
const char *obody = orig->body->data;
const char *sbody = spec->body->data;
if (strncmp(sbody, "/key_", 5) != 0 && orig->invis &&
strncmp(obody, "/key_", 5) == 0 && !strchr(obody, ' '))
{
conString *buf = print_def(NULL, NULL, orig);
eprintf("%ARecommendation: leave \"%S\" defined and "
"define a macro named \"%s\" instead.",
warning_attr, buf, obody+1);
conStringfree(buf);
}
kill_macro(orig); /* this guarantees intrie will succeed */
do_hook(H_REDEF, "!Redefined %s %S", "%s %S",
"binding", ascii_to_print(spec->bind));
} else {
eprintf("Binding %S already exists.", ascii_to_print(spec->bind));
return 0;
}
}
return bind_key(spec, spec->bind);
}
/* add_macro
* Install a permanent Macro in appropriate structures.
* Only the keybinding is checked for conflicts; everything else is assumed
* assumed to be error- and conflict-free. If the bind_key_macro fails, the
* macro will be nuked.
*/
static int add_numbered_macro(Macro *macro, unsigned int hash, int num,
ListEntry *numnode)
{
if (!macro) return 0;
if (*macro->bind) {
int result = bind_key_macro(macro);
if (result <= 0) {
nuke_macro(macro);
return -result;
}
}
macro->num = num ? num : ++mnum;
macro->numnode = inlist((void *)macro, maclist, numnode);
if (*macro->name) {
macro->hashnode = hashed_insert((void *)macro, hash, macro_table);
if (macro->builtin) { /* macro->builtin was set in complete_macro() */
macro->builtin->macro = macro;
}
}
if (macro->trig.str) {
macro->trignode = sinsert((void *)macro,
macro->world ? macro->world->triglist : triglist, (Cmp *)rpricmp);
}
if (macro->flags & MACRO_HOOK) {
int i;
for (i = 0; i < (int)NUM_HOOKS; i++) {
if (VEC_ISSET(i, ¯o->hook))
sinsert((void *)macro, &hooklist[i], (Cmp *)rpricmp);
}
}
macro->flags &= ~MACRO_TEMP;
if (!*macro->name && (macro->trig.str || macro->flags & MACRO_HOOK) &&
macro->shots == 0 && pedantic)
{
wprintf("new macro (#%d) does not have a name.", macro->num);
}
return macro->num;
}
/* rebind_key_macros
* Unbinds macros with keynames, and attempts to rebind them.
*/
void rebind_key_macros(void)
{
Macro *p;
ListEntry *node;
const char *code;
for (node = maclist->tail; node; node = node->prev) {
p = MAC(node);
if (!*p->keyname) continue;
code = get_keycode(p->keyname);
if (strcmp(code, p->bind) == 0) {
/* same code, don't need to rebind */
} else {
if (*p->bind) unbind_key(p->bind);
FREE(p->bind);
p->bind = STRDUP(code);
if (!*code) {
wprintf("no code for key \"%s\"", p->keyname);
} else if (bind_key_macro(p)) {
/* bind_key_macro can't return -1 here */
do_hook(H_REDEF, "!Redefined %s %s", "%s %s",
"key", p->keyname);
} else {
kill_macro(p);
}
}
}
}
/* compares m1 and m2 based on reverse priority and fallthru */
static int rpricmp(const Macro *m1, const Macro *m2)
{
if (m2->pri != m1->pri) return m2->pri - m1->pri;
else return m2->fallthru - m1->fallthru;
}
struct Value *handle_def_command(String *args, int offset)
{
Macro *spec;
if (!(args->len - offset) || !(spec = macro_spec(args, offset, NULL, NULL)))
return shareval(val_zero);
return newint(complete_macro(spec, macro_hash(spec->name), 0, NULL));
}
/* Fill in "don't care" fields with default values, and add_numbered_macro().
* If error checking fails, spec will be nuked.
*/
static int complete_macro(Macro *spec, unsigned int hash, int num,
ListEntry *numnode)
{
Macro *orig = NULL;
int i;
if (spec->name && *spec->name) {
if (strchr("#@!/", *spec->name) || strchr(spec->name, ' ')) {
eprintf("illegal macro name \"%s\".", spec->name);
nuke_macro(spec);
return 0;
}
if (keyword(spec->name) ||
((spec->builtin = find_builtin_cmd(spec->name)) &&
(spec->builtin->reserved)))
{
eprintf("\"%s\" is a reserved word.", spec->name);
nuke_macro(spec);
return 0;
}
if (spec->builtin) {
do_hook(H_CONFLICT,
"!warning: macro \"%s\" conflicts with the builtin command.",
"%s", spec->name);
}
}
if (spec->expr) {
/* Compiling expr at /def time allows us to report errors at /def time
* (and means we don't have to test at runtime) */
extern char current_opt;
current_opt = 'E';
spec->exprprog = compile_tf(spec->expr, 0, -1, 1, 2);
current_opt = '\0';
if (!spec->exprprog) {
nuke_macro(spec);
return 0;
}
}
if (spec->body && defcompile) {
spec->prog = compile_tf(spec->body, 0, SUB_MACRO, 0,
!spec->shots ? 2 : spec->shots > 10 ? 1 : 0);
if (!spec->prog) {
nuke_macro(spec);
return 0;
}
}
if (spec->world == &AnyWorld) spec->world = xworld();
if (spec->pri < 0) spec->pri = 1;
if (spec->prob < 0) spec->prob = 100;
if (spec->shots < 0) spec->shots = 0;
if (spec->invis) spec->invis = 1;
if (spec->fallthru < 0) spec->fallthru = 0;
if (spec->quiet < 0) spec->quiet = 0;
if (!spec->name) spec->name = STRNDUP("", 0);
if (!spec->body) (spec->body = blankline)->links++;
/*if (!spec->expr) (spec->expr = blankline)->links++;*/
if (spec->nsubattr > 0 && spec->trig.mflag != MATCH_REGEXP) {
eprintf("\"-P\" requires \"-mregexp -t<pattern>\"");
nuke_macro(spec);
return 0;
}
spec->attr &= ~F_NONE;
if (spec->nsubattr) {
int n = pcre_info(spec->trig.ri->re, NULL, NULL);
for (i = 0; i < spec->nsubattr; i++) {
spec->subattr[i].attr &= ~F_NONE;
if (spec->subattr[i].subexp > n) {
eprintf("-P%d: trigger has only %d subexpressions",
spec->subattr[i].subexp, n);
nuke_macro(spec);
return 0;
}
}
}
if (!spec->keyname) spec->keyname = STRNDUP("", 0);
else if (*spec->keyname) {
if (spec->bind) FREE(spec->bind);
spec->bind = get_keycode(spec->keyname);
if (!spec->bind) {
eprintf("unknown key name \"%s\".", spec->keyname);
nuke_macro(spec);
return 0;
}
spec->bind = STRDUP(spec->bind);
if (!*spec->bind)
wprintf("no code for key \"%s\".", spec->keyname);
}
if (!spec->bind) spec->bind = STRNDUP("", 0);
if (*spec->name &&
(orig = (Macro *)hashed_find(spec->name, hash, macro_table)))
{
if (macro_equal(orig, spec)) {
/* identical redefinition has no effect */
nuke_macro(spec);
return orig->num;
} else if (!redef) {
eprintf("macro %s already exists", spec->name);
nuke_macro(spec);
return 0;
}
}
if (!add_numbered_macro(spec, hash, num, numnode)) return 0;
if (orig) {
do_hook(H_REDEF, "!Redefined %s %s", "%s %s", "macro", orig->name);
kill_macro(orig);
}
return spec->num;
}
/* define a new Macro with hook */
int add_hook(char *args, const char *body)
{
hookvec_t hook;
VEC_ZERO(&hook);
if (!parse_hook(&args, &hook)) return 0;
if (args && !*args) args = NULL;
return add_new_macro(NULL, "", &hook, args, body, 0, 100, 0, 0, matching);
}
/* /edit: Edit an existing macro.
* Actually editing the macro in place is quite hairy, so instead we
* remove the old one, create a replacement and add it. If the replacement
* fails, we re-add the original. Either way, the number and position in
* maclist are unchanged.
*/
struct Value *handle_edit_command(String *args, int offset)
{
Macro *spec, *macro = NULL;
int error = 0;
int num;
unsigned int hash = 0;
ListEntry *numnode;
if (!(args->len - offset) || !(spec = macro_spec(args, offset, NULL, NULL))) {
return shareval(val_zero);
} else if (!spec->name) {
eprintf("You must specify a macro.");
} else if (spec->name[0] == '$') {
macro = match_exact(0, spec->name + 1, F_ATTR);
if (macro) hash = macro_hash(macro->name);
} else {
hash = macro_hash(spec->name); /* used for lookup and insertion */
if (!(macro = find_hashed_macro(spec->name, hash)))
eprintf("macro %s does not exist", spec->name);
}
if (!macro) {
nuke_macro(spec);
return shareval(val_zero);
}
num = macro->num;
numnode = macro->numnode->prev;
kill_macro(macro);
FREE(spec->name);
spec->name = STRDUP(macro->name);
if (!spec->body && macro->body) (spec->body = macro->body)->links++;
if (!spec->expr && macro->expr) (spec->expr = macro->expr)->links++;
if (!spec->bind && macro->bind) spec->bind = STRDUP(macro->bind);
if (!spec->keyname && macro->keyname) spec->keyname =STRDUP(macro->keyname);
if (!spec->wtype.str && macro->wtype.str)
error += !copy_pattern(&spec->wtype, ¯o->wtype);
if (!spec->trig.str && macro->trig.str)
error += !copy_pattern(&spec->trig, ¯o->trig);
if (!(spec->flags & (MACRO_HOOK | MACRO_NOHOOK))) {
spec->flags |= MACRO_HOOK;
spec->hook = macro->hook;
if (macro->hargs.str)
error += !copy_pattern(&spec->hargs, ¯o->hargs);
}
if (!spec->world) spec->world = macro->world;
else if (spec->world == &AnyWorld) spec->world = xworld();
if (spec->pri < 0) spec->pri = macro->pri;
if (spec->prob < 0) spec->prob = macro->prob;
if (spec->shots < 0) spec->shots = macro->shots;
if (spec->fallthru < 0) spec->fallthru = macro->fallthru;
if (spec->quiet < 0) spec->quiet = macro->quiet;
if (spec->attr == 0) spec->attr = macro->attr;
if (spec->nsubattr == 0 && macro->nsubattr > 0) {
spec->nsubattr = macro->nsubattr;
spec->subattr = MALLOC(spec->nsubattr * sizeof(subattr_t));
memcpy(spec->subattr, macro->subattr,
spec->nsubattr * sizeof(subattr_t));
}
spec->used[USED_NAME] = macro->used[USED_NAME];
spec->used[USED_TRIG] = macro->used[USED_TRIG];
spec->used[USED_HOOK] = macro->used[USED_HOOK];
spec->used[USED_KEY] = macro->used[USED_KEY];
if (!error) {
complete_macro(spec, hash, num, numnode);
return newint(spec->num);
}
/* Edit failed. Resurrect original macro. */
macro = dead_macros;
macro->flags &= ~MACRO_DEAD;
dead_macros = macro->tnext;
add_numbered_macro(macro, hash, num, numnode);
return shareval(val_zero);
}
/********************************
* Routines for removing macros *
********************************/
void kill_macro(Macro *macro)
{
/* Remove macro from maclist, macro_table, and key_trie, and put it on
* the dead_macros list. When called from find_and_run_matches(), this
* allows a new macro to be defined without conflicting with the name
* or binding of this macro.
* The macro must NOT be removed from triglist and hooklist, so
* find_and_run_matches() can work correctly when a macro kills itself,
* is a one-shot, or defines another macro that is inserted immediately
* after it in triglist/hooklist. macro will be removed from triglist
* and hooklist in nuke_macro().
*/
if (macro->flags & MACRO_DEAD) return;
macro->flags |= MACRO_DEAD;
macro->tnext = dead_macros;
dead_macros = macro;
unlist(macro->numnode, maclist);
if (*macro->name) hash_remove(macro->hashnode, macro_table);
if (*macro->bind) unbind_key(macro->bind);
}
void nuke_dead_macros(void)
{
Macro *macro;
while ((macro = dead_macros)) {
dead_macros = dead_macros->tnext;
nuke_macro(macro);
}
}
/* free macro structure */
static void nuke_macro(Macro *m)
{
if (!(m->flags & MACRO_DEAD) && !(m->flags & MACRO_TEMP)) {
kill_macro(m);
}
if (m->trignode)
unlist(m->trignode, m->world ? m->world->triglist : triglist);
if (m->flags & MACRO_HOOK) {
int i;
ListEntry *node;
for (i = 0; i < (int)NUM_HOOKS; i++) {
if (!VEC_ISSET(i, &m->hook)) continue;
for (node = hooklist[i].head; node; node = node->next) {
if (MAC(node) == m) {
unlist(node, &hooklist[i]);
break; /* macro can only be in hooklist[i] once */
}
}
}
}
if (m->body) conStringfree(m->body);
if (m->expr) conStringfree(m->expr);
if (m->bind) FREE(m->bind);
if (m->keyname) FREE(m->keyname);
if (m->subattr) FREE(m->subattr);
if (m->prog) prog_free(m->prog);
if (m->exprprog) prog_free(m->exprprog);
if (m->builtin && m->builtin->macro == m)
m->builtin->macro = NULL;
free_pattern(&m->trig);
free_pattern(&m->hargs);
free_pattern(&m->wtype);
if (m->name) FREE(m->name);
FREE(m);
}
/* delete a macro */
int remove_macro_by_name(const char *str)
{
Macro *macro;
if (!(macro = find_macro(str))) {
eprintf("Macro \"%s\" was not defined.", str);
return 0;
}
kill_macro(macro);
return 1;
}
/* delete specified macros */
struct Value *handle_purge_command(String *args, int offset)
{
Macro *spec;
ListEntry *node, *next;
int result = 0;
int mflag;
AuxPat aux;
if (!(spec = macro_spec(args, offset, &mflag, NULL)))
return shareval(val_zero);
if (spec->name && *spec->name == '#') {
spec->num = atoi(spec->name + 1);
FREE(spec->name);
spec->name = NULL;
}
if (!(init_aux_patterns(spec, mflag, &aux)))
goto error;
for (node = maclist->head; node; node = next) {
next = node->next;
if (macro_match(spec, MAC(node), &aux)) {
kill_macro(MAC(node));
result++;
}
}
/* regrelease(); */
error:
free_aux_patterns(&aux);
nuke_macro(spec);
return newint(result);
}
/* delete macro by number */
struct Value *handle_undefn_command(String *args, int offset)
{
int num, result = 0;
Macro *macro;
const char *ptr = args->data + offset;
while (*ptr) {
if ((num = numarg(&ptr)) >= 0 && (macro = find_num_macro(num))) {
kill_macro(macro);
result++;
}
}
return newint(result);
}
Macro *find_num_macro(int num)
{
if (maclist->tail && num >= MAC(maclist->tail)->num) {
/* search from high end, assuming high macros are more likely;
* stop when past */
ListEntry *node = maclist->head;
for ( ; node && MAC(node)->num >= num; node = node->next)
if (MAC(node)->num == num) return MAC(node);
}
eprintf("no macro with number %d", num);
return NULL;
}
void remove_world_macros(World *w)
{
ListEntry *node, *next;
/* This could be more efficient by using w->triglist and
* the hooklists, but this is not used often. */
for (node = maclist->head; node; node = next) {
next = node->next;
if (MAC(node)->world == w)
kill_macro(MAC(node));
}
}
/**************************
* Routine to list macros *
**************************/
/* convert hook vector to string */
static const String *hook_name(const hookvec_t *hook)
{
int i;
STATIC_BUFFER(buf);
Stringtrunc(buf, 0);
for (i = 0; i < (int)NUM_HOOKS; i++) {
/* ^^^^^ Some brain dead compilers need that cast */
if (!VEC_ISSET(i, hook)) continue;
if (buf->len) Stringadd(buf, '|');
Stringcat(buf, hook_table[i].name);
}
return buf;
}
static conString *print_def(TFILE *file, String *buffer, Macro *p)
{
int mflag = -1;
if (!buffer)
buffer = Stringnew(NULL, 0, 0);
buffer->links++;
if (file && file == tfout)
Sprintf(buffer, "%% %d: /def ", p->num);
else Stringcpy(buffer, "/def ");
if (p->invis) Stringcat(buffer, "-i ");
if (p->trig.str || (p->flags & MACRO_HOOK))
Sappendf(buffer, "-%sp%d ", p->fallthru ? "F" : "", p->pri);
if (p->prob != 100)
Sappendf(buffer, "-c%d ", p->prob);
if (p->attr) {
Stringcat(attr2str(Stringcat(buffer, "-a"), p->attr), " ");
}
if (p->nsubattr > 0) {
int i;
mflag = MATCH_REGEXP;
Stringcat(buffer, "-P");
for (i = 0; i < p->nsubattr; i++) {
if (i > 0) Stringadd(buffer, ';');
if (p->subattr[i].subexp == -1)
Stringadd(buffer, 'L');
else if (p->subattr[i].subexp == -2)
Stringadd(buffer, 'R');
else
Sappendf(buffer, "%d", (int)p->subattr[i].subexp);
attr2str(buffer, p->subattr[i].attr);
}
Stringadd(buffer, ' ');
}
if (p->shots)
Sappendf(buffer, "-n%d ", p->shots);
if (p->world)
Sappendf(buffer, "-w'%q' ", '\'', p->world->name);
if (p->wtype.str) {
if (p->wtype.mflag != mflag)
Sappendf(buffer, "-m%S ", &enum_match[p->wtype.mflag]);
mflag = p->wtype.mflag;
Sappendf(buffer, "-T'%q' ", '\'', p->wtype.str);
}
if (p->expr)
Sappendf(buffer, "-E'%q' ", '\'', p->expr->data);
if (p->trig.str) {
if (p->trig.mflag != mflag)
Sappendf(buffer, "-m%S ", &enum_match[p->trig.mflag]);
mflag = p->trig.mflag;
Sappendf(buffer, "-t'%q' ", '\'', p->trig.str);
}
if (p->flags & MACRO_HOOK) {
if (p->hargs.str && *p->hargs.str) {
if (p->hargs.mflag != mflag)
Sappendf(buffer, "-m%S ", &enum_match[mflag = p->hargs.mflag]);
Sappendf(buffer, "-h'%S %q' ",
hook_name(&p->hook), '\'', p->hargs.str);
} else {
Sappendf(buffer, "-h%S ", hook_name(&p->hook));
}
}
#if 0 /* obsolete */
if (*p->keyname)
Sappendf(buffer, "-B'%s' ", p->keyname);
else
#endif
if (*p->bind)
Sappendf(buffer, "-b'%q' ", '\'', ascii_to_print(p->bind)->data);
if (p->quiet) Stringcat(buffer, "-q ");
if (*p->name == '-') Stringcat(buffer, "- ");
if (*p->name) Sappendf(buffer, "%s ", p->name);
if (p->body && p->body->len) Sappendf(buffer, "= %S", p->body);
if (file) {
tfputline(CS(buffer), file);
Stringfree(buffer);
return NULL;
} else {
return CS(buffer);
}
}
/* list all specified macros */
static int list_defs(TFILE *file, Macro *spec, int mflag, ListOpts *listopts)
{
Macro *p;
ListEntry *node;
AuxPat aux;
String *buffer = NULL;
int result = 0, i;
Vector macs = vector_init(1024);
if (!(init_aux_patterns(spec, mflag, &aux))) goto error;
if (spec->name && *spec->name == '#') {
spec->num = atoi(spec->name + 1);
FREE(spec->name);
spec->name = NULL;
}
/* maclist is in reverse numeric order, so we start from tail */
for (node = maclist->tail; node; node = node->prev) {
p = MAC(node);
if (!macro_match(spec, p, &aux)) continue;
vector_add(&macs, p);
}
if (listopts && listopts->cmp)
vector_sort(&macs, listopts->cmp);
for (i = 0; i < macs.size; i++) {
p = macs.ptrs[i];
result = p->num;
if (!buffer)
(buffer = Stringnew(NULL, 0, 0))->links++;
if (listopts && listopts->shortflag) {
Sprintf(buffer, "%% %d: ", p->num);
if (p->attr & F_NOHISTORY) Stringcat(buffer, "(nohistory) ");
if (p->attr & F_NOLOG) Stringcat(buffer, "(nolog) ");
if (p->attr & F_GAG) Stringcat(buffer, "(gag) ");
else if (p->attr & (F_HWRITE | F_EXCLUSIVE)) {
if (p->attr & F_UNDERLINE) Stringcat(buffer, "(underline) ");
if (p->attr & F_REVERSE) Stringcat(buffer, "(reverse) ");
if (p->attr & F_FLASH) Stringcat(buffer, "(flash) ");
if (p->attr & F_DIM) Stringcat(buffer, "(dim) ");
if (p->attr & F_BOLD) Stringcat(buffer, "(bold) ");
if (p->attr & F_BELL) Stringcat(buffer, "(bell) ");
if (p->attr & F_HILITE) Stringcat(buffer, "(hilite) ");
if (p->attr & F_FGCOLOR)
Sappendf(buffer, "(%S) ",
&enum_color[attr2fgcolor(p->attr)]);
if (p->attr & F_BGCOLOR)
Sappendf(buffer, "(bg%S) ",
&enum_color[attr2bgcolor(p->attr)]);
} else if (p->nsubattr > 0) {
Stringcat(buffer, "(partial) ");
} else if (p->trig.str) {
Stringcat(buffer, "(trig");
if (listopts && listopts->usedflag)
Sappendf(buffer, " %d", p->used[USED_TRIG]);
Stringcat(buffer, ") ");
}
if (p->trig.str)
Sappendf(buffer, "'%q' ", '\'', p->trig.str);
#if 0 /* obsolete */
if (*p->keyname) {
Stringcat(buffer, "(key");
if (listopts && listopts->usedflag)
Sappendf(buffer, " %d", p->used[USED_KEY]);
Sappendf(buffer, ") '%s' ", p->keyname);
} else
#endif
if (*p->bind) {
Stringcat(buffer, "(bind");
if (listopts && listopts->usedflag)
Sappendf(buffer, " %d", p->used[USED_KEY]);
Sappendf(buffer, ") '%q' ", '\'',
ascii_to_print(p->bind)->data);
if (p->keyname && *p->keyname)
Sappendf(buffer, "(%s) ", p->keyname);
}
if (p->flags & MACRO_HOOK) {
Stringcat(buffer, "(hook");
if (listopts && listopts->usedflag)
Sappendf(buffer, " %d", p->used[USED_HOOK]);
Sappendf(buffer, ") %S ", hook_name(&p->hook));
}
if (*p->name) {
Sappendf(buffer, "%s ", p->name);
if (listopts && listopts->usedflag)
Sappendf(buffer, "(%d) ", p->used[USED_NAME]);
}
tfputline(CS(buffer), file ? file : tfout);
} else {
print_def(file ? file : tfout, buffer, p);
}
/* If something is sharing buffer, we can't reuse it in next loop. */
if (buffer->links > 1) {
Stringfree(buffer);
buffer = NULL;
}
}
/* regrelease(); */
vector_free(&macs);
error:
free_aux_patterns(&aux);
if (buffer) {
Stringfree(buffer);
buffer = NULL;
}
return result;
}
/* write specified macros to file */
int save_macros(String *args, int offset)
{
Macro *spec;
TFILE *file = NULL;
int result = 1;
const char *name, *mode = "w";
char opt, *next;
int mflag;
startopt(CS(args), "a");
while ((opt = nextopt(NULL, NULL, NULL, &offset))) {
if (opt != 'a') return 0;
mode = "a";
}
next = args->data + offset;
name = stringarg(&next, NULL);
offset = next - args->data;
if (!(spec = macro_spec(args, offset, &mflag, NULL))) result = 0;
if (result && !(file = tfopen(expand_filename(name), mode))) {
operror(name);
result = 0;
}
if (result) {
oprintf("%% %sing macros to %s", *mode=='w' ? "Writ" : "Append",
file->name);
result = list_defs(file, spec, mflag, NULL);
}
if (file) tfclose(file);
if (spec) nuke_macro(spec);
return result;
}
/* list macros on screen */
struct Value *handle_list_command(String *args, int offset)
{
Macro *spec;
int result = 1;
int mflag;
ListOpts opts = { 0, 0, NULL };
if (!(spec = macro_spec(args, offset, &mflag, &opts))) result = 0;
if (result) result = list_defs(NULL, spec, mflag, &opts);
if (spec) nuke_macro(spec);
return newint(result);
}
/**************************
* Routines to use macros *
**************************/
/* Do a macro! */
int do_macro(Macro *macro, String *args, int offset, int used_type,
int kbnumlocal)
{
int result, old_invis_flag, oldblock;
const char *command;
char numbuf[16];
if (*macro->name) {
command = macro->name;
} else {
sprintf(numbuf, "#%d", macro->num);
command = numbuf;
}
if (used_type >= 0) {
macro->used[used_type]++;
}
old_invis_flag = invis_flag;
invis_flag = macro->invis;
oldblock = block; /* XXX ? */
block = 0; /* XXX ? */
if (!macro->prog) {
const char *old_command = current_command;
current_command = command; /* for macro compiler errors */
macro->prog = compile_tf(macro->body, 0, SUB_MACRO, 0,
!macro->shots ? 2 : macro->shots > 10 ? 1 : 0);
current_command = old_command;
}
if (!macro->prog)
result = 0;
else
result = prog_run(macro->prog, args, offset, command, kbnumlocal);
invis_flag = old_invis_flag;
block = oldblock; /* XXX ? */
return result;
}
/* get body of macro */
const char *macro_body(const char *name)
{
Macro *m;
const char *body;
if (!name) return NULL;
if (strncmp("world_", name, 6) == 0 && (body = world_info(NULL, name + 6)))
return body;
if (!(m = find_macro(name))) return NULL;
return m->body ? m->body->data : "";
}
/****************************************
* Routines to check triggers and hooks *
****************************************/
/* do_hook
* Call macros that match <hooknum> and optionally the filled-in <argfmt>, and
* prints the message in <fmt>. Returns the number of matches that were run.
* A leading '!' in <fmt> is replaced with "% ", file name, and line number.
* Note that calling do_hook() makes the caller non-atomic; be careful.
*/
int do_hook(int hooknum, const char *fmt, const char *argfmt, ...)
{
va_list ap;
int ran = 0;
String *line = NULL;
String *args = NULL;
/* do_hook is re-entrant, so we can't use static buffer. macro regexps
* may save a pointer to args, so we can't even use an auto buffer. */
va_start(ap, argfmt);
if (hookflag || hilite || gag) {
(args = Stringnew(NULL, 96, 0))->links++;
vSprintf(args, 0, argfmt, ap);
}
va_end(ap);
if (fmt) {
(line = Stringnew(NULL, 96, 0))->links++;
if (*fmt == '!') {
eprefix(line);
fmt++;
}
va_start(ap, argfmt);
vSprintf(line, SP_APPEND, fmt, ap);
va_end(ap);
}
if (hookflag || hilite || gag) {
ran = find_and_run_matches(args, hooknum, &line, xworld(), TRUE, 0);
Stringfree(args);
}
if (line) {
Stringfree(line);
}
return ran;
}
/* Find and run one or more matches for a hook or trig.
* text is text to be matched; if NULL, *linep is used.
* If %Pn subs are to be allowed, text should be NULL.
* If <hooknum> is <0, this looks for a trigger;
* if <hooknum> is >=0 it is a hook number. If <linep> is non-NULL,
* attributes of matching macros will be applied to *<linep>.
*/
int find_and_run_matches(String *text, int hooknum, String **linep,
World *world, int globalflag, int exec_list_long)
{
Queue runq[1]; /* queue of macros to run */
Macro *nonfallthru = NULL; /* list of non fall-thrus */
int num = 0; /* # of non-fall-thrus */
int ran = 0; /* # of executed macros */
int lowerlimit = -1; /* lowest priority that can match */
int header = 0; /* which headers have we printed? */
ListEntry *gnode, *wnode, **nodep;
Pattern *pattern;
Macro *macro;
const char *worldtype = NULL;
/* Macros are sorted by decreasing priority, with fall-thrus first. So,
* we search the global and world lists in parallel. For each matching
* fall-thru, we apply its attributes; if it's a hook, we add it to a
* queue, if it's a trigger, we execute it immediately. When we find a
* matching non-fall-thru, we collect a list of other non-fall-thru
* matches of the same priority and select one, and apply its attributes;
* again, if it's a hook, we add it to queue, if it's a trigger, we
* execute.
* Then, we print the line.
* Then, if macros were queued (because this is a hook), we run all the
* queued macros.
* The point of the queue is so the line can be printed before any output
* generated by the macros. We would like to do this for triggers as well
* as hooks, but then /substitute wouldn't work.
*/
/* Note: kill_macro() does not remove macros from any lists, so this will
* work correctly when a macro kills itself, or inserts a new macro just
* after itself in a list.
*/
if (world)
worldtype = world_type(world);
if (!worldtype)
worldtype = "";
if (!text)
text = *linep;
text->links++; /* in case substitute() frees text */
recur_count++;
if (hooknum>=0) {
gnode = hooklist[hooknum].head;
wnode = NULL;
} else {
gnode = triglist->head;
wnode = world ? world->triglist->head : NULL;
}
if (exec_list_long == 0) {
init_queue(runq);
}
while (gnode || wnode) {
nodep = (!wnode) ? &gnode : (!gnode) ? &wnode :
(rpricmp(MAC(wnode), MAC(gnode)) > 0) ? &gnode : &wnode;
macro = MAC(*nodep);
*nodep = (*nodep)->next;
if (macro->pri < lowerlimit && exec_list_long == 0)
break;
if (macro->flags & MACRO_DEAD) continue;
if (!(
(hooknum<0 && (
(borg && macro->body && (macro->prob > 0)) ||
(hilite && ((macro->attr & F_HWRITE) || macro->nsubattr)) ||
(gag && (macro->attr & F_GAG)))) ||
(hooknum>=0 && VEC_ISSET(hooknum, ¯o->hook))))
{
continue;
}
/* triggers are listed by world, but hooks are not, so we must check */
if (macro->world && macro->world != world) continue;
if (!globalflag && !macro->world) continue;
if (macro->wtype.str) {
if (!world) continue;
if (!patmatch(¯o->wtype, NULL, worldtype))
continue;
}
if (macro->exprprog) {
struct Value *result = NULL;
int expr_condition;
result = expr_value_safe(macro->exprprog);
expr_condition = valbool(result);
freeval(result);
if (!expr_condition) continue;
}
pattern = hooknum>=0 ? ¯o->hargs : ¯o->trig;
if ((hooknum>=0 && !macro->hargs.str) || patmatch(pattern, CS(text), NULL))
{
if (exec_list_long == 0) {
if (macro->fallthru) {
if (linep && *linep)
apply_attrs_of_match(macro, text, hooknum, *linep);
if (hooknum>=0) {
enqueue(runq, macro);
} else {
ran += run_match(macro, text, hooknum);
if (linep && hooknum<0) {
/* in case of /substitute */ /* XXX */
Stringfree(text);
text = *linep;
text->links++;
}
}
} else {
/* collect list of non-fall-thru matches */
lowerlimit = macro->pri;
num++;
macro->tnext = nonfallthru;
nonfallthru = macro;
}
} else {
ran += (lowerlimit < 0);
if (header < 3 && macro->pri < lowerlimit) {
oputs("% The following matching macros would not be "
"applied:");
header = 3;
}
if (header < 2 && !macro->fallthru) {
oprintf("%% One of the following macros would %sbe "
"applied:", ran > 1 ? "also " : "");
lowerlimit = macro->pri;
header = 2;
}
if (header < 1) {
oputs("% All of the following macros would be applied:");
header = 1;
}
if (exec_list_long > 1)
print_def(tfout, NULL, macro);
else if (macro->name)
oprintf("%% %c %10d %s", macro->fallthru ? 'F' : ' ',
macro->pri, macro->name);
else
oprintf("%% %c %10d #%d", macro->fallthru ? 'F' :' ',
macro->pri, macro->num);
}
}
}
if (exec_list_long == 0) {
/* select exactly one of the non fall-thrus. */
if (num > 0) {
for (macro = nonfallthru, num = RRAND(0, num-1); num; num--)
macro = macro->tnext;
if (linep && *linep)
apply_attrs_of_match(macro, text, hooknum, *linep);
if (hooknum>=0) {
enqueue(runq, macro);
} else {
ran += run_match(macro, text, hooknum);
}
}
/* print the line! */
if (hooknum>=0 && linep && *linep) {
if (hook_table[hooknum].hooktype & HT_ALERT) {
alert(CS(*linep));
} else if (hook_table[hooknum].hooktype & HT_WORLD) {
/* Note: world_output() must come before alert(), otherwise
* world_output() could cause an activity hook that would
* clobber the alert */
if (xworld())
world_output(xworld(), CS(*linep));
if (!xsock_is_fg()) {
alert(CS(*linep));
}
} else {
tfputline(CS(*linep), tferr); /* XXX conStr bug??? */
}
if (hook_table[hooknum].hooktype & HT_XSOCK) {
xsock_alert_id(); /* alert should clear when xsock is fg'd */
}
}
/* run all of the queued macros */
while ((macro = (Macro*)dequeue(runq))) {
ran += run_match(macro, text, hooknum);
}
} else {
oprintf("%% %s would have %s %d macro%s.",
hooknum>=0 ? "Event" : "Text", hooknum>=0 ? "hooked" : "triggered",
ran, (ran != 1) ? "s" : "");
}
recur_count--;
Stringfree(text);
return ran;
}
/* apply attributes of a macro that has been selected by a trigger or hook */
static void apply_attrs_of_match(
Macro *macro, /* macro to apply */
String *text, /* argument text that matched trigger/hook */
int hooknum, /* hook number */
String *line) /* line to which attributes are applied */
{
RegInfo *old, *ri;
if (!hilite && !gag) return;
/* Apply attributes (full and partial) to line. */
if (!hilite)
line->attrs = adj_attr(line->attrs, macro->attr & F_GAG);
else if (!gag)
line->attrs = adj_attr(line->attrs, macro->attr & ~F_GAG);
else
line->attrs = adj_attr(line->attrs, macro->attr);
ri = (hooknum>=0 ? macro->hargs : macro->trig).ri;
if (hooknum<0 && macro->trig.mflag == MATCH_REGEXP && line->len && hilite
&& macro->nsubattr)
{
int i, x, offset = 0;
int start, end;
int *saved_ovector = NULL;
if (text)
old = new_reg_scope(ri, text);
check_charattrs(line, line->len, 0, __FILE__, __LINE__);
do {
for (x = 0; x < macro->nsubattr; x++) {
if (macro->subattr[x].subexp == -1) {
start = 0;
end = ri->ovector[0];
} else if (macro->subattr[x].subexp == -2) {
start = ri->ovector[1];
end = line->len;
} else {
start = ri->ovector[macro->subattr[x].subexp * 2];
end = ri->ovector[macro->subattr[x].subexp * 2 + 1];
}
for (i = start; i < end; ++i)
line->charattrs[i] =
adj_attr(line->charattrs[i], macro->subattr[x].attr);
}
if (offset == ri->ovector[1]) break; /* offset wouldn't move */
offset = ri->ovector[1];
if (!saved_ovector) {
saved_ovector = ri->ovector;
ri->ovector = NULL;
}
} while (offset < line->len &&
tf_reg_exec(ri, CS(text), NULL, offset) > 0);
/* restore original startp/endp */
if (saved_ovector) {
if (ri->ovector) FREE(ri->ovector);
ri->ovector = saved_ovector;
}
(ri->Str = CS(line))->links++;
if (text)
restore_reg_scope(old);
}
}
typedef struct max_ctr {
int bucket[2];
int count[2];
const int maxid, flagid;
const char *label;
} max_ctr_t;
static max_ctr_t trig_ctr = {{0,0}, {0,0}, VAR_max_trig, VAR_borg, "Trigger"};
static max_ctr_t hook_ctr = {{0,0}, {0,0}, VAR_max_hook, VAR_hook, "Hook"};
static int test_max_counter(max_ctr_t *c)
{
static int bucketsize = 5;
int now = time(NULL) / bucketsize;
if (now != c->bucket[1]) {
c->bucket[0] = now - 1;
c->bucket[1] = now;
c->count[0] = (now == c->bucket[1] + 1) ? c->count[1] : 1;
c->count[1] = 0;
}
if (c->count[0] + ++c->count[1] > special_var[c->maxid].val.u.ival) {
set_var_by_id(c->flagid, 0);
eprintf("%s rate exceeded %%%s (%d) in less than %ds. "
"Setting %%%s off.", c->label,
special_var[c->maxid].val.name, special_var[c->maxid].val.u.ival,
2*bucketsize, special_var[c->flagid].val.name);
return 0;
}
return 1;
}
/* run a macro that has been selected by a trigger or hook */
static int run_match(
Macro *macro, /* macro to run */
String *text, /* argument text that matched trigger/hook */
int hooknum) /* hook number */
{
int ran = 0;
struct Sock *callingsock = xsock;
RegInfo *old;
if (hooknum < 0) { /* trigger */
if (!borg) return 0;
if (text && max_trig > 0 && !test_max_counter(&trig_ctr)) return 0;
} else { /* hook */
if (max_hook > 0 && !test_max_counter(&hook_ctr)) return 0;
}
if (text)
old = new_reg_scope(hooknum>=0 ? macro->hargs.ri : macro->trig.ri, text);
/* Execute the macro. */
if ((hooknum>=0 && hookflag) || (hooknum<0 && borg)) {
callingsock = xsock;
if (macro->prob == 100 || RRAND(0, 99) < macro->prob) {
if (macro->shots && !--macro->shots) kill_macro(macro);
if (mecho > macro->invis) {
char numbuf[16];
if (!*macro->name) sprintf(numbuf, "#%d", macro->num);
tfprintf(tferr, "%S%s%s: /%s %S%A", do_mprefix(),
hooknum>=0 ? hook_table[hooknum].name : "",
hooknum>=0 ? " HOOK" : "TRIGGER",
*macro->name ? macro->name : numbuf,
text, mecho_attr);
}
if (macro->body && macro->body->len) {
do_macro(macro, text, 0, hooknum>=0 ? USED_HOOK : USED_TRIG, 0);
ran += !macro->quiet;
}
}
/* Restore xsock, in case macro called fg_sock(). main_loop() will
* set xsock=fsock, so any fg_sock() will effect xsock after the
* find_and_run_matches() loop is complete.
*/
xsock = callingsock;
}
if (text)
restore_reg_scope(old);
return ran;
}
#if USE_DMALLOC
void free_macros(void)
{
while (maclist->head) nuke_macro((Macro *)maclist->head->datum);
free_hash(macro_table);
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1