Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Magic assoc arrays



Trying to have some fun at the weekend, I hacked this...

This implements four magic associative arrays:

  - `$commands[cmd]' gives the path to `cmd'. Only for the external
    commands hashtable, for now. It returns whatever is in that table
    (like the `hash' builtin), i.e. it doesn't try to find out if the
    thing is really an executable command. This could be changed,
    though.
    Setting `commands[foo]=...' is like `hash foo=...'.
    As with the `compstate' hash I have made full assignments
    (`commands=(...)') special: all key/value pairs are set, but the
    table is not cleared before that. I thought this to be safer, but
    maybe...
    It also uses HASHLISTALL to decide if it should fill the hashtable 
    if it couldn't find the name immediatly.
  - `$functions[name]' gives the definition of function `name'.
     Assignment is like `foo() { ... }'.
     Small ugliness: for autoloaded functions `$functions[foo]'
     returns the string `undefined', but setting it to this value is
     not handled specially (the result would be a function calling the 
     command `undefined').
  - `$options[alwayslastprompt]' gives `on' or `off'. Setting is like
    `(|un)setopt'.
  - `$parameters[foo]' gives the type(!) of the parameter `foo', the
    way `${(t)...}' does it. Rather silly, since the only thing I'm
    really interested in is `${(k)parameters}'. But then, this also
    allows us to quickly get at names of parameters with certain flags 
    by doing some matching on the values.

Since I just wanted to play and now only want to give an example of
what might be done, there is no doc for this. If anyone decides that
this is worth having, I would write one.

To implement this I had to change `struct hashtable' to be able to
define special scan-functions. The normal calling interface is still
just `scanhashtable()' which then calls the special function, if
any. This should probably be changed, but I didn't feel like modifying 
every call to it just for this.

Bye
 Sven

diff -u oos/hashtable.c Src/hashtable.c
--- oos/hashtable.c	Mon May  3 11:07:06 1999
+++ Src/hashtable.c	Mon May  3 11:35:48 1999
@@ -350,6 +350,10 @@
 {
     struct scanstatus st;
 
+    if (ht->scanfunc) {
+	ht->scanfunc(ht, scanfunc, scanflags);
+	return;
+    }
     if (sorted) {
 	int i, ct = ht->ct;
 	VARARR(HashNode, hnsorttab, ct);
diff -u oos/options.c Src/options.c
--- oos/options.c	Mon May  3 11:07:07 1999
+++ Src/options.c	Mon May  3 11:35:48 1999
@@ -45,15 +45,6 @@
 /**/
 HashTable optiontab;
  
-typedef struct optname *Optname;
-
-struct optname {
-    HashNode next;		/* next in hash chain */
-    char *nam;			/* hash data */
-    int flags;
-    int optno;			/* option number */
-};
-
 /* The canonical option name table */
 
 #define OPT_CSH		EMULATE_CSH
diff -u oos/params.c Src/params.c
--- oos/params.c	Mon May  3 11:07:07 1999
+++ Src/params.c	Mon May  3 11:38:19 1999
@@ -412,6 +412,495 @@
 	return NULL;
 }
 
+/* empty dummy function for special hash parameters */
+
+static void
+shempty()
+{
+}
+
+/* Create a simple special hash parameter. */
+
+/**/
+Param
+createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan)
+{
+    Param pm;
+    HashTable ht;
+
+    if (!(pm = createparam(name, PM_SPECIAL|PM_REMOVABLE|PM_HASHED)))
+	return NULL;
+
+    pm->level = locallevel;
+    pm->gets.hfn = hashgetfn;
+    pm->sets.hfn = hashsetfn;
+    pm->unsetfn = stdunsetfn;
+    pm->u.hash = ht = newhashtable(7, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = (TableFunc) shempty;
+    ht->filltable   = NULL;
+    ht->addnode     = (AddNodeFunc) shempty;
+    ht->getnode     = ht->getnode2 = get;
+    ht->removenode  = (RemoveNodeFunc) shempty;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = (FreeNodeFunc) shempty;
+    ht->printnode   = NULL;
+    ht->scanfunc    = scan;
+
+    return pm;
+}
+
+/* Functions for the parameters special parameter. */
+
+static HashNode
+getpmparameter(HashTable ht, char *name)
+{
+    Param rpm, pm = NULL;
+
+    HEAPALLOC {
+	pm = (Param) zhalloc(sizeof(struct param));
+	pm->nam = dupstring(name);
+	pm->flags = PM_SCALAR | PM_READONLY;
+	pm->sets.cfn = NULL;
+	pm->gets.cfn = strgetfn;
+	pm->unsetfn = NULL;
+	pm->ct = 0;
+	pm->env = NULL;
+	pm->ename = NULL;
+	pm->old = NULL;
+	pm->level = 0;
+	if ((rpm = (Param) realparamtab->getnode(realparamtab, name)) &&
+	    !(rpm->flags & PM_UNSET))
+	    pm->u.str = paramtypestr(rpm);
+	else {
+	    pm->u.str = "";
+	    pm->flags |= PM_UNSET;
+	}
+    } LASTALLOC;
+
+    return (HashNode) pm;
+}
+
+static void
+scanpmparameters(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    pm.flags = PM_SCALAR | PM_READONLY;
+    pm.sets.cfn = NULL;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = NULL;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < realparamtab->hsize; i++)
+	for (hn = realparamtab->nodes[i]; hn; hn = hn->next) {
+	    pm.nam = hn->nam;
+	    if (func != scancountparams)
+		pm.u.str = paramtypestr((Param) hn);
+	    func((HashNode) &pm, flags);
+	}
+}
+
+/* Functions for the commands special parameter. */
+
+static void
+setpmcommand(Param pm, char *value)
+{
+    if (isset(RESTRICTED))
+	zwarnnam(NULL, "restricted: %s", value, 0);
+    else {
+	Cmdnam cn = zcalloc(sizeof(*cn));
+
+	cn->flags = HASHED;
+	cn->u.cmd = ztrdup(value);
+
+	cmdnamtab->addnode(cmdnamtab, ztrdup(pm->nam), (HashNode) cn);
+    }
+}
+
+static void
+unsetpmcommand(Param pm, int exp)
+{
+    HashNode hn = cmdnamtab->removenode(cmdnamtab, pm->nam);
+
+    if (hn)
+	cmdnamtab->freenode(hn);
+}
+
+static void
+setpmcommands(Param pm, HashTable ht)
+{
+    int i;
+    HashNode hn;
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next) {
+	    Cmdnam cn = zcalloc(sizeof(*cn));
+	    struct value v;
+
+	    v.isarr = v.inv = v.a = 0;
+	    v.b = -1;
+	    v.arr = NULL;
+	    v.pm = (Param) hn;
+
+	    cn->flags = HASHED;
+	    cn->u.cmd = ztrdup(getstrvalue(&v));
+
+	    cmdnamtab->addnode(cmdnamtab, ztrdup(hn->nam), (HashNode) cn);
+	}
+}
+
+static HashNode
+getpmcommand(HashTable ht, char *name)
+{
+    Cmdnam cmd;
+    Param pm = NULL;
+
+    if (!(cmd = (Cmdnam) cmdnamtab->getnode(cmdnamtab, name)) &&
+	isset(HASHLISTALL)) {
+	cmdnamtab->filltable(cmdnamtab);
+	cmd = (Cmdnam) cmdnamtab->getnode(cmdnamtab, name);
+    }
+    HEAPALLOC {
+	pm = (Param) zhalloc(sizeof(struct param));
+	pm->nam = dupstring(name);
+	pm->flags = PM_SCALAR;
+	pm->sets.cfn = setpmcommand;
+	pm->gets.cfn = strgetfn;
+	pm->unsetfn = unsetpmcommand;
+	pm->ct = 0;
+	pm->env = NULL;
+	pm->ename = NULL;
+	pm->old = NULL;
+	pm->level = 0;
+	if (cmd) {
+	    if (cmd->flags & HASHED)
+		pm->u.str = cmd->u.cmd;
+	    else {
+		pm->u.str = zhalloc(strlen(*(cmd->u.name)) +
+				    strlen(name) + 2);
+		strcpy(pm->u.str, *(cmd->u.name));
+		strcat(pm->u.str, "/");
+		strcat(pm->u.str, name);
+	    }
+	} else {
+	    pm->u.str = "";
+	    pm->flags |= PM_UNSET;
+	}
+    } LASTALLOC;
+
+    return (HashNode) pm;
+}
+
+static void
+scanpmcommands(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    Cmdnam cmd;
+
+    if (isset(HASHLISTALL))
+	cmdnamtab->filltable(cmdnamtab);
+
+    pm.flags = PM_SCALAR;
+    pm.sets.cfn = setpmcommand;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = unsetpmcommand;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < cmdnamtab->hsize; i++)
+	for (hn = cmdnamtab->nodes[i]; hn; hn = hn->next) {
+	    pm.nam = hn->nam;
+	    cmd = (Cmdnam) hn;
+	    if (func != scancountparams) {
+		if (cmd->flags & HASHED)
+		    pm.u.str = cmd->u.cmd;
+		else {
+		    pm.u.str = zhalloc(strlen(*(cmd->u.name)) +
+				       strlen(cmd->nam) + 2);
+		    strcpy(pm.u.str, *(cmd->u.name));
+		    strcat(pm.u.str, "/");
+		    strcat(pm.u.str, cmd->nam);
+		}
+	    }
+	    func((HashNode) &pm, flags);
+	}
+}
+
+/* Functions for the functions special parameter. */
+
+static void
+setfunction(char *name, char *value)
+{
+    char *val;
+    Shfunc shf;
+    List list;
+    int sn;
+
+    val = ztrdup(value);
+    val = metafy(val, strlen(val), META_REALLOC);
+
+    HEAPALLOC {
+	list = parse_string(val);
+    } LASTALLOC;
+
+    if (!list || list == &dummy_list) {
+	zwarnnam(NULL, "invalid function definition", val, 0);
+	zsfree(val);
+	return;
+    }
+    PERMALLOC {
+	shf = (Shfunc) zalloc(sizeof(*shf));
+	shf->funcdef = (List) dupstruct(list);
+	shf->flags = 0;
+
+	if (!strncmp(name, "TRAP", 4) &&
+	    (sn = getsignum(name + 4)) != -1) {
+	    if (settrap(sn, shf->funcdef)) {
+		freestruct(shf->funcdef);
+		zfree(shf, sizeof(*shf));
+		zsfree(val);
+		LASTALLOC_RETURN;
+	    }
+	    sigtrapped[sn] |= ZSIG_FUNC;
+	}
+	shfunctab->addnode(shfunctab, ztrdup(name), shf);
+    } LASTALLOC;
+
+    zsfree(val);
+}
+
+static void
+setpmfunction(Param pm, char *value)
+{
+    setfunction(pm->nam, value);
+}
+
+static void
+unsetpmfunction(Param pm, int exp)
+{
+    HashNode hn = shfunctab->removenode(shfunctab, pm->nam);
+
+    if (hn)
+	shfunctab->freenode(hn);
+}
+
+static void
+setpmfunctions(Param pm, HashTable ht)
+{
+    int i;
+    HashNode hn;
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next) {
+	    struct value v;
+
+	    v.isarr = v.inv = v.a = 0;
+	    v.b = -1;
+	    v.arr = NULL;
+	    v.pm = (Param) hn;
+
+	    setfunction(hn->nam, getstrvalue(&v));
+	}
+}
+
+static HashNode
+getpmfunction(HashTable ht, char *name)
+{
+    Shfunc shf;
+    Param pm = NULL;
+
+    HEAPALLOC {
+	pm = (Param) zhalloc(sizeof(struct param));
+	pm->nam = dupstring(name);
+	pm->flags = PM_SCALAR;
+	pm->sets.cfn = setpmfunction;
+	pm->gets.cfn = strgetfn;
+	pm->unsetfn = unsetpmfunction;
+	pm->ct = 0;
+	pm->env = NULL;
+	pm->ename = NULL;
+	pm->old = NULL;
+	pm->level = 0;
+
+	if ((shf = (Shfunc) shfunctab->getnode(shfunctab, name))) {
+	    if (shf->flags & PM_UNDEFINED)
+		pm->u.str = "undefined";
+	    else {
+		char *t = getpermtext((void *) dupstruct((void *)
+							 shf->funcdef)), *h;
+
+		h = dupstring(t);
+		zsfree(t);
+		unmetafy(h, NULL);
+
+		pm->u.str = h;
+	    }
+	} else {
+	    pm->u.str = "";
+	    pm->flags |= PM_UNSET;
+	}
+    } LASTALLOC;
+
+    return (HashNode) pm;
+}
+
+static void
+scanpmfunctions(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    pm.flags = PM_SCALAR;
+    pm.sets.cfn = setpmcommand;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = unsetpmcommand;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < shfunctab->hsize; i++)
+	for (hn = shfunctab->nodes[i]; hn; hn = hn->next) {
+	    if (!(hn->flags & DISABLED)) {
+		pm.nam = hn->nam;
+		if (func != scancountparams) {
+		    if (((Shfunc) hn)->flags & PM_UNDEFINED)
+			pm.u.str = "undefined";
+		    else {
+			char *t = getpermtext((void *)
+					      dupstruct((void *) ((Shfunc) hn)->funcdef));
+
+			unmetafy((pm.u.str = dupstring(t)), NULL);
+			zsfree(t);
+		    }
+		}
+		func((HashNode) &pm, flags);
+	    }
+	}
+}
+
+/* Functions for the options special parameter. */
+
+static void
+setpmoption(Param pm, char *value)
+{
+    int n;
+
+    if (!value || (strcmp(value, "on") && strcmp(value, "off")))
+	zwarnnam(NULL, "invalid value: %s", value, 0);
+    else if (!(n = optlookup(pm->nam)))
+	zwarnnam(NULL, "no such option: %s", pm->nam, 0);
+    else if (dosetopt(n, (value && strcmp(value, "off")), 0))
+	zwarnnam(NULL, "can't change option: %s", pm->nam, 0);
+}
+
+static void
+unsetpmoption(Param pm, int exp)
+{
+    int n;
+
+    if (!(n = optlookup(pm->nam)))
+	zwarnnam(NULL, "no such option: %s", pm->nam, 0);
+    else if (dosetopt(n, 0, 0))
+	zwarnnam(NULL, "can't change option: %s", pm->nam, 0);
+}
+
+static void
+setpmoptions(Param pm, HashTable ht)
+{
+    int i;
+    HashNode hn;
+
+    for (i = 0; i < ht->hsize; i++)
+	for (hn = ht->nodes[i]; hn; hn = hn->next) {
+	    struct value v;
+	    char *val;
+
+	    v.isarr = v.inv = v.a = 0;
+	    v.b = -1;
+	    v.arr = NULL;
+	    v.pm = (Param) hn;
+
+	    val = getstrvalue(&v);
+	    if (!val || (strcmp(val, "on") && strcmp(val, "off")))
+		zwarnnam(NULL, "invalid value: %s", val, 0);
+	    else if (dosetopt(optlookup(hn->nam),
+			      (val && strcmp(val, "off")), 0))
+		zwarnnam(NULL, "can't change option: %s", hn->nam, 0);
+	}
+}
+
+static HashNode
+getpmoption(HashTable ht, char *name)
+{
+    Param pm = NULL;
+    int n;
+
+    HEAPALLOC {
+	pm = (Param) zhalloc(sizeof(struct param));
+	pm->nam = dupstring(name);
+	pm->flags = PM_SCALAR;
+	pm->sets.cfn = setpmoption;
+	pm->gets.cfn = strgetfn;
+	pm->unsetfn = unsetpmoption;
+	pm->ct = 0;
+	pm->env = NULL;
+	pm->ename = NULL;
+	pm->old = NULL;
+	pm->level = 0;
+
+	if ((n = optlookup(name)))
+	    pm->u.str = dupstring(opts[n] ? "on" : "off");
+	else {
+	    pm->u.str = "";
+	    pm->flags |= PM_UNSET;
+	}
+    } LASTALLOC;
+
+    return (HashNode) pm;
+}
+
+static void
+scanpmoptions(HashTable ht, ScanFunc func, int flags)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+
+    pm.flags = PM_SCALAR;
+    pm.sets.cfn = setpmoption;
+    pm.gets.cfn = strgetfn;
+    pm.unsetfn = unsetpmoption;
+    pm.ct = 0;
+    pm.env = NULL;
+    pm.ename = NULL;
+    pm.old = NULL;
+    pm.level = 0;
+
+    for (i = 0; i < optiontab->hsize; i++)
+	for (hn = optiontab->nodes[i]; hn; hn = hn->next) {
+	    pm.nam = hn->nam;
+	    pm.u.str = opts[((Optname) hn)->optno] ? "on" : "off";
+	    func((HashNode) &pm, flags);
+	}
+}
+
 /* Set up parameter hash table.  This will add predefined  *
  * parameter entries as well as setting up parameter table *
  * entries for environment variables we inherit.           */
