From 727a69dc1ddae01bac99c3a802980d673621e8b2 Mon Sep 17 00:00:00 2001 From: kre Date: Wed, 7 Jun 2017 05:08:32 +0000 Subject: [PATCH] A better LINENO implementation. This version deletes (well, #if 0's out) the LINENO hack, and uses the LINENO var for both ${LINENO} and $((LINENO)). (Code to invert the LINENO hack when required, like when de-compiling the execution tree to provide the "jobs" command strings, is still included, that can be deleted when the LINENO hack is completely removed - look for refs to VSLINENO throughout the code. The var funclinno in parser.c can also be removed, it is used only for the LINENO hack.) This version produces accurate results: $((LINENO)) was made as accurate as the LINENO hack made ${LINENO} which is very good. That's why the LINENO hack is not yet completely removed, so it can be easily re-enabled. If you can tell the difference when it is in use, or not in use, then something has broken (or I managed to miss a case somewhere.) The way that LINENO works is documented in its own (new) section in the man page, so nothing more about that, or the new options, etc, here. This version introduces the possibility of having a "reference" function associated with a variable, which gets called whenever the value of the variable is required (that's what implements LINENO). There is just one function pointer however, so any particular variable gets at most one of the set function (as used for PATH, etc) or the reference function. The VFUNCREF bit in the var flags indicates which func the variable in question uses (if any - the func ptr, as before, can be NULL). I would not call the results of this perfect yet, but it is close. --- bin/sh/arith_token.c | 6 +- bin/sh/arith_tokens.h | 4 +- bin/sh/arithmetic.c | 39 +++++++-- bin/sh/arithmetic.h | 4 +- bin/sh/eval.c | 42 ++++++++-- bin/sh/exec.c | 14 +++- bin/sh/exec.h | 8 +- bin/sh/expand.c | 118 +++++++++++++++++++++----- bin/sh/expand.h | 3 +- bin/sh/input.c | 9 +- bin/sh/jobs.c | 27 +++--- bin/sh/nodetypes | 4 +- bin/sh/option.list | 3 +- bin/sh/parser.c | 155 ++++++++++++++++++++++++---------- bin/sh/parser.h | 10 ++- bin/sh/sh.1 | 190 ++++++++++++++++++++++++++++++++++++++---- bin/sh/show.c | 29 +++++-- bin/sh/syntax.c | 7 +- bin/sh/syntax.h | 4 +- bin/sh/var.c | 94 ++++++++++++++++----- bin/sh/var.h | 17 +++- 21 files changed, 629 insertions(+), 158 deletions(-) diff --git a/bin/sh/arith_token.c b/bin/sh/arith_token.c index 80fbed3e58c7..bc553675723e 100644 --- a/bin/sh/arith_token.c +++ b/bin/sh/arith_token.c @@ -1,4 +1,4 @@ -/* $NetBSD: arith_token.c,v 1.4 2017/05/29 22:54:07 kre Exp $ */ +/* $NetBSD: arith_token.c,v 1.5 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 2002 @@ -39,7 +39,7 @@ #include #ifndef lint -__RCSID("$NetBSD: arith_token.c,v 1.4 2017/05/29 22:54:07 kre Exp $"); +__RCSID("$NetBSD: arith_token.c,v 1.5 2017/06/07 05:08:32 kre Exp $"); #endif /* not lint */ #include @@ -98,6 +98,7 @@ arith_token(void) * and nothing else does. They continue for the * longest unbroken sequence of alphanumerics ( + _ ) */ + arith_var_lno = arith_lno; p = buf; while (buf++, is_in_name(*buf)) ; @@ -115,6 +116,7 @@ arith_token(void) * operator, white space, or an error. */ case '\n': + arith_lno++; VTRACE(DBG_ARITH, ("Arith: newline\n")); /* FALLTHROUGH */ case ' ': diff --git a/bin/sh/arith_tokens.h b/bin/sh/arith_tokens.h index 838877713e60..f8a7f77c6951 100644 --- a/bin/sh/arith_tokens.h +++ b/bin/sh/arith_tokens.h @@ -1,4 +1,4 @@ -/* $NetBSD: arith_tokens.h,v 1.1 2017/03/20 11:26:07 kre Exp $ */ +/* $NetBSD: arith_tokens.h,v 1.2 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1993 @@ -112,4 +112,6 @@ union a_token_val { extern union a_token_val a_t_val; +extern int arith_lno, arith_var_lno; + int arith_token(void); diff --git a/bin/sh/arithmetic.c b/bin/sh/arithmetic.c index fb5da20d47a8..3a5430fa7902 100644 --- a/bin/sh/arithmetic.c +++ b/bin/sh/arithmetic.c @@ -1,4 +1,4 @@ -/* $NetBSD: arithmetic.c,v 1.2 2017/05/29 22:54:07 kre Exp $ */ +/* $NetBSD: arithmetic.c,v 1.3 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1993 @@ -39,7 +39,7 @@ #include #ifndef lint -__RCSID("$NetBSD: arithmetic.c,v 1.2 2017/05/29 22:54:07 kre Exp $"); +__RCSID("$NetBSD: arithmetic.c,v 1.3 2017/06/07 05:08:32 kre Exp $"); #endif /* not lint */ #include @@ -47,6 +47,7 @@ __RCSID("$NetBSD: arithmetic.c,v 1.2 2017/05/29 22:54:07 kre Exp $"); #include #include #include +#include #include "shell.h" #include "arithmetic.h" @@ -71,6 +72,8 @@ union a_token_val a_t_val; static int last_token; +int arith_lno, arith_var_lno; + #define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { @@ -109,8 +112,15 @@ arith_lookupvarint(char *varname) const char *str; char *p; intmax_t result; + const int oln = line_number; + VTRACE(DBG_ARITH, ("Arith var lookup(\"%s\") with lno=%d\n", varname, + arith_var_lno)); + + line_number = arith_var_lno; str = lookupvar(varname); + line_number = oln; + if (uflag && str == NULL) arith_err("variable not set"); if (str == NULL || *str == '\0') @@ -365,14 +375,33 @@ assignment(int var, int noeval) } intmax_t -arith(const char *s) +arith(const char *s, int lno) { struct stackmark smark; intmax_t result; + const char *p; + int nls = 0; setstackmark(&smark); - CTRACE(DBG_ARITH, ("Arith(\"%s\")\n", s)); + arith_lno = lno; + + CTRACE(DBG_ARITH, ("Arith(\"%s\", %d) @%d\n", s, lno, arith_lno)); + + /* check if it is possible we might reference LINENO */ + p = s; + while ((p = strchr(p, 'L')) != NULL) { + if (p[1] == 'I' && p[2] == 'N') { + /* if it is possible, we need to correct airth_lno */ + p = s; + while ((p = strchr(p, '\n')) != NULL) + nls++, p++; + VTRACE(DBG_ARITH, ("Arith found %d newlines\n", nls)); + arith_lno -= nls; + break; + } + p++; + } arith_buf = arith_startbuf = s; @@ -420,7 +449,7 @@ expcmd(int argc, char **argv) } else p = ""; - i = arith(p); + i = arith(p, line_number); out1fmt(ARITH_FORMAT_STR "\n", i); return !i; diff --git a/bin/sh/arithmetic.h b/bin/sh/arithmetic.h index 18f1b309e3fd..51808d373c3d 100644 --- a/bin/sh/arithmetic.h +++ b/bin/sh/arithmetic.h @@ -1,4 +1,4 @@ -/* $NetBSD: arithmetic.h,v 1.1 2017/03/20 11:26:07 kre Exp $ */ +/* $NetBSD: arithmetic.h,v 1.2 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1995 @@ -39,4 +39,4 @@ #define ARITH_FORMAT_STR "%" PRIdMAX -intmax_t arith(const char *); +intmax_t arith(const char *, int); diff --git a/bin/sh/eval.c b/bin/sh/eval.c index 04fee1f71df6..cbc3bf56fa85 100644 --- a/bin/sh/eval.c +++ b/bin/sh/eval.c @@ -1,4 +1,4 @@ -/* $NetBSD: eval.c,v 1.142 2017/06/07 04:44:17 kre Exp $ */ +/* $NetBSD: eval.c,v 1.143 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; #else -__RCSID("$NetBSD: eval.c,v 1.142 2017/06/07 04:44:17 kre Exp $"); +__RCSID("$NetBSD: eval.c,v 1.143 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -217,7 +217,7 @@ evalstring(char *s, int flag) struct stackmark smark; setstackmark(&smark); - setinputstring(s, 1, atoi(line_num.text + line_num.name_len + 1)); + setinputstring(s, 1, line_number); while ((n = parsecmd(0)) != NEOF) { TRACE(("evalstring: "); showtree(n)); @@ -317,7 +317,9 @@ evaltree(union node *n, int flags) evalcase(n, sflags); break; case NDEFUN: - defun(n->narg.text, n->narg.next); + CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n", n->narg.text, + n->narg.lineno, fnline1 ? " LINENO=1" : "")); + defun(n->narg.text, n->narg.next, n->narg.lineno); exitstatus = 0; break; case NNOT: @@ -473,9 +475,11 @@ evalcase(union node *n, int flags) setstackmark(&smark); arglist.lastp = &arglist.list; + line_number = n->ncase.lineno; expandarg(n->ncase.expr, &arglist, EXP_TILDE); for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + line_number = patp->narg.lineno; if (casematch(patp, arglist.list->text)) { while (cp != NULL && evalskip == 0 && nflag == 0) { @@ -483,6 +487,7 @@ evalcase(union node *n, int flags) ncp = cp->nclist.next; else ncp = NULL; + line_number = cp->nclist.lineno; evaltree(cp->nclist.body, ncp ? (flags | EV_MORE) : flags); status = exitstatus; @@ -766,21 +771,25 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd) char * volatile lastarg; const char * volatile path = pathval(); volatile int temp_path; + const int savefuncline = funclinebase; + const int savefuncabs = funclineabs; vforked = 0; /* First expand the arguments. */ - TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + TRACE(("evalcommand(%p, %d) called\n", cmd, flags)); setstackmark(&smark); back_exitstatus = 0; if (cmd != NULL) - set_lineno(cmd->ncmd.lineno); + line_number = cmd->ncmd.lineno; arglist.lastp = &arglist.list; varflag = 1; /* Expand arguments, ignoring the initial 'name=value' ones */ for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { char *p = argp->narg.text; + + line_number = argp->narg.lineno; if (varflag && is_name(*p)) { do { p++; @@ -799,6 +808,8 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd) varlist.lastp = &varlist.list; for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { char *p = argp->narg.text; + + line_number = argp->narg.lineno; if (!is_name(*p)) break; do @@ -879,6 +890,7 @@ evalcommand(union node *cmd, int flgs, struct backcmd *backcmd) do { int argsused, use_syspath; + find_command(argv[0], &cmdentry, cmd_flags, path); if (cmdentry.cmdtype == CMDUNKNOWN) { exitstatus = 127; @@ -1031,11 +1043,27 @@ normal_fork: } poplocalvars(); localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; handler = savehandler; longjmp(handler->loc, 1); } savehandler = handler; handler = &jmploc; + if (cmdentry.u.func) { + if (cmdentry.lno_frel) + funclinebase = cmdentry.lineno - 1; + else + funclinebase = 0; + funclineabs = cmdentry.lineno; + + VTRACE(DBG_EVAL, + ("function: node: %d '%s' # %d%s; funclinebase=%d\n", + cmdentry.u.func->type, + NODETYPENAME(cmdentry.u.func->type), + cmdentry.lineno, cmdentry.lno_frel?" (=1)":"", + funclinebase)); + } listmklocal(varlist.list, VEXPORT); /* stop shell blowing its stack */ if (++funcnest > 1000) @@ -1045,6 +1073,8 @@ normal_fork: INTOFF; poplocalvars(); localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; freeparam(&shellparam); shellparam = saveparam; handler = savehandler; diff --git a/bin/sh/exec.c b/bin/sh/exec.c index a18c332c40ae..51847b910f4a 100644 --- a/bin/sh/exec.c +++ b/bin/sh/exec.c @@ -1,4 +1,4 @@ -/* $NetBSD: exec.c,v 1.48 2017/06/04 20:27:14 kre Exp $ */ +/* $NetBSD: exec.c,v 1.49 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; #else -__RCSID("$NetBSD: exec.c,v 1.48 2017/06/04 20:27:14 kre Exp $"); +__RCSID("$NetBSD: exec.c,v 1.49 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -91,6 +91,8 @@ struct tblentry { union param param; /* definition of builtin function */ short cmdtype; /* index identifying command */ char rehash; /* if set, cd done since entry created */ + char fn_ln1; /* for functions, LINENO from 1 */ + int lineno; /* for functions abs LINENO of definition */ char cmdname[ARB]; /* name of command */ }; @@ -676,6 +678,8 @@ success: if (cmdp) { cmdp->rehash = 0; entry->cmdtype = cmdp->cmdtype; + entry->lineno = cmdp->lineno; + entry->lno_frel = cmdp->fn_ln1; entry->u = cmdp->param; } else entry->cmdtype = CMDUNKNOWN; @@ -971,6 +975,8 @@ addcmdentry(char *name, struct cmdentry *entry) freefunc(cmdp->param.func); } cmdp->cmdtype = entry->cmdtype; + cmdp->lineno = entry->lineno; + cmdp->fn_ln1 = entry->lno_frel; cmdp->param = entry->u; } INTON; @@ -982,12 +988,14 @@ addcmdentry(char *name, struct cmdentry *entry) */ void -defun(char *name, union node *func) +defun(char *name, union node *func, int lineno) { struct cmdentry entry; INTOFF; entry.cmdtype = CMDFUNCTION; + entry.lineno = lineno; + entry.lno_frel = fnline1; entry.u.func = copyfunc(func); addcmdentry(name, &entry); INTON; diff --git a/bin/sh/exec.h b/bin/sh/exec.h index 045601102428..019b8bf47e4e 100644 --- a/bin/sh/exec.h +++ b/bin/sh/exec.h @@ -1,4 +1,4 @@ -/* $NetBSD: exec.h,v 1.25 2017/06/04 20:27:14 kre Exp $ */ +/* $NetBSD: exec.h,v 1.26 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -43,7 +43,9 @@ struct cmdentry { - int cmdtype; + short cmdtype; + short lno_frel; /* for functions: Line numbers count from 1 */ + int lineno; /* for functions: Abs line number of defn */ union param { int index; int (*bltin)(int, char**); @@ -70,6 +72,6 @@ void hashcd(void); void changepath(const char *); void deletefuncs(void); void getcmdentry(char *, struct cmdentry *); -void defun(char *, union node *); +void defun(char *, union node *, int); int unsetfunc(char *); void hash_special_builtins(void); diff --git a/bin/sh/expand.c b/bin/sh/expand.c index b548da7ef77f..95689ad767ce 100644 --- a/bin/sh/expand.c +++ b/bin/sh/expand.c @@ -1,4 +1,4 @@ -/* $NetBSD: expand.c,v 1.112 2017/06/05 02:15:55 kre Exp $ */ +/* $NetBSD: expand.c,v 1.113 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; #else -__RCSID("$NetBSD: expand.c,v 1.112 2017/06/05 02:15:55 kre Exp $"); +__RCSID("$NetBSD: expand.c,v 1.113 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -119,6 +119,7 @@ STATIC int patmatch(const char *, const char *, int); STATIC char *cvtnum(int, char *); static int collate_range_cmp(wchar_t, wchar_t); STATIC void add_args(struct strlist *); +STATIC void rmescapes_nl(char *); /* * Expand shell variables and backquotes inside a here document. @@ -168,6 +169,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag) ifsfirst.next = NULL; ifslastp = NULL; argstr(arg->narg.text, flag); + line_number = arg->narg.lineno; if (arglist == NULL) { STACKSTRNUL(expdest); CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n", @@ -257,6 +259,11 @@ argstr(const char *p, int flag) STPUTC(c, expdest); ifs_split = 0; break; + case CTLNONL: + if (flag & EXP_NL) + STPUTC(c, expdest); + line_number++; + break; case CTLQUOTEEND: ifs_split = EXP_IFS_SPLIT; break; @@ -304,6 +311,8 @@ argstr(const char *p, int flag) } break; default: + if (c == '\n') + line_number++; STPUTC(c, expdest); if (flag & ifs_split && strchr(ifs, c) != NULL) { /* We need to get the output split here... */ @@ -336,6 +345,8 @@ exptilde(const char *p, int flag) case CTLENDARI: case CTLQUOTEMARK: return (startp); + case CTLNONL: + break; case ':': if (flag & EXP_VARTILDE) goto done; @@ -467,17 +478,18 @@ expari(const char *p, int flag) */ quoted = *p++ == '"'; begoff = expdest - stackblock(); - VTRACE(DBG_EXPAND, ("expari: '%c' \"%s\" begoff %d\n", p[-1],p,begoff)); - p = argstr(p, 0); /* expand $(( )) string */ + VTRACE(DBG_EXPAND, ("expari: '%c' \"%s\" begoff %d quotes %x\n", + p[-1],p,begoff, quotes)); + p = argstr(p, EXP_NL); /* expand $(( )) string */ STPUTC('\0', expdest); start = stackblock() + begoff; VTRACE(DBG_EXPAND, ("expari: argstr added: '%s' start: \"%.8s\"\n", ed, start)); removerecordregions(begoff); if (quotes) - rmescapes(start); /* should be a no-op */ + rmescapes_nl(start); /* convert CRTNONL back into \n's */ q = grabstackstr(expdest); - result = arith(start); + result = arith(start, line_number); VTRACE(DBG_EXPAND, ("expari: after arith: result=%jd '%s' q@'%.3s'\n", result, ed, q)); ungrabstackstr(q, expdest); @@ -782,9 +794,14 @@ evalvar(const char *p, int flag) again: /* jump here after setting a variable with ${var=text} */ if (varflags & VSLINENO) { - set = 1; - special = p - var; - val = NULL; + if (line_num.flags & VUNSET) { + set = 0; + val = NULL; + } else { + set = 1; + special = p - var; + val = NULL; + } } else if (special) { set = varisset(var, varflags & VSNUL); val = NULL; @@ -951,6 +968,8 @@ evalvar(const char *p, int flag) for (;;) { if ((c = *p++) == CTLESC) p++; + else if (c == CTLNONL) + ; else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { if (set) argbackq = argbackq->next; @@ -1181,6 +1200,10 @@ ifsbreakup(char *string, struct arglist *arglist) while (p < string + ifsp->endoff) { had_param_ch = 1; q = p; + if (*p == CTLNONL) { + p++; + continue; + } if (*p == CTLESC) p++; if (ifsp->inquotes) { @@ -1220,6 +1243,8 @@ ifsbreakup(char *string, struct arglist *arglist) /* Ignore further trailing IFS whitespace */ for (; p < string + ifsp->endoff; p++) { q = p; + if (*p == CTLNONL) + continue; if (*p == CTLESC) p++; if (strchr(ifs, *p) == NULL) { @@ -1366,7 +1391,7 @@ expmeta(char *enddir, char *name) if (*q == '!') q++; for (;;) { - while (*q == CTLQUOTEMARK) + while (*q == CTLQUOTEMARK || *q == CTLNONL) q++; if (*q == CTLESC) q++; @@ -1381,7 +1406,7 @@ expmeta(char *enddir, char *name) metaflag = 1; } else if (*p == '\0') break; - else if (*p == CTLQUOTEMARK) + else if (*p == CTLQUOTEMARK || *p == CTLNONL) continue; else if (*p == CTLESC) p++; @@ -1395,7 +1420,7 @@ expmeta(char *enddir, char *name) if (enddir != expdir) metaflag++; for (p = name ; ; p++) { - if (*p == CTLQUOTEMARK) + if (*p == CTLQUOTEMARK || *p == CTLNONL) continue; if (*p == CTLESC) p++; @@ -1411,7 +1436,7 @@ expmeta(char *enddir, char *name) if (start != name) { p = name; while (p < start) { - while (*p == CTLQUOTEMARK) + while (*p == CTLQUOTEMARK || *p == CTLNONL) p++; if (*p == CTLESC) p++; @@ -1438,7 +1463,7 @@ expmeta(char *enddir, char *name) } matchdot = 0; p = start; - while (*p == CTLQUOTEMARK) + while (*p == CTLQUOTEMARK || *p == CTLNONL) p++; if (*p == CTLESC) p++; @@ -1606,6 +1631,7 @@ patmatch(const char *pattern, const char *string, int squoted) goto backtrack; break; case CTLQUOTEMARK: + case CTLNONL: continue; case '?': if (squoted && *q == CTLESC) @@ -1617,7 +1643,7 @@ patmatch(const char *pattern, const char *string, int squoted) c = *p; while (c == CTLQUOTEMARK || c == '*') c = *++p; - if (c != CTLESC && c != CTLQUOTEMARK && + if (c != CTLESC && c != CTLQUOTEMARK && c != CTLNONL && c != '?' && c != '*' && c != '[') { while (*q != c) { if (squoted && *q == CTLESC && @@ -1648,7 +1674,7 @@ patmatch(const char *pattern, const char *string, int squoted) if (*endp == '!') endp++; for (;;) { - while (*endp == CTLQUOTEMARK) + while (*endp == CTLQUOTEMARK || *endp==CTLNONL) endp++; if (*endp == '\0') goto dft; /* no matching ] */ @@ -1670,7 +1696,7 @@ patmatch(const char *pattern, const char *string, int squoted) chr = (unsigned char)*q++; c = *p++; do { - if (c == CTLQUOTEMARK) + if (c == CTLQUOTEMARK || c == CTLNONL) continue; if (c == '\0') { p = savep, q = saveq; @@ -1729,7 +1755,7 @@ backtrack: /* - * Remove any CTLESC characters from a string. + * Remove any CTLESC or CTLNONL characters from a string. */ void @@ -1738,13 +1764,13 @@ rmescapes(char *str) char *p, *q; p = str; - while (*p != CTLESC && *p != CTLQUOTEMARK) { + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLNONL) { if (*p++ == '\0') return; } q = p; while (*p) { - if (*p == CTLQUOTEMARK) { + if (*p == CTLQUOTEMARK || *p == CTLNONL) { p++; continue; } @@ -1755,6 +1781,58 @@ rmescapes(char *str) *q = '\0'; } +/* + * and a special version for dealing with expressions to be parsed + * by the arithmetic evaluator. That needs to be able to count \n's + * even ones that were \newline elided \n's, so we have to put the + * latter back into the string - just being careful to put them only + * at a place where white space can reasonably occur in the string + * -- then the \n we insert will just be white space, and ignored + * for all purposes except line counting. + */ + +void +rmescapes_nl(char *str) +{ + char *p, *q; + int nls = 0, holdnl = 0, holdlast; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLNONL) { + if (*p++ == '\0') + return; + } + if (p > str) /* must reprocess char before stopper (if any) */ + --p; /* so we do not place a \n badly */ + q = p; + while (*p) { + if (*p == CTLQUOTEMARK) { + p++; + continue; + } + if (*p == CTLNONL) { + p++; + nls++; + continue; + } + if (*p == CTLESC) + p++; + + holdlast = holdnl; + holdnl = is_in_name(*p); /* letters, digits, _ */ + if (q == str || is_space(q[-1]) || (*p != '=' && q[-1] != *p)) { + if (nls > 0 && holdnl != holdlast) { + while (nls > 0) + *q++ = '\n', nls--; + } + } + *q++ = *p++; + } + while (--nls >= 0) + *q++ = '\n'; + *q = '\0'; +} + /* diff --git a/bin/sh/expand.h b/bin/sh/expand.h index 1946c7f41d3f..022b911207bd 100644 --- a/bin/sh/expand.h +++ b/bin/sh/expand.h @@ -1,4 +1,4 @@ -/* $NetBSD: expand.h,v 1.22 2017/06/03 10:31:16 kre Exp $ */ +/* $NetBSD: expand.h,v 1.23 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -58,6 +58,7 @@ struct arglist { #define EXP_IFS_SPLIT 0x20 /* need to record arguments for ifs breakup */ #define EXP_IN_QUOTES 0x40 /* don't set EXP_IFS_SPLIT again */ #define EXP_GLOB 0x80 /* perform filename globbing */ +#define EXP_NL 0x100 /* keep CRTNONL in output */ #define EXP_FULL (EXP_SPLIT | EXP_GLOB) diff --git a/bin/sh/input.c b/bin/sh/input.c index 01cb77dae353..c35cfea1e2bf 100644 --- a/bin/sh/input.c +++ b/bin/sh/input.c @@ -1,4 +1,4 @@ -/* $NetBSD: input.c,v 1.57 2017/06/07 04:44:17 kre Exp $ */ +/* $NetBSD: input.c,v 1.58 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; #else -__RCSID("$NetBSD: input.c,v 1.57 2017/06/07 04:44:17 kre Exp $"); +__RCSID("$NetBSD: input.c,v 1.58 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -65,6 +65,7 @@ __RCSID("$NetBSD: input.c,v 1.57 2017/06/07 04:44:17 kre Exp $"); #include "alias.h" #include "parser.h" #include "myhistedit.h" +#include "show.h" #define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ @@ -482,12 +483,14 @@ setinputstring(char *string, int push, int line1) { INTOFF; - if (push) + if (push) /* XXX: always, as it happens */ pushfile(); parsenextc = string; parselleft = parsenleft = strlen(string); parsefile->buf = NULL; plinno = line1; + TRACE(("setinputstring(\"%.20s%s\" (%d), %d, %d)\n", string, + (parsenleft > 20 ? "..." : ""), parsenleft, push, line1)); INTON; } diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c index 9f17e638b586..a79816d12246 100644 --- a/bin/sh/jobs.c +++ b/bin/sh/jobs.c @@ -1,4 +1,4 @@ -/* $NetBSD: jobs.c,v 1.85 2017/05/18 13:34:17 kre Exp $ */ +/* $NetBSD: jobs.c,v 1.86 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; #else -__RCSID("$NetBSD: jobs.c,v 1.85 2017/05/18 13:34:17 kre Exp $"); +__RCSID("$NetBSD: jobs.c,v 1.86 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -1431,22 +1431,25 @@ cmdputs(const char *s) nleft = cmdnleft; while (nleft > 0 && (c = *p++) != 0) { switch (c) { + case CTLNONL: + c = '\0'; + break; case CTLESC: c = *p++; break; case CTLVAR: subtype = *p++; - if (subtype & VSLINENO) { + if (subtype & VSLINENO) { /* undo LINENO hack */ if ((subtype & VSTYPE) == VSLENGTH) - str = "${#LINENO"; + str = "${#LINENO"; /*}*/ else - str = "${LINENO"; + str = "${LINENO"; /*}*/ while (is_digit(*p)) p++; } else if ((subtype & VSTYPE) == VSLENGTH) - str = "${#"; + str = "${#"; /*}*/ else - str = "${"; + str = "${"; /*}*/ if (!(subtype & VSQUOTE) != !(quoted & 1)) { quoted ^= 1; c = '"'; @@ -1454,7 +1457,7 @@ cmdputs(const char *s) c = *str++; } break; - case CTLENDVAR: + case CTLENDVAR: /*{*/ c = '}'; if (quoted & 1) str = "\""; @@ -1471,9 +1474,11 @@ cmdputs(const char *s) break; case CTLARI: c = '$'; - str = "(("; + if (*p == ' ') + p++; + str = "(("; /*))*/ break; - case CTLENDARI: + case CTLENDARI: /*((*/ c = ')'; str = ")"; break; @@ -1492,7 +1497,7 @@ cmdputs(const char *s) if (subtype & VSNUL) c = ':'; else - c = *str++; + c = *str++; /*{*/ if (c != '}') quoted <<= 1; else if (*p == CTLENDVAR) diff --git a/bin/sh/nodetypes b/bin/sh/nodetypes index 8e5c8d08b872..4ddeda3052b2 100644 --- a/bin/sh/nodetypes +++ b/bin/sh/nodetypes @@ -1,4 +1,4 @@ -# $NetBSD: nodetypes,v 1.16 2017/06/07 04:44:17 kre Exp $ +# $NetBSD: nodetypes,v 1.17 2017/06/07 05:08:32 kre Exp $ # Copyright (c) 1991, 1993 # The Regents of the University of California. All rights reserved. # @@ -104,6 +104,7 @@ NCLIST nclist # a case next nodeptr # the next case in list pattern nodeptr # list of patterns for this case body nodeptr # code to execute for this case + lineno int NDEFUN narg # define a function. The "next" field contains @@ -114,6 +115,7 @@ NARG narg # represents a word next nodeptr # next word in list text string # the text of the word backquote nodelist # list of commands in back quotes + lineno int NTO nfile # fd> fname NCLOBBER nfile # fd>| fname diff --git a/bin/sh/option.list b/bin/sh/option.list index 6abceba0b4d9..c3c7c5bd249e 100644 --- a/bin/sh/option.list +++ b/bin/sh/option.list @@ -1,4 +1,4 @@ -/* $NetBSD: option.list,v 1.2 2017/05/28 14:14:22 kre Exp $ */ +/* $NetBSD: option.list,v 1.3 2017/06/07 05:08:32 kre Exp $ */ /* * define the shell's settable options @@ -63,6 +63,7 @@ usefork fork F # use fork(2) instead of vfork(2) pflag nopriv p # preserve privs if set[ug]id posix posix # be closer to POSIX compat qflag quietprofile q # disable -v/-x in startup files +fnline1 lineno_fn_relative L on # number lines in funcs starting at 1 // editline/history related options ("vi" is standard, 'V' and others are not) // only one of vi/emacs can be set, hence the "set" definition, value diff --git a/bin/sh/parser.c b/bin/sh/parser.c index 2d3acc61cc68..fdb3e223851b 100644 --- a/bin/sh/parser.c +++ b/bin/sh/parser.c @@ -1,4 +1,4 @@ -/* $NetBSD: parser.c,v 1.133 2017/06/07 04:44:17 kre Exp $ */ +/* $NetBSD: parser.c,v 1.134 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; #else -__RCSID("$NetBSD: parser.c,v 1.133 2017/06/07 04:44:17 kre Exp $"); +__RCSID("$NetBSD: parser.c,v 1.134 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -100,6 +100,7 @@ struct heredoc *heredoc; int quoteflag; /* set if (part of) last token was quoted */ int startlinno; /* line # where last token started */ int funclinno; /* line # where the current function started */ +int elided_nl; /* count of \ \n (deleted \n's) we have seen */ STATIC union node *list(int, int); @@ -109,7 +110,7 @@ STATIC union node *command(void); STATIC union node *simplecmd(union node **, union node *); STATIC union node *makename(void); STATIC void parsefname(void); -STATIC void slurp_heredoc(char *const, const int, const int); +STATIC int slurp_heredoc(char *const, const int, const int); STATIC void readheredocs(void); STATIC int peektoken(void); STATIC int readtoken(void); @@ -124,6 +125,9 @@ STATIC int pgetc_linecont(void); static const char EOFhere[] = "EOF reading here (<<) document"; +#ifdef DEBUG +int parsing = 0; +#endif /* * Read and parse a command. Returns NEOF on end of file. (NULL is a @@ -136,6 +140,9 @@ parsecmd(int interact) int t; union node *n; +#ifdef DEBUG + parsing++; +#endif tokpushback = 0; doprompt = interact; if (doprompt) @@ -144,12 +151,22 @@ parsecmd(int interact) setprompt(0); needprompt = 0; t = readtoken(); +#ifdef DEBUG + parsing--; +#endif if (t == TEOF) return NEOF; if (t == TNL) return NULL; + +#ifdef DEBUG + parsing++; +#endif tokpushback++; n = list(1, 0); +#ifdef DEBUG + parsing--; +#endif if (heredoclist) error("%d: Here document (<<%s) expected but not present", heredoclist->startline, heredoclist->eofmark); @@ -397,6 +414,7 @@ command(void) n2->type = NARG; n2->narg.text = wordtext; n2->narg.backquote = backquotelist; + n2->narg.lineno = startlinno - elided_nl; *app = n2; app = &n2->narg.next; } @@ -414,6 +432,7 @@ command(void) n2->narg.text = argvars; n2->narg.backquote = NULL; n2->narg.next = NULL; + n2->narg.lineno = startlinno - elided_nl; n1->nfor.args = n2; /* * Newline or semicolon here is optional (but note @@ -437,13 +456,14 @@ command(void) case TCASE: n1 = stalloc(sizeof(struct ncase)); n1->type = NCASE; - n1->ncase.lineno = startlinno; + n1->ncase.lineno = startlinno - elided_nl; if (readtoken() != TWORD) synexpect(TWORD, 0); n1->ncase.expr = n2 = stalloc(sizeof(struct narg)); n2->type = NARG; n2->narg.text = wordtext; n2->narg.backquote = backquotelist; + n2->narg.lineno = startlinno - elided_nl; n2->narg.next = NULL; while (readtoken() == TNL); if (lasttoken != TWORD || ! equal(wordtext, "in")) @@ -470,6 +490,7 @@ command(void) for (;;) { *app = ap = stalloc(sizeof(struct narg)); ap->type = NARG; + ap->narg.lineno = startlinno - elided_nl; ap->narg.text = wordtext; ap->narg.backquote = backquotelist; if (checkkwd = 2, readtoken() != TPIPE) @@ -482,6 +503,7 @@ command(void) if (lasttoken != TRP) { synexpect(TRP, 0); } + cp->nclist.lineno = startlinno - elided_nl; cp->nclist.body = list(0, 0); checkkwd = 2; @@ -601,6 +623,7 @@ simplecmd(union node **rpp, union node *redir) { union node *args, **app; union node *n = NULL; + int line = 0; #ifdef BOGUS_NOT_COMMAND union node *n2; int negate = 0; @@ -627,13 +650,18 @@ simplecmd(union node **rpp, union node *redir) for (;;) { if (readtoken() == TWORD) { + if (line == 0) + line = startlinno; n = stalloc(sizeof(struct narg)); n->type = NARG; n->narg.text = wordtext; n->narg.backquote = backquotelist; + n->narg.lineno = startlinno - elided_nl; *app = n; app = &n->narg.next; } else if (lasttoken == TREDIR) { + if (line == 0) + line = startlinno; *rpp = n = redirnode; rpp = &n->nfile.next; parsefname(); /* read name of redirection file */ @@ -646,7 +674,10 @@ simplecmd(union node **rpp, union node *redir) rmescapes(n->narg.text); if (strchr(n->narg.text, '/')) synerror("Bad function name"); + VTRACE(DBG_PARSE, ("Function '%s' seen @%d\n", + n->narg.text, plinno)); n->type = NDEFUN; + n->narg.lineno = plinno - elided_nl; n->narg.next = command(); funclinno = 0; goto checkneg; @@ -662,6 +693,7 @@ simplecmd(union node **rpp, union node *redir) *rpp = NULL; n = stalloc(sizeof(struct ncmd)); n->type = NCMD; + n->ncmd.lineno = line - elided_nl; n->ncmd.backgnd = 0; n->ncmd.args = args; n->ncmd.redirect = redir; @@ -692,6 +724,7 @@ makename(void) n->narg.next = NULL; n->narg.text = wordtext; n->narg.backquote = backquotelist; + n->narg.lineno = startlinno - elided_nl; return n; } @@ -816,11 +849,12 @@ checkend(int c, char * const eofmark, const int striptabs) * Input any here documents. */ -STATIC void +STATIC int slurp_heredoc(char *const eofmark, const int striptabs, const int sq) { int c; char *out; + int lines = plinno; c = pgetc(); @@ -861,6 +895,7 @@ slurp_heredoc(char *const eofmark, const int striptabs, const int sq) if (c == '\\') { /* A backslash */ c = pgetc(); /* followed by */ if (c == '\n') { /* a newline? */ + /*XXX CTLNONL ?? XXX*/ plinno++; continue; /* :drop both */ } @@ -889,9 +924,22 @@ slurp_heredoc(char *const eofmark, const int striptabs, const int sq) wordtext = out; VTRACE(DBG_PARSE, - ("Slurped a heredoc (to '%s')%s: len %d, \"%.*s%s\" @%d\n", - eofmark, striptabs ? " tab stripped" : "", c, (c > 16 ? 16 : c), + ("Slurped a %d line %sheredoc (to '%s')%s: len %d, \"%.*s%s\" @%d\n", + plinno - lines, sq ? "quoted " : "", eofmark, + striptabs ? " tab stripped" : "", c, (c > 16 ? 16 : c), wordtext, (c > 16 ? "..." : ""), plinno)); + + return (plinno - lines); +} + +static char * +insert_elided_nl(char *str) +{ + while (elided_nl > 0) { + STPUTC(CTLNONL, str); + elided_nl--; + } + return str; } STATIC void @@ -899,8 +947,14 @@ readheredocs(void) { struct heredoc *here; union node *n; + int line, l; + line = 0; /*XXX - gcc! obviously unneeded */ + if (heredoclist) + line = heredoclist->startline + 1; + l = 0; while (heredoclist) { + line += l; here = heredoclist; heredoclist = here->next; if (needprompt) { @@ -908,7 +962,7 @@ readheredocs(void) needprompt = 0; } - slurp_heredoc(here->eofmark, here->striptabs, + l = slurp_heredoc(here->eofmark, here->striptabs, here->here->nhere.type == NHERE); n = stalloc(sizeof(struct narg)); @@ -924,7 +978,7 @@ readheredocs(void) /* * Now "parse" here docs that have unquoted eofmarkers. */ - setinputstring(wordtext, 1, startlinno); + setinputstring(wordtext, 1, line); readtoken1(pgetc(), DQSYNTAX, 1); n->narg.text = wordtext; n->narg.backquote = backquotelist; @@ -978,8 +1032,8 @@ readtoken(void) lasttoken = t = pp - parsekwd + KWDOFFSET; VTRACE(DBG_PARSE, - ("keyword %s recognized\n", - tokname[t])); + ("keyword %s recognized @%d\n", + tokname[t], plinno)); goto out; } } @@ -993,8 +1047,8 @@ readtoken(void) out: checkkwd = (t == TNOT) ? savecheckkwd : 0; } - VTRACE(DBG_PARSE, ("%stoken %s %s\n", alreadyseen ? "reread " : "", - tokname[t], t == TWORD ? wordtext : "")); + VTRACE(DBG_PARSE, ("%stoken %s %s @%d\n", alreadyseen ? "reread " : "", + tokname[t], t == TWORD ? wordtext : "", plinno)); return (t); } @@ -1032,6 +1086,7 @@ xxreadtoken(void) setprompt(2); needprompt = 0; } + elided_nl = 0; startlinno = plinno; for (;;) { /* until token or start of word found */ c = pgetc_macro(); @@ -1315,12 +1370,15 @@ parsebackq(VSS *const stack, char * const in, handler = &jmploc; INTON; if (oldstyle) { - /* We must read until the closing backquote, giving special - treatment to some slashes, and then push the string and - reread it as input, interpreting it normally. */ + /* + * We must read until the closing backquote, giving special + * treatment to some slashes, and then push the string and + * reread it as input, interpreting it normally. + */ int pc; int psavelen; char *pstr; + int line1 = plinno; /* * Because the entire `...` is read here, we don't @@ -1333,25 +1391,12 @@ parsebackq(VSS *const stack, char * const in, setprompt(2); needprompt = 0; } - switch (pc = pgetc_linecont()) { - case '`': - goto done; - + pc = pgetc_linecont(); + if (pc == '`') + break; + switch (pc) { case '\\': - if ((pc = pgetc()) == '\n') { - plinno++; - if (doprompt) - setprompt(2); - else - setprompt(0); - /* - * If eating a newline, avoid putting - * the newline into the new character - * stream (via the STPUTC after the - * switch). - */ - continue; - } + pc = pgetc(); /* cannot be '\n' */ if (pc != '\\' && pc != '`' && pc != '$' && (!ISDBLQUOTE() || pc != '"')) STPUTC('\\', out); @@ -1359,6 +1404,7 @@ parsebackq(VSS *const stack, char * const in, case '\n': plinno++; + out = insert_elided_nl(out); needprompt = doprompt; break; @@ -1372,12 +1418,11 @@ parsebackq(VSS *const stack, char * const in, } STPUTC(pc, out); } - done: STPUTC('\0', out); psavelen = out - stackblock(); if (psavelen > 0) { pstr = grabstackstr(out); - setinputstring(pstr, 1, startlinno); + setinputstring(pstr, 1, line1); } } nlpp = pbqlist; @@ -1549,10 +1594,13 @@ readtoken1(int firstc, char const *syn, int magicq) bqlist = NULL; arinest = 0; parenlevel = 0; + elided_nl = 0; STARTSTACKSTR(out); for (c = firstc ;; c = pgetc_macro()) { /* until of token */ + if (syntax == ARISYNTAX) + out = insert_elided_nl(out); CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ switch (syntax[c]) { case CNL: /* '\n' */ @@ -1583,6 +1631,7 @@ readtoken1(int firstc, char const *syn, int magicq) } if (c == '\n') { plinno++; + elided_nl++; if (doprompt) setprompt(2); else @@ -1656,6 +1705,7 @@ readtoken1(int firstc, char const *syn, int magicq) } continue; case CVAR: /* '$' */ + out = insert_elided_nl(out); PARSESUB(); /* parse substitution */ continue; case CENDVAR: /* CLOSEBRACE */ @@ -1665,6 +1715,7 @@ readtoken1(int firstc, char const *syn, int magicq) } else { USTPUTC(c, out); } + out = insert_elided_nl(out); continue; case CLP: /* '(' in arithmetic */ parenlevel++; @@ -1676,6 +1727,7 @@ readtoken1(int firstc, char const *syn, int magicq) --parenlevel; } else { if (pgetc_linecont() == ')') { + out = insert_elided_nl(out); if (--arinest == 0) { TS_POP(); USTPUTC(CTLENDARI, out); @@ -1750,23 +1802,22 @@ readtoken1(int firstc, char const *syn, int magicq) */ parsesub: { - char buf[10]; int subtype; int typeloc; int flags; char *p; static const char types[] = "}-+?="; - int i; - int linno; c = pgetc_linecont(); - if (c != '(' && c != OPENBRACE && !is_name(c) && !is_special(c)) { + if (c != '('/*)*/ && c != OPENBRACE && !is_name(c) && !is_special(c)) { USTPUTC('$', out); pungetc(); - } else if (c == '(') { /* $(command) or $((arith)) */ - if (pgetc_linecont() == '(') { + } else if (c == '('/*)*/) { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(' /*')'*/ ) { + out = insert_elided_nl(out); PARSEARITH(); } else { + out = insert_elided_nl(out); pungetc(); out = parsebackq(stack, out, &bqlist, 0); } @@ -1822,9 +1873,18 @@ parsesub: { STPUTC(c, out); c = pgetc_linecont(); } while (is_in_name(c)); +#if 0 if (out - p == 6 && strncmp(p, "LINENO", 6) == 0) { - /* Replace the variable name with the - * current line number. */ + int i; + int linno; + char buf[10]; + + /* + * The "LINENO hack" + * + * Replace the variable name with the + * current line number. + */ linno = plinno; if (funclinno != 0) linno -= funclinno - 1; @@ -1834,9 +1894,10 @@ parsesub: { STPUTC(buf[i], out); flags |= VSLINENO; } +#endif } else if (is_digit(c)) { do { - USTPUTC(c, out); + STPUTC(c, out); c = pgetc_linecont(); } while (subtype != VSNORMAL && is_digit(c)); } @@ -1911,6 +1972,8 @@ parsearith: { /* * we collapse embedded arithmetic expansion to * parentheses, which should be equivalent + * + * XXX It isn't, must fix, soonish... */ USTPUTC('(', out); USTPUTC('(', out); @@ -1940,6 +2003,7 @@ parsearith: { + #ifdef mkinit RESET { tokpushback = 0; @@ -2058,6 +2122,7 @@ pgetc_linecont(void) c = pgetc(); if (c == '\n') { plinno++; + elided_nl++; if (doprompt) setprompt(2); else diff --git a/bin/sh/parser.h b/bin/sh/parser.h index 69fe5ce69c85..a19f9c85b560 100644 --- a/bin/sh/parser.h +++ b/bin/sh/parser.h @@ -1,4 +1,4 @@ -/* $NetBSD: parser.h,v 1.21 2016/03/31 23:11:05 christos Exp $ */ +/* $NetBSD: parser.h,v 1.22 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -46,7 +46,9 @@ #define CTLENDARI '\207' #define CTLQUOTEMARK '\210' #define CTLQUOTEEND '\211' /* only inside ${...} */ -#define CTL_LAST '\211' /* last 'special' character */ +#define CTLNONL '\212' /* The \n in a deleted \ \n sequence */ + /* pure concidence that (CTLNONL & 0x7f) == '\n' */ +#define CTL_LAST '\212' /* last 'special' character */ /* variable substitution byte (follows CTLVAR) */ #define VSTYPE 0x0f /* type of variable substitution */ @@ -83,3 +85,7 @@ union node *parsecmd(int); void fixredir(union node *, const char *, int); int goodname(char *); const char *getprompt(void *); + +#ifdef DEBUG +extern int parsing; +#endif diff --git a/bin/sh/sh.1 b/bin/sh/sh.1 index edf040e71b4f..2a2474ee725e 100644 --- a/bin/sh/sh.1 +++ b/bin/sh/sh.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: sh.1,v 1.148 2017/06/06 22:38:52 kre Exp $ +.\" $NetBSD: sh.1,v 1.149 2017/06/07 05:08:32 kre Exp $ .\" Copyright (c) 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" @@ -294,19 +294,23 @@ the file system is searched for commands each time the function is invoked. Force the shell to behave interactively. .It Fl I Em ignoreeof Ignore EOFs from input when interactive. -.\" .It Fl L Em lineno_fn_relative -.\" When set, before a function is defined, -.\" causes the variable -.\" .Ev LINENO -.\" when used within the function, -.\" to refer to the line number defined such that -.\" first line of the function is line 1. -.\" When reset, -.\" .Ev LINENO -.\" in a function refers to the line number within the file -.\" in which the definition of the function occurs -.\" (which can be the number of lines of shell input from standard input -.\" when a function is defined interactively from the command prompt.) +.It Fl L Em lineno_fn_relative +When set, before a function is defined, +causes the variable +.Ev LINENO +when used within the function, +to refer to the line number defined such that +first line of the function is line 1. +When reset, +.Ev LINENO +in a function refers to the line number within the file +in which the definition of the function occurs. +This option defaults to +.Dq on +in this shell. +For more details see the section +.Sx LINENO +below. .It Fl m Em monitor Turn on job control (set automatically when interactive). .It Fl n Em noexec @@ -2228,6 +2232,12 @@ differs amongst shell implementations. Using .Dq local \&\- is an extension not implemented by most shells. +.Pp +See the section +.Sx LINENO +below for details of the effects of making the variable +.Ev LINENO +local. .It pwd Op Fl \&LP Print the current directory. If @@ -2765,8 +2775,9 @@ See .Xr nls 7 . .It Ev LINENO The current line number in the script or function. -The value of this variable is automatically set by the -shell, even if made readonly. +See the section +.Sx LINENO +below for more details. .It Ev MAIL The name of a mail file, that will be checked for the arrival of new mail. Overridden by @@ -2840,6 +2851,153 @@ that were used when building the shell will be listed. behaves like any other variable that has the read-only and un-exportable attributes set. .El +.Ss Ev LINENO +.Ev LINENO +is in many respects a normal shell variable, containing an +integer value. and can be expanded using any of the forms +mentioned above which can be used for any other variable. +.Pp +.Ev LINENO +can be exported, +made readonly (which prevents attempts to assign to it, +and to unset it, but which does not change the value, +that is the current line number, from being obtained when +.Ev LINENO +is referenced,) +and can be unset. +All of those act as they would with any other variable. +However, +.Ev LINENO +should normally not ever be set, in this +shell doing so reverses the effect of an earlier unset, +but does not otherwise affect the value obtained. +If unset, +.Ev LINENO +should not be set again. +If +.Ev LINENO +is set or unset, different shells act differently. +The value of +.Ev LINENO +is never imported from the environment when the shell is +started, though if present there, as with any other variable, +.Ev LINENO +will be exported by this shell. +.Pp +.Ev LINENO +is set automatically by the shell to be the number of the source +line on which it occurs. +When exported, +.Ev LINENO +is exported with its value set to the line number it would have +had had it been referenced on the command line of the command to +which it is exported. +Line numbers are counted from 1, which is the first line the shell +reads from any particular file. +For this shell, standard input, including in an interactive shell, +the user's terminal, is just another file and lines are counted +there as well, however note that not all shells count interactive +lines this way, it is wise to rely upon LINENO only having a useful +value in a script, or a function. +.Pp +The role of +.Ev LINENO +in functions is less clear. +In some shells, +.Ev LINENO +continues to refer to the line number in the script which defines +the function, +in others lines count from one within the function, always (and +resume counting normally once the function definition is complete) +and others count in functions from one if the function is defined +interactively, but otherwise just reference the line number in the +script in which the function is defined. +This shell gives the user the option to choose. +If the +.Fl L +flag (the +.Ic lineno_fn_relative +option) is set, when the function is defined, then the function +defaults to counting lines with one being the first line of the +function. +When the +.Fl L +flag is not set, the shell counts lines in a function definition +in the same continuous sequence as the lines that surround the +function definition. Further, if +.Ev LINENO +is made local +(see +.Sx Built-ins +above) +inside the function, the function can decide which +behavior it prefers. +If +.Ev LINENO +is made local and inherited, and not given a value, as in +.Dl local -I LINENO +then from that point in the function, +.Ev LINENO +will give the line number as if lines are counted in sequence +with the lines that surround the function definition. +If +.Ev LINENO +is made local, and in that same command, given a value, as +.Dl local Oo Fl I Ns Cm \&| Ns Fl N Ns Oc LINENO=value +then +.Ev LINENO +will give the line number as if lines are counted from one +from the beginning of the function. +The value nominally assigned in this case is irrelevant, and ignored. +For completeness, if lineno is made local and unset, as in +.Dl local -N LINENO +then +.Ev LINENO +is simply unset inside the function, and gives no value at all. +.Pp +Now for some technical details. +The line on which +.Ev LINENO +occurs in a parameter expansion, is the line that contains the +.Sq \&$ +that begins the expansion of +.Ev LINENO . +In the case of nested expansions, that +.Sq \&$ +is the one that actually has +.Ev LINENO +as its parameter. +In an arithmetic expansion, where no +.Sq \&$ +is used to evaluate +.Ev LINENO +but +.Ev LINENO +is simply referenced as a variable, then the value is the +line number of the line that contains the +.Sq L +of +.Ev LINENO . +For functions line one of the function definition (when relevant) +is the line that contains the first character of the +function name in the definition. +When exported, the line number of the command is the line number +where the first character of the word which becomes the command name occurs. +.Pp +When the shell opens a new file, for any reason, +it counts lines from one in that file, +and then resumes its original counting once it resumes reading the +previous input stream. +When handling a string passed to +.Ic eval +the line number starts at the line on which the string starts, +and then if the string contains internal newline characters, +those characters increase the line number. +This means that references to +.Ev LINENO +in such a case can produce values larger than would be +produced by a reference on the line after the +.Ic eval . .Sh FILES .Bl -item .It diff --git a/bin/sh/show.c b/bin/sh/show.c index 1f8fcad330dc..6a300eae29ee 100644 --- a/bin/sh/show.c +++ b/bin/sh/show.c @@ -1,4 +1,4 @@ -/* $NetBSD: show.c,v 1.42 2017/05/29 14:03:23 kre Exp $ */ +/* $NetBSD: show.c,v 1.43 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -39,7 +39,7 @@ #if 0 static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; #else -__RCSID("$NetBSD: show.c,v 1.42 2017/05/29 14:03:23 kre Exp $"); +__RCSID("$NetBSD: show.c,v 1.43 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -65,6 +65,7 @@ __RCSID("$NetBSD: show.c,v 1.42 2017/05/29 14:03:23 kre Exp $"); #include "syntax.h" #include "input.h" #include "output.h" +#include "var.h" #include "builtins.h" #if defined(DEBUG) && !defined(DBG_PID) @@ -654,12 +655,17 @@ sharg(union node *arg, TFILE *fp) trace_putc(*++p, fp); break; + case CTLNONL: + trace_putc('\\', fp); + trace_putc('\n', fp); + break; + case CTLVAR: subtype = *++p; if (!quoted != !(subtype & VSQUOTE)) trace_putc('"', fp); trace_putc('$', fp); - trace_putc('{', fp); + trace_putc('{', fp); /*}*/ if ((subtype & VSTYPE) == VSLENGTH) trace_putc('#', fp); if (subtype & VSLINENO) @@ -750,6 +756,8 @@ sharg(union node *arg, TFILE *fp) quoted--; break; case CTLARI: + if (*p == ' ') + p++; trace_puts("$(( ", fp); break; case CTLENDARI: @@ -943,13 +951,22 @@ trace_id(TFILE *tf) } else indent[0] = '\0'; - lno = plinno; /* only approximate for now - as good as we can do */ + /* + * If we are in the parser, then plinno is the current line + * number being processed (parser line no). + * If we are elsewhere, then line_number gives the source + * line of whatever we are currently doing (close enough.) + */ + if (parsing) + lno = plinno; + else + lno = line_number; if (DFlags & DBG_PID) { i = getpid(); if (DFlags & DBG_LINE) - (void) asprintf(&p, "%5d%c%s\t%4d @\t", i, - i == tf->pid ? ':' : '=', indent, lno); + (void) asprintf(&p, "%5d%c%s\t%4d%c@\t", i, + i == tf->pid ? ':' : '=', indent, lno, parsing?'-':'+'); else (void) asprintf(&p, "%5d%c%s\t", i, i == tf->pid ? ':' : '=', indent); diff --git a/bin/sh/syntax.c b/bin/sh/syntax.c index e0647294f6d2..00b557121f25 100644 --- a/bin/sh/syntax.c +++ b/bin/sh/syntax.c @@ -1,7 +1,7 @@ -/* $NetBSD: syntax.c,v 1.3 2012/03/28 20:11:25 christos Exp $ */ +/* $NetBSD: syntax.c,v 1.4 2017/06/07 05:08:32 kre Exp $ */ #include -__RCSID("$NetBSD: syntax.c,v 1.3 2012/03/28 20:11:25 christos Exp $"); +__RCSID("$NetBSD: syntax.c,v 1.4 2017/06/07 05:08:32 kre Exp $"); #include #include "shell.h" @@ -102,4 +102,7 @@ const char is_type[257] = { 0, set('-', ISSPECL) set('*', ISSPECL) set('@', ISSPECL) + set(' ', ISSPACE) + set('\t', ISSPACE) + set('\n', ISSPACE) }; diff --git a/bin/sh/syntax.h b/bin/sh/syntax.h index e1ebd17b4e5e..f34dc9352317 100644 --- a/bin/sh/syntax.h +++ b/bin/sh/syntax.h @@ -1,4 +1,4 @@ -/* $NetBSD: syntax.h,v 1.7 2017/05/15 20:00:36 kre Exp $ */ +/* $NetBSD: syntax.h,v 1.8 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -56,6 +56,7 @@ #define ISLOWER 04 /* a lower case letter */ #define ISUNDER 010 /* an underscore */ #define ISSPECL 020 /* the name of a special parameter */ +#define ISSPACE 040 /* a white space character */ #define PEOF (CHAR_MIN - 1) #define SYNBASE (-PEOF) @@ -75,6 +76,7 @@ #define is_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER)) #define is_in_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT)) #define is_special(c) (sh_ctype(c) & (ISSPECL|ISDIGIT)) +#define is_space(c) (sh_ctype(c) & ISSPACE) #define digit_val(c) ((c) - '0') extern const char basesyntax[]; diff --git a/bin/sh/var.c b/bin/sh/var.c index dd05e92b31a9..ebc576475284 100644 --- a/bin/sh/var.c +++ b/bin/sh/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.56 2017/06/07 04:44:17 kre Exp $ */ +/* $NetBSD: var.c,v 1.57 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; #else -__RCSID("$NetBSD: var.c,v 1.56 2017/06/07 04:44:17 kre Exp $"); +__RCSID("$NetBSD: var.c,v 1.57 2017/06/07 05:08:32 kre Exp $"); #endif #endif /* not lint */ @@ -82,8 +82,12 @@ struct varinit { struct var *var; int flags; const char *text; - void (*func)(const char *); + union var_func_union v_u; }; +#define func v_u.set_func +#define rfunc v_u.ref_func + +char *get_lineno(struct var *); struct localvar *localvars; @@ -102,40 +106,45 @@ struct var vvers; struct var voptind; struct var line_num; +struct var line_num; +int line_number; +int funclinebase = 0; +int funclineabs = 0; + char ifs_default[] = " \t\n"; const struct varinit varinit[] = { #ifndef SMALL { &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=", - sethistsize }, + { .set_func= sethistsize } }, #endif { &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n", - NULL }, + { NULL } }, { &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=", - NULL }, + { NULL } }, { &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=", - NULL }, + { NULL } }, { &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=", - NULL }, + { NULL } }, { &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH, - changepath }, + { .set_func= changepath } }, /* * vps1 depends on uid */ { &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ", - NULL }, + { NULL } }, { &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ", - NULL }, + { NULL } }, #ifndef SMALL { &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=", - setterm }, + { .set_func= setterm } }, #endif { &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1", - getoptsreset }, - { &line_num, VSTRFIXED|VTEXTFIXED, "LINENO=1", - NULL }, + { .set_func= getoptsreset } }, + { &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF, "LINENO=1", + { .ref_func= get_lineno } }, { NULL, 0, NULL, - NULL } + { NULL } } }; struct var *vartab[VTABSIZE]; @@ -170,6 +179,7 @@ INIT { * PPID is readonly * Always default IFS * NETBSD_SHELL is a constant (readonly), and is never exported + * LINENO is simply magic... */ snprintf(buf, sizeof(buf), "%d", (int)getppid()); setvar("PPID", buf, VREADONLY); @@ -232,7 +242,7 @@ initvar(void) *vpp = vp; vp->text = strdup(ip->text); vp->flags = ip->flags; - vp->func = ip->func; + vp->v_u = ip->v_u; } /* * PS1 depends on uid @@ -346,9 +356,10 @@ setvareq(char *s, int flags) error("%.*s: is read only", vp->name_len, s); if (flags & VNOSET) return; + INTOFF; - if (vp->func && (flags & VNOFUNC) == 0) + if (vp->func && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC)) (*vp->func)(s + vp->name_len + 1); if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) @@ -368,6 +379,7 @@ setvareq(char *s, int flags) */ if (vp == &vmpath || (vp == &vmail && ! mpathset())) chkmail(1); + INTON; return; } @@ -375,7 +387,7 @@ setvareq(char *s, int flags) if (flags & VNOSET) return; vp = ckmalloc(sizeof (*vp)); - vp->flags = flags & ~VNOFUNC; + vp->flags = flags & ~(VNOFUNC|VFUNCREF); vp->text = s; vp->name_len = nlen; vp->next = *vpp; @@ -423,6 +435,8 @@ lookupvar(const char *name) v = find_var(name, NULL, NULL); if (v == NULL || v->flags & VUNSET) return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; return v->text + v->name_len + 1; } @@ -449,6 +463,8 @@ bltinlookup(const char *name, int doall) if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT))) return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; return v->text + v->name_len + 1; } @@ -477,8 +493,12 @@ environment(void) ep = env = stalloc((nenv + 1) * sizeof *env); for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { for (vp = *vpp ; vp ; vp = vp->next) - if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) - *ep++ = vp->text; + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) { + if (vp->rfunc && (vp->flags & VFUNCREF)) + *ep++ = (*vp->rfunc)(vp); + else + *ep++ = vp->text; + } } *ep = NULL; return env; @@ -644,7 +664,13 @@ showvars(const char *name, int flag, int show_value, const char *xtra) out1fmt("%s ", name); if (xtra) out1fmt("%s ", xtra); - for (p = vp->text ; *p != '=' ; p++) + p = vp->text; + if (vp->rfunc && (vp->flags & VFUNCREF) != 0) { + p = (*vp->rfunc)(vp); + if (p == NULL) + p = vp->text; + } + for ( ; *p != '=' ; p++) out1c(*p); if (!(vp->flags & VUNSET) && show_value) { out1fmt("="); @@ -812,6 +838,13 @@ mklocal(const char *name, int flags) unsetvar(name, 0); else vp->flags |= flags & (VUNSET|VEXPORT); + + if (vp == &line_num) { + if (name[vp->name_len] == '=') + funclinebase = funclineabs -1; + else + funclinebase = 0; + } } } lvp->vp = vp; @@ -838,10 +871,11 @@ poplocalvars(void) if (vp == NULL) { /* $- saved */ memcpy(optlist, lvp->text, sizeof_optlist); ckfree(lvp->text); + optschanged(); } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { (void)unsetvar(vp->text, 0); } else { - if (vp->func && (vp->flags & VNOFUNC) == 0) + if (vp->func && (vp->flags & (VNOFUNC|VFUNCREF)) == 0) (*vp->func)(lvp->text + vp->name_len + 1); if ((vp->flags & VTEXTFIXED) == 0) ckfree(vp->text); @@ -1003,3 +1037,17 @@ find_var(const char *name, struct var ***vppp, int *lenp) } return NULL; } + +char * +get_lineno(struct var *vp) +{ + static char lineno_buf[8 + 14]; + int ln = line_number; + + if (vp->flags & VUNSET) + return NULL; + + ln -= funclinebase; + snprintf(lineno_buf, sizeof(lineno_buf), "LINENO=%d", ln);; + return lineno_buf; +} diff --git a/bin/sh/var.h b/bin/sh/var.h index 7b1ce6c45230..c0a10b27fa91 100644 --- a/bin/sh/var.h +++ b/bin/sh/var.h @@ -1,4 +1,4 @@ -/* $NetBSD: var.h,v 1.29 2017/06/07 04:44:17 kre Exp $ */ +/* $NetBSD: var.h,v 1.30 2017/06/07 05:08:32 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -48,16 +48,21 @@ #define VNOFUNC 0x0040 /* don't call the callback function */ #define VNOSET 0x0080 /* do not set variable - just readonly test */ #define VNOEXPORT 0x0100 /* variable may not be exported */ +#define VFUNCREF 0x0200 /* the function is called on ref, not set */ +struct var; + +union var_func_union { /* function to be called when: */ + void (*set_func)(const char *); /* variable gets set/unset */ + char*(*ref_func)(struct var *); /* variable is referenced */ +}; struct var { struct var *next; /* next entry in hash list */ int flags; /* flags are defined above */ char *text; /* name=value */ int name_len; /* length of name */ - void (*func)(const char *); - /* function to be called when */ - /* the variable gets set/unset */ + union var_func_union v_u; /* function to apply (sometimes) */ }; @@ -86,6 +91,10 @@ extern struct var vtermcap; extern struct var vhistsize; #endif +extern int line_number; +extern int funclinebase; +extern int funclineabs; + /* * The following macros access the values of the above variables. * They have to skip over the name. They return the null string