diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2019-05-14 15:31:59 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2019-05-14 15:43:25 +0300 |
commit | 8652a500669059d77ce7bace6e9e9da1b30c54b0 (patch) | |
tree | 54ee082e3804c0574e67252be8fbceb48b8f156a | |
parent | 65f41a742e025487f8ec7f2e7ca2a3af3283fc96 (diff) | |
download | grecs-8652a500669059d77ce7bace6e9e9da1b30c54b0.tar.gz grecs-8652a500669059d77ce7bace6e9e9da1b30c54b0.tar.bz2 |
wordsplit: rewrite positional parameters implementation
This improves 3e07e3ad
* include/wordsplit.h (ws_paramv,ws_paramc)
(ws_parambuf,ws_paramidx,ws_paramsiz): New fields.
(WRDSO_PARAMV,WRDSO_PARAM_NEGIDX): New options.
(WRDSE_BADPARAM): New error code.
(wordsplit_free_parambuf): New proto.
* src/wordsplit.c (wordsplit_init): Initialize new fields.
(wsplt_assign_var): Fix double-free and memory leak.
(expvar): Expand positional parameters.
(begin_var_p): Add '#'
(wordsplit_free_envbuf): Fix condition.
(wordsplit_free_parambuf): New function.
(wordsplit_free): Call wordsplit_free_parambuf.
(_wordsplit_errstr): New error description.
* tests/wordsplit.at: Update wsp invocations.
Test positional parameters.
* tests/wsp.c: Rewrite.
-rw-r--r-- | include/wordsplit.h | 20 | ||||
-rw-r--r-- | src/wordsplit.c | 201 | ||||
-rw-r--r-- | tests/wordsplit.at | 122 | ||||
-rw-r--r-- | tests/wsp.c | 832 |
4 files changed, 704 insertions, 471 deletions
diff --git a/include/wordsplit.h b/include/wordsplit.h index a175275..d7eb26f 100644 --- a/include/wordsplit.h +++ b/include/wordsplit.h @@ -77,2 +77,12 @@ struct wordsplit size_t ws_envsiz; /* Size of the ws_envbuf array */ + + char const **ws_paramv; /* [WRDSO_PARAMV] User-supplied positional + parameters */ + size_t ws_paramc; /* Number of positional parameters */ + + /* Temporary storage for parameters. Works similarly to ws_enbuf. + */ + char **ws_parambuf; + size_t ws_paramidx; + size_t ws_paramsiz; @@ -101,3 +111,3 @@ struct wordsplit return values. */ - + const char *ws_input; /* Input string (the S argument to wordsplit. */ @@ -228,2 +238,8 @@ struct wordsplit +/* Enable positional parameters */ +#define WRDSO_PARAMV 0x00004000 +/* Enable negative positional indices (${-1} is the last positional + parameter) */ +#define WRDSO_PARAM_NEGIDX 0x00008000 + #define WRDSO_BSKEEP WRDSO_BSKEEP_WORD @@ -252,2 +268,3 @@ struct wordsplit #define WRDSE_USERERR 9 +#define WRDSE_BADPARAM 10 @@ -258,2 +275,3 @@ void wordsplit_free_words (wordsplit_t *ws); void wordsplit_free_envbuf (wordsplit_t *ws); +void wordsplit_free_parambuf (struct wordsplit *ws); int wordsplit_get_words (wordsplit_t *ws, size_t *wordc, char ***wordv); diff --git a/src/wordsplit.c b/src/wordsplit.c index 521a1eb..6a33636 100644 --- a/src/wordsplit.c +++ b/src/wordsplit.c @@ -1,3 +1,3 @@ /* wordsplit - a word splitter - Copyright (C) 2009-2018 Sergey Poznyakoff + Copyright (C) 2009-2019 Sergey Poznyakoff @@ -29,2 +29,3 @@ #include <glob.h> +#include <limits.h> @@ -58,2 +59,5 @@ +#define to_num(c) \ + (ISDIGIT(c) ? c - '0' : (ISXDIGIT(c) ? toupper(c) - 'A' + 10 : 255 )) + #define ALLOC_INIT 128 @@ -286,2 +290,10 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len, } + + if (!(wsp->ws_options & WRDSO_PARAMV)) + { + wsp->ws_paramv = NULL; + wsp->ws_paramc = 0; + } + wsp->ws_paramidx = wsp->ws_paramsiz = 0; + wsp->ws_parambuf = NULL; @@ -1038,3 +1050,3 @@ static int wsplt_assign_var (struct wordsplit *wsp, const char *name, size_t namelen, - char *value) + char const *value) { @@ -1073,3 +1085,3 @@ wsplt_assign_var (struct wordsplit *wsp, const char *name, size_t namelen, free (newenv[j-1]); - free (newenv[j-1]); + free (newenv); return _wsplt_nomem (wsp); @@ -1144,2 +1156,66 @@ wsplt_assign_var (struct wordsplit *wsp, const char *name, size_t namelen, +int +wsplt_assign_param (struct wordsplit *wsp, int param_idx, char *value) +{ + char *v; + + if (param_idx < 0) + return WRDSE_BADPARAM; + if (param_idx == wsp->ws_paramc) + { + char **parambuf; + if (!wsp->ws_parambuf) + { + size_t i; + + parambuf = calloc ((size_t)param_idx + 1, sizeof (parambuf[0])); + if (!parambuf) + return _wsplt_nomem (wsp); + + for (i = 0; i < wsp->ws_paramc; i++) + { + parambuf[i] = strdup (wsp->ws_paramv[i]); + if (!parambuf[i]) + { + for (; i > 1; i--) + free (parambuf[i-1]); + free (parambuf); + return _wsplt_nomem (wsp); + } + } + + wsp->ws_parambuf = parambuf; + wsp->ws_paramidx = param_idx; + wsp->ws_paramsiz = param_idx + 1; + } + else + { + size_t n = wsp->ws_paramsiz; + + if ((size_t) -1 / 3 * 2 / sizeof (wsp->ws_parambuf[0]) <= n) + return _wsplt_nomem (wsp); + n += (n + 1) / 2; + parambuf = realloc (wsp->ws_parambuf, n * sizeof (wsp->ws_parambuf[0])); + if (!parambuf) + return _wsplt_nomem (wsp); + wsp->ws_parambuf = parambuf; + wsp->ws_paramsiz = n; + wsp->ws_parambuf[param_idx] = NULL; + } + + wsp->ws_paramv = (const char**) wsp->ws_parambuf; + wsp->ws_paramc = param_idx + 1; + } + else if (param_idx > wsp->ws_paramc) + return WRDSE_BADPARAM; + + v = strdup (value); + if (!v) + return _wsplt_nomem (wsp); + + free (wsp->ws_parambuf[param_idx]); + wsp->ws_parambuf[param_idx] = v; + return WRDSE_OK; +} + /* Recover from what looked like a variable reference, but turned out @@ -1179,2 +1255,4 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, struct wordsplit ws; + int is_param = 0; + long param_idx = 0; @@ -1187,3 +1265,3 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, } - else if (ISDIGIT (str[0])) + else if ((wsp->ws_options & WRDSO_PARAMV) && ISDIGIT (str[0])) { @@ -1191,4 +1269,27 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, *pend = str; + is_param = 1; + param_idx = to_num (str[0]); } - else if (str[0] == '{' && (ISVARBEG (str[1]) || ISDIGIT (str[1]))) + else if ((wsp->ws_options & WRDSO_PARAMV) && str[0] == '#') + { + char b[16]; + snprintf (b, sizeof(b), "%d", (int) wsp->ws_paramc); + value = strdup (b); + if (!value) + return _wsplt_nomem (wsp); + if (wsnode_new (wsp, &newnode)) + return 1; + wsnode_insert (wsp, newnode, *ptail, 0); + *ptail = newnode; + newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg; + newnode->v.word = value; + return 0; + } + else if (str[0] == '{' + && (ISVARBEG (str[1]) + || (is_param = (((wsp->ws_options & WRDSO_PARAMV) + && ISDIGIT (str[1])) + || ((wsp->ws_options & WRDSO_PARAM_NEGIDX) + && (str[1] == '-' + && ISDIGIT (str[2]))))) != 0)) { @@ -1196,3 +1297,3 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, len--; - for (i = 1; i < len; i++) + for (i = str[0] == '-' ? 1 : 0; i < len; i++) { @@ -1224,5 +1325,12 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, } - else if (ISDIGIT (str[1])) + else if (is_param) { - if (!ISDIGIT (str[i])) + if (ISDIGIT (str[i])) + { + param_idx = param_idx * 10 + to_num (str[i]); + if ((str[0] == '-' && -param_idx < INT_MIN) + || param_idx > INT_MAX) + return expvar_recover (wsp, str - 1, ptail, pend, flg); + } + else { @@ -1236,2 +1344,6 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, } + + if (is_param && str[0] == '-') + param_idx = wsp->ws_paramc - param_idx; + if (i == len) @@ -1256,10 +1368,11 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, { - rc = wordsplit_find_env (wsp, str, i, &vptr); - if (rc == WRDSE_OK) + if (is_param) { - if (vptr) + if (param_idx >= 0 && param_idx < wsp->ws_paramc) { - value = strdup (vptr); + value = strdup (wsp->ws_paramv[param_idx]); if (!value) rc = WRDSE_NOSPACE; + else + rc = WRDSE_OK; } @@ -1268,7 +1381,22 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, } - else if (wsp->ws_flags & WRDSF_GETVAR) - rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure); else - rc = WRDSE_UNDEF; - + { + rc = wordsplit_find_env (wsp, str, i, &vptr); + if (rc == WRDSE_OK) + { + if (vptr) + { + value = strdup (vptr); + if (!value) + rc = WRDSE_NOSPACE; + } + else + rc = WRDSE_UNDEF; + } + else if (wsp->ws_flags & WRDSF_GETVAR) + rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure); + else + rc = WRDSE_UNDEF; + } + if (rc == WRDSE_OK @@ -1323,3 +1451,13 @@ expvar (struct wordsplit *wsp, const char *str, size_t len, if (defstr[-1] == '=') - wsplt_assign_var (wsp, str, i, value); + { + if (is_param) + rc = wsplt_assign_param (wsp, param_idx, value); + else + rc = wsplt_assign_var (wsp, str, i, value); + } + if (rc) + { + free (value); + return rc; + } } @@ -1460,3 +1598,3 @@ begin_var_p (int c) { - return c == '{' || ISVARBEG (c) || ISDIGIT (c); + return c == '{' || c == '#' || ISVARBEG (c) || ISDIGIT (c); } @@ -2108,5 +2246,2 @@ scan_word (struct wordsplit *wsp, size_t start, int consume_all) -#define to_num(c) \ - (ISDIGIT(c) ? c - '0' : (ISXDIGIT(c) ? toupper(c) - 'A' + 10 : 255 )) - static int @@ -2500,3 +2635,3 @@ wordsplit_free_envbuf (struct wordsplit *ws) { - if (ws->ws_flags & WRDSF_NOCMD) + if (!(ws->ws_flags & WRDSF_ENV)) return; @@ -2515,2 +2650,19 @@ wordsplit_free_envbuf (struct wordsplit *ws) void +wordsplit_free_parambuf (struct wordsplit *ws) +{ + if (!(ws->ws_options & WRDSO_PARAMV)) + return; + if (ws->ws_parambuf) + { + size_t i; + + for (i = 0; ws->ws_parambuf[i]; i++) + free (ws->ws_parambuf[i]); + free (ws->ws_parambuf); + ws->ws_paramidx = ws->ws_paramsiz = 0; + ws->ws_parambuf = NULL; + } +} + +void wordsplit_clearerr (struct wordsplit *ws) @@ -2531,2 +2683,3 @@ wordsplit_free (struct wordsplit *ws) wordsplit_free_envbuf (ws); + wordsplit_free_parambuf (ws); } @@ -2559,3 +2712,5 @@ const char *_wordsplit_errstr[] = { N_("unbalanced parenthesis"), - N_("globbing error") + N_("globbing error"), + N_("user-defined error"), + N_("invalid parameter number in assignment") }; diff --git a/tests/wordsplit.at b/tests/wordsplit.at index 1f2e80d..ebd168b 100644 --- a/tests/wordsplit.at +++ b/tests/wordsplit.at @@ -110,3 +110,3 @@ dnl Test worsplit-specific behavior dnl ------------------------------------------------------------ -TESTWSP([append],[wsp-append],[append], +TESTWSP([append],[wsp-append],[-append], [jeden dwa trzy @@ -135,3 +135,3 @@ TOTAL: 2 -TESTWSP([dooffs],[wsp-doofs ],[dooffs 3 jeden dwa trzy], +TESTWSP([dooffs],[wsp-doofs ],[-dooffs jeden dwa trzy], [cztery piec], @@ -216,3 +216,3 @@ TOTAL: 3 -TESTWSP([undefined variables 2],[],[keepundef], +TESTWSP([undefined variables 2],[],[-keepundef], [a $FOO test a${FOO}b], @@ -228,3 +228,3 @@ TOTAL: 4 -TESTWSP([warn about undefined variables],[],[warnundef], +TESTWSP([warn about undefined variables],[],[-warnundef], [$FOO], @@ -237,3 +237,3 @@ TOTAL: 0 -TESTWSP([bail out on undefined variables],[],[undef], +TESTWSP([bail out on undefined variables],[],[-undef], [$FOO], @@ -244,3 +244,3 @@ TESTWSP([bail out on undefined variables],[],[undef], -TESTWSP([disable variable expansion],[],[novar], +TESTWSP([disable variable expansion],[],[-novar], [$FOO], @@ -254,3 +254,3 @@ TOTAL: 1 TESTWSP([K/V environment],[wsp-env-kv wsp-env_kv], -[env_kv], +[-env_kv], [$FOO a$BAZ], @@ -264,3 +264,3 @@ TOTAL: 2 -TESTWSP([nosplit with variable expansion],[wsp-var-nosplit],[nosplit], +TESTWSP([nosplit with variable expansion],[wsp-var-nosplit],[-nosplit], [a $FOO test], @@ -273,3 +273,3 @@ TOTAL: 1 -TESTWSP([nosplit without variable expansion],[],[nosplit novar], +TESTWSP([nosplit without variable expansion],[],[-nosplit -novar], [a $FOO test], @@ -282,3 +282,3 @@ TOTAL: 1 -TESTWSP([nosplit: empty expansion],[],[nosplit trimnl], +TESTWSP([nosplit: empty expansion],[],[-nosplit -trimnl], [$FOO], @@ -291,11 +291,2 @@ TOTAL: 1 -TESTWSP([default value (defined)],[],[], -[${FOO:-bar}], -[NF: 1 -0: qux -TOTAL: 1 -], -[], -[FOO=qux]) - TESTWSP([default value],[],[], @@ -453,3 +444,3 @@ WSPGROUP() -TESTWSP([ignore quotes],[wsp-ignore-quotes ],[-quote], +TESTWSP([ignore quotes],[wsp-ignore-quotes ],[-noquote], ["a text"], @@ -464,3 +455,3 @@ WSPGROUP(wsp-delim) TESTWSP([custom delimiters (squeeze)],[], -[delim : -ws trimnl], +[-delim : -nows -trimnl], [semicolon: separated::list: of :words], @@ -476,3 +467,3 @@ TOTAL: 5 TESTWSP([custom delimiters (no squeeze)],[], -[delim : -ws -squeeze_delims trimnl], +[-delim : -nows -nosqueeze_delims -trimnl], [semicolon: separated::list: of :words], @@ -489,3 +480,3 @@ TOTAL: 6 TESTWSP([custom, with returned delimiters],[], -[delim : -ws trimnl return_delims], +[-delim : -nows -trimnl -return_delims], [semicolon: separated::list: of :words], @@ -505,3 +496,3 @@ TOTAL: 9 TESTWSP([custom, with returned & squeezed delimiters],[], -[delim : -ws trimnl return_delims -squeeze_delims], +[-delim : -nows -trimnl -return_delims -nosqueeze_delims], [semicolon: separated::list: of :words], @@ -523,3 +514,3 @@ WSPGROUP(wsp-sed) -TESTWSP([sed expressions],[],[sed], +TESTWSP([sed expressions],[],[-sed], [arg1 s/foo/bar/g;s/bar baz/quz quux/ arg2], @@ -534,3 +525,3 @@ WSPGROUP() -TESTWSP([C escapes on],[wcp-c-escape],[cescapes], +TESTWSP([C escapes on],[wcp-c-escape],[-cescapes], [a\ttab form\ffeed and new\nline], @@ -544,3 +535,3 @@ TOTAL: 4 -TESTWSP([C escapes off],[wcp-c-escape-off],[-cescapes], +TESTWSP([C escapes off],[wcp-c-escape-off],[-nocescapes], [a\ttab form\ffeed and new\nline], @@ -554,3 +545,3 @@ TOTAL: 4 -TESTWSP([ws elimination],[wsp-ws-elim],[delim ' ()' ws return_delims], +TESTWSP([ws elimination],[wsp-ws-elim],[-delim ' ()' -ws -return_delims], [( list items )], @@ -565,3 +556,3 @@ TOTAL: 4 TESTWSP([ws elimination + return delim],[wsp-ws-elim-ret], -[-default novar nocmd delim ":," return_delims ws dquote], +[-nodefault -novar -nocmd -delim ":," -return_delims -ws -dquote], ["foo" : "bar", "quux" : "baaz" ], @@ -578,3 +569,3 @@ TOTAL: 7 -TESTWSP([empty quotes],[wsp-empty-quotes],[delim : ws return_delims], +TESTWSP([empty quotes],[wsp-empty-quotes],[-delim : -ws -return_delims], [t=""], @@ -586,3 +577,3 @@ TOTAL: 1 TESTWSP([delimiter following empty quotes], -[],[delim : ws return_delims], +[],[-delim : -ws -return_delims], [t="":r], @@ -597,3 +588,3 @@ TESTWSP([suppress ws trimming within quotes], [], -[default delim , ws return_delims], +[-default -delim , -ws -return_delims], [nocomponent,nonewline, formatfield="In message %{text}, "], @@ -610,3 +601,3 @@ TESTWSP([unescape], [wsp-unescape wsp-unescape-simple], -[-default novar nocmd quote escape ':+:\\""'], +[-nodefault -novar -nocmd -quote -escape ':+:\\""'], [\Seen "quote \"" "bs \\"], @@ -621,3 +612,3 @@ TESTWSP([unescape: word/quote], [wsp-unescape wsp-unescape-word], -[-default novar nocmd quote escape-word '\\""' escape-quote ':+0x:\\""'], +[-nodefault -novar -nocmd -quote -escape-word '\\""' -escape-quote ':+0x:\\""'], [\Seen "quote \"" "bs \\" "3\x31 \101" 3\x31 \101], @@ -633,3 +624,3 @@ TOTAL: 6 -TESTWSP([dquote],[],[-default novar nocmd dquote], +TESTWSP([dquote],[],[-nodefault -novar -nocmd -dquote], [a "quoted example" isn't it], @@ -643,3 +634,3 @@ TOTAL: 4 -TESTWSP([squote],[],[-default novar nocmd squote], +TESTWSP([squote],[],[-nodefault -novar -nocmd -squote], [a 'quoted example' isn"t it], @@ -655,3 +646,3 @@ WSPGROUP(wsp-incr) -TESTWSP([incremental],[],[incremental], +TESTWSP([incremental],[],[-incremental], [incremental "input test" line @@ -673,3 +664,3 @@ TOTAL: 3 -TESTWSP([incremental append],[],[incremental append], +TESTWSP([incremental append],[],[-incremental -append], [incremental "input test" line @@ -695,3 +686,3 @@ TOTAL: 3 TESTWSP([incremental ws], -[],[return_delims -squeeze_delims incremental ws], +[],[-return_delims -nosqueeze_delims -incremental -ws], [a list test @@ -713,3 +704,3 @@ TOTAL: 3 -TESTWSP([incremental nosplit],[],[incremental nosplit], +TESTWSP([incremental nosplit],[],[-incremental -nosplit], [incremental "input test" line @@ -723,3 +714,3 @@ TOTAL: 1 -TESTWSP([simple command substitution],[],[-nocmd], +TESTWSP([simple command substitution],[],[-cmd], [begin $(words a b) end], @@ -733,3 +724,3 @@ TOTAL: 4 -TESTWSP([quoted command substitution],[],[-nocmd], +TESTWSP([quoted command substitution],[],[-cmd], [begin "$(words a b)" end], @@ -742,3 +733,3 @@ TOTAL: 3 -TESTWSP([coalesced command substitution],[],[-nocmd], +TESTWSP([coalesced command substitution],[],[-cmd], [begin($(words a b))end], @@ -750,3 +741,3 @@ TOTAL: 2 -TESTWSP([quoted coalesced command substitution],[],[-nocmd], +TESTWSP([quoted coalesced command substitution],[],[-cmd], ["begin($(words a b))end"], @@ -757,3 +748,3 @@ TOTAL: 1 -TESTWSP([variable and command substitution],[],[-nocmd -novar], +TESTWSP([variable and command substitution],[],[-cmd -var], [begin $X $(words $X $Y) end], @@ -768,3 +759,3 @@ TOTAL: 5 -TESTWSP([variable expansion and command substitution in quotes],[],[-nocmd -novar], +TESTWSP([variable expansion and command substitution in quotes],[],[-cmd -var], ["${BEGIN}($(words $X $Y))end"], @@ -775,3 +766,3 @@ TOTAL: 1 -TESTWSP([nested commands],[],[-nocmd -novar], +TESTWSP([nested commands],[],[-cmd -var], [$(words output $(words in$SUFFIX text) end)], @@ -794,3 +785,3 @@ mkdir dir -wsp pathexpand<<'EOT' +wsp -pathexpand<<'EOT' begin dir/*.c end @@ -815,3 +806,3 @@ mkdir dir -wsp pathexpand<<'EOT' +wsp -pathexpand<<'EOT' begin dir/*.d end @@ -835,3 +826,3 @@ mkdir dir -wsp pathexpand nullglob<<'EOT' +wsp -pathexpand -nullglob<<'EOT' begin dir/*.d end @@ -854,3 +845,3 @@ mkdir dir -wsp pathexpand failglob<<'EOT' +wsp -pathexpand -failglob<<'EOT' begin dir/*.d end @@ -864,3 +855,3 @@ AT_CLEANUP -TESTWSP([append],[],[-- extra arguments follow], +TESTWSP([append],[],[-append-args extra arguments follow], [some words and], @@ -877,3 +868,3 @@ TOTAL: 3 TESTWSP([append + dooffs + env],[], -[-env dooffs 2 preface words V=2 -- extra arguments follow], +[-env none -dooffs preface words -- V=2 -append-args extra arguments follow], [some words and var=$V], @@ -894,3 +885,3 @@ TOTAL: 4 TESTWSP([maxwords],[], -[trimnl maxwords 3], +[-trimnl -maxwords 3], [ws_maxwords limits the number of returned words], @@ -904,3 +895,3 @@ TOTAL: 3 TESTWSP([maxwords return_delims],[], -[trimnl maxwords 8 return_delims delim :-], +[-trimnl -maxwords 8 -return_delims -delim :-], [foo:::bar-:baz-quux:ux:zu], @@ -919,3 +910,3 @@ TOTAL: 8 TESTWSP([maxwords return_delims -squeeze_delims],[], -[trimnl maxwords 8 return_delims -squeeze_delims delim :-], +[-trimnl -maxwords 8 -return_delims -nosqueeze_delims -delim :-], [foo:::bar-:baz:qux-], @@ -934,3 +925,3 @@ TOTAL: 8 TESTWSP([maxwords incremental],[], -[trimnl maxwords 3 incremental], +[-trimnl -maxwords 3 -incremental], [foo bar baz qux uz @@ -952,3 +943,3 @@ TOTAL: 3 -TESTWSP([variable nosplit],[],[novar novarsplit], +TESTWSP([variable nosplit],[],[-novar -novarsplit], [begin ${VAR:- a b} end], @@ -961,3 +952,3 @@ TOTAL: 3 -TESTWSP([command nosplit],[],[nocmd nocmdsplit], +TESTWSP([command nosplit],[],[-nocmd -nocmdsplit], [begin $(words a b) end], @@ -970,2 +961,15 @@ TOTAL: 3 +TESTWSP([positional parameters],[],[one two three four five six seven eight nine ten eleven twelve], +[$0 $5 ${10} +$#], +[NF: 3 +0: one +1: six +2: eleven +TOTAL: 3 +NF: 1 +0: 12 +TOTAL: 1 +]) + m4_popdef([TESTWSP]) diff --git a/tests/wsp.c b/tests/wsp.c index bd13e63..958d01f 100644 --- a/tests/wsp.c +++ b/tests/wsp.c @@ -1,3 +1,3 @@ /* grecs - Gray's Extensible Configuration System - Copyright (C) 2014-2016 Sergey Poznyakoff + Copyright (C) 2014-2019 Sergey Poznyakoff @@ -31,62 +31,180 @@ char *progname; -struct kwd +/* Global options */ +enum + { + TRIMNL_OPTION = 0x01, /* Remove trailing newline */ + PLAINTEXT_OPTION = 0x02 /* Print intput verbatim (no escapes) */ + }; + +/* Environment types */ +enum env_type + { + env_none, /* No environment */ + env_null, /* Null environment */ + env_sys /* Use system environment */ + }; + +struct wsclosure { - const char *name; - int tok; + int options; /* Global options */ + struct wordsplit ws; /* The wordsplit structure */ + int wsflags; /* Wordsplit flags */ + enum env_type env_type; /* Environment type */ + int offarg; /* Index of the first of the initial words in + the argv array. The ws.ws_dooffs field gives + the number of such variables. Forces the + WRDSF_DOOFFS flag. */ + char **fenvbase; /* Environment for testing the ws_getenv function */ + int fenvidx; /* Number of variables in fenvbase */ + int fenvmax; /* Size of fenbase (entries) */ + int append_start; /* First argument to append (index in argv) */ + int append_count; /* Number of arguments to append */ }; -struct kwd bool_keytab[] = { - { "append", WRDSF_APPEND }, - /*{ "reuse", WRDSF_REUSE },*/ - { "undef", WRDSF_UNDEF }, - { "novar", WRDSF_NOVAR }, - { "nocmd", WRDSF_NOCMD }, - { "ws", WRDSF_WS }, - { "quote", WRDSF_QUOTE }, - { "squote", WRDSF_SQUOTE }, - { "dquote", WRDSF_DQUOTE }, - { "squeeze_delims", WRDSF_SQUEEZE_DELIMS }, - { "return_delims", WRDSF_RETURN_DELIMS }, - { "sed", WRDSF_SED_EXPR }, - { "debug", WRDSF_SHOWDBG }, - { "nosplit", WRDSF_NOSPLIT }, - { "keepundef", WRDSF_KEEPUNDEF }, - { "warnundef", WRDSF_WARNUNDEF }, - { "cescapes", WRDSF_CESCAPES }, - { "default", WRDSF_DEFFLAGS }, - { "env_kv", WRDSF_ENV_KV }, - { "incremental", WRDSF_INCREMENTAL }, - { "pathexpand", WRDSF_PATHEXPAND }, - { NULL, 0 } -}; +/* Command line option types */ +enum + { + ws_no_argument, /* Option requires no arguments */ + ws_boolean, /* Option is boolean (can be prefixed with -no) */ + ws_required_argument, /* Option requires one argument */ + ws_multiple_arguments /* Option takes multiple arguments, terminated with + "--" or end of argument list */ + }; -struct kwd opt_keytab[] = { - { "nullglob", WRDSO_NULLGLOB }, - { "failglob", WRDSO_FAILGLOB }, - { "dotglob", WRDSO_DOTGLOB }, - { "bskeep_words", WRDSO_BSKEEP_WORD }, - { "bskeep_quote", WRDSO_BSKEEP_QUOTE }, - { "bskeep", WRDSO_BSKEEP_WORD|WRDSO_BSKEEP_QUOTE }, - { "novarsplit", WRDSO_NOVARSPLIT }, - { "nocmdsplit", WRDSO_NOCMDSPLIT }, - { NULL, 0 } +/* Structure describing a single command-line option */ +struct wsopt +{ + const char *name; /* Option name */ + int tok; /* Corresponding flag */ + int arg; /* Option type (see the enum above) */ + void (*setfn) (int tok, int neg, char *arg, struct wsclosure *wsc); + /* Setter function */ }; -struct kwd string_keytab[] = { - { "delim", WRDSF_DELIM }, - { "comment", WRDSF_COMMENT }, - { "escape", WRDSF_ESCAPE }, - { NULL, 0 } -}; +/* Index of the next argument in the argv */ +static int wsoptind = -1; +/* Parse next argument from the command line. Return EOF on end of arguments + or when the "--" argument is seen. */ static int -kwxlat (struct kwd *kwp, const char *str, int *res) +getwsopt (int argc, char **argv, struct wsopt *wso, struct wsclosure *wsc) { - for (; kwp->name; kwp++) - if (strcmp (kwp->name, str) == 0) - { - *res = kwp->tok; - return 0; - } - return -1; + int negate = 0; + char *opt; + + if (wsoptind == -1) + wsoptind = 1; + if (wsoptind == argc) + return EOF; + + opt = argv[wsoptind++]; + if (strcmp (opt, "--") == 0) + return EOF; + if (*opt != '-') + { + if (strchr (opt, '=')) + { + assert (wsc->fenvidx < wsc->fenvmax - 1); + wsc->fenvbase[wsc->fenvidx++] = opt; + return 0; + } + wsoptind--; + return EOF; + } + opt++; /* skip past initial dash */ + if (strncmp (opt, "no-", 3) == 0) + { + negate = 1; + opt += 3; + } + else if (strncmp (opt, "no", 2) == 0) + { + negate = 1; + opt += 2; + } + + for (; wso->name; wso++) + { + if (wso->arg == ws_boolean && wso->name[0] == 'n' && wso->name[1] == 'o' + && strcmp (wso->name + 2, opt) == 0) + { + negate ^= 1; + break; + } + if (strcmp (wso->name, opt) == 0) + break; + } + + if (wso->name) + { + char *arg; + if (wso->arg == ws_multiple_arguments) + { + while (1) + { + if (wsoptind == argc) + break; + arg = argv[wsoptind++]; + if (strcmp (arg, "--") == 0) + break; + wso->setfn (wso->tok, negate, arg, wsc); + } + } + else + { + if (wso->arg == ws_required_argument) + { + if (wsoptind == argc) + { + fprintf (stderr, "%s: missing arguments for -%s\n", + progname, opt); + exit (1); + } + arg = argv[wsoptind++]; + } + wso->setfn (wso->tok, negate, arg, wsc); + } + return 0; + } + + fprintf (stderr, "%s: unrecognized option: -%s\n", + progname, opt); + fprintf (stderr, "%s: try %s -help for more detail\n", + progname, progname); + exit (1); +} + +/* Setter functions for various options */ + +static void +setfn_flag (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + if (neg) + wsc->wsflags &= ~flag; + else + wsc->wsflags |= flag; +} + +static void +setfn_option (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + wsc->wsflags |= WRDSF_OPTIONS; + if (neg) + wsc->ws.ws_options &= ~flag; + else + wsc->ws.ws_options |= flag; +} + +static void +setfn_delim (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + wsc->wsflags |= flag; + wsc->ws.ws_delim = arg; +} + +static void +setfn_comment (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + wsc->wsflags |= flag; + wsc->ws.ws_comment = arg; } @@ -94,3 +212,195 @@ kwxlat (struct kwd *kwp, const char *str, int *res) static void -help () +set_escape_string (wordsplit_t *ws, int *wsflags, int q, const char *str) +{ + if (*str == ':') + { + while (*++str != ':') + { + int f; + switch (*str) + { + case '+': + f = WRDSO_BSKEEP; + break; + + case '0': + f = WRDSO_OESC; + break; + + case 'x': + f = WRDSO_XESC; + break; + + default: + fprintf (stderr, "%s: invalid escape flag near %s\n", + progname, str); + abort (); + } + WRDSO_ESC_SET (ws, q, f); + } + *wsflags |= WRDSF_OPTIONS; + ++str; + } + ws->ws_escape[q] = str; +} + +static void +setfn_escape (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + wsc->wsflags |= flag; + set_escape_string (&wsc->ws, &wsc->wsflags, 0, arg); + set_escape_string (&wsc->ws, &wsc->wsflags, 1, arg); +} + +static void +setfn_escape_qw (char *arg, int quote, struct wsclosure *wsc) +{ + if (!(wsc->wsflags & WRDSF_ESCAPE)) + { + wsc->wsflags |= WRDSF_ESCAPE; + wsc->ws.ws_escape[!quote] = NULL; + } + set_escape_string (&wsc->ws, &wsc->wsflags, quote, arg); +} + +static void +setfn_escape_word (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + setfn_escape_qw (arg, 0, wsc); +} + +static void +setfn_escape_quote (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + setfn_escape_qw (arg, 1, wsc); +} + +static void +setfn_maxwords (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + char *p; + + wsc->wsflags |= WRDSF_OPTIONS; + wsc->ws.ws_options |= WRDSO_MAXWORDS; + + wsc->ws.ws_maxwords = strtoul (arg, &p, 10); + if (*p) + { + fprintf (stderr, "%s: invalid number: %s\n", progname, arg); + exit (1); + } +} + +static void +setfn_global (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + if (neg) + wsc->options &= ~flag; + else + wsc->options |= flag; +} + +static void +setfn_env (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + if (strcmp (arg, "none") == 0) + wsc->env_type = env_none; + else if (strcmp (arg, "null") == 0) + wsc->env_type = env_null; + else if (strcmp (arg, "sys") == 0) + wsc->env_type = env_sys; + else + { + fprintf (stderr, "%s: environment flag: %s\n", progname, arg); + exit (1); + } +} + +static void +setfn_dooffs (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + if (!(wsc->wsflags & flag)) + { + wsc->wsflags |= flag; + wsc->offarg = wsoptind - 1; + wsc->ws.ws_offs = 0; + } + wsc->ws.ws_offs++; +} + +static void +setfn_append (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + if (wsc->append_count == 0) + wsc->append_start = wsoptind - 1; + wsc->append_count++; +} + +static void help (void); + +static void +setfn_help (int flag, int neg, char *arg, struct wsclosure *wsc) +{ + help (); + exit (0); +} + +/* Available options: */ +struct wsopt opttab[] = { + /* Global options */ + { "trimnl", TRIMNL_OPTION, ws_boolean, setfn_global }, + { "plaintext", PLAINTEXT_OPTION, ws_boolean, setfn_global }, + { "env", 0, ws_required_argument, setfn_env }, + + /* Wordsplit flags */ + { "append", WRDSF_APPEND, ws_boolean, setfn_flag }, + /*{ "reuse", WRDSF_REUSE, ws_boolean, setfn_flag },*/ + { "undef", WRDSF_UNDEF, ws_boolean, setfn_flag }, + { "novar", WRDSF_NOVAR, ws_boolean, setfn_flag }, + { "nocmd", WRDSF_NOCMD, ws_boolean, setfn_flag }, + { "ws", WRDSF_WS, ws_boolean, setfn_flag }, + { "quote", WRDSF_QUOTE, ws_boolean, setfn_flag }, + { "squote", WRDSF_SQUOTE, ws_boolean, setfn_flag }, + { "dquote", WRDSF_DQUOTE, ws_boolean, setfn_flag }, + { "squeeze_delims", WRDSF_SQUEEZE_DELIMS, ws_boolean, setfn_flag }, + { "return_delims", WRDSF_RETURN_DELIMS, ws_boolean, setfn_flag }, + { "sed", WRDSF_SED_EXPR, ws_boolean, setfn_flag }, + { "debug", WRDSF_SHOWDBG, ws_boolean, setfn_flag }, + { "nosplit", WRDSF_NOSPLIT, ws_boolean, setfn_flag }, + { "keepundef", WRDSF_KEEPUNDEF, ws_boolean, setfn_flag }, + { "warnundef", WRDSF_WARNUNDEF, ws_boolean, setfn_flag }, + { "cescapes", WRDSF_CESCAPES, ws_boolean, setfn_flag }, + { "default", WRDSF_DEFFLAGS, ws_boolean, setfn_flag }, + { "env_kv", WRDSF_ENV_KV, ws_boolean, setfn_flag }, + { "incremental", WRDSF_INCREMENTAL, ws_boolean, setfn_flag }, + { "pathexpand", WRDSF_PATHEXPAND, ws_boolean, setfn_flag }, + { "default", WRDSF_DEFFLAGS, ws_boolean, setfn_flag }, + /* Wordsplit options */ + { "nullglob", WRDSO_NULLGLOB, ws_boolean, setfn_option }, + { "failglob", WRDSO_FAILGLOB, ws_boolean, setfn_option }, + { "dotglob", WRDSO_DOTGLOB, ws_boolean, setfn_option }, + { "bskeep_words", WRDSO_BSKEEP_WORD, ws_boolean, setfn_option }, + { "bskeep_quote", WRDSO_BSKEEP_QUOTE, ws_boolean, setfn_option }, + { "bskeep", WRDSO_BSKEEP_WORD|WRDSO_BSKEEP_QUOTE, + ws_boolean, setfn_option }, + { "novarsplit", WRDSO_NOVARSPLIT, ws_boolean, setfn_option }, + { "nocmdsplit", WRDSO_NOCMDSPLIT, ws_boolean, setfn_option }, + { "maxwords", WRDSO_MAXWORDS, ws_required_argument, setfn_maxwords }, + /* String options */ + { "delim", WRDSF_DELIM, ws_required_argument, setfn_delim }, + { "comment", WRDSF_COMMENT,ws_required_argument, setfn_comment }, + { "escape", WRDSF_ESCAPE, ws_required_argument, setfn_escape }, + { "escape-word", WRDSF_ESCAPE, ws_required_argument, setfn_escape_word }, + { "escape-quote", WRDSF_ESCAPE, ws_required_argument, setfn_escape_quote }, + + { "dooffs", WRDSF_DOOFFS, ws_multiple_arguments, setfn_dooffs }, + { "append-args", 0, ws_multiple_arguments, setfn_append }, + + { "help", 0, ws_no_argument, setfn_help }, + + { NULL, 0 } +}; + +static void +help (void) { @@ -100,26 +410,25 @@ help () printf ("options are:\n"); - printf (" [-]trimnl\n"); - printf (" [-]plaintext\n"); - printf (" -env\n"); - printf (" env sys|none|null\n"); - putchar ('\n'); - for (i = 0; bool_keytab[i].name; i++) - printf (" [-]%s\n", bool_keytab[i].name); - putchar ('\n'); - for (i = 0; string_keytab[i].name; i++) + for (i = 0; opttab[i].name; i++) { - printf (" -%s\n", string_keytab[i].name); - printf (" %s ARG\n", string_keytab[i].name); - } - printf (" escape-word ARG\n"); - printf (" escape-quote ARG\n"); - putchar ('\n'); - for (i = 0; opt_keytab[i].name; i++) - { - printf (" [-]%s\n", opt_keytab[i].name); + printf (" -"); + if (opttab[i].arg == ws_boolean) + printf ("[no]"); + if (strncmp (opttab[i].name, "no", 2) == 0) + printf ("%s", opttab[i].name + 2); + else + printf ("%s", opttab[i].name); + switch (opttab[i].arg) + { + case ws_no_argument: + case ws_boolean: + break; + case ws_required_argument: + printf(" ARG"); + break; + case ws_multiple_arguments: + printf(" ARGS... --"); + } + putchar ('\n'); } putchar ('\n'); - printf (" -dooffs\n"); - printf (" dooffs COUNT ARGS...\n"); - exit (0) |