@@ -515,6 +1004,15 @@
 	setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION));
 	setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
 	for (t = sigs; (*sigptr++ = ztrdup(*t++)); );
+
+	pm = createspecialhash("parameters", getpmparameter, scanpmparameters);
+	pm->flags |= PM_READONLY;
+	pm = createspecialhash("commands", getpmcommand, scanpmcommands);
+	pm->sets.hfn = setpmcommands;
+	pm = createspecialhash("functions", getpmfunction, scanpmfunctions);
+	pm->sets.hfn = setpmfunctions;
+	pm = createspecialhash("options", getpmoption, scanpmoptions);
+	pm->sets.hfn = setpmoptions;
     } LASTALLOC;
 
     noerrs = 0;
@@ -850,8 +1348,7 @@
 	    *inv = 0;	/* We've already obtained the "index" (key) */
 	    *w = v->b = -1;
 	    r = isset(KSHARRAYS) ? 1 : 0;
-	} else
-	if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
+	} else if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0))
 	    r++;
 	if (word && !v->isarr) {
 	    s = t = getstrvalue(v);
@@ -2712,6 +3209,47 @@
     if (pm->flags & PM_TIED)
 	zsfree(pm->ename);
     zfree(pm, sizeof(struct param));
+}
+
+/* Return a string describing the type of a parameter. */
+
+/**/
+char *
+paramtypestr(Param pm)
+{
+    char *val;
+    int f = pm->flags;
+
+    if (!(f & PM_UNSET)) {
+	switch (PM_TYPE(f)) {
+	case PM_SCALAR:  val = "scalar"; break;
+	case PM_ARRAY:   val = "array"; break;
+	case PM_INTEGER: val = "integer"; break;
+	case PM_HASHED:  val = "association"; break;
+	}
+	val = dupstring(val);
+	if (f & PM_LEFT)
+	    val = dyncat(val, "-left");
+	if (f & PM_RIGHT_B)
+	    val = dyncat(val, "-right_blanks");
+	if (f & PM_RIGHT_Z)
+	    val = dyncat(val, "-right_zeros");
+	if (f & PM_LOWER)
+	    val = dyncat(val, "-lower");
+	if (f & PM_UPPER)
+	    val = dyncat(val, "-upper");
+	if (f & PM_READONLY)
+	    val = dyncat(val, "-readonly");
+	if (f & PM_TAGGED)
+	    val = dyncat(val, "-tag");
+	if (f & PM_EXPORTED)
+	    val = dyncat(val, "-export");
+	if (f & PM_UNIQUE)
+	    val = dyncat(val, "-unique");
+    } else
+	val = dupstring("");
+
+    return val;
 }
 
 /* Print a parameter */
