implement $KSH_MATCH and, to make it usable, ${foo@/bar/baz};

add a real-life example (for slagtc’s programmable tab completion)
to the manpage
This commit is contained in:
tg 2016-08-01 21:38:07 +00:00
parent 8f135b3904
commit 757e25fb21
8 changed files with 197 additions and 34 deletions

41
check.t
View File

@ -1,4 +1,4 @@
# $MirOS: src/bin/mksh/check.t,v 1.747 2016/08/01 21:29:05 tg Exp $ # $MirOS: src/bin/mksh/check.t,v 1.748 2016/08/01 21:37:59 tg Exp $
# -*- mode: sh -*- # -*- mode: sh -*-
#- #-
# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
@ -30,7 +30,7 @@
# (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date # (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
expected-stdout: expected-stdout:
@(#)MIRBSD KSH R53 2016/07/28 @(#)MIRBSD KSH R53 2016/08/01
description: description:
Check version of shell. Check version of shell.
stdin: stdin:
@ -39,7 +39,7 @@ name: KSH_VERSION
category: shell:legacy-no category: shell:legacy-no
--- ---
expected-stdout: expected-stdout:
@(#)LEGACY KSH R53 2016/07/28 @(#)LEGACY KSH R53 2016/08/01
description: description:
Check version of legacy shell. Check version of legacy shell.
stdin: stdin:
@ -1791,6 +1791,41 @@ stdin:
expected-stdout: expected-stdout:
1 . 1 .
--- ---
name: expand-slashes-1
description:
Check that side effects in substring replacement are handled correctly
stdin:
foo=n1n1n1n2n3
i=2
n=1
echo 1 ${foo//n$((n++))/[$((++i))]} .
echo 2 $n , $i .
expected-stdout:
1 [3][3][3]n2n3 .
2 2 , 3 .
---
name: expand-slashes-2
description:
Check that side effects in substring replacement are handled correctly
stdin:
foo=n1n1n1n2n3
i=2
n=1
echo 1 ${foo@/n$((n++))/[$((++i))]} .
echo 2 $n , $i .
expected-stdout:
1 [3]n1n1[4][5] .
2 5 , 5 .
---
name: expand-slashes-3
description:
Check that we can access the replaced string
stdin:
foo=n1n1n1n2n3
echo 1 ${foo@/n[12]/[$KSH_MATCH]} .
expected-stdout:
1 [n1][n1][n1][n2]n3 .
---
name: eglob-bad-1 name: eglob-bad-1
description: description:
Check that globbing isn't done when glob has syntax error Check that globbing isn't done when glob has syntax error

65
eval.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.191 2016/07/25 00:04:41 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/eval.c,v 1.192 2016/08/01 21:38:01 tg Exp $");
/* /*
* string expansion * string expansion
@ -186,7 +186,7 @@ evalonestr(const char *cp, int f)
rv = (char *) *XPptrv(w); rv = (char *) *XPptrv(w);
break; break;
default: default:
rv = evalstr(cp, f&~DOGLOB); rv = evalstr(cp, f & ~DOGLOB);
break; break;
} }
XPfree(w); XPfree(w);
@ -478,12 +478,14 @@ expand(
strndupx(x.str, beg, num, ATEMP); strndupx(x.str, beg, num, ATEMP);
goto do_CSUBST; goto do_CSUBST;
} }
case 0x100 | '/':
case '/': { case '/': {
char *s, *p, *d, *sbeg, *end; char *s, *p, *d, *sbeg, *end;
char *pat, *rrep; char *pat = NULL, *rrep = null;
char fpat = 0, *tpat1, *tpat2; char fpat = 0, *tpat1, *tpat2;
char *ws, *wpat, *wrep;
s = wdcopy(sp, ATEMP); s = ws = wdcopy(sp, ATEMP);
p = s + (wdscan(sp, ADELIM) - sp); p = s + (wdscan(sp, ADELIM) - sp);
d = s + (wdscan(sp, CSUBST) - sp); d = s + (wdscan(sp, CSUBST) - sp);
p[-2] = EOS; p[-2] = EOS;
@ -492,16 +494,24 @@ expand(
else else
d[-2] = EOS; d[-2] = EOS;
sp += (d ? d : p) - s - 1; sp += (d ? d : p) - s - 1;
if (!(stype & 0x80) && if (!(stype & 0x180) &&
s[0] == CHAR && s[0] == CHAR &&
(s[1] == '#' || s[1] == '%')) (s[1] == '#' || s[1] == '%'))
fpat = s[1]; fpat = s[1];
pat = evalstr(s + (fpat ? 2 : 0), wpat = s + (fpat ? 2 : 0);
DOTILDE | DOSCALAR | DOPAT); wrep = d ? p : NULL;
rrep = d ? evalstr(p, if (!(stype & 0x100)) {
DOTILDE | DOSCALAR) : null; rrep = wrep ? evalstr(wrep,
afree(s, ATEMP); DOTILDE | DOSCALAR) :
null;
}
/* prepare string on which to work */
strdupx(s, str_val(st->var), ATEMP);
sbeg = s;
again_search:
pat = evalstr(wpat,
DOTILDE | DOSCALAR | DOPAT);
/* check for special cases */ /* check for special cases */
if (!*pat && !fpat) { if (!*pat && !fpat) {
/* /*
@ -510,19 +520,15 @@ expand(
*/ */
goto no_repl; goto no_repl;
} }
if ((stype & 0x80) && if ((stype & 0x180) &&
gmatchx(null, pat, false)) { gmatchx(null, pat, false)) {
/* /*
* pattern matches empty * pattern matches empty
* string => don't loop * string => don't loop
*/ */
stype &= ~0x80; stype &= ~0x180;
} }
/* prepare string on which to work */
strdupx(s, str_val(st->var), ATEMP);
sbeg = s;
/* first see if we have any match at all */ /* first see if we have any match at all */
if (fpat == '#') { if (fpat == '#') {
/* anchor at the beginning */ /* anchor at the beginning */
@ -567,13 +573,27 @@ expand(
break; break;
p--; p--;
} }
strndupx(end, sbeg, p - sbeg, ATEMP);
record_match(end);
afree(end, ATEMP);
if (stype & 0x100) {
if (rrep != null)
afree(rrep, ATEMP);
rrep = wrep ? evalstr(wrep,
DOTILDE | DOSCALAR) :
null;
}
strndupx(end, s, sbeg - s, ATEMP); strndupx(end, s, sbeg - s, ATEMP);
d = shf_smprintf(Tf_sss, end, rrep, p); d = shf_smprintf(Tf_sss, end, rrep, p);
afree(end, ATEMP); afree(end, ATEMP);
sbeg = d + (sbeg - s) + strlen(rrep); sbeg = d + (sbeg - s) + strlen(rrep);
afree(s, ATEMP); afree(s, ATEMP);
s = d; s = d;
if (stype & 0x80) if (stype & 0x100) {
afree(tpat1, ATEMP);
afree(pat, ATEMP);
goto again_search;
} else if (stype & 0x80)
goto again_repl; goto again_repl;
end_repl: end_repl:
afree(tpat1, ATEMP); afree(tpat1, ATEMP);
@ -582,6 +602,7 @@ expand(
afree(pat, ATEMP); afree(pat, ATEMP);
if (rrep != null) if (rrep != null)
afree(rrep, ATEMP); afree(rrep, ATEMP);
afree(ws, ATEMP);
goto do_CSUBST; goto do_CSUBST;
} }
case '#': case '#':
@ -733,6 +754,7 @@ expand(
debunk(dp, dp, strlen(dp) + 1)); debunk(dp, dp, strlen(dp) + 1));
break; break;
case '0': case '0':
case 0x100 | '/':
case '/': case '/':
case 0x100 | '#': case 0x100 | '#':
case 0x100 | 'Q': case 0x100 | 'Q':
@ -1207,6 +1229,7 @@ varsub(Expand *xp, const char *sp, const char *word,
case '#': case '#':
case '?': case '?':
case '0': case '0':
case 0x100 | '/':
case '/': case '/':
case 0x100 | '#': case 0x100 | '#':
case 0x100 | 'Q': case 0x100 | 'Q':
@ -1237,6 +1260,7 @@ varsub(Expand *xp, const char *sp, const char *word,
case '#': case '#':
case '?': case '?':
case '0': case '0':
case 0x100 | '/':
case '/': case '/':
case 0x100 | '#': case 0x100 | '#':
case 0x100 | 'Q': case 0x100 | 'Q':
@ -1277,13 +1301,13 @@ varsub(Expand *xp, const char *sp, const char *word,
c = stype & 0x7F; c = stype & 0x7F;
/* test the compiler's code generator */ /* test the compiler's code generator */
if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' || if (((stype < 0x100) && (ctype(c, C_SUBOP2) ||
(((stype & 0x80) ? *xp->str == '\0' : xp->str == null) && (((stype & 0x80) ? *xp->str == '\0' : xp->str == null) &&
(state != XARG || (ifs0 || xp->split ? (state != XARG || (ifs0 || xp->split ?
(xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ? (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
c == '=' || c == '-' || c == '?' : c == '+'))) || c == '=' || c == '-' || c == '?' : c == '+'))) ||
stype == (0x80 | '0') || stype == (0x100 | '#') || stype == (0x80 | '0') || stype == (0x100 | '#') ||
stype == (0x100 | 'Q')) stype == (0x100 | 'Q') || (stype & 0x7F) == '/')
/* expand word instead of variable value */ /* expand word instead of variable value */
state = XBASE; state = XBASE;
if (Flag(FNOUNSET) && xp->str == null && !zero_ok && if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
@ -1420,6 +1444,7 @@ trimsub(char *str, char *pat, int how)
for (p = str; p <= end; p += utf_ptradj(p)) { for (p = str; p <= end; p += utf_ptradj(p)) {
c = *p; *p = '\0'; c = *p; *p = '\0';
if (gmatchx(str, pat, false)) { if (gmatchx(str, pat, false)) {
record_match(str);
*p = c; *p = c;
return (p); return (p);
} }
@ -1431,6 +1456,7 @@ trimsub(char *str, char *pat, int how)
for (p = end; p >= str; p--) { for (p = end; p >= str; p--) {
c = *p; *p = '\0'; c = *p; *p = '\0';
if (gmatchx(str, pat, false)) { if (gmatchx(str, pat, false)) {
record_match(str);
*p = c; *p = c;
return (p); return (p);
} }
@ -1458,6 +1484,7 @@ trimsub(char *str, char *pat, int how)
for (p = str; p <= end; p++) for (p = str; p <= end; p++)
if (gmatchx(p, pat, false)) { if (gmatchx(p, pat, false)) {
trimsub_match: trimsub_match:
record_match(p);
strndupx(end, str, p - str, ATEMP); strndupx(end, str, p - str, ATEMP);
return (end); return (end);
} }

3
exec.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.178 2016/07/25 00:04:41 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/exec.c,v 1.179 2016/08/01 21:38:02 tg Exp $");
#ifndef MKSH_DEFAULT_EXECSHELL #ifndef MKSH_DEFAULT_EXECSHELL
#define MKSH_DEFAULT_EXECSHELL MKSH_UNIXROOT "/bin/sh" #define MKSH_DEFAULT_EXECSHELL MKSH_UNIXROOT "/bin/sh"
@ -390,6 +390,7 @@ execute(struct op * volatile t,
for (ap = (const char **)t->vars; *ap; ap++) { for (ap = (const char **)t->vars; *ap; ap++) {
if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) && if (i || ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
gmatchx(ccp, s, false))) { gmatchx(ccp, s, false))) {
record_match(ccp);
rv = execute(t->left, flags & XERROK, rv = execute(t->left, flags & XERROK,
xerrok); xerrok);
i = 0; i = 0;

16
funcs.c
View File

@ -38,7 +38,7 @@
#endif #endif
#endif #endif
__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.304 2016/08/01 14:23:24 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.305 2016/08/01 21:38:02 tg Exp $");
#if HAVE_KILLPG #if HAVE_KILLPG
/* /*
@ -3202,14 +3202,20 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
/* = */ /* = */
case TO_STEQL: case TO_STEQL:
if (te->flags & TEF_DBRACKET) if (te->flags & TEF_DBRACKET) {
return (gmatchx(opnd1, opnd2, false)); if ((i = gmatchx(opnd1, opnd2, false)))
record_match(opnd1);
return (i);
}
return (strcmp(opnd1, opnd2) == 0); return (strcmp(opnd1, opnd2) == 0);
/* != */ /* != */
case TO_STNEQ: case TO_STNEQ:
if (te->flags & TEF_DBRACKET) if (te->flags & TEF_DBRACKET) {
return (!gmatchx(opnd1, opnd2, false)); if ((i = gmatchx(opnd1, opnd2, false)))
record_match(opnd1);
return (!i);
}
return (strcmp(opnd1, opnd2) != 0); return (strcmp(opnd1, opnd2) != 0);
/* < */ /* < */

13
lex.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.227 2016/07/25 21:05:21 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.228 2016/08/01 21:38:03 tg Exp $");
/* /*
* states while lexing word * states while lexing word
@ -462,10 +462,12 @@ yylex(int cf)
break; break;
} }
} else if (c == '/') { } else if (c == '/') {
c2 = ADELIM;
parse_adelim_slash:
*wp++ = CHAR; *wp++ = CHAR;
*wp++ = c; *wp++ = c;
if ((c = getsc()) == '/') { if ((c = getsc()) == '/') {
*wp++ = ADELIM; *wp++ = c2;
*wp++ = c; *wp++ = c;
} else } else
ungetsc(c); ungetsc(c);
@ -475,6 +477,13 @@ yylex(int cf)
statep->ls_adelim.num = 1; statep->ls_adelim.num = 1;
statep->nparen = 0; statep->nparen = 0;
break; break;
} else if (c == '@') {
c2 = getsc();
ungetsc(c2);
if (c2 == '/') {
c2 = CHAR;
goto parse_adelim_slash;
}
} }
/* /*
* If this is a trim operation, * If this is a trim operation,

74
mksh.1
View File

@ -1,4 +1,4 @@
.\" $MirOS: src/bin/mksh/mksh.1,v 1.408 2016/08/01 19:40:00 tg Exp $ .\" $MirOS: src/bin/mksh/mksh.1,v 1.409 2016/08/01 21:38:04 tg Exp $
.\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $ .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
.\"- .\"-
.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@ -1682,6 +1682,25 @@ Inefficiently implemented, may be slow.
.Pp .Pp
.Sm off .Sm off
.It Xo .It Xo
.Pf ${ Ar name
.Pf @/ Ar pattern / Ar string No }
.Xc
.Sm on
The same as
.Sm off
.Xo
.Pf ${ Ar name
.Pf // Ar pattern / Ar string No } ,
.Xc
.Sm on
except that both
.Ar pattern
and
.Ar string
are expanded anew for each iteration.
.Pp
.Sm off
.It Xo
.Pf ${ Ar name : Ns Ar pos .Pf ${ Ar name : Ns Ar pos
.Pf : Ns Ar len Ns } .Pf : Ns Ar len Ns }
.Xc .Xc
@ -1933,6 +1952,54 @@ The effective group id of the shell.
The real group id of the shell. The real group id of the shell.
.It Ev KSHUID .It Ev KSHUID
The real user id of the shell. The real user id of the shell.
.It Ev KSH_MATCH
The last matched string.
In a future version, this will be an indexed array,
with indexes 1 and up capturing matching groups.
Set by string comparisons (== and !=) in double-bracket test
expressions when a match is found (when != returns false), by
.Ic case
when a match is encountered, and by the substitution operations
.Sm off
.Xo
.Pf ${ Ar x
.Pf # Ar pat No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf ## Ar pat No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf % Ar pat No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf %% Ar pat No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf / Ar pat / Ar rpl No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf /# Ar pat / Ar rpl No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf /% Ar pat / Ar rpl No } ,
.Xc
.Xo
.Pf ${ Ar x
.Pf // Ar pat / Ar rpl No } ,
.Xc
and
.Xo
.Pf ${ Ar x
.Pf @/ Ar pat / Ar rpl No } .
.Xc
.Sm on
See the end of the Emacs editing mode documentation for an example.
.It Ev KSH_VERSION .It Ev KSH_VERSION
The name and version of the shell (read-only). The name and version of the shell (read-only).
See also the version commands in See also the version commands in
@ -5794,6 +5861,11 @@ Immediately after a
.Ic yank , .Ic yank ,
replaces the inserted text string with the next previously killed text string. replaces the inserted text string with the next previously killed text string.
.El .El
.Pp
The tab completion escapes characters the same way as the following code:
.Bd -literal
print \-nr \-\- "${x@/[\e"\-\e$&\-*:\-?[\e\e\e\`{\-\e}${IFS\-$\*(aq \et\en\*(aq}]/\e\e$KSH_MATCH}"
.Ed
.Ss Vi editing mode .Ss Vi editing mode
.Em Note: .Em Note:
The vi command-line editing mode is orphaned, yet still functional. The vi command-line editing mode is orphaned, yet still functional.

5
sh.h
View File

@ -175,9 +175,9 @@
#endif #endif
#ifdef EXTERN #ifdef EXTERN
__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.782 2016/08/01 20:23:15 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.783 2016/08/01 21:38:05 tg Exp $");
#endif #endif
#define MKSH_VERSION "R53 2016/07/28" #define MKSH_VERSION "R53 2016/08/01"
/* arithmetic types: C implementation */ /* arithmetic types: C implementation */
#if !HAVE_CAN_INTTYPES #if !HAVE_CAN_INTTYPES
@ -2319,6 +2319,7 @@ uint32_t chvt_rndsetup(const void *, size_t) MKSH_A_PURE;
mksh_ari_t rndget(void); mksh_ari_t rndget(void);
void rndset(unsigned long); void rndset(unsigned long);
void rndpush(const void *); void rndpush(const void *);
void record_match(const char *);
enum Test_op { enum Test_op {
/* non-operator */ /* non-operator */

14
var.c
View File

@ -28,7 +28,7 @@
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
__RCSID("$MirOS: src/bin/mksh/var.c,v 1.206 2016/07/25 21:02:13 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/var.c,v 1.207 2016/08/01 21:38:07 tg Exp $");
/*- /*-
* Variables * Variables
@ -1739,3 +1739,15 @@ rndpush(const void *s)
BAFHUpdateOctet_reg(h, 0); BAFHUpdateOctet_reg(h, 0);
qh_state = h; qh_state = h;
} }
/* record last glob match */
void
record_match(const char *istr)
{
struct tbl *vp;
vp = local("KSH_MATCH", false);
unset(vp, 1);
vp->flag = DEFINED | RDONLY;
setstr(vp, istr, 0x4);
}