From 9531e12b3657cf4305becbbeed15bf89570ece00 Mon Sep 17 00:00:00 2001 From: tg Date: Sun, 6 Sep 2009 17:42:15 +0000 Subject: [PATCH] merge the nameref code, using mksh standard scoping as discussed --- check.t | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- eval.c | 26 +++++++++---- funcs.c | 26 ++++++++----- main.c | 3 +- mksh.1 | 31 +++++++++++++-- sh.h | 6 ++- var.c | 65 +++++++++++++++++++++++++++++--- 7 files changed, 244 insertions(+), 28 deletions(-) diff --git a/check.t b/check.t index f6e0082..6ad15bf 100644 --- a/check.t +++ b/check.t @@ -1,4 +1,4 @@ -# $MirOS: src/bin/mksh/check.t,v 1.302 2009/08/30 21:01:59 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.303 2009/09/06 17:42:11 tg Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ @@ -4197,7 +4197,7 @@ name: xxx-param-subst-qmark-1 description: Check suppresion of error message with null string. According to POSIX, it shouldn't print the error as 'word' isn't ommitted. - ksh88, Solaris /bin/sh and /usr/xpg4/bin/sh all print the error, + ksh88/93, Solaris /bin/sh and /usr/xpg4/bin/sh all print the error, that's why the condition is reversed. stdin: unset foo @@ -4731,6 +4731,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -4753,6 +4754,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -4799,6 +4801,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -4823,6 +4826,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -4846,6 +4850,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -4868,6 +4873,7 @@ expected-stdout: integer='typeset -i' local=typeset login='exec login' + nameref='typeset -n' nohup='nohup ' r='fc -e -' source='PATH=$PATH:. command .' @@ -5006,6 +5012,33 @@ expected-stdout: g 0<0>1<1>2<4> = g h 0<0>1<1>2<4> = h --- +name: arrays-7 +description: + Check if we can get the array keys (indices) for indexed arrays, + Korn shell style, in some corner cases +stdin: + print !arz: ${!arz} + print !arz[0]: ${!arz[0]} + print !arz[1]: ${!arz[1]} + arz=foo + print !arz: ${!arz} + print !arz[0]: ${!arz[0]} + print !arz[1]: ${!arz[1]} + unset arz + print !arz: ${!arz} + print !arz[0]: ${!arz[0]} + print !arz[1]: ${!arz[1]} +expected-stdout: + !arz: 0 + !arz[0]: + !arz[1]: + !arz: arz + !arz[0]: 0 + !arz[1]: + !arz: 0 + !arz[0]: + !arz[1]: +--- name: varexpand-substr-1 description: Check if bash-style substring expansion works @@ -6100,3 +6133,81 @@ expected-stdout: mypid=nz =15 --- +name: nameref-1 +description: + Testsuite for nameref (bound variables) +stdin: + bar=global + typeset -n ir2=bar + typeset -n ind=ir2 + print !ind: ${!ind} + print ind: $ind + print !ir2: ${!ir2} + print ir2: $ir2 + typeset +n ind + print !ind: ${!ind} + print ind: $ind + typeset -n ir2=ind + print !ir2: ${!ir2} + print ir2: $ir2 + set|grep ^ir2|sed 's/^/s1: /' + typeset|grep ' ir2'|sed -e 's/^/s2: /' -e 's/nameref/typeset -n/' + blub=(e1 e2 e3) + typeset -n ind=blub + typeset -n ir2=blub[2] + print !ind[1]: ${!ind[1]} + print !ir2: $!ir2 + print ind[1]: ${ind[1]} + print ir2: $ir2 +expected-stdout: + !ind: bar + ind: global + !ir2: bar + ir2: global + !ind: ind + ind: ir2 + !ir2: ind + ir2: ir2 + s1: ir2=ind + s2: typeset -n ir2 + !ind[1]: 1 + !ir2: ir2 + ind[1]: e2 + ir2: e3 +--- +name: nameref-2da +description: + Testsuite for nameref (bound variables) + Functions, argument given directly, after local +stdin: + function foo { + typeset bar=lokal baz=auch + typeset -n v=bar + print entering + print !v: ${!v} + print !bar: ${!bar} + print !baz: ${!baz} + print bar: $bar + print v: $v + v=123 + print bar: $bar + print v: $v + print exiting + } + bar=global + print bar: $bar + foo bar + print bar: $bar +expected-stdout: + bar: global + entering + !v: bar + !bar: bar + !baz: baz + bar: lokal + v: lokal + bar: 123 + v: 123 + exiting + bar: global +--- diff --git a/eval.c b/eval.c index 0a3b6db..60c7623 100644 --- a/eval.c +++ b/eval.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.68 2009/08/28 22:39:09 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.69 2009/09/06 17:42:12 tg Exp $"); /* * string expansion @@ -1021,8 +1021,7 @@ varsub(Expand *xp, const char *sp, const char *word, if (!(vp->flag&ISSET)) continue; XPput(wv, c == '!' ? shf_smprintf("%lu", - vp->flag & AINDEX ? - (unsigned long)vp->ua.index : 0) : + arrayindex(vp)) : str_val(vp)); } if (XPsize(wv) == 0) { @@ -1037,14 +1036,27 @@ varsub(Expand *xp, const char *sp, const char *word, state = XARG; } } else { - if (*sp == '!' && sp[1]) - return (-1); /* Can't assign things like $! or $1 */ if ((stype & 0x7f) == '=' && ctype(*sp, C_VAR1 | C_DIGIT)) return (-1); - xp->var = global(sp); - xp->str = str_val(xp->var); + if (*sp == '!' && sp[1]) { + ++sp; + xp->var = global(sp); + if (cstrchr(sp, '[')) { + if (xp->var->flag & ISSET) + xp->str = shf_smprintf("%lu", + arrayindex(xp->var)); + else + xp->str = null; + } else if (xp->var->flag & ISSET) + xp->str = xp->var->name; + else + xp->str = "0"; /* ksh93 compat */ + } else { + xp->var = global(sp); + xp->str = str_val(xp->var); + } state = XSUB; } } diff --git a/funcs.c b/funcs.c index 58b8cc8..5d17cd9 100644 --- a/funcs.c +++ b/funcs.c @@ -25,7 +25,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.128 2009/08/30 21:02:00 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.129 2009/09/06 17:42:12 tg Exp $"); #if HAVE_KILLPG /* @@ -47,6 +47,8 @@ __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.128 2009/08/30 21:02:00 tg Exp $"); #define c_ulimit c_label #endif +extern uint8_t set_refflag; + /* A leading = means assignments before command are kept; * a leading * means a POSIX special builtin; * a leading + means a POSIX regular builtin @@ -896,7 +898,7 @@ c_typeset(const char **wp) } /* see comment below regarding possible opions */ - opts = istset ? "L#R#UZ#afi#lprtux" : "p"; + opts = istset ? "L#R#UZ#afi#lnprtux" : "p"; fieldstr = basestr = NULL; builtin_opt.flags |= GF_PLUSOPT; @@ -947,6 +949,9 @@ c_typeset(const char **wp) case 'l': flag = LCASEV; break; + case 'n': + set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1; + break; case 'p': /* export, readonly: POSIX -p flag */ /* typeset: show values as well */ @@ -995,13 +1000,14 @@ c_typeset(const char **wp) builtin_opt.optind++; } - if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) { + if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) { bi_errorf("only -t, -u and -x options may be used with -f"); + set_refflag = 0; return (1); } if (wp[builtin_opt.optind]) { /* Take care of exclusions. - * At this point, flags in fset are cleared in fclr and vise + * At this point, flags in fset are cleared in fclr and vice * versa. This property should be preserved. */ if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ @@ -1015,8 +1021,8 @@ c_typeset(const char **wp) /* Setting these attributes clears the others, unless they * are also set in this command */ - if (fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | - INTEGER | INT_U | INT_L)) + if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | + INTEGER | INT_U | INT_L)) || set_refflag) fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | INTEGER | INT_U | INT_L); } @@ -1047,9 +1053,11 @@ c_typeset(const char **wp) "%s() %T\n", wp[i], f->val.t); } else if (!typeset(wp[i], fset, fclr, field, base)) { bi_errorf("%s: not identifier", wp[i]); + set_refflag = 0; return (1); } } + set_refflag = 0; return (rv); } @@ -1112,6 +1120,8 @@ c_typeset(const char **wp) * be suitable for re-entry... */ shf_puts("typeset ", shl_stdout); + if (((vp->flag&(ARRAY|ASSOC))==ASSOC)) + shf_puts("-n ", shl_stdout); if ((vp->flag&INTEGER)) shf_puts("-i ", shl_stdout); if ((vp->flag&EXPORT)) @@ -1160,9 +1170,7 @@ c_typeset(const char **wp) if ((vp->flag&ARRAY) && any_set) shprintf("%s[%lu]", vp->name, - vp->flag & AINDEX ? - (unsigned long)vp->ua.index : - 0); + arrayindex(vp)); else shf_puts(vp->name, shl_stdout); if (thing == '-' && (vp->flag&ISSET)) { diff --git a/main.c b/main.c index 4811105..86cae76 100644 --- a/main.c +++ b/main.c @@ -33,7 +33,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/main.c,v 1.143 2009/08/29 11:26:44 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.144 2009/09/06 17:42:13 tg Exp $"); extern char **environ; @@ -67,6 +67,7 @@ static const char *initcoms[] = { "autoload=typeset -fu", "functions=typeset -f", "history=fc -l", + "nameref=typeset -n", "nohup=nohup ", r_fc_e_, "source=PATH=$PATH:. command .", diff --git a/mksh.1 b/mksh.1 index ba19817..443ec3d 100644 --- a/mksh.1 +++ b/mksh.1 @@ -1,4 +1,4 @@ -.\" $MirOS: src/bin/mksh/mksh.1,v 1.180 2009/09/05 17:12:49 tg Exp $ +.\" $MirOS: src/bin/mksh/mksh.1,v 1.181 2009/09/06 17:42:13 tg Exp $ .\" $OpenBSD: ksh.1,v 1.129 2009/05/28 06:09:06 jmc Exp $ .\"- .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 @@ -48,7 +48,7 @@ .el .xD \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 .. .\"- -.Dd $Mdocdate: September 5 2009 $ +.Dd $Mdocdate: September 6 2009 $ .Dt MKSH 1 .Os MirBSD .Sh NAME @@ -883,6 +883,7 @@ history=\*(aqfc \-l\*(aq integer=\*(aqtypeset \-i\*(aq local=\*(aqtypeset\*(aq login=\*(aqexec login\*(aq +nameref=\*(aqtypeset -n\*(aq nohup=\*(aqnohup \*(aq r=\*(aqfc \-e \-\*(aq stop=\*(aqkill \-STOP\*(aq @@ -1261,6 +1262,18 @@ of the string value of parameter The number of elements in the array .Ar name . .Pp +.It Pf ${! Ns Ar name Ns } +The name of the variable referred to by +.Ar name . +This will be +.Ar name +except when +.Ar name +is a name reference (bound variable), created by the +.Ic nameref +command (which is an alias for +.Ic typeset Fl n ) . +.Pp .It Pf ${! Ns Ar name Ns [*]} .It Pf ${! Ns Ar name Ns [@]} The names of indices (keys) in the array @@ -4110,7 +4123,7 @@ A command that exits with a zero value. .Pp .It Xo .Ic typeset -.Oo Op Ic +\-alprtUux +.Oo Op Ic +\-alpnrtUux .Op Fl L Ns Op Ar n .Op Fl R Ns Op Ar n .Op Fl Z Ns Op Ar n @@ -4191,6 +4204,18 @@ All upper case characters in values are converted to lower case. when used with the .Fl i option.) +.It Fl n +Create a bound variable (name reference): any access to the variable +.Ar name +will access the variable +.Ar value +in the current scope (this is different from +.At +.Nm ksh93 ! ) +instead. +This can be used by functions to access variables whose names are +passed as parametres, instead of using +.Ic eval . .It Fl p Print complete .Ic typeset diff --git a/sh.h b/sh.h index 6980b4c..260c5bf 100644 --- a/sh.h +++ b/sh.h @@ -134,7 +134,7 @@ #endif #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.337 2009/08/30 21:02:01 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.338 2009/09/06 17:42:14 tg Exp $"); #endif #define MKSH_VERSION "R39 2009/08/30" @@ -906,6 +906,7 @@ struct tbl { /* table item */ #define EXPRINEVAL BIT(23) /* contents currently being evaluated */ #define EXPRLVALUE BIT(24) /* useable as lvalue (temp flag) */ #define AINDEX BIT(25) /* array index >0 = ua.index filled in */ +#define ASSOC BIT(26) /* ARRAY ? associative : reference */ /* flag bits used for taliases/builtins/aliases/keywords/functions */ #define KEEPASN BIT(8) /* keep command assignments (eg, var=x cmd) */ #define FINUSE BIT(9) /* function being executed */ @@ -919,6 +920,9 @@ struct tbl { /* table item */ #define USERATTRIB (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\ LCASEV|UCASEV_AL|INT_U|INT_L) +#define arrayindex(vp) ((unsigned long)((vp)->flag & AINDEX ? \ + (vp)->ua.index : 0)) + /* command types */ #define CNONE 0 /* undefined */ #define CSHELL 1 /* built-in */ diff --git a/var.c b/var.c index 6d6649b..96b0503 100644 --- a/var.c +++ b/var.c @@ -22,7 +22,7 @@ #include "sh.h" -__RCSID("$MirOS: src/bin/mksh/var.c,v 1.86 2009/08/28 22:44:47 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/var.c,v 1.87 2009/09/06 17:42:15 tg Exp $"); /* * Variables @@ -47,6 +47,8 @@ static const char *array_index_calc(const char *, bool *, uint32_t *); static int rnd_get(void); static void rnd_set(unsigned long); +uint8_t set_refflag = 0; + /* * create a new block for function calls and simple commands * assume caller has allocated and set up e->loc @@ -135,7 +137,7 @@ initvar(void) } /* Used to calculate an array index for global()/local(). Sets *arrayp to - * non-zero if this is an array, sets *valp to the array index, returns + * true if this is an array, sets *valp to the array index, returns * the basename of the array. */ static const char * @@ -143,9 +145,39 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp) { const char *p; int len; + char *ap = NULL; *arrayp = false; + redo_from_ref: p = skip_varname(n, false); + if (!set_refflag && (p != n) && ksh_isalphx(n[0])) { + struct block *l = e->loc; + struct tbl *vp; + char *vn; + uint32_t h; + + strndupx(vn, n, p - n, ATEMP); + h = hash(vn); + /* check if this is a reference */ + for (l = e->loc; ; l = l->next) { + if ((vp = ktsearch(&l->vars, vn, h)) != NULL) + break; + if (l->next == NULL) + break; + } + afree(vn, ATEMP); + if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) == + (DEFINED|ASSOC)) { + char *cp; + + /* gotcha! */ + cp = shf_smprintf("%s%s", str_val(vp), p); + afree(ap, ATEMP); + n = ap = cp; + goto redo_from_ref; + } + } + if (p != n && *p == '[' && (len = array_ref_len(p))) { char *sub, *tmp; mksh_ari_t rval; @@ -646,6 +678,9 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) if (*val == '[') { int len; + if (set_refflag) + errorf("%s: reference variable cannot be an array", + var); len = array_ref_len(val); if (len == 0) return (NULL); @@ -680,6 +715,26 @@ typeset(const char *var, Tflag set, Tflag clr, int field, int base) vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) : global(tvar); + if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC) + vp->flag &= ~ASSOC; + else if (set_refflag == 1) { + if (vp->flag & ARRAY) { + struct tbl *a, *tmp; + + /* Free up entire array */ + for (a = vp->u.array; a; ) { + tmp = a; + a = a->u.array; + if (tmp->flag & ALLOC) + afree(tmp->val.s, tmp->areap); + afree(tmp, tmp->areap); + } + vp->u.array = NULL; + vp->flag &= ~ARRAY; + } + vp->flag |= ASSOC; + } + set &= ~(LOCAL|LOCAL_COPY); vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp; @@ -803,8 +858,8 @@ unset(struct tbl *vp, int array_ref) } /* return a pointer to the first char past a legal variable name (returns the - * argument if there is no legal name, returns * a pointer to the terminating - * null if whole string is legal). + * argument if there is no legal name, returns a pointer to the terminating + * NUL if whole string is legal). */ const char * skip_varname(const char *s, int aok) @@ -1240,7 +1295,7 @@ arraysearch(struct tbl *vp, uint32_t val) struct tbl *prev, *curr, *new; size_t len; - vp->flag |= ARRAY|DEFINED; + vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC; /* The table entry is always [0] */ if (val == 0) return (vp);