diff -u oos/subst.c Src/subst.c
--- oos/subst.c	Mon May  3 11:07:08 1999
+++ Src/subst.c	Mon May  3 11:35:49 1999
@@ -1008,33 +1008,7 @@
 
 	if (wantt) {
 	    if (v && v->pm && !(v->pm->flags & PM_UNSET)) {
-		int f = v->pm->flags;
-
-		switch (PM_TYPE(f)) {
-		case PM_SCALAR:  val = "scalar"; break;
-		case PM_ARRAY:   val = "array"; break;
-		case PM_INTEGER: val = "integer"; break;
-		case PM_HASHED:  val = "association"; break;
-		}
-		val = dupstring(val);
-		if (f & PM_LEFT)
-		    val = dyncat(val, "-left");
-		if (f & PM_RIGHT_B)
-		    val = dyncat(val, "-right_blanks");
-		if (f & PM_RIGHT_Z)
-		    val = dyncat(val, "-right_zeros");
-		if (f & PM_LOWER)
-		    val = dyncat(val, "-lower");
-		if (f & PM_UPPER)
-		    val = dyncat(val, "-upper");
-		if (f & PM_READONLY)
-		    val = dyncat(val, "-readonly");
-		if (f & PM_TAGGED)
-		    val = dyncat(val, "-tag");
-		if (f & PM_EXPORTED)
-		    val = dyncat(val, "-export");
-		if (f & PM_UNIQUE)
-		    val = dyncat(val, "-unique");
+		val = paramtypestr(v->pm);
 		vunset = 0;
 	    } else
 		val = dupstring("");
diff -u oos/zsh.h Src/zsh.h
--- oos/zsh.h	Mon May  3 11:07:09 1999
+++ Src/zsh.h	Mon May  3 11:35:50 1999
@@ -228,6 +228,7 @@
 typedef struct hashnode  *HashNode;
 typedef struct hashtable *HashTable;
 
+typedef struct optname   *Optname;
 typedef struct reswd     *Reswd;
 typedef struct alias     *Alias;
 typedef struct param     *Param;
@@ -684,6 +685,8 @@
  * scanhashtable or scanmatchtable    */
 typedef void     (*ScanFunc)       _((HashNode, int));
 
+typedef void     (*ScanTabFunc)    _((HashTable, ScanFunc, int));
+
 typedef void (*PrintTableStats) _((HashTable));
 
 /* hash table for standard open hashing */
@@ -707,6 +710,7 @@
     ScanFunc enablenode;	/* pointer to function to enable a node       */
     FreeNodeFunc freenode;	/* pointer to function to free a node         */
     ScanFunc printnode;		/* pointer to function to print a node        */
+    ScanTabFunc scanfunc;	/* pointer to function to scan table          */
 
 #ifdef HASHTABLE_INTERNAL_MEMBERS
     HASHTABLE_INTERNAL_MEMBERS	/* internal use in hashtable.c                */
@@ -725,6 +729,15 @@
  * you can disable builtins, shell functions, aliases and *
  * reserved words.                                        */
 #define DISABLED	(1<<0)
+
+/* node in shell option table */
+
+struct optname {
+    HashNode next;		/* next in hash chain */
+    char *nam;			/* hash data */
+    int flags;
+    int optno;			/* option number */
+};
 
 /* node in shell reserved word hash table (reswdtab) */
 

--
Sven Wischnowsky                         wischnow@xxxxxxxxxxxxxxxxxxxxxxx



Messages sorted by: Reverse Date, Date, Thread, Author