xref: /third_party/mksh/eval.c (revision c84f3f3c)
1c84f3f3cSopenharmony_ci/*	$OpenBSD: eval.c,v 1.40 2013/09/14 20:09:30 millert Exp $	*/
2c84f3f3cSopenharmony_ci
3c84f3f3cSopenharmony_ci/*-
4c84f3f3cSopenharmony_ci * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
5c84f3f3cSopenharmony_ci *		 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,
6c84f3f3cSopenharmony_ci *		 2019, 2020
7c84f3f3cSopenharmony_ci *	mirabilos <m@mirbsd.org>
8c84f3f3cSopenharmony_ci *
9c84f3f3cSopenharmony_ci * Provided that these terms and disclaimer and all copyright notices
10c84f3f3cSopenharmony_ci * are retained or reproduced in an accompanying document, permission
11c84f3f3cSopenharmony_ci * is granted to deal in this work without restriction, including un-
12c84f3f3cSopenharmony_ci * limited rights to use, publicly perform, distribute, sell, modify,
13c84f3f3cSopenharmony_ci * merge, give away, or sublicence.
14c84f3f3cSopenharmony_ci *
15c84f3f3cSopenharmony_ci * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
16c84f3f3cSopenharmony_ci * the utmost extent permitted by applicable law, neither express nor
17c84f3f3cSopenharmony_ci * implied; without malicious intent or gross negligence. In no event
18c84f3f3cSopenharmony_ci * may a licensor, author or contributor be held liable for indirect,
19c84f3f3cSopenharmony_ci * direct, other damage, loss, or other issues arising in any way out
20c84f3f3cSopenharmony_ci * of dealing in the work, even if advised of the possibility of such
21c84f3f3cSopenharmony_ci * damage or existence of a defect, except proven that it results out
22c84f3f3cSopenharmony_ci * of said person's immediate fault when using the work as intended.
23c84f3f3cSopenharmony_ci */
24c84f3f3cSopenharmony_ci
25c84f3f3cSopenharmony_ci#include "sh.h"
26c84f3f3cSopenharmony_ci
27c84f3f3cSopenharmony_ci__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.231 2020/05/05 21:34:27 tg Exp $");
28c84f3f3cSopenharmony_ci
29c84f3f3cSopenharmony_ci/*
30c84f3f3cSopenharmony_ci * string expansion
31c84f3f3cSopenharmony_ci *
32c84f3f3cSopenharmony_ci * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution.
33c84f3f3cSopenharmony_ci * second pass: alternation ({,}), filename expansion (*?[]).
34c84f3f3cSopenharmony_ci */
35c84f3f3cSopenharmony_ci
36c84f3f3cSopenharmony_ci/* expansion generator state */
37c84f3f3cSopenharmony_citypedef struct {
38c84f3f3cSopenharmony_ci	/* not including an "int type;" member, see expand() */
39c84f3f3cSopenharmony_ci	/* string */
40c84f3f3cSopenharmony_ci	const char *str;
41c84f3f3cSopenharmony_ci	/* source */
42c84f3f3cSopenharmony_ci	union {
43c84f3f3cSopenharmony_ci		/* string[] */
44c84f3f3cSopenharmony_ci		const char **strv;
45c84f3f3cSopenharmony_ci		/* file */
46c84f3f3cSopenharmony_ci		struct shf *shf;
47c84f3f3cSopenharmony_ci	} u;
48c84f3f3cSopenharmony_ci	/* variable in ${var...} */
49c84f3f3cSopenharmony_ci	struct tbl *var;
50c84f3f3cSopenharmony_ci	/* split "$@" / call waitlast in $() */
51c84f3f3cSopenharmony_ci	bool split;
52c84f3f3cSopenharmony_ci} Expand;
53c84f3f3cSopenharmony_ci
54c84f3f3cSopenharmony_ci#define XBASE		0	/* scanning original string */
55c84f3f3cSopenharmony_ci#define XARGSEP		1	/* ifs0 between "$*" */
56c84f3f3cSopenharmony_ci#define XARG		2	/* expanding $*, $@ */
57c84f3f3cSopenharmony_ci#define XCOM		3	/* expanding $() */
58c84f3f3cSopenharmony_ci#define XNULLSUB	4	/* "$@" when $# is 0, so don't generate word */
59c84f3f3cSopenharmony_ci#define XSUB		5	/* expanding ${} string */
60c84f3f3cSopenharmony_ci#define XSUBMID		6	/* middle of expanding ${}; must be XSUB+1 */
61c84f3f3cSopenharmony_ci#define XSUBPAT		7	/* expanding [[ x = ${} ]] string */
62c84f3f3cSopenharmony_ci#define XSUBPATMID	8	/* middle, must be XSUBPAT+1 */
63c84f3f3cSopenharmony_ci
64c84f3f3cSopenharmony_ci#define isXSUB(t)	((t) == XSUB || (t) == XSUBPAT)
65c84f3f3cSopenharmony_ci
66c84f3f3cSopenharmony_ci/* States used for field splitting */
67c84f3f3cSopenharmony_ci#define IFS_WORD	0	/* word has chars (or quotes except "$@") */
68c84f3f3cSopenharmony_ci#define IFS_WS		1	/* have seen IFS white-space */
69c84f3f3cSopenharmony_ci#define IFS_NWS		2	/* have seen IFS non-white-space */
70c84f3f3cSopenharmony_ci#define IFS_IWS		3	/* beginning of word, ignore IFS WS */
71c84f3f3cSopenharmony_ci#define IFS_QUOTE	4	/* beg.w/quote, become IFS_WORD unless "$@" */
72c84f3f3cSopenharmony_ci
73c84f3f3cSopenharmony_ci#define STYPE_CHAR	0xFF
74c84f3f3cSopenharmony_ci#define STYPE_DBL	0x100
75c84f3f3cSopenharmony_ci#define STYPE_AT	0x200
76c84f3f3cSopenharmony_ci#define STYPE_SINGLE	0x2FF
77c84f3f3cSopenharmony_ci#define STYPE_MASK	0x300
78c84f3f3cSopenharmony_ci
79c84f3f3cSopenharmony_cistatic int varsub(Expand *, const char *, const char *, unsigned int *, int *);
80c84f3f3cSopenharmony_cistatic int comsub(Expand *, const char *, int);
81c84f3f3cSopenharmony_cistatic char *valsub(struct op *, Area *);
82c84f3f3cSopenharmony_cistatic char *trimsub(char *, char *, int);
83c84f3f3cSopenharmony_cistatic void glob(char *, XPtrV *, bool);
84c84f3f3cSopenharmony_cistatic void globit(XString *, char **, char *, XPtrV *, int);
85c84f3f3cSopenharmony_cistatic const char *maybe_expand_tilde(const char *, XString *, char **, bool);
86c84f3f3cSopenharmony_ci#ifndef MKSH_NOPWNAM
87c84f3f3cSopenharmony_cistatic char *homedir(char *);
88c84f3f3cSopenharmony_ci#endif
89c84f3f3cSopenharmony_cistatic void alt_expand(XPtrV *, char *, char *, char *, int);
90c84f3f3cSopenharmony_cistatic int utflen(const char *) MKSH_A_PURE;
91c84f3f3cSopenharmony_cistatic void utfincptr(const char *, mksh_ari_t *);
92c84f3f3cSopenharmony_ci
93c84f3f3cSopenharmony_ci/* UTFMODE functions */
94c84f3f3cSopenharmony_cistatic int
95c84f3f3cSopenharmony_ciutflen(const char *s)
96c84f3f3cSopenharmony_ci{
97c84f3f3cSopenharmony_ci	size_t n;
98c84f3f3cSopenharmony_ci
99c84f3f3cSopenharmony_ci	if (UTFMODE) {
100c84f3f3cSopenharmony_ci		n = 0;
101c84f3f3cSopenharmony_ci		while (*s) {
102c84f3f3cSopenharmony_ci			s += utf_ptradj(s);
103c84f3f3cSopenharmony_ci			++n;
104c84f3f3cSopenharmony_ci		}
105c84f3f3cSopenharmony_ci	} else
106c84f3f3cSopenharmony_ci		n = strlen(s);
107c84f3f3cSopenharmony_ci
108c84f3f3cSopenharmony_ci	if (n > 2147483647)
109c84f3f3cSopenharmony_ci		n = 2147483647;
110c84f3f3cSopenharmony_ci	return ((int)n);
111c84f3f3cSopenharmony_ci}
112c84f3f3cSopenharmony_ci
113c84f3f3cSopenharmony_cistatic void
114c84f3f3cSopenharmony_ciutfincptr(const char *s, mksh_ari_t *lp)
115c84f3f3cSopenharmony_ci{
116c84f3f3cSopenharmony_ci	const char *cp = s;
117c84f3f3cSopenharmony_ci
118c84f3f3cSopenharmony_ci	while ((*lp)--)
119c84f3f3cSopenharmony_ci		cp += utf_ptradj(cp);
120c84f3f3cSopenharmony_ci	*lp = cp - s;
121c84f3f3cSopenharmony_ci}
122c84f3f3cSopenharmony_ci
123c84f3f3cSopenharmony_ci/* compile and expand word */
124c84f3f3cSopenharmony_cichar *
125c84f3f3cSopenharmony_cisubstitute(const char *cp, int f)
126c84f3f3cSopenharmony_ci{
127c84f3f3cSopenharmony_ci	struct source *s, *sold;
128c84f3f3cSopenharmony_ci
129c84f3f3cSopenharmony_ci	sold = source;
130c84f3f3cSopenharmony_ci	s = pushs(SWSTR, ATEMP);
131c84f3f3cSopenharmony_ci	s->start = s->str = cp;
132c84f3f3cSopenharmony_ci	source = s;
133c84f3f3cSopenharmony_ci	if (yylex(ONEWORD) != LWORD)
134c84f3f3cSopenharmony_ci		internal_errorf(Tbadsubst);
135c84f3f3cSopenharmony_ci	source = sold;
136c84f3f3cSopenharmony_ci	afree(s, ATEMP);
137c84f3f3cSopenharmony_ci	return (evalstr(yylval.cp, f));
138c84f3f3cSopenharmony_ci}
139c84f3f3cSopenharmony_ci
140c84f3f3cSopenharmony_ci/*
141c84f3f3cSopenharmony_ci * expand arg-list
142c84f3f3cSopenharmony_ci */
143c84f3f3cSopenharmony_cichar **
144c84f3f3cSopenharmony_cieval(const char **ap, int f)
145c84f3f3cSopenharmony_ci{
146c84f3f3cSopenharmony_ci	XPtrV w;
147c84f3f3cSopenharmony_ci
148c84f3f3cSopenharmony_ci	if (*ap == NULL) {
149c84f3f3cSopenharmony_ci		union mksh_ccphack vap;
150c84f3f3cSopenharmony_ci
151c84f3f3cSopenharmony_ci		vap.ro = ap;
152c84f3f3cSopenharmony_ci		return (vap.rw);
153c84f3f3cSopenharmony_ci	}
154c84f3f3cSopenharmony_ci	XPinit(w, 32);
155c84f3f3cSopenharmony_ci	/* space for shell name */
156c84f3f3cSopenharmony_ci	XPput(w, NULL);
157c84f3f3cSopenharmony_ci	while (*ap != NULL)
158c84f3f3cSopenharmony_ci		expand(*ap++, &w, f);
159c84f3f3cSopenharmony_ci	XPput(w, NULL);
160c84f3f3cSopenharmony_ci	return ((char **)XPclose(w) + 1);
161c84f3f3cSopenharmony_ci}
162c84f3f3cSopenharmony_ci
163c84f3f3cSopenharmony_ci/*
164c84f3f3cSopenharmony_ci * expand string
165c84f3f3cSopenharmony_ci */
166c84f3f3cSopenharmony_cichar *
167c84f3f3cSopenharmony_cievalstr(const char *cp, int f)
168c84f3f3cSopenharmony_ci{
169c84f3f3cSopenharmony_ci	XPtrV w;
170c84f3f3cSopenharmony_ci	char *dp = null;
171c84f3f3cSopenharmony_ci
172c84f3f3cSopenharmony_ci	XPinit(w, 1);
173c84f3f3cSopenharmony_ci	expand(cp, &w, f);
174c84f3f3cSopenharmony_ci	if (XPsize(w))
175c84f3f3cSopenharmony_ci		dp = *XPptrv(w);
176c84f3f3cSopenharmony_ci	XPfree(w);
177c84f3f3cSopenharmony_ci	return (dp);
178c84f3f3cSopenharmony_ci}
179c84f3f3cSopenharmony_ci
180c84f3f3cSopenharmony_ci/*
181c84f3f3cSopenharmony_ci * expand string - return only one component
182c84f3f3cSopenharmony_ci * used from iosetup to expand redirection files
183c84f3f3cSopenharmony_ci */
184c84f3f3cSopenharmony_cichar *
185c84f3f3cSopenharmony_cievalonestr(const char *cp, int f)
186c84f3f3cSopenharmony_ci{
187c84f3f3cSopenharmony_ci	XPtrV w;
188c84f3f3cSopenharmony_ci	char *rv;
189c84f3f3cSopenharmony_ci
190c84f3f3cSopenharmony_ci	XPinit(w, 1);
191c84f3f3cSopenharmony_ci	expand(cp, &w, f);
192c84f3f3cSopenharmony_ci	switch (XPsize(w)) {
193c84f3f3cSopenharmony_ci	case 0:
194c84f3f3cSopenharmony_ci		rv = null;
195c84f3f3cSopenharmony_ci		break;
196c84f3f3cSopenharmony_ci	case 1:
197c84f3f3cSopenharmony_ci		rv = (char *) *XPptrv(w);
198c84f3f3cSopenharmony_ci		break;
199c84f3f3cSopenharmony_ci	default:
200c84f3f3cSopenharmony_ci		rv = evalstr(cp, f & ~DOGLOB);
201c84f3f3cSopenharmony_ci		break;
202c84f3f3cSopenharmony_ci	}
203c84f3f3cSopenharmony_ci	XPfree(w);
204c84f3f3cSopenharmony_ci	return (rv);
205c84f3f3cSopenharmony_ci}
206c84f3f3cSopenharmony_ci
207c84f3f3cSopenharmony_ci/* for nested substitution: ${var:=$var2} */
208c84f3f3cSopenharmony_citypedef struct SubType {
209c84f3f3cSopenharmony_ci	struct tbl *var;	/* variable for ${var..} */
210c84f3f3cSopenharmony_ci	struct SubType *prev;	/* old type */
211c84f3f3cSopenharmony_ci	struct SubType *next;	/* poped type (to avoid re-allocating) */
212c84f3f3cSopenharmony_ci	size_t	base;		/* start position of expanded word */
213c84f3f3cSopenharmony_ci	unsigned short stype;	/* [=+-?%#] action after expanded word */
214c84f3f3cSopenharmony_ci	short	f;		/* saved value of f (DOPAT, etc) */
215c84f3f3cSopenharmony_ci	uint8_t	quotep;		/* saved value of quote (for ${..[%#]..}) */
216c84f3f3cSopenharmony_ci	uint8_t	quotew;		/* saved value of quote (for ${..[+-=]..}) */
217c84f3f3cSopenharmony_ci} SubType;
218c84f3f3cSopenharmony_ci
219c84f3f3cSopenharmony_civoid
220c84f3f3cSopenharmony_ciexpand(
221c84f3f3cSopenharmony_ci    /* input word */
222c84f3f3cSopenharmony_ci    const char *ccp,
223c84f3f3cSopenharmony_ci    /* output words */
224c84f3f3cSopenharmony_ci    XPtrV *wp,
225c84f3f3cSopenharmony_ci    /* DO* flags */
226c84f3f3cSopenharmony_ci    int f)
227c84f3f3cSopenharmony_ci{
228c84f3f3cSopenharmony_ci	int c = 0;
229c84f3f3cSopenharmony_ci	/* expansion type */
230c84f3f3cSopenharmony_ci	int type;
231c84f3f3cSopenharmony_ci	/* quoted */
232c84f3f3cSopenharmony_ci	int quote = 0;
233c84f3f3cSopenharmony_ci	/* destination string and live pointer */
234c84f3f3cSopenharmony_ci	XString ds;
235c84f3f3cSopenharmony_ci	char *dp;
236c84f3f3cSopenharmony_ci	/* source */
237c84f3f3cSopenharmony_ci	const char *sp;
238c84f3f3cSopenharmony_ci	/* second pass flags */
239c84f3f3cSopenharmony_ci	int fdo;
240c84f3f3cSopenharmony_ci	/* have word */
241c84f3f3cSopenharmony_ci	int word;
242c84f3f3cSopenharmony_ci	/* field splitting of parameter/command substitution */
243c84f3f3cSopenharmony_ci	int doblank;
244c84f3f3cSopenharmony_ci	/* expansion variables */
245c84f3f3cSopenharmony_ci	Expand x = {
246c84f3f3cSopenharmony_ci		NULL, { NULL }, NULL, 0
247c84f3f3cSopenharmony_ci	};
248c84f3f3cSopenharmony_ci	SubType st_head, *st;
249c84f3f3cSopenharmony_ci	/* record number of trailing newlines in COMSUB */
250c84f3f3cSopenharmony_ci	int newlines = 0;
251c84f3f3cSopenharmony_ci	bool saw_eq, make_magic;
252c84f3f3cSopenharmony_ci	unsigned int tilde_ok;
253c84f3f3cSopenharmony_ci	size_t len;
254c84f3f3cSopenharmony_ci	char *cp;
255c84f3f3cSopenharmony_ci
256c84f3f3cSopenharmony_ci	if (ccp == NULL)
257c84f3f3cSopenharmony_ci		internal_errorf("expand(NULL)");
258c84f3f3cSopenharmony_ci	/* for alias, readonly, set, typeset commands */
259c84f3f3cSopenharmony_ci	if ((f & DOVACHECK) && is_wdvarassign(ccp)) {
260c84f3f3cSopenharmony_ci		f &= ~(DOVACHECK | DOBLANK | DOGLOB | DOTILDE);
261c84f3f3cSopenharmony_ci		f |= DOASNTILDE | DOSCALAR;
262c84f3f3cSopenharmony_ci	}
263c84f3f3cSopenharmony_ci	if (Flag(FNOGLOB))
264c84f3f3cSopenharmony_ci		f &= ~DOGLOB;
265c84f3f3cSopenharmony_ci	if (Flag(FMARKDIRS))
266c84f3f3cSopenharmony_ci		f |= DOMARKDIRS;
267c84f3f3cSopenharmony_ci	if (Flag(FBRACEEXPAND) && (f & DOGLOB))
268c84f3f3cSopenharmony_ci		f |= DOBRACE;
269c84f3f3cSopenharmony_ci
270c84f3f3cSopenharmony_ci	/* init destination string */
271c84f3f3cSopenharmony_ci	Xinit(ds, dp, 128, ATEMP);
272c84f3f3cSopenharmony_ci	type = XBASE;
273c84f3f3cSopenharmony_ci	sp = ccp;
274c84f3f3cSopenharmony_ci	fdo = 0;
275c84f3f3cSopenharmony_ci	saw_eq = false;
276c84f3f3cSopenharmony_ci	/* must be 1/0 */
277c84f3f3cSopenharmony_ci	tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0;
278c84f3f3cSopenharmony_ci	doblank = 0;
279c84f3f3cSopenharmony_ci	make_magic = false;
280c84f3f3cSopenharmony_ci	word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
281c84f3f3cSopenharmony_ci	/* clang doesn't know OSUBST comes before CSUBST */
282c84f3f3cSopenharmony_ci	memset(&st_head, 0, sizeof(st_head));
283c84f3f3cSopenharmony_ci	st = &st_head;
284c84f3f3cSopenharmony_ci
285c84f3f3cSopenharmony_ci	while (/* CONSTCOND */ 1) {
286c84f3f3cSopenharmony_ci		Xcheck(ds, dp);
287c84f3f3cSopenharmony_ci
288c84f3f3cSopenharmony_ci		switch (type) {
289c84f3f3cSopenharmony_ci		case XBASE:
290c84f3f3cSopenharmony_ci			/* original prefixed string */
291c84f3f3cSopenharmony_ci			c = ord(*sp++);
292c84f3f3cSopenharmony_ci			switch (c) {
293c84f3f3cSopenharmony_ci			case EOS:
294c84f3f3cSopenharmony_ci				c = 0;
295c84f3f3cSopenharmony_ci				break;
296c84f3f3cSopenharmony_ci			case CHAR:
297c84f3f3cSopenharmony_ci				c = ord(*sp++);
298c84f3f3cSopenharmony_ci				break;
299c84f3f3cSopenharmony_ci			case QCHAR:
300c84f3f3cSopenharmony_ci				/* temporary quote */
301c84f3f3cSopenharmony_ci				quote |= 2;
302c84f3f3cSopenharmony_ci				c = ord(*sp++);
303c84f3f3cSopenharmony_ci				break;
304c84f3f3cSopenharmony_ci			case OQUOTE:
305c84f3f3cSopenharmony_ci				if (word != IFS_WORD)
306c84f3f3cSopenharmony_ci					word = IFS_QUOTE;
307c84f3f3cSopenharmony_ci				tilde_ok = 0;
308c84f3f3cSopenharmony_ci				quote = 1;
309c84f3f3cSopenharmony_ci				continue;
310c84f3f3cSopenharmony_ci			case CQUOTE:
311c84f3f3cSopenharmony_ci				if (word == IFS_QUOTE)
312c84f3f3cSopenharmony_ci					word = IFS_WORD;
313c84f3f3cSopenharmony_ci				quote = st->quotew;
314c84f3f3cSopenharmony_ci				continue;
315c84f3f3cSopenharmony_ci			case COMASUB:
316c84f3f3cSopenharmony_ci			case COMSUB:
317c84f3f3cSopenharmony_ci			case FUNASUB:
318c84f3f3cSopenharmony_ci			case FUNSUB:
319c84f3f3cSopenharmony_ci			case VALSUB:
320c84f3f3cSopenharmony_ci				tilde_ok = 0;
321c84f3f3cSopenharmony_ci				if (f & DONTRUNCOMMAND) {
322c84f3f3cSopenharmony_ci					word = IFS_WORD;
323c84f3f3cSopenharmony_ci					*dp++ = '$';
324c84f3f3cSopenharmony_ci					switch (c) {
325c84f3f3cSopenharmony_ci					case COMASUB:
326c84f3f3cSopenharmony_ci					case COMSUB:
327c84f3f3cSopenharmony_ci						*dp++ = '(';
328c84f3f3cSopenharmony_ci						c = ORD(')');
329c84f3f3cSopenharmony_ci						break;
330c84f3f3cSopenharmony_ci					case FUNASUB:
331c84f3f3cSopenharmony_ci					case FUNSUB:
332c84f3f3cSopenharmony_ci					case VALSUB:
333c84f3f3cSopenharmony_ci						*dp++ = '{';
334c84f3f3cSopenharmony_ci						*dp++ = c == VALSUB ? '|' : ' ';
335c84f3f3cSopenharmony_ci						c = ORD('}');
336c84f3f3cSopenharmony_ci						break;
337c84f3f3cSopenharmony_ci					}
338c84f3f3cSopenharmony_ci					while (*sp != '\0') {
339c84f3f3cSopenharmony_ci						Xcheck(ds, dp);
340c84f3f3cSopenharmony_ci						*dp++ = *sp++;
341c84f3f3cSopenharmony_ci					}
342c84f3f3cSopenharmony_ci					if ((unsigned int)c == ORD(/*{*/'}'))
343c84f3f3cSopenharmony_ci						*dp++ = ';';
344c84f3f3cSopenharmony_ci					*dp++ = c;
345c84f3f3cSopenharmony_ci				} else {
346c84f3f3cSopenharmony_ci					type = comsub(&x, sp, c);
347c84f3f3cSopenharmony_ci					if (type != XBASE && (f & DOBLANK))
348c84f3f3cSopenharmony_ci						doblank++;
349c84f3f3cSopenharmony_ci					sp = strnul(sp) + 1;
350c84f3f3cSopenharmony_ci					newlines = 0;
351c84f3f3cSopenharmony_ci				}
352c84f3f3cSopenharmony_ci				continue;
353c84f3f3cSopenharmony_ci			case EXPRSUB:
354c84f3f3cSopenharmony_ci				tilde_ok = 0;
355c84f3f3cSopenharmony_ci				if (f & DONTRUNCOMMAND) {
356c84f3f3cSopenharmony_ci					word = IFS_WORD;
357c84f3f3cSopenharmony_ci					*dp++ = '$'; *dp++ = '('; *dp++ = '(';
358c84f3f3cSopenharmony_ci					while (*sp != '\0') {
359c84f3f3cSopenharmony_ci						Xcheck(ds, dp);
360c84f3f3cSopenharmony_ci						*dp++ = *sp++;
361c84f3f3cSopenharmony_ci					}
362c84f3f3cSopenharmony_ci					*dp++ = ')'; *dp++ = ')';
363c84f3f3cSopenharmony_ci				} else {
364c84f3f3cSopenharmony_ci					struct tbl v;
365c84f3f3cSopenharmony_ci
366c84f3f3cSopenharmony_ci					v.flag = DEFINED|ISSET|INTEGER;
367c84f3f3cSopenharmony_ci					/* not default */
368c84f3f3cSopenharmony_ci					v.type = 10;
369c84f3f3cSopenharmony_ci					v.name[0] = '\0';
370c84f3f3cSopenharmony_ci					v_evaluate(&v, substitute(sp, 0),
371c84f3f3cSopenharmony_ci					    KSH_UNWIND_ERROR, true);
372c84f3f3cSopenharmony_ci					sp = strnul(sp) + 1;
373c84f3f3cSopenharmony_ci					x.str = str_val(&v);
374c84f3f3cSopenharmony_ci					type = XSUB;
375c84f3f3cSopenharmony_ci					if (f & DOBLANK)
376c84f3f3cSopenharmony_ci						doblank++;
377c84f3f3cSopenharmony_ci				}
378c84f3f3cSopenharmony_ci				continue;
379c84f3f3cSopenharmony_ci			case OSUBST: {
380c84f3f3cSopenharmony_ci				/* ${{#}var{:}[=+-?#%]word} */
381c84f3f3cSopenharmony_ci			/*-
382c84f3f3cSopenharmony_ci			 * format is:
383c84f3f3cSopenharmony_ci			 *	OSUBST [{x] plain-variable-part \0
384c84f3f3cSopenharmony_ci			 *	    compiled-word-part CSUBST [}x]
385c84f3f3cSopenharmony_ci			 * This is where all syntax checking gets done...
386c84f3f3cSopenharmony_ci			 */
387c84f3f3cSopenharmony_ci				/* skip the { or x (}) */
388c84f3f3cSopenharmony_ci				const char *varname = ++sp;
389c84f3f3cSopenharmony_ci				unsigned int stype;
390c84f3f3cSopenharmony_ci				int slen = 0;
391c84f3f3cSopenharmony_ci
392c84f3f3cSopenharmony_ci				/* skip variable */
393c84f3f3cSopenharmony_ci				sp = cstrchr(sp, '\0') + 1;
394c84f3f3cSopenharmony_ci				type = varsub(&x, varname, sp, &stype, &slen);
395c84f3f3cSopenharmony_ci				if (type < 0) {
396c84f3f3cSopenharmony_ci					char *beg, *end, *str;
397c84f3f3cSopenharmony_ci unwind_substsyn:
398c84f3f3cSopenharmony_ci					/* restore sp */
399c84f3f3cSopenharmony_ci					sp = varname - 2;
400c84f3f3cSopenharmony_ci					beg = wdcopy(sp, ATEMP);
401c84f3f3cSopenharmony_ci					end = (wdscan(cstrchr(sp, '\0') + 1,
402c84f3f3cSopenharmony_ci					    CSUBST) - sp) + beg;
403c84f3f3cSopenharmony_ci					/* ({) the } or x is already skipped */
404c84f3f3cSopenharmony_ci					if (end < wdscan(beg, EOS))
405c84f3f3cSopenharmony_ci						*end = EOS;
406c84f3f3cSopenharmony_ci					str = snptreef(NULL, 64, Tf_S, beg);
407c84f3f3cSopenharmony_ci					afree(beg, ATEMP);
408c84f3f3cSopenharmony_ci					errorf(Tf_sD_s, str, Tbadsubst);
409c84f3f3cSopenharmony_ci				}
410c84f3f3cSopenharmony_ci				if (f & DOBLANK)
411c84f3f3cSopenharmony_ci					doblank++;
412c84f3f3cSopenharmony_ci				tilde_ok = 0;
413c84f3f3cSopenharmony_ci				if (word == IFS_QUOTE && type != XNULLSUB)
414c84f3f3cSopenharmony_ci					word = IFS_WORD;
415c84f3f3cSopenharmony_ci				if (type == XBASE) {
416c84f3f3cSopenharmony_ci					/* expand? */
417c84f3f3cSopenharmony_ci					if (!st->next) {
418c84f3f3cSopenharmony_ci						SubType *newst;
419c84f3f3cSopenharmony_ci
420c84f3f3cSopenharmony_ci						newst = alloc(sizeof(SubType), ATEMP);
421c84f3f3cSopenharmony_ci						newst->next = NULL;
422c84f3f3cSopenharmony_ci						newst->prev = st;
423c84f3f3cSopenharmony_ci						st->next = newst;
424c84f3f3cSopenharmony_ci					}
425c84f3f3cSopenharmony_ci					st = st->next;
426c84f3f3cSopenharmony_ci					st->stype = stype;
427c84f3f3cSopenharmony_ci					st->base = Xsavepos(ds, dp);
428c84f3f3cSopenharmony_ci					st->f = f;
429c84f3f3cSopenharmony_ci					if (x.var == vtemp) {
430c84f3f3cSopenharmony_ci						st->var = tempvar(vtemp->name);
431c84f3f3cSopenharmony_ci						st->var->flag &= ~INTEGER;
432c84f3f3cSopenharmony_ci						/* can't fail here */
433c84f3f3cSopenharmony_ci						setstr(st->var,
434c84f3f3cSopenharmony_ci						    str_val(x.var),
435c84f3f3cSopenharmony_ci						    KSH_RETURN_ERROR | 0x4);
436c84f3f3cSopenharmony_ci					} else
437c84f3f3cSopenharmony_ci						st->var = x.var;
438c84f3f3cSopenharmony_ci
439c84f3f3cSopenharmony_ci					st->quotew = st->quotep = quote;
440c84f3f3cSopenharmony_ci					/* skip qualifier(s) */
441c84f3f3cSopenharmony_ci					if (stype)
442c84f3f3cSopenharmony_ci						sp += slen;
443c84f3f3cSopenharmony_ci					switch (stype & STYPE_SINGLE) {
444c84f3f3cSopenharmony_ci					case ORD('#') | STYPE_AT:
445c84f3f3cSopenharmony_ci					case ORD('Q') | STYPE_AT:
446c84f3f3cSopenharmony_ci						break;
447c84f3f3cSopenharmony_ci					case ORD('0'): {
448c84f3f3cSopenharmony_ci						char *beg, *mid, *end, *stg;
449c84f3f3cSopenharmony_ci						mksh_ari_t from = 0, num = -1, flen, finc = 0;
450c84f3f3cSopenharmony_ci
451c84f3f3cSopenharmony_ci						beg = wdcopy(sp, ATEMP);
452c84f3f3cSopenharmony_ci						mid = beg + (wdscan(sp, ADELIM) - sp);
453c84f3f3cSopenharmony_ci						stg = beg + (wdscan(sp, CSUBST) - sp);
454c84f3f3cSopenharmony_ci						mid[-2] = EOS;
455c84f3f3cSopenharmony_ci						if (ord(mid[-1]) == ORD(/*{*/ '}')) {
456c84f3f3cSopenharmony_ci							sp += mid - beg - 1;
457c84f3f3cSopenharmony_ci							end = NULL;
458c84f3f3cSopenharmony_ci						} else {
459c84f3f3cSopenharmony_ci							end = mid +
460c84f3f3cSopenharmony_ci							    (wdscan(mid, ADELIM) - mid);
461c84f3f3cSopenharmony_ci							if (ord(end[-1]) != ORD(/*{*/ '}'))
462c84f3f3cSopenharmony_ci								/* more than max delimiters */
463c84f3f3cSopenharmony_ci								goto unwind_substsyn;
464c84f3f3cSopenharmony_ci							end[-2] = EOS;
465c84f3f3cSopenharmony_ci							sp += end - beg - 1;
466c84f3f3cSopenharmony_ci						}
467c84f3f3cSopenharmony_ci						evaluate(substitute(stg = wdstrip(beg, 0), 0),
468c84f3f3cSopenharmony_ci						    &from, KSH_UNWIND_ERROR, true);
469c84f3f3cSopenharmony_ci						afree(stg, ATEMP);
470c84f3f3cSopenharmony_ci						if (end) {
471c84f3f3cSopenharmony_ci							evaluate(substitute(stg = wdstrip(mid, 0), 0),
472c84f3f3cSopenharmony_ci							    &num, KSH_UNWIND_ERROR, true);
473c84f3f3cSopenharmony_ci							afree(stg, ATEMP);
474c84f3f3cSopenharmony_ci						}
475c84f3f3cSopenharmony_ci						afree(beg, ATEMP);
476c84f3f3cSopenharmony_ci						beg = str_val(st->var);
477c84f3f3cSopenharmony_ci						flen = utflen(beg);
478c84f3f3cSopenharmony_ci						if (from < 0) {
479c84f3f3cSopenharmony_ci							if (-from < flen)
480c84f3f3cSopenharmony_ci								finc = flen + from;
481c84f3f3cSopenharmony_ci						} else
482c84f3f3cSopenharmony_ci							finc = from < flen ? from : flen;
483c84f3f3cSopenharmony_ci						if (UTFMODE)
484c84f3f3cSopenharmony_ci							utfincptr(beg, &finc);
485c84f3f3cSopenharmony_ci						beg += finc;
486c84f3f3cSopenharmony_ci						flen = utflen(beg);
487c84f3f3cSopenharmony_ci						if (num < 0 || num > flen)
488c84f3f3cSopenharmony_ci							num = flen;
489c84f3f3cSopenharmony_ci						if (UTFMODE)
490c84f3f3cSopenharmony_ci							utfincptr(beg, &num);
491c84f3f3cSopenharmony_ci						strndupx(x.str, beg, num, ATEMP);
492c84f3f3cSopenharmony_ci						goto do_CSUBST;
493c84f3f3cSopenharmony_ci					    }
494c84f3f3cSopenharmony_ci					case ORD('/') | STYPE_AT:
495c84f3f3cSopenharmony_ci					case ORD('/'): {
496c84f3f3cSopenharmony_ci						char *s, *p, *d, *sbeg;
497c84f3f3cSopenharmony_ci						char *pat = NULL, *rrep;
498c84f3f3cSopenharmony_ci						char fpat = 0, *tpat1, *tpat2;
499c84f3f3cSopenharmony_ci						char *ws, *wpat, *wrep, tch;
500c84f3f3cSopenharmony_ci						size_t rreplen;
501c84f3f3cSopenharmony_ci
502c84f3f3cSopenharmony_ci						s = ws = wdcopy(sp, ATEMP);
503c84f3f3cSopenharmony_ci						p = s + (wdscan(sp, ADELIM) - sp);
504c84f3f3cSopenharmony_ci						d = s + (wdscan(sp, CSUBST) - sp);
505c84f3f3cSopenharmony_ci						p[-2] = EOS;
506c84f3f3cSopenharmony_ci						if (ord(p[-1]) == ORD(/*{*/ '}'))
507c84f3f3cSopenharmony_ci							d = NULL;
508c84f3f3cSopenharmony_ci						else
509c84f3f3cSopenharmony_ci							d[-2] = EOS;
510c84f3f3cSopenharmony_ci						sp += (d ? d : p) - s - 1;
511c84f3f3cSopenharmony_ci						if (!(stype & STYPE_MASK) &&
512c84f3f3cSopenharmony_ci						    s[0] == CHAR &&
513c84f3f3cSopenharmony_ci						    ctype(s[1], C_SUB2))
514c84f3f3cSopenharmony_ci							fpat = s[1];
515c84f3f3cSopenharmony_ci						wpat = s + (fpat ? 2 : 0);
516c84f3f3cSopenharmony_ci						if (!(wrep = d ? p : NULL)) {
517c84f3f3cSopenharmony_ci							rrep = null;
518c84f3f3cSopenharmony_ci							rreplen = 0;
519c84f3f3cSopenharmony_ci						} else if (!(stype & STYPE_AT)) {
520c84f3f3cSopenharmony_ci							rrep = evalstr(wrep,
521c84f3f3cSopenharmony_ci							    DOTILDE | DOSCALAR);
522c84f3f3cSopenharmony_ci							rreplen = strlen(rrep);
523c84f3f3cSopenharmony_ci						} else {
524c84f3f3cSopenharmony_ci							rrep = NULL;
525c84f3f3cSopenharmony_ci							/* shut up GCC */
526c84f3f3cSopenharmony_ci							rreplen = 0;
527c84f3f3cSopenharmony_ci						}
528c84f3f3cSopenharmony_ci
529c84f3f3cSopenharmony_ci						/* prepare string on which to work */
530c84f3f3cSopenharmony_ci						strdupx(s, str_val(st->var), ATEMP);
531c84f3f3cSopenharmony_ci						sbeg = s;
532c84f3f3cSopenharmony_ci again_search:
533c84f3f3cSopenharmony_ci						pat = evalstr(wpat,
534c84f3f3cSopenharmony_ci						    DOTILDE | DOSCALAR | DOPAT);
535c84f3f3cSopenharmony_ci						/* check for special cases */
536c84f3f3cSopenharmony_ci						if (!*pat && !fpat) {
537c84f3f3cSopenharmony_ci							/*
538c84f3f3cSopenharmony_ci							 * empty unanchored
539c84f3f3cSopenharmony_ci							 * pattern => reject
540c84f3f3cSopenharmony_ci							 */
541c84f3f3cSopenharmony_ci							goto no_repl;
542c84f3f3cSopenharmony_ci						}
543c84f3f3cSopenharmony_ci						if ((stype & STYPE_MASK) &&
544c84f3f3cSopenharmony_ci						    gmatchx(null, pat, false)) {
545c84f3f3cSopenharmony_ci							/*
546c84f3f3cSopenharmony_ci							 * pattern matches empty
547c84f3f3cSopenharmony_ci							 * string => don't loop
548c84f3f3cSopenharmony_ci							 */
549c84f3f3cSopenharmony_ci							stype &= ~STYPE_MASK;
550c84f3f3cSopenharmony_ci						}
551c84f3f3cSopenharmony_ci
552c84f3f3cSopenharmony_ci						/* first see if we have any match at all */
553c84f3f3cSopenharmony_ci						if (ord(fpat) == ORD('#')) {
554c84f3f3cSopenharmony_ci							/* anchor at the beginning */
555c84f3f3cSopenharmony_ci							tpat1 = shf_smprintf("%s%c*", pat, MAGIC);
556c84f3f3cSopenharmony_ci							tpat2 = tpat1;
557c84f3f3cSopenharmony_ci						} else if (ord(fpat) == ORD('%')) {
558c84f3f3cSopenharmony_ci							/* anchor at the end */
559c84f3f3cSopenharmony_ci							tpat1 = shf_smprintf("%c*%s", MAGIC, pat);
560c84f3f3cSopenharmony_ci							tpat2 = pat;
561c84f3f3cSopenharmony_ci						} else {
562c84f3f3cSopenharmony_ci							/* float */
563c84f3f3cSopenharmony_ci							tpat1 = shf_smprintf("%c*%s%c*", MAGIC, pat, MAGIC);
564c84f3f3cSopenharmony_ci							tpat2 = tpat1 + 2;
565c84f3f3cSopenharmony_ci						}
566c84f3f3cSopenharmony_ci again_repl:
567c84f3f3cSopenharmony_ci						/*
568c84f3f3cSopenharmony_ci						 * this would not be necessary if gmatchx would return
569c84f3f3cSopenharmony_ci						 * the start and end values of a match found, like re*
570c84f3f3cSopenharmony_ci						 */
571c84f3f3cSopenharmony_ci						if (!gmatchx(sbeg, tpat1, false))
572c84f3f3cSopenharmony_ci							goto end_repl;
573c84f3f3cSopenharmony_ci						d = strnul(s);
574c84f3f3cSopenharmony_ci						/* now anchor the beginning of the match */
575c84f3f3cSopenharmony_ci						if (ord(fpat) != ORD('#'))
576c84f3f3cSopenharmony_ci							while (sbeg <= d) {
577c84f3f3cSopenharmony_ci								if (gmatchx(sbeg, tpat2, false))
578c84f3f3cSopenharmony_ci									break;
579c84f3f3cSopenharmony_ci								else
580c84f3f3cSopenharmony_ci									sbeg++;
581c84f3f3cSopenharmony_ci							}
582c84f3f3cSopenharmony_ci						/* now anchor the end of the match */
583c84f3f3cSopenharmony_ci						p = d;
584c84f3f3cSopenharmony_ci						if (ord(fpat) != ORD('%'))
585c84f3f3cSopenharmony_ci							while (p >= sbeg) {
586c84f3f3cSopenharmony_ci								bool gotmatch;
587c84f3f3cSopenharmony_ci
588c84f3f3cSopenharmony_ci								c = ord(*p);
589c84f3f3cSopenharmony_ci								*p = '\0';
590c84f3f3cSopenharmony_ci								gotmatch = tobool(gmatchx(sbeg, pat, false));
591c84f3f3cSopenharmony_ci								*p = c;
592c84f3f3cSopenharmony_ci								if (gotmatch)
593c84f3f3cSopenharmony_ci									break;
594c84f3f3cSopenharmony_ci								p--;
595c84f3f3cSopenharmony_ci							}
596c84f3f3cSopenharmony_ci
597c84f3f3cSopenharmony_ci						/* record partial string as match */
598c84f3f3cSopenharmony_ci						tch = *p;
599c84f3f3cSopenharmony_ci						*p = '\0';
600c84f3f3cSopenharmony_ci						record_match(sbeg);
601c84f3f3cSopenharmony_ci						*p = tch;
602c84f3f3cSopenharmony_ci						/* get replacement string, if necessary */
603c84f3f3cSopenharmony_ci						if ((stype & STYPE_AT) &&
604c84f3f3cSopenharmony_ci						    rrep != null) {
605c84f3f3cSopenharmony_ci							afree(rrep, ATEMP);
606c84f3f3cSopenharmony_ci							/* might access match! */
607c84f3f3cSopenharmony_ci							rrep = evalstr(wrep,
608c84f3f3cSopenharmony_ci							    DOTILDE | DOSCALAR);
609c84f3f3cSopenharmony_ci							rreplen = strlen(rrep);
610c84f3f3cSopenharmony_ci						}
611c84f3f3cSopenharmony_ci
612c84f3f3cSopenharmony_ci						/*
613c84f3f3cSopenharmony_ci						 * string:
614c84f3f3cSopenharmony_ci						 * |--------|---------|-------\0
615c84f3f3cSopenharmony_ci						 * s  n1    sbeg  n2  p  n3   d
616c84f3f3cSopenharmony_ci						 *
617c84f3f3cSopenharmony_ci						 * replacement:
618c84f3f3cSopenharmony_ci						 *          |------------|
619c84f3f3cSopenharmony_ci						 *          rrep  rreplen
620c84f3f3cSopenharmony_ci						 */
621c84f3f3cSopenharmony_ci
622c84f3f3cSopenharmony_ci						/* move strings around and replace */
623c84f3f3cSopenharmony_ci						{
624c84f3f3cSopenharmony_ci							size_t n1 = sbeg - s;
625c84f3f3cSopenharmony_ci							size_t n2 = p - sbeg;
626c84f3f3cSopenharmony_ci							size_t n3 = d - p;
627c84f3f3cSopenharmony_ci							/* move part3 to the front, OR… */
628c84f3f3cSopenharmony_ci							if (rreplen < n2)
629c84f3f3cSopenharmony_ci								memmove(sbeg + rreplen,
630c84f3f3cSopenharmony_ci								    p, n3 + 1);
631c84f3f3cSopenharmony_ci							/* … adjust size, move to back */
632c84f3f3cSopenharmony_ci							if (rreplen > n2) {
633c84f3f3cSopenharmony_ci								s = aresize(s,
634c84f3f3cSopenharmony_ci								    n1 + rreplen + n3 + 1,
635c84f3f3cSopenharmony_ci								    ATEMP);
636c84f3f3cSopenharmony_ci								memmove(s + n1 + rreplen,
637c84f3f3cSopenharmony_ci								    s + n1 + n2,
638c84f3f3cSopenharmony_ci								    n3 + 1);
639c84f3f3cSopenharmony_ci							}
640c84f3f3cSopenharmony_ci							/* insert replacement */
641c84f3f3cSopenharmony_ci							if (rreplen)
642c84f3f3cSopenharmony_ci								memcpy(s + n1, rrep, rreplen);
643c84f3f3cSopenharmony_ci							/* continue after the place */
644c84f3f3cSopenharmony_ci							sbeg = s + n1 + rreplen;
645c84f3f3cSopenharmony_ci						}
646c84f3f3cSopenharmony_ci						if (stype & STYPE_AT) {
647c84f3f3cSopenharmony_ci							afree(tpat1, ATEMP);
648c84f3f3cSopenharmony_ci							afree(pat, ATEMP);
649c84f3f3cSopenharmony_ci							goto again_search;
650c84f3f3cSopenharmony_ci						} else if (stype & STYPE_DBL)
651c84f3f3cSopenharmony_ci							goto again_repl;
652c84f3f3cSopenharmony_ci end_repl:
653c84f3f3cSopenharmony_ci						afree(tpat1, ATEMP);
654c84f3f3cSopenharmony_ci						x.str = s;
655c84f3f3cSopenharmony_ci no_repl:
656c84f3f3cSopenharmony_ci						afree(pat, ATEMP);
657c84f3f3cSopenharmony_ci						if (rrep != null)
658c84f3f3cSopenharmony_ci							afree(rrep, ATEMP);
659c84f3f3cSopenharmony_ci						afree(ws, ATEMP);
660c84f3f3cSopenharmony_ci						goto do_CSUBST;
661c84f3f3cSopenharmony_ci					    }
662c84f3f3cSopenharmony_ci					case ORD('#'):
663c84f3f3cSopenharmony_ci					case ORD('%'):
664c84f3f3cSopenharmony_ci						/* ! DOBLANK,DOBRACE */
665c84f3f3cSopenharmony_ci						f = (f & DONTRUNCOMMAND) |
666c84f3f3cSopenharmony_ci						    DOPAT | DOTILDE |
667c84f3f3cSopenharmony_ci						    DOTEMP | DOSCALAR;
668c84f3f3cSopenharmony_ci						tilde_ok = 1;
669c84f3f3cSopenharmony_ci						st->quotew = quote = 0;
670c84f3f3cSopenharmony_ci						/*
671c84f3f3cSopenharmony_ci						 * Prepend open pattern (so |
672c84f3f3cSopenharmony_ci						 * in a trim will work as
673c84f3f3cSopenharmony_ci						 * expected)
674c84f3f3cSopenharmony_ci						 */
675c84f3f3cSopenharmony_ci						if (!Flag(FSH)) {
676c84f3f3cSopenharmony_ci							*dp++ = MAGIC;
677c84f3f3cSopenharmony_ci							*dp++ = ORD(0x80 | '@');
678c84f3f3cSopenharmony_ci						}
679c84f3f3cSopenharmony_ci						break;
680c84f3f3cSopenharmony_ci					case ORD('='):
681c84f3f3cSopenharmony_ci						/*
682c84f3f3cSopenharmony_ci						 * Tilde expansion for string
683c84f3f3cSopenharmony_ci						 * variables in POSIX mode is
684c84f3f3cSopenharmony_ci						 * governed by Austinbug 351.
685c84f3f3cSopenharmony_ci						 * In non-POSIX mode historic
686c84f3f3cSopenharmony_ci						 * ksh behaviour (enable it!)
687c84f3f3cSopenharmony_ci						 * us followed.
688c84f3f3cSopenharmony_ci						 * Not doing tilde expansion
689c84f3f3cSopenharmony_ci						 * for integer variables is a
690c84f3f3cSopenharmony_ci						 * non-POSIX thing - makes
691c84f3f3cSopenharmony_ci						 * sense though, since ~ is
692c84f3f3cSopenharmony_ci						 * a arithmetic operator.
693c84f3f3cSopenharmony_ci						 */
694c84f3f3cSopenharmony_ci						if (!(x.var->flag & INTEGER))
695c84f3f3cSopenharmony_ci							f |= DOASNTILDE | DOTILDE;
696c84f3f3cSopenharmony_ci						f |= DOTEMP | DOSCALAR;
697c84f3f3cSopenharmony_ci						/*
698c84f3f3cSopenharmony_ci						 * These will be done after the
699c84f3f3cSopenharmony_ci						 * value has been assigned.
700c84f3f3cSopenharmony_ci						 */
701c84f3f3cSopenharmony_ci						f &= ~(DOBLANK|DOGLOB|DOBRACE);
702c84f3f3cSopenharmony_ci						tilde_ok = 1;
703c84f3f3cSopenharmony_ci						break;
704c84f3f3cSopenharmony_ci					case ORD('?'):
705c84f3f3cSopenharmony_ci						if (*sp == CSUBST)
706c84f3f3cSopenharmony_ci							errorf("%s: parameter null or not set",
707c84f3f3cSopenharmony_ci							    st->var->name);
708c84f3f3cSopenharmony_ci						f &= ~DOBLANK;
709c84f3f3cSopenharmony_ci						f |= DOTEMP;
710c84f3f3cSopenharmony_ci						/* FALLTHROUGH */
711c84f3f3cSopenharmony_ci					default:
712c84f3f3cSopenharmony_ci						/* '-' '+' '?' */
713c84f3f3cSopenharmony_ci						if (quote)
714c84f3f3cSopenharmony_ci							word = IFS_WORD;
715c84f3f3cSopenharmony_ci						else if (dp == Xstring(ds, dp))
716c84f3f3cSopenharmony_ci							word = IFS_IWS;
717c84f3f3cSopenharmony_ci						/* Enable tilde expansion */
718c84f3f3cSopenharmony_ci						tilde_ok = 1;
719c84f3f3cSopenharmony_ci						f |= DOTILDE;
720c84f3f3cSopenharmony_ci					}
721c84f3f3cSopenharmony_ci				} else
722c84f3f3cSopenharmony_ci					/* skip word */
723c84f3f3cSopenharmony_ci					sp += wdscan(sp, CSUBST) - sp;
724c84f3f3cSopenharmony_ci				continue;
725c84f3f3cSopenharmony_ci			    }
726c84f3f3cSopenharmony_ci			case CSUBST:
727c84f3f3cSopenharmony_ci				/* only get here if expanding word */
728c84f3f3cSopenharmony_ci do_CSUBST:
729c84f3f3cSopenharmony_ci				/* ({) skip the } or x */
730c84f3f3cSopenharmony_ci				sp++;
731c84f3f3cSopenharmony_ci				/* in case of ${unset:-} */
732c84f3f3cSopenharmony_ci				tilde_ok = 0;
733c84f3f3cSopenharmony_ci				*dp = '\0';
734c84f3f3cSopenharmony_ci				quote = st->quotep;
735c84f3f3cSopenharmony_ci				f = st->f;
736c84f3f3cSopenharmony_ci				if (f & DOBLANK)
737c84f3f3cSopenharmony_ci					doblank--;
738c84f3f3cSopenharmony_ci				switch (st->stype & STYPE_SINGLE) {
739c84f3f3cSopenharmony_ci				case ORD('#'):
740c84f3f3cSopenharmony_ci				case ORD('%'):
741c84f3f3cSopenharmony_ci					if (!Flag(FSH)) {
742c84f3f3cSopenharmony_ci						/* Append end-pattern */
743c84f3f3cSopenharmony_ci						*dp++ = MAGIC;
744c84f3f3cSopenharmony_ci						*dp++ = ')';
745c84f3f3cSopenharmony_ci					}
746c84f3f3cSopenharmony_ci					*dp = '\0';
747c84f3f3cSopenharmony_ci					dp = Xrestpos(ds, dp, st->base);
748c84f3f3cSopenharmony_ci					/*
749c84f3f3cSopenharmony_ci					 * Must use st->var since calling
750c84f3f3cSopenharmony_ci					 * global would break things
751c84f3f3cSopenharmony_ci					 * like x[i+=1].
752c84f3f3cSopenharmony_ci					 */
753c84f3f3cSopenharmony_ci					x.str = trimsub(str_val(st->var),
754c84f3f3cSopenharmony_ci						dp, st->stype);
755c84f3f3cSopenharmony_ci					if (x.str[0] != '\0') {
756c84f3f3cSopenharmony_ci						word = IFS_IWS;
757c84f3f3cSopenharmony_ci						type = XSUB;
758c84f3f3cSopenharmony_ci					} else if (quote) {
759c84f3f3cSopenharmony_ci						word = IFS_WORD;
760c84f3f3cSopenharmony_ci						type = XSUB;
761c84f3f3cSopenharmony_ci					} else {
762c84f3f3cSopenharmony_ci						if (dp == Xstring(ds, dp))
763c84f3f3cSopenharmony_ci							word = IFS_IWS;
764c84f3f3cSopenharmony_ci						type = XNULLSUB;
765c84f3f3cSopenharmony_ci					}
766c84f3f3cSopenharmony_ci					if (f & DOBLANK)
767c84f3f3cSopenharmony_ci						doblank++;
768c84f3f3cSopenharmony_ci					st = st->prev;
769c84f3f3cSopenharmony_ci					continue;
770c84f3f3cSopenharmony_ci				case ORD('='):
771c84f3f3cSopenharmony_ci					/*
772c84f3f3cSopenharmony_ci					 * Restore our position and substitute
773c84f3f3cSopenharmony_ci					 * the value of st->var (may not be
774c84f3f3cSopenharmony_ci					 * the assigned value in the presence
775c84f3f3cSopenharmony_ci					 * of integer/right-adj/etc attributes).
776c84f3f3cSopenharmony_ci					 */
777c84f3f3cSopenharmony_ci					dp = Xrestpos(ds, dp, st->base);
778c84f3f3cSopenharmony_ci					/*
779c84f3f3cSopenharmony_ci					 * Must use st->var since calling
780c84f3f3cSopenharmony_ci					 * global would cause with things
781c84f3f3cSopenharmony_ci					 * like x[i+=1] to be evaluated twice.
782c84f3f3cSopenharmony_ci					 */
783c84f3f3cSopenharmony_ci					/*
784c84f3f3cSopenharmony_ci					 * Note: not exported by FEXPORT
785c84f3f3cSopenharmony_ci					 * in AT&T ksh.
786c84f3f3cSopenharmony_ci					 */
787c84f3f3cSopenharmony_ci					/*
788c84f3f3cSopenharmony_ci					 * XXX POSIX says readonly is only
789c84f3f3cSopenharmony_ci					 * fatal for special builtins (setstr
790c84f3f3cSopenharmony_ci					 * does readonly check).
791c84f3f3cSopenharmony_ci					 */
792c84f3f3cSopenharmony_ci					len = strlen(dp) + 1;
793c84f3f3cSopenharmony_ci					setstr(st->var,
794c84f3f3cSopenharmony_ci					    debunk(alloc(len, ATEMP),
795c84f3f3cSopenharmony_ci					    dp, len), KSH_UNWIND_ERROR);
796c84f3f3cSopenharmony_ci					x.str = str_val(st->var);
797c84f3f3cSopenharmony_ci					type = XSUB;
798c84f3f3cSopenharmony_ci					if (f & DOBLANK)
799c84f3f3cSopenharmony_ci						doblank++;
800c84f3f3cSopenharmony_ci					st = st->prev;
801c84f3f3cSopenharmony_ci					word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
802c84f3f3cSopenharmony_ci					continue;
803c84f3f3cSopenharmony_ci				case ORD('?'):
804c84f3f3cSopenharmony_ci					dp = Xrestpos(ds, dp, st->base);
805c84f3f3cSopenharmony_ci
806c84f3f3cSopenharmony_ci					errorf(Tf_sD_s, st->var->name,
807c84f3f3cSopenharmony_ci					    debunk(dp, dp, strlen(dp) + 1));
808c84f3f3cSopenharmony_ci					break;
809c84f3f3cSopenharmony_ci				case ORD('#') | STYPE_AT:
810c84f3f3cSopenharmony_ci					x.str = shf_smprintf("%08X",
811c84f3f3cSopenharmony_ci					    (unsigned int)hash(str_val(st->var)));
812c84f3f3cSopenharmony_ci					goto common_CSUBST;
813c84f3f3cSopenharmony_ci				case ORD('Q') | STYPE_AT: {
814c84f3f3cSopenharmony_ci					struct shf shf;
815c84f3f3cSopenharmony_ci
816c84f3f3cSopenharmony_ci					shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
817c84f3f3cSopenharmony_ci					print_value_quoted(&shf, str_val(st->var));
818c84f3f3cSopenharmony_ci					x.str = shf_sclose(&shf);
819c84f3f3cSopenharmony_ci					goto common_CSUBST;
820c84f3f3cSopenharmony_ci				    }
821c84f3f3cSopenharmony_ci				case ORD('0'):
822c84f3f3cSopenharmony_ci				case ORD('/') | STYPE_AT:
823c84f3f3cSopenharmony_ci				case ORD('/'):
824c84f3f3cSopenharmony_ci common_CSUBST:
825c84f3f3cSopenharmony_ci					dp = Xrestpos(ds, dp, st->base);
826c84f3f3cSopenharmony_ci					type = XSUB;
827c84f3f3cSopenharmony_ci					word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
828c84f3f3cSopenharmony_ci					if (f & DOBLANK)
829c84f3f3cSopenharmony_ci						doblank++;
830c84f3f3cSopenharmony_ci					st = st->prev;
831c84f3f3cSopenharmony_ci					continue;
832c84f3f3cSopenharmony_ci				/* default: '-' '+' */
833c84f3f3cSopenharmony_ci				}
834c84f3f3cSopenharmony_ci				st = st->prev;
835c84f3f3cSopenharmony_ci				type = XBASE;
836c84f3f3cSopenharmony_ci				continue;
837c84f3f3cSopenharmony_ci
838c84f3f3cSopenharmony_ci			case OPAT:
839c84f3f3cSopenharmony_ci				/* open pattern: *(foo|bar) */
840c84f3f3cSopenharmony_ci				/* Next char is the type of pattern */
841c84f3f3cSopenharmony_ci				make_magic = true;
842c84f3f3cSopenharmony_ci				c = ord(*sp++) | 0x80U;
843c84f3f3cSopenharmony_ci				break;
844c84f3f3cSopenharmony_ci
845c84f3f3cSopenharmony_ci			case SPAT:
846c84f3f3cSopenharmony_ci				/* pattern separator (|) */
847c84f3f3cSopenharmony_ci				make_magic = true;
848c84f3f3cSopenharmony_ci				c = ORD('|');
849c84f3f3cSopenharmony_ci				break;
850c84f3f3cSopenharmony_ci
851c84f3f3cSopenharmony_ci			case CPAT:
852c84f3f3cSopenharmony_ci				/* close pattern */
853c84f3f3cSopenharmony_ci				make_magic = true;
854c84f3f3cSopenharmony_ci				c = ORD(/*(*/ ')');
855c84f3f3cSopenharmony_ci				break;
856c84f3f3cSopenharmony_ci			}
857c84f3f3cSopenharmony_ci			break;
858c84f3f3cSopenharmony_ci
859c84f3f3cSopenharmony_ci		case XNULLSUB:
860c84f3f3cSopenharmony_ci			/*
861c84f3f3cSopenharmony_ci			 * Special case for "$@" (and "${foo[@]}") - no
862c84f3f3cSopenharmony_ci			 * word is generated if $# is 0 (unless there is
863c84f3f3cSopenharmony_ci			 * other stuff inside the quotes).
864c84f3f3cSopenharmony_ci			 */
865c84f3f3cSopenharmony_ci			type = XBASE;
866c84f3f3cSopenharmony_ci			if (f & DOBLANK) {
867c84f3f3cSopenharmony_ci				doblank--;
868c84f3f3cSopenharmony_ci				if (dp == Xstring(ds, dp) && word != IFS_WORD)
869c84f3f3cSopenharmony_ci					word = IFS_IWS;
870c84f3f3cSopenharmony_ci			}
871c84f3f3cSopenharmony_ci			continue;
872c84f3f3cSopenharmony_ci
873c84f3f3cSopenharmony_ci		case XSUBPAT:
874c84f3f3cSopenharmony_ci		case XSUBPATMID:
875c84f3f3cSopenharmony_ci XSUBPAT_beg:
876c84f3f3cSopenharmony_ci			switch ((c = ord(*x.str++))) {
877c84f3f3cSopenharmony_ci			case 0:
878c84f3f3cSopenharmony_ci				goto XSUB_end;
879c84f3f3cSopenharmony_ci			case ORD('\\'):
880c84f3f3cSopenharmony_ci				if ((c = ord(*x.str)) == 0)
881c84f3f3cSopenharmony_ci					/* keep backslash at EOS */
882c84f3f3cSopenharmony_ci					c = ORD('\\');
883c84f3f3cSopenharmony_ci				else
884c84f3f3cSopenharmony_ci					++x.str;
885c84f3f3cSopenharmony_ci				quote |= 2;
886c84f3f3cSopenharmony_ci				break;
887c84f3f3cSopenharmony_ci			/* ctype(c, C_PATMO) */
888c84f3f3cSopenharmony_ci			case ORD('!'):
889c84f3f3cSopenharmony_ci			case ORD('*'):
890c84f3f3cSopenharmony_ci			case ORD('+'):
891c84f3f3cSopenharmony_ci			case ORD('?'):
892c84f3f3cSopenharmony_ci			case ORD('@'):
893c84f3f3cSopenharmony_ci				if (ord(*x.str) == ORD('('/*)*/)) {
894c84f3f3cSopenharmony_ci					++x.str;
895c84f3f3cSopenharmony_ci					c |= 0x80U;
896c84f3f3cSopenharmony_ci					make_magic = true;
897c84f3f3cSopenharmony_ci				}
898c84f3f3cSopenharmony_ci				break;
899c84f3f3cSopenharmony_ci			case ORD('('):
900c84f3f3cSopenharmony_ci				c = ORD(' ') | 0x80U;
901c84f3f3cSopenharmony_ci				/* FALLTHROUGH */
902c84f3f3cSopenharmony_ci			case ORD('|'):
903c84f3f3cSopenharmony_ci			case ORD(')'):
904c84f3f3cSopenharmony_ci				make_magic = true;
905c84f3f3cSopenharmony_ci				break;
906c84f3f3cSopenharmony_ci			}
907c84f3f3cSopenharmony_ci			break;
908c84f3f3cSopenharmony_ci
909c84f3f3cSopenharmony_ci		case XSUB:
910c84f3f3cSopenharmony_ci			if (!quote && (f & DODBMAGIC)) {
911c84f3f3cSopenharmony_ci				const char *cs = x.str;
912c84f3f3cSopenharmony_ci				int level = 0;
913c84f3f3cSopenharmony_ci
914c84f3f3cSopenharmony_ci				while ((c = *cs++))
915c84f3f3cSopenharmony_ci					switch (c) {
916c84f3f3cSopenharmony_ci					case '\\':
917c84f3f3cSopenharmony_ci						if ((c = *cs))
918c84f3f3cSopenharmony_ci							++cs;
919c84f3f3cSopenharmony_ci						break;
920c84f3f3cSopenharmony_ci					case ORD('('):
921c84f3f3cSopenharmony_ci						++level;
922c84f3f3cSopenharmony_ci						break;
923c84f3f3cSopenharmony_ci					case ORD(')'):
924c84f3f3cSopenharmony_ci						--level;
925c84f3f3cSopenharmony_ci						break;
926c84f3f3cSopenharmony_ci					}
927c84f3f3cSopenharmony_ci				/* balanced parentheses? */
928c84f3f3cSopenharmony_ci				if (!level) {
929c84f3f3cSopenharmony_ci					type = XSUBPAT;
930c84f3f3cSopenharmony_ci					goto XSUBPAT_beg;
931c84f3f3cSopenharmony_ci				}
932c84f3f3cSopenharmony_ci			}
933c84f3f3cSopenharmony_ci			/* FALLTHROUGH */
934c84f3f3cSopenharmony_ci		case XSUBMID:
935c84f3f3cSopenharmony_ci			if ((c = ord(*x.str++)) == 0) {
936c84f3f3cSopenharmony_ci XSUB_end:
937c84f3f3cSopenharmony_ci				type = XBASE;
938c84f3f3cSopenharmony_ci				if (f & DOBLANK)
939c84f3f3cSopenharmony_ci					doblank--;
940c84f3f3cSopenharmony_ci				continue;
941c84f3f3cSopenharmony_ci			}
942c84f3f3cSopenharmony_ci			break;
943c84f3f3cSopenharmony_ci
944c84f3f3cSopenharmony_ci		case XARGSEP:
945c84f3f3cSopenharmony_ci			type = XARG;
946c84f3f3cSopenharmony_ci			quote = 1;
947c84f3f3cSopenharmony_ci			/* FALLTHROUGH */
948c84f3f3cSopenharmony_ci		case XARG:
949c84f3f3cSopenharmony_ci			if ((c = ord(*x.str++)) == '\0') {
950c84f3f3cSopenharmony_ci				/*
951c84f3f3cSopenharmony_ci				 * force null words to be created so
952c84f3f3cSopenharmony_ci				 * set -- "" 2 ""; echo "$@" will do
953c84f3f3cSopenharmony_ci				 * the right thing
954c84f3f3cSopenharmony_ci				 */
955c84f3f3cSopenharmony_ci				if (quote && x.split)
956c84f3f3cSopenharmony_ci					word = IFS_WORD;
957c84f3f3cSopenharmony_ci				if ((x.str = *x.u.strv++) == NULL) {
958c84f3f3cSopenharmony_ci					type = XBASE;
959c84f3f3cSopenharmony_ci					if (f & DOBLANK)
960c84f3f3cSopenharmony_ci						doblank--;
961c84f3f3cSopenharmony_ci					continue;
962c84f3f3cSopenharmony_ci				}
963c84f3f3cSopenharmony_ci				c = ord(ifs0);
964c84f3f3cSopenharmony_ci				if ((f & DOHEREDOC)) {
965c84f3f3cSopenharmony_ci					/* pseudo-field-split reliably */
966c84f3f3cSopenharmony_ci					if (c == 0)
967c84f3f3cSopenharmony_ci						c = ORD(' ');
968c84f3f3cSopenharmony_ci					break;
969c84f3f3cSopenharmony_ci				}
970c84f3f3cSopenharmony_ci				if ((f & DOSCALAR)) {
971c84f3f3cSopenharmony_ci					/* do not field-split */
972c84f3f3cSopenharmony_ci					if (x.split) {
973c84f3f3cSopenharmony_ci						c = ORD(' ');
974c84f3f3cSopenharmony_ci						break;
975c84f3f3cSopenharmony_ci					}
976c84f3f3cSopenharmony_ci					if (c == 0)
977c84f3f3cSopenharmony_ci						continue;
978c84f3f3cSopenharmony_ci				}
979c84f3f3cSopenharmony_ci				if (c == 0) {
980c84f3f3cSopenharmony_ci					if (quote && !x.split)
981c84f3f3cSopenharmony_ci						continue;
982c84f3f3cSopenharmony_ci					if (!quote && word == IFS_WS)
983c84f3f3cSopenharmony_ci						continue;
984c84f3f3cSopenharmony_ci					/* this is so we don't terminate */
985c84f3f3cSopenharmony_ci					c = ORD(' ');
986c84f3f3cSopenharmony_ci					/* now force-emit a word */
987c84f3f3cSopenharmony_ci					goto emit_word;
988c84f3f3cSopenharmony_ci				}
989c84f3f3cSopenharmony_ci				if (quote && x.split) {
990c84f3f3cSopenharmony_ci					/* terminate word for "$@" */
991c84f3f3cSopenharmony_ci					type = XARGSEP;
992c84f3f3cSopenharmony_ci					quote = 0;
993c84f3f3cSopenharmony_ci				}
994c84f3f3cSopenharmony_ci			}
995c84f3f3cSopenharmony_ci			break;
996c84f3f3cSopenharmony_ci
997c84f3f3cSopenharmony_ci		case XCOM:
998c84f3f3cSopenharmony_ci			if (x.u.shf == NULL) {
999c84f3f3cSopenharmony_ci				/* $(<...) failed */
1000c84f3f3cSopenharmony_ci				subst_exstat = 1;
1001c84f3f3cSopenharmony_ci				/* fake EOF */
1002c84f3f3cSopenharmony_ci				c = -1;
1003c84f3f3cSopenharmony_ci			} else if (newlines) {
1004c84f3f3cSopenharmony_ci				/* spit out saved NLs */
1005c84f3f3cSopenharmony_ci				c = ORD('\n');
1006c84f3f3cSopenharmony_ci				--newlines;
1007c84f3f3cSopenharmony_ci			} else {
1008c84f3f3cSopenharmony_ci				while ((c = shf_getc(x.u.shf)) == 0 ||
1009c84f3f3cSopenharmony_ci				    cinttype(c, C_NL)) {
1010c84f3f3cSopenharmony_ci#ifdef MKSH_WITH_TEXTMODE
1011c84f3f3cSopenharmony_ci					if (c == ORD('\r')) {
1012c84f3f3cSopenharmony_ci						c = shf_getc(x.u.shf);
1013c84f3f3cSopenharmony_ci						switch (c) {
1014c84f3f3cSopenharmony_ci						case ORD('\n'):
1015c84f3f3cSopenharmony_ci							break;
1016c84f3f3cSopenharmony_ci						default:
1017c84f3f3cSopenharmony_ci							shf_ungetc(c, x.u.shf);
1018c84f3f3cSopenharmony_ci							/* FALLTHROUGH */
1019c84f3f3cSopenharmony_ci						case -1:
1020c84f3f3cSopenharmony_ci							c = ORD('\r');
1021c84f3f3cSopenharmony_ci							break;
1022c84f3f3cSopenharmony_ci						}
1023c84f3f3cSopenharmony_ci					}
1024c84f3f3cSopenharmony_ci#endif
1025c84f3f3cSopenharmony_ci					if (c == ORD('\n'))
1026c84f3f3cSopenharmony_ci						/* save newlines */
1027c84f3f3cSopenharmony_ci						newlines++;
1028c84f3f3cSopenharmony_ci				}
1029c84f3f3cSopenharmony_ci				if (newlines && c != -1) {
1030c84f3f3cSopenharmony_ci					shf_ungetc(c, x.u.shf);
1031c84f3f3cSopenharmony_ci					c = ORD('\n');
1032c84f3f3cSopenharmony_ci					--newlines;
1033c84f3f3cSopenharmony_ci				}
1034c84f3f3cSopenharmony_ci			}
1035c84f3f3cSopenharmony_ci			if (c == -1) {
1036c84f3f3cSopenharmony_ci				newlines = 0;
1037c84f3f3cSopenharmony_ci				if (x.u.shf)
1038c84f3f3cSopenharmony_ci					shf_close(x.u.shf);
1039c84f3f3cSopenharmony_ci				if (x.split)
1040c84f3f3cSopenharmony_ci					subst_exstat = waitlast();
1041c84f3f3cSopenharmony_ci				type = XBASE;
1042c84f3f3cSopenharmony_ci				if (f & DOBLANK)
1043c84f3f3cSopenharmony_ci					doblank--;
1044c84f3f3cSopenharmony_ci				continue;
1045c84f3f3cSopenharmony_ci			}
1046c84f3f3cSopenharmony_ci			break;
1047c84f3f3cSopenharmony_ci		}
1048c84f3f3cSopenharmony_ci
1049c84f3f3cSopenharmony_ci		/* check for end of word or IFS separation */
1050c84f3f3cSopenharmony_ci		if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
1051c84f3f3cSopenharmony_ci		    !make_magic && ctype(c, C_IFS))) {
1052c84f3f3cSopenharmony_ci			/*-
1053c84f3f3cSopenharmony_ci			 * How words are broken up:
1054c84f3f3cSopenharmony_ci			 *			|	value of c
1055c84f3f3cSopenharmony_ci			 *	word		|	ws	nws	0
1056c84f3f3cSopenharmony_ci			 *	-----------------------------------
1057c84f3f3cSopenharmony_ci			 *	IFS_WORD		w/WS	w/NWS	w
1058c84f3f3cSopenharmony_ci			 *	IFS_WS			-/WS	-/NWS	-
1059c84f3f3cSopenharmony_ci			 *	IFS_NWS			-/NWS	w/NWS	-
1060c84f3f3cSopenharmony_ci			 *	IFS_IWS			-/WS	w/NWS	-
1061c84f3f3cSopenharmony_ci			 * (w means generate a word)
1062c84f3f3cSopenharmony_ci			 */
1063c84f3f3cSopenharmony_ci			if ((word == IFS_WORD) || (word == IFS_QUOTE) || (c &&
1064c84f3f3cSopenharmony_ci			    (word == IFS_IWS || word == IFS_NWS) &&
1065c84f3f3cSopenharmony_ci			    !ctype(c, C_IFSWS))) {
1066c84f3f3cSopenharmony_ci emit_word:
1067c84f3f3cSopenharmony_ci				if (f & DOHERESTR)
1068c84f3f3cSopenharmony_ci					*dp++ = '\n';
1069c84f3f3cSopenharmony_ci				*dp++ = '\0';
1070c84f3f3cSopenharmony_ci				cp = Xclose(ds, dp);
1071c84f3f3cSopenharmony_ci				if (fdo & DOBRACE)
1072c84f3f3cSopenharmony_ci					/* also does globbing */
1073c84f3f3cSopenharmony_ci					alt_expand(wp, cp, cp,
1074c84f3f3cSopenharmony_ci					    cp + Xlength(ds, (dp - 1)),
1075c84f3f3cSopenharmony_ci					    fdo | (f & DOMARKDIRS));
1076c84f3f3cSopenharmony_ci				else if (fdo & DOGLOB)
1077c84f3f3cSopenharmony_ci					glob(cp, wp, tobool(f & DOMARKDIRS));
1078c84f3f3cSopenharmony_ci				else if ((f & DOPAT) || !(fdo & DOMAGIC))
1079c84f3f3cSopenharmony_ci					XPput(*wp, cp);
1080c84f3f3cSopenharmony_ci				else
1081c84f3f3cSopenharmony_ci					XPput(*wp, debunk(cp, cp,
1082c84f3f3cSopenharmony_ci					    strlen(cp) + 1));
1083c84f3f3cSopenharmony_ci				fdo = 0;
1084c84f3f3cSopenharmony_ci				saw_eq = false;
1085c84f3f3cSopenharmony_ci				/* must be 1/0 */
1086c84f3f3cSopenharmony_ci				tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0;
1087c84f3f3cSopenharmony_ci				if (c == 0)
1088c84f3f3cSopenharmony_ci					return;
1089c84f3f3cSopenharmony_ci				Xinit(ds, dp, 128, ATEMP);
1090c84f3f3cSopenharmony_ci			} else if (c == 0) {
1091c84f3f3cSopenharmony_ci				return;
1092c84f3f3cSopenharmony_ci			} else if (isXSUB(type) && ctype(c, C_IFS) &&
1093c84f3f3cSopenharmony_ci			    !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) {
1094c84f3f3cSopenharmony_ci				*(cp = alloc(1, ATEMP)) = '\0';
1095c84f3f3cSopenharmony_ci				XPput(*wp, cp);
1096c84f3f3cSopenharmony_ci				++type;
1097c84f3f3cSopenharmony_ci			}
1098c84f3f3cSopenharmony_ci			if (word != IFS_NWS)
1099c84f3f3cSopenharmony_ci				word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
1100c84f3f3cSopenharmony_ci		} else {
1101c84f3f3cSopenharmony_ci			if (isXSUB(type))
1102c84f3f3cSopenharmony_ci				++type;
1103c84f3f3cSopenharmony_ci
1104c84f3f3cSopenharmony_ci			/* age tilde_ok info - ~ code tests second bit */
1105c84f3f3cSopenharmony_ci			tilde_ok <<= 1;
1106c84f3f3cSopenharmony_ci			/* mark any special second pass chars */
1107c84f3f3cSopenharmony_ci			if (!quote)
1108c84f3f3cSopenharmony_ci				switch (ord(c)) {
1109c84f3f3cSopenharmony_ci				case ORD('['):
1110c84f3f3cSopenharmony_ci				case ORD('!'):
1111c84f3f3cSopenharmony_ci				case ORD('-'):
1112c84f3f3cSopenharmony_ci				case ORD(']'):
1113c84f3f3cSopenharmony_ci					/*
1114c84f3f3cSopenharmony_ci					 * For character classes - doesn't hurt
1115c84f3f3cSopenharmony_ci					 * to have magic !,-,]s outside of
1116c84f3f3cSopenharmony_ci					 * [...] expressions.
1117c84f3f3cSopenharmony_ci					 */
1118c84f3f3cSopenharmony_ci					if (f & (DOPAT | DOGLOB)) {
1119c84f3f3cSopenharmony_ci						fdo |= DOMAGIC;
1120c84f3f3cSopenharmony_ci						if ((unsigned int)c == ORD('['))
1121c84f3f3cSopenharmony_ci							fdo |= f & DOGLOB;
1122c84f3f3cSopenharmony_ci						*dp++ = MAGIC;
1123c84f3f3cSopenharmony_ci					}
1124c84f3f3cSopenharmony_ci					break;
1125c84f3f3cSopenharmony_ci				case ORD('*'):
1126c84f3f3cSopenharmony_ci				case ORD('?'):
1127c84f3f3cSopenharmony_ci					if (f & (DOPAT | DOGLOB)) {
1128c84f3f3cSopenharmony_ci						fdo |= DOMAGIC | (f & DOGLOB);
1129c84f3f3cSopenharmony_ci						*dp++ = MAGIC;
1130c84f3f3cSopenharmony_ci					}
1131c84f3f3cSopenharmony_ci					break;
1132c84f3f3cSopenharmony_ci				case ORD('{'):
1133c84f3f3cSopenharmony_ci				case ORD('}'):
1134c84f3f3cSopenharmony_ci				case ORD(','):
1135c84f3f3cSopenharmony_ci					if ((f & DOBRACE) &&
1136c84f3f3cSopenharmony_ci					    (ord(c) == ORD('{' /*}*/) ||
1137c84f3f3cSopenharmony_ci					    (fdo & DOBRACE))) {
1138c84f3f3cSopenharmony_ci						fdo |= DOBRACE|DOMAGIC;
1139c84f3f3cSopenharmony_ci						*dp++ = MAGIC;
1140c84f3f3cSopenharmony_ci					}
1141c84f3f3cSopenharmony_ci					break;
1142c84f3f3cSopenharmony_ci				case ORD('='):
1143c84f3f3cSopenharmony_ci					/* Note first unquoted = for ~ */
1144c84f3f3cSopenharmony_ci					if (!(f & DOTEMP) && (!Flag(FPOSIX) ||
1145c84f3f3cSopenharmony_ci					    (f & DOASNTILDE)) && !saw_eq) {
1146c84f3f3cSopenharmony_ci						saw_eq = true;
1147c84f3f3cSopenharmony_ci						tilde_ok = 1;
1148c84f3f3cSopenharmony_ci					}
1149c84f3f3cSopenharmony_ci					break;
1150c84f3f3cSopenharmony_ci				case ORD(':'):
1151c84f3f3cSopenharmony_ci					/* : */
1152c84f3f3cSopenharmony_ci					/* Note unquoted : for ~ */
1153c84f3f3cSopenharmony_ci					if (!(f & DOTEMP) && (f & DOASNTILDE))
1154c84f3f3cSopenharmony_ci						tilde_ok = 1;
1155c84f3f3cSopenharmony_ci					break;
1156c84f3f3cSopenharmony_ci				case ORD('~'):
1157c84f3f3cSopenharmony_ci					/*
1158c84f3f3cSopenharmony_ci					 * tilde_ok is reset whenever
1159c84f3f3cSopenharmony_ci					 * any of ' " $( $(( ${ } are seen.
1160c84f3f3cSopenharmony_ci					 * Note that tilde_ok must be preserved
1161c84f3f3cSopenharmony_ci					 * through the sequence ${A=a=}~
1162c84f3f3cSopenharmony_ci					 */
1163c84f3f3cSopenharmony_ci					if (type == XBASE &&
1164c84f3f3cSopenharmony_ci					    (f & (DOTILDE | DOASNTILDE)) &&
1165c84f3f3cSopenharmony_ci					    (tilde_ok & 2)) {
1166c84f3f3cSopenharmony_ci						const char *tcp;
1167c84f3f3cSopenharmony_ci						char *tdp = dp;
1168c84f3f3cSopenharmony_ci
1169c84f3f3cSopenharmony_ci						tcp = maybe_expand_tilde(sp,
1170c84f3f3cSopenharmony_ci						    &ds, &tdp,
1171c84f3f3cSopenharmony_ci						    tobool(f & DOASNTILDE));
1172c84f3f3cSopenharmony_ci						if (tcp) {
1173c84f3f3cSopenharmony_ci							if (dp != tdp)
1174c84f3f3cSopenharmony_ci								word = IFS_WORD;
1175c84f3f3cSopenharmony_ci							dp = tdp;
1176c84f3f3cSopenharmony_ci							sp = tcp;
1177c84f3f3cSopenharmony_ci							continue;
1178c84f3f3cSopenharmony_ci						}
1179c84f3f3cSopenharmony_ci					}
1180c84f3f3cSopenharmony_ci					break;
1181c84f3f3cSopenharmony_ci				}
1182c84f3f3cSopenharmony_ci			else
1183c84f3f3cSopenharmony_ci				/* undo temporary */
1184c84f3f3cSopenharmony_ci				quote &= ~2;
1185c84f3f3cSopenharmony_ci
1186c84f3f3cSopenharmony_ci			if (make_magic) {
1187c84f3f3cSopenharmony_ci				make_magic = false;
1188c84f3f3cSopenharmony_ci				fdo |= DOMAGIC | (f & DOGLOB);
1189c84f3f3cSopenharmony_ci				*dp++ = MAGIC;
1190c84f3f3cSopenharmony_ci			} else if (ISMAGIC(c)) {
1191c84f3f3cSopenharmony_ci				fdo |= DOMAGIC;
1192c84f3f3cSopenharmony_ci				*dp++ = MAGIC;
1193c84f3f3cSopenharmony_ci			}
1194c84f3f3cSopenharmony_ci			/* save output char */
1195c84f3f3cSopenharmony_ci			*dp++ = c;
1196c84f3f3cSopenharmony_ci			word = IFS_WORD;
1197c84f3f3cSopenharmony_ci		}
1198c84f3f3cSopenharmony_ci	}
1199c84f3f3cSopenharmony_ci}
1200c84f3f3cSopenharmony_ci
1201c84f3f3cSopenharmony_cistatic bool
1202c84f3f3cSopenharmony_cihasnonempty(const char **strv)
1203c84f3f3cSopenharmony_ci{
1204c84f3f3cSopenharmony_ci	size_t i = 0;
1205c84f3f3cSopenharmony_ci
1206c84f3f3cSopenharmony_ci	while (strv[i])
1207c84f3f3cSopenharmony_ci		if (*strv[i++])
1208c84f3f3cSopenharmony_ci			return (true);
1209c84f3f3cSopenharmony_ci	return (false);
1210c84f3f3cSopenharmony_ci}
1211c84f3f3cSopenharmony_ci
1212c84f3f3cSopenharmony_ci/*
1213c84f3f3cSopenharmony_ci * Prepare to generate the string returned by ${} substitution.
1214c84f3f3cSopenharmony_ci */
1215c84f3f3cSopenharmony_cistatic int
1216c84f3f3cSopenharmony_civarsub(Expand *xp, const char *sp, const char *word,
1217c84f3f3cSopenharmony_ci    /* becomes qualifier type */
1218c84f3f3cSopenharmony_ci    unsigned int *stypep,
1219c84f3f3cSopenharmony_ci    /* becomes qualifier type len (=, :=, etc.) valid iff *stypep != 0 */
1220c84f3f3cSopenharmony_ci    int *slenp)
1221c84f3f3cSopenharmony_ci{
1222c84f3f3cSopenharmony_ci	unsigned int c;
1223c84f3f3cSopenharmony_ci	int state;		/* next state: XBASE, XARG, XSUB, XNULLSUB */
1224c84f3f3cSopenharmony_ci	unsigned int stype;	/* substitution type */
1225c84f3f3cSopenharmony_ci	int slen = 0;
1226c84f3f3cSopenharmony_ci	const char *p;
1227c84f3f3cSopenharmony_ci	struct tbl *vp;
1228c84f3f3cSopenharmony_ci	bool zero_ok = false;
1229c84f3f3cSopenharmony_ci	int sc;
1230c84f3f3cSopenharmony_ci	XPtrV wv;
1231c84f3f3cSopenharmony_ci
1232c84f3f3cSopenharmony_ci	if ((stype = ord(sp[0])) == '\0')
1233c84f3f3cSopenharmony_ci		/* Bad variable name */
1234c84f3f3cSopenharmony_ci		return (-1);
1235c84f3f3cSopenharmony_ci
1236c84f3f3cSopenharmony_ci	xp->var = NULL;
1237c84f3f3cSopenharmony_ci
1238c84f3f3cSopenharmony_ci	/* entirety of named array? */
1239c84f3f3cSopenharmony_ci	if ((p = cstrchr(sp, '[')) && (sc = ord(p[1])) &&
1240c84f3f3cSopenharmony_ci	    ord(p[2]) == ORD(']'))
1241c84f3f3cSopenharmony_ci		/* keep p (for ${!foo[1]} below)! */
1242c84f3f3cSopenharmony_ci		switch (sc) {
1243c84f3f3cSopenharmony_ci		case ORD('*'):
1244c84f3f3cSopenharmony_ci			sc = 3;
1245c84f3f3cSopenharmony_ci			break;
1246c84f3f3cSopenharmony_ci		case ORD('@'):
1247c84f3f3cSopenharmony_ci			sc = 7;
1248c84f3f3cSopenharmony_ci			break;
1249c84f3f3cSopenharmony_ci		default:
1250c84f3f3cSopenharmony_ci			/* bit2 = @, bit1 = array, bit0 = enabled */
1251c84f3f3cSopenharmony_ci			sc = 0;
1252c84f3f3cSopenharmony_ci		}
1253c84f3f3cSopenharmony_ci	else
1254c84f3f3cSopenharmony_ci		/* $* and $@ checked below */
1255c84f3f3cSopenharmony_ci		sc = 0;
1256c84f3f3cSopenharmony_ci
1257c84f3f3cSopenharmony_ci	/*-
1258c84f3f3cSopenharmony_ci	 * ${%var}, string width (-U: screen columns, +U: octets)
1259c84f3f3cSopenharmony_ci	 * ${#var}, string length (-U: characters, +U: octets) or array size
1260c84f3f3cSopenharmony_ci	 * ${!var}, variable name
1261c84f3f3cSopenharmony_ci	 * ${*…} -> set flag for argv
1262c84f3f3cSopenharmony_ci	 * ${@…} -> set flag for argv
1263c84f3f3cSopenharmony_ci	 */
1264c84f3f3cSopenharmony_ci	if (ctype(stype, C_SUB2 | CiVAR1)) {
1265c84f3f3cSopenharmony_ci		switch (stype) {
1266c84f3f3cSopenharmony_ci		case ORD('*'):
1267c84f3f3cSopenharmony_ci			if (!sc)
1268c84f3f3cSopenharmony_ci				sc = 1;
1269c84f3f3cSopenharmony_ci			goto nopfx;
1270c84f3f3cSopenharmony_ci		case ORD('@'):
1271c84f3f3cSopenharmony_ci			if (!sc)
1272c84f3f3cSopenharmony_ci				sc = 5;
1273c84f3f3cSopenharmony_ci			goto nopfx;
1274c84f3f3cSopenharmony_ci		}
1275c84f3f3cSopenharmony_ci		/* varname required */
1276c84f3f3cSopenharmony_ci		if ((c = ord(sp[1])) == '\0') {
1277c84f3f3cSopenharmony_ci			if (stype == ORD('%'))
1278c84f3f3cSopenharmony_ci				/* $% */
1279c84f3f3cSopenharmony_ci				return (-1);
1280c84f3f3cSopenharmony_ci			/* $# or $! */
1281c84f3f3cSopenharmony_ci			goto nopfx;
1282c84f3f3cSopenharmony_ci		}
1283c84f3f3cSopenharmony_ci		/* can’t have any modifiers for ${#…} or ${%…} or ${!…} */
1284c84f3f3cSopenharmony_ci		if (*word != CSUBST)
1285c84f3f3cSopenharmony_ci			return (-1);
1286c84f3f3cSopenharmony_ci		/* check for argv past prefix */
1287c84f3f3cSopenharmony_ci		if (!sc) switch (c) {
1288c84f3f3cSopenharmony_ci		case ORD('*'):
1289c84f3f3cSopenharmony_ci			sc = 1;
1290c84f3f3cSopenharmony_ci			break;
1291c84f3f3cSopenharmony_ci		case ORD('@'):
1292c84f3f3cSopenharmony_ci			sc = 5;
1293c84f3f3cSopenharmony_ci			break;
1294c84f3f3cSopenharmony_ci		}
1295c84f3f3cSopenharmony_ci		/* skip past prefix */
1296c84f3f3cSopenharmony_ci		++sp;
1297c84f3f3cSopenharmony_ci		/* determine result */
1298c84f3f3cSopenharmony_ci		switch (stype) {
1299c84f3f3cSopenharmony_ci		case ORD('!'):
1300c84f3f3cSopenharmony_ci			if (sc & 2) {
1301c84f3f3cSopenharmony_ci				stype = 0;
1302c84f3f3cSopenharmony_ci				XPinit(wv, 32);
1303c84f3f3cSopenharmony_ci				vp = global(arrayname(sp));
1304c84f3f3cSopenharmony_ci				do {
1305c84f3f3cSopenharmony_ci					if (vp->flag & ISSET)
1306c84f3f3cSopenharmony_ci						XPput(wv, shf_smprintf(Tf_lu,
1307c84f3f3cSopenharmony_ci						    arrayindex(vp)));
1308c84f3f3cSopenharmony_ci				} while ((vp = vp->u.array));
1309c84f3f3cSopenharmony_ci				goto arraynames;
1310c84f3f3cSopenharmony_ci			}
1311c84f3f3cSopenharmony_ci			xp->var = global(sp);
1312c84f3f3cSopenharmony_ci			/* use saved p from above */
1313c84f3f3cSopenharmony_ci			xp->str = p ? shf_smprintf("%s[%lu]", xp->var->name,
1314c84f3f3cSopenharmony_ci			    arrayindex(xp->var)) : xp->var->name;
1315c84f3f3cSopenharmony_ci			break;
1316c84f3f3cSopenharmony_ci#ifdef DEBUG
1317c84f3f3cSopenharmony_ci		default:
1318c84f3f3cSopenharmony_ci			internal_errorf("stype mismatch");
1319c84f3f3cSopenharmony_ci			/* NOTREACHED */
1320c84f3f3cSopenharmony_ci#endif
1321c84f3f3cSopenharmony_ci		case ORD('%'):
1322c84f3f3cSopenharmony_ci			/* cannot do this on an array */
1323c84f3f3cSopenharmony_ci			if (sc)
1324c84f3f3cSopenharmony_ci				return (-1);
1325c84f3f3cSopenharmony_ci			p = str_val(global(sp));
1326c84f3f3cSopenharmony_ci			zero_ok = p != null;
1327c84f3f3cSopenharmony_ci			/* partial utf_mbswidth reimplementation */
1328c84f3f3cSopenharmony_ci			sc = 0;
1329c84f3f3cSopenharmony_ci			while (*p) {
1330c84f3f3cSopenharmony_ci				if (!UTFMODE ||
1331c84f3f3cSopenharmony_ci				    (wv.len = utf_mbtowc(&c, p)) == (size_t)-1)
1332c84f3f3cSopenharmony_ci					/* not UTFMODE or not UTF-8 */
1333c84f3f3cSopenharmony_ci					c = rtt2asc(*p++);
1334c84f3f3cSopenharmony_ci				else
1335c84f3f3cSopenharmony_ci					/* UTFMODE and UTF-8 */
1336c84f3f3cSopenharmony_ci					p += wv.len;
1337c84f3f3cSopenharmony_ci				/* c == char or wchar at p++ */
1338c84f3f3cSopenharmony_ci				if ((slen = utf_wcwidth(c)) == -1) {
1339c84f3f3cSopenharmony_ci					/* 646, 8859-1, 10646 C0/C1 */
1340c84f3f3cSopenharmony_ci					sc = -1;
1341c84f3f3cSopenharmony_ci					break;
1342c84f3f3cSopenharmony_ci				}
1343c84f3f3cSopenharmony_ci				sc += slen;
1344c84f3f3cSopenharmony_ci			}
1345c84f3f3cSopenharmony_ci			if (0)
1346c84f3f3cSopenharmony_ci				/* FALLTHROUGH */
1347c84f3f3cSopenharmony_ci		case ORD('#'):
1348c84f3f3cSopenharmony_ci			  switch (sc & 3) {
1349c84f3f3cSopenharmony_ci			case 3:
1350c84f3f3cSopenharmony_ci				vp = global(arrayname(sp));
1351c84f3f3cSopenharmony_ci				if (vp->flag & (ISSET|ARRAY))
1352c84f3f3cSopenharmony_ci					zero_ok = true;
1353c84f3f3cSopenharmony_ci				sc = 0;
1354c84f3f3cSopenharmony_ci				do {
1355c84f3f3cSopenharmony_ci					if (vp->flag & ISSET)
1356c84f3f3cSopenharmony_ci						sc++;
1357c84f3f3cSopenharmony_ci				} while ((vp = vp->u.array));
1358c84f3f3cSopenharmony_ci				break;
1359c84f3f3cSopenharmony_ci			case 1:
1360c84f3f3cSopenharmony_ci				sc = e->loc->argc;
1361c84f3f3cSopenharmony_ci				break;
1362c84f3f3cSopenharmony_ci			default:
1363c84f3f3cSopenharmony_ci				p = str_val(global(sp));
1364c84f3f3cSopenharmony_ci				zero_ok = p != null;
1365c84f3f3cSopenharmony_ci				sc = utflen(p);
1366c84f3f3cSopenharmony_ci				break;
1367c84f3f3cSopenharmony_ci			}
1368c84f3f3cSopenharmony_ci			/* ${%var} also here */
1369c84f3f3cSopenharmony_ci			if (Flag(FNOUNSET) && sc == 0 && !zero_ok)
1370c84f3f3cSopenharmony_ci				errorf(Tf_parm, sp);
1371c84f3f3cSopenharmony_ci			xp->str = shf_smprintf(Tf_d, sc);
1372c84f3f3cSopenharmony_ci			break;
1373c84f3f3cSopenharmony_ci		}
1374c84f3f3cSopenharmony_ci		/* unqualified variable/string substitution */
1375c84f3f3cSopenharmony_ci		*stypep = 0;
1376c84f3f3cSopenharmony_ci		return (XSUB);
1377c84f3f3cSopenharmony_ci	}
1378c84f3f3cSopenharmony_ci nopfx:
1379c84f3f3cSopenharmony_ci
1380c84f3f3cSopenharmony_ci	/* check for qualifiers in word part */
1381c84f3f3cSopenharmony_ci	stype = 0;
1382c84f3f3cSopenharmony_ci	/*slen = 0;*/
1383c84f3f3cSopenharmony_ci	c = word[/*slen +*/ 0] == CHAR ? ord(word[/*slen +*/ 1]) : 0;
1384c84f3f3cSopenharmony_ci	if (c == ORD(':')) {
1385c84f3f3cSopenharmony_ci		slen += 2;
1386c84f3f3cSopenharmony_ci		stype = STYPE_DBL;
1387c84f3f3cSopenharmony_ci		c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
1388c84f3f3cSopenharmony_ci	}
1389c84f3f3cSopenharmony_ci	if (!stype && c == ORD('/')) {
1390c84f3f3cSopenharmony_ci		slen += 2;
1391c84f3f3cSopenharmony_ci		stype = c;
1392c84f3f3cSopenharmony_ci		if (word[slen] == ADELIM &&
1393c84f3f3cSopenharmony_ci		    ord(word[slen + 1]) == c) {
1394c84f3f3cSopenharmony_ci			slen += 2;
1395c84f3f3cSopenharmony_ci			stype |= STYPE_DBL;
1396c84f3f3cSopenharmony_ci		}
1397c84f3f3cSopenharmony_ci	} else if (stype == STYPE_DBL && (c == ORD(' ') || c == ORD('0'))) {
1398c84f3f3cSopenharmony_ci		stype |= ORD('0');
1399c84f3f3cSopenharmony_ci	} else if (ctype(c, C_SUB1)) {
1400c84f3f3cSopenharmony_ci		slen += 2;
1401c84f3f3cSopenharmony_ci		stype |= c;
1402c84f3f3cSopenharmony_ci	} else if (ctype(c, C_SUB2)) {
1403c84f3f3cSopenharmony_ci		/* Note: ksh88 allows :%, :%%, etc */
1404c84f3f3cSopenharmony_ci		slen += 2;
1405c84f3f3cSopenharmony_ci		stype = c;
1406c84f3f3cSopenharmony_ci		if (word[slen + 0] == CHAR && ord(word[slen + 1]) == c) {
1407c84f3f3cSopenharmony_ci			stype |= STYPE_DBL;
1408c84f3f3cSopenharmony_ci			slen += 2;
1409c84f3f3cSopenharmony_ci		}
1410c84f3f3cSopenharmony_ci	} else if (c == ORD('@')) {
1411c84f3f3cSopenharmony_ci		/* @x where x is command char */
1412c84f3f3cSopenharmony_ci		switch (c = ord(word[slen + 2]) == CHAR ?
1413c84f3f3cSopenharmony_ci		    ord(word[slen + 3]) : 0) {
1414c84f3f3cSopenharmony_ci		case ORD('#'):
1415c84f3f3cSopenharmony_ci		case ORD('/'):
1416c84f3f3cSopenharmony_ci		case ORD('Q'):
1417c84f3f3cSopenharmony_ci			break;
1418c84f3f3cSopenharmony_ci		default:
1419c84f3f3cSopenharmony_ci			return (-1);
1420c84f3f3cSopenharmony_ci		}
1421c84f3f3cSopenharmony_ci		stype |= STYPE_AT | c;
1422c84f3f3cSopenharmony_ci		slen += 4;
1423c84f3f3cSopenharmony_ci	} else if (stype)
1424c84f3f3cSopenharmony_ci		/* : is not ok */
1425c84f3f3cSopenharmony_ci		return (-1);
1426c84f3f3cSopenharmony_ci	if (!stype && *word != CSUBST)
1427c84f3f3cSopenharmony_ci		return (-1);
1428c84f3f3cSopenharmony_ci
1429c84f3f3cSopenharmony_ci	if (!sc) {
1430c84f3f3cSopenharmony_ci		xp->var = global(sp);
1431c84f3f3cSopenharmony_ci		xp->str = str_val(xp->var);
1432c84f3f3cSopenharmony_ci		/* can't assign things like $! or $1 */
1433c84f3f3cSopenharmony_ci		if ((stype & STYPE_SINGLE) == ORD('=') &&
1434c84f3f3cSopenharmony_ci		    !*xp->str && ctype(*sp, C_VAR1 | C_DIGIT))
1435c84f3f3cSopenharmony_ci			return (-1);
1436c84f3f3cSopenharmony_ci		state = XSUB;
1437c84f3f3cSopenharmony_ci	} else {
1438c84f3f3cSopenharmony_ci		/* can’t assign/trim a vector (yet) */
1439c84f3f3cSopenharmony_ci		switch (stype & STYPE_SINGLE) {
1440c84f3f3cSopenharmony_ci		case ORD('-'):
1441c84f3f3cSopenharmony_ci		case ORD('+'):
1442c84f3f3cSopenharmony_ci			/* allowed ops */
1443c84f3f3cSopenharmony_ci		case 0:
1444c84f3f3cSopenharmony_ci			/* or no ops */
1445c84f3f3cSopenharmony_ci			break;
1446c84f3f3cSopenharmony_ci	/*	case ORD('='):
1447c84f3f3cSopenharmony_ci		case ORD('?'):
1448c84f3f3cSopenharmony_ci		case ORD('#'):
1449c84f3f3cSopenharmony_ci		case ORD('%'):
1450c84f3f3cSopenharmony_ci		case ORD('/'):
1451c84f3f3cSopenharmony_ci		case ORD('/') | STYPE_AT:
1452c84f3f3cSopenharmony_ci		case ORD('0'):
1453c84f3f3cSopenharmony_ci		case ORD('#') | STYPE_AT:
1454c84f3f3cSopenharmony_ci		case ORD('Q') | STYPE_AT:
1455c84f3f3cSopenharmony_ci	*/	default:
1456c84f3f3cSopenharmony_ci			return (-1);
1457c84f3f3cSopenharmony_ci		}
1458c84f3f3cSopenharmony_ci		/* do what we can */
1459c84f3f3cSopenharmony_ci		if (sc & 2) {
1460c84f3f3cSopenharmony_ci			XPinit(wv, 32);
1461c84f3f3cSopenharmony_ci			vp = global(arrayname(sp));
1462c84f3f3cSopenharmony_ci			do {
1463c84f3f3cSopenharmony_ci				if (vp->flag & ISSET)
1464c84f3f3cSopenharmony_ci					XPput(wv, str_val(vp));
1465c84f3f3cSopenharmony_ci			} while ((vp = vp->u.array));
1466c84f3f3cSopenharmony_ci arraynames:
1467c84f3f3cSopenharmony_ci			if ((c = (XPsize(wv) == 0)))
1468c84f3f3cSopenharmony_ci				XPfree(wv);
1469c84f3f3cSopenharmony_ci			else {
1470c84f3f3cSopenharmony_ci				XPput(wv, NULL);
1471c84f3f3cSopenharmony_ci				xp->u.strv = (const char **)XPptrv(wv);
1472c84f3f3cSopenharmony_ci			}
1473c84f3f3cSopenharmony_ci		} else {
1474c84f3f3cSopenharmony_ci			if ((c = (e->loc->argc == 0)))
1475c84f3f3cSopenharmony_ci				xp->var = global(sp);
1476c84f3f3cSopenharmony_ci			else
1477c84f3f3cSopenharmony_ci				xp->u.strv = (const char **)e->loc->argv + 1;
1478c84f3f3cSopenharmony_ci			/* POSIX 2009? */
1479c84f3f3cSopenharmony_ci			zero_ok = true;
1480c84f3f3cSopenharmony_ci		}
1481c84f3f3cSopenharmony_ci		/* have we got any elements? */
1482c84f3f3cSopenharmony_ci		if (c) {
1483c84f3f3cSopenharmony_ci			/* no */
1484c84f3f3cSopenharmony_ci			xp->str = null;
1485c84f3f3cSopenharmony_ci			state = sc & 4 ? XNULLSUB : XSUB;
1486c84f3f3cSopenharmony_ci		} else {
1487c84f3f3cSopenharmony_ci			/* yes → load first */
1488c84f3f3cSopenharmony_ci			xp->str = *xp->u.strv++;
1489c84f3f3cSopenharmony_ci			/* $@ or ${foo[@]} */
1490c84f3f3cSopenharmony_ci			xp->split = tobool(sc & 4);
1491c84f3f3cSopenharmony_ci			state = XARG;
1492c84f3f3cSopenharmony_ci		}
1493c84f3f3cSopenharmony_ci	}
1494c84f3f3cSopenharmony_ci
1495c84f3f3cSopenharmony_ci	c = stype & STYPE_CHAR;
1496c84f3f3cSopenharmony_ci	/* test the compiler's code generator */
1497c84f3f3cSopenharmony_ci	if ((!(stype & STYPE_AT) && (ctype(c, C_SUB2) ||
1498c84f3f3cSopenharmony_ci	    (((stype & STYPE_DBL) ? *xp->str == '\0' : xp->str == null) &&
1499c84f3f3cSopenharmony_ci	    (state != XARG || (ifs0 || xp->split ?
1500c84f3f3cSopenharmony_ci	    (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
1501c84f3f3cSopenharmony_ci	    ctype(c, C_EQUAL | C_MINUS | C_QUEST) : c == ORD('+')))) ||
1502c84f3f3cSopenharmony_ci	    stype == (ORD('0') | STYPE_DBL) ||
1503c84f3f3cSopenharmony_ci	    stype == (ORD('#') | STYPE_AT) ||
1504c84f3f3cSopenharmony_ci	    stype == (ORD('Q') | STYPE_AT) ||
1505c84f3f3cSopenharmony_ci	    (stype & STYPE_CHAR) == ORD('/'))
1506c84f3f3cSopenharmony_ci		/* expand word instead of variable value */
1507c84f3f3cSopenharmony_ci		state = XBASE;
1508c84f3f3cSopenharmony_ci	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
1509c84f3f3cSopenharmony_ci	    (ctype(c, C_SUB2) || (state != XBASE && c != ORD('+'))))
1510c84f3f3cSopenharmony_ci		errorf(Tf_parm, sp);
1511c84f3f3cSopenharmony_ci	*stypep = stype;
1512c84f3f3cSopenharmony_ci	*slenp = slen;
1513c84f3f3cSopenharmony_ci	return (state);
1514c84f3f3cSopenharmony_ci}
1515c84f3f3cSopenharmony_ci
1516c84f3f3cSopenharmony_ci/*
1517c84f3f3cSopenharmony_ci * Run the command in $(...) and read its output.
1518c84f3f3cSopenharmony_ci */
1519c84f3f3cSopenharmony_cistatic int
1520c84f3f3cSopenharmony_cicomsub(Expand *xp, const char *cp, int fn)
1521c84f3f3cSopenharmony_ci{
1522c84f3f3cSopenharmony_ci	Source *s, *sold;
1523c84f3f3cSopenharmony_ci	struct op *t;
1524c84f3f3cSopenharmony_ci	struct shf *shf;
1525c84f3f3cSopenharmony_ci	bool doalias = false;
1526c84f3f3cSopenharmony_ci	uint8_t old_utfmode = UTFMODE;
1527c84f3f3cSopenharmony_ci
1528c84f3f3cSopenharmony_ci	switch (fn) {
1529c84f3f3cSopenharmony_ci	case COMASUB:
1530c84f3f3cSopenharmony_ci		fn = COMSUB;
1531c84f3f3cSopenharmony_ci		if (0)
1532c84f3f3cSopenharmony_ci			/* FALLTHROUGH */
1533c84f3f3cSopenharmony_ci	case FUNASUB:
1534c84f3f3cSopenharmony_ci		  fn = FUNSUB;
1535c84f3f3cSopenharmony_ci		doalias = true;
1536c84f3f3cSopenharmony_ci	}
1537c84f3f3cSopenharmony_ci
1538c84f3f3cSopenharmony_ci	s = pushs(SSTRING, ATEMP);
1539c84f3f3cSopenharmony_ci	s->start = s->str = cp;
1540c84f3f3cSopenharmony_ci	sold = source;
1541c84f3f3cSopenharmony_ci	t = compile(s, true, doalias);
1542c84f3f3cSopenharmony_ci	afree(s, ATEMP);
1543c84f3f3cSopenharmony_ci	source = sold;
1544c84f3f3cSopenharmony_ci
1545c84f3f3cSopenharmony_ci	UTFMODE = old_utfmode;
1546c84f3f3cSopenharmony_ci
1547c84f3f3cSopenharmony_ci	if (t == NULL)
1548c84f3f3cSopenharmony_ci		return (XBASE);
1549c84f3f3cSopenharmony_ci
1550c84f3f3cSopenharmony_ci	/* no waitlast() unless specifically enabled later */
1551c84f3f3cSopenharmony_ci	xp->split = false;
1552c84f3f3cSopenharmony_ci
1553c84f3f3cSopenharmony_ci	if (t->type == TCOM &&
1554c84f3f3cSopenharmony_ci	    *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
1555c84f3f3cSopenharmony_ci		/* $(<file) */
1556c84f3f3cSopenharmony_ci		struct ioword *io = *t->ioact;
1557c84f3f3cSopenharmony_ci		char *name;
1558c84f3f3cSopenharmony_ci
1559c84f3f3cSopenharmony_ci		switch (io->ioflag & IOTYPE) {
1560c84f3f3cSopenharmony_ci		case IOREAD:
1561c84f3f3cSopenharmony_ci			shf = shf_open(name = evalstr(io->ioname, DOTILDE),
1562c84f3f3cSopenharmony_ci				O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC);
1563c84f3f3cSopenharmony_ci			if (shf == NULL)
1564c84f3f3cSopenharmony_ci				warningf(!Flag(FTALKING), Tf_sD_s_sD_s,
1565c84f3f3cSopenharmony_ci				    name, Tcant_open, "$(<...) input",
1566c84f3f3cSopenharmony_ci				    cstrerror(errno));
1567c84f3f3cSopenharmony_ci			break;
1568c84f3f3cSopenharmony_ci		case IOHERE:
1569c84f3f3cSopenharmony_ci			if (!herein(io, &name)) {
1570c84f3f3cSopenharmony_ci				xp->str = name;
1571c84f3f3cSopenharmony_ci				/* as $(…) requires, trim trailing newlines */
1572c84f3f3cSopenharmony_ci				name = strnul(name);
1573c84f3f3cSopenharmony_ci				while (name > xp->str && name[-1] == '\n')
1574c84f3f3cSopenharmony_ci					--name;
1575c84f3f3cSopenharmony_ci				*name = '\0';
1576c84f3f3cSopenharmony_ci				return (XSUB);
1577c84f3f3cSopenharmony_ci			}
1578c84f3f3cSopenharmony_ci			shf = NULL;
1579c84f3f3cSopenharmony_ci			break;
1580c84f3f3cSopenharmony_ci		default:
1581c84f3f3cSopenharmony_ci			errorf(Tf_sD_s, T_funny_command,
1582c84f3f3cSopenharmony_ci			    snptreef(NULL, 32, Tft_R, io));
1583c84f3f3cSopenharmony_ci		}
1584c84f3f3cSopenharmony_ci	} else if (fn == FUNSUB) {
1585c84f3f3cSopenharmony_ci		int ofd1;
1586c84f3f3cSopenharmony_ci		struct temp *tf = NULL;
1587c84f3f3cSopenharmony_ci
1588c84f3f3cSopenharmony_ci		/*
1589c84f3f3cSopenharmony_ci		 * create a temporary file, open for reading and writing,
1590c84f3f3cSopenharmony_ci		 * with an shf open for reading (buffered) but yet unused
1591c84f3f3cSopenharmony_ci		 */
1592c84f3f3cSopenharmony_ci		maketemp(ATEMP, TT_FUNSUB, &tf);
1593c84f3f3cSopenharmony_ci		if (!tf->shf) {
1594c84f3f3cSopenharmony_ci			errorf(Tf_temp,
1595c84f3f3cSopenharmony_ci			    Tcreate, tf->tffn, cstrerror(errno));
1596c84f3f3cSopenharmony_ci		}
1597c84f3f3cSopenharmony_ci		/* extract shf from temporary file, unlink and free it */
1598c84f3f3cSopenharmony_ci		shf = tf->shf;
1599c84f3f3cSopenharmony_ci		unlink(tf->tffn);
1600c84f3f3cSopenharmony_ci		afree(tf, ATEMP);
1601c84f3f3cSopenharmony_ci		/* save stdout and let it point to the tempfile */
1602c84f3f3cSopenharmony_ci		ofd1 = savefd(1);
1603c84f3f3cSopenharmony_ci		ksh_dup2(shf_fileno(shf), 1, false);
1604c84f3f3cSopenharmony_ci		/*
1605c84f3f3cSopenharmony_ci		 * run tree, with output thrown into the tempfile,
1606c84f3f3cSopenharmony_ci		 * in a new function block
1607c84f3f3cSopenharmony_ci		 */
1608c84f3f3cSopenharmony_ci		valsub(t, NULL);
1609c84f3f3cSopenharmony_ci		subst_exstat = exstat & 0xFF;
1610c84f3f3cSopenharmony_ci		/* rewind the tempfile and restore regular stdout */
1611c84f3f3cSopenharmony_ci		lseek(shf_fileno(shf), (off_t)0, SEEK_SET);
1612c84f3f3cSopenharmony_ci		restfd(1, ofd1);
1613c84f3f3cSopenharmony_ci	} else if (fn == VALSUB) {
1614c84f3f3cSopenharmony_ci		xp->str = valsub(t, ATEMP);
1615c84f3f3cSopenharmony_ci		subst_exstat = exstat & 0xFF;
1616c84f3f3cSopenharmony_ci		return (XSUB);
1617c84f3f3cSopenharmony_ci	} else {
1618c84f3f3cSopenharmony_ci		int ofd1, pv[2];
1619c84f3f3cSopenharmony_ci
1620c84f3f3cSopenharmony_ci		openpipe(pv);
1621c84f3f3cSopenharmony_ci		shf = shf_fdopen(pv[0], SHF_RD, NULL);
1622c84f3f3cSopenharmony_ci		ofd1 = savefd(1);
1623c84f3f3cSopenharmony_ci		if (pv[1] != 1) {
1624c84f3f3cSopenharmony_ci			ksh_dup2(pv[1], 1, false);
1625c84f3f3cSopenharmony_ci			close(pv[1]);
1626c84f3f3cSopenharmony_ci		}
1627c84f3f3cSopenharmony_ci		execute(t, XXCOM | XPIPEO | XFORK, NULL);
1628c84f3f3cSopenharmony_ci		restfd(1, ofd1);
1629c84f3f3cSopenharmony_ci		startlast();
1630c84f3f3cSopenharmony_ci		/* waitlast() */
1631c84f3f3cSopenharmony_ci		xp->split = true;
1632c84f3f3cSopenharmony_ci	}
1633c84f3f3cSopenharmony_ci
1634c84f3f3cSopenharmony_ci	xp->u.shf = shf;
1635c84f3f3cSopenharmony_ci	return (XCOM);
1636c84f3f3cSopenharmony_ci}
1637c84f3f3cSopenharmony_ci
1638c84f3f3cSopenharmony_ci/*
1639c84f3f3cSopenharmony_ci * perform #pattern and %pattern substitution in ${}
1640c84f3f3cSopenharmony_ci */
1641c84f3f3cSopenharmony_cistatic char *
1642c84f3f3cSopenharmony_citrimsub(char *str, char *pat, int how)
1643c84f3f3cSopenharmony_ci{
1644c84f3f3cSopenharmony_ci	char *end = strnul(str);
1645c84f3f3cSopenharmony_ci	char *p, c;
1646c84f3f3cSopenharmony_ci
1647c84f3f3cSopenharmony_ci	switch (how & (STYPE_CHAR | STYPE_DBL)) {
1648c84f3f3cSopenharmony_ci	case ORD('#'):
1649c84f3f3cSopenharmony_ci		/* shortest match at beginning */
1650c84f3f3cSopenharmony_ci		for (p = str; p <= end; p += utf_ptradj(p)) {
1651c84f3f3cSopenharmony_ci			c = *p; *p = '\0';
1652c84f3f3cSopenharmony_ci			if (gmatchx(str, pat, false)) {
1653c84f3f3cSopenharmony_ci				record_match(str);
1654c84f3f3cSopenharmony_ci				*p = c;
1655c84f3f3cSopenharmony_ci				return (p);
1656c84f3f3cSopenharmony_ci			}
1657c84f3f3cSopenharmony_ci			*p = c;
1658c84f3f3cSopenharmony_ci		}
1659c84f3f3cSopenharmony_ci		break;
1660c84f3f3cSopenharmony_ci	case ORD('#') | STYPE_DBL:
1661c84f3f3cSopenharmony_ci		/* longest match at beginning */
1662c84f3f3cSopenharmony_ci		for (p = end; p >= str; p--) {
1663c84f3f3cSopenharmony_ci			c = *p; *p = '\0';
1664c84f3f3cSopenharmony_ci			if (gmatchx(str, pat, false)) {
1665c84f3f3cSopenharmony_ci				record_match(str);
1666c84f3f3cSopenharmony_ci				*p = c;
1667c84f3f3cSopenharmony_ci				return (p);
1668c84f3f3cSopenharmony_ci			}
1669c84f3f3cSopenharmony_ci			*p = c;
1670c84f3f3cSopenharmony_ci		}
1671c84f3f3cSopenharmony_ci		break;
1672c84f3f3cSopenharmony_ci	case ORD('%'):
1673c84f3f3cSopenharmony_ci		/* shortest match at end */
1674c84f3f3cSopenharmony_ci		p = end;
1675c84f3f3cSopenharmony_ci		while (p >= str) {
1676c84f3f3cSopenharmony_ci			if (gmatchx(p, pat, false))
1677c84f3f3cSopenharmony_ci				goto trimsub_match;
1678c84f3f3cSopenharmony_ci			if (UTFMODE) {
1679c84f3f3cSopenharmony_ci				char *op = p;
1680c84f3f3cSopenharmony_ci				while ((p-- > str) && ((rtt2asc(*p) & 0xC0) == 0x80))
1681c84f3f3cSopenharmony_ci					;
1682c84f3f3cSopenharmony_ci				if ((p < str) || (p + utf_ptradj(p) != op))
1683c84f3f3cSopenharmony_ci					p = op - 1;
1684c84f3f3cSopenharmony_ci			} else
1685c84f3f3cSopenharmony_ci				--p;
1686c84f3f3cSopenharmony_ci		}
1687c84f3f3cSopenharmony_ci		break;
1688c84f3f3cSopenharmony_ci	case ORD('%') | STYPE_DBL:
1689c84f3f3cSopenharmony_ci		/* longest match at end */
1690c84f3f3cSopenharmony_ci		for (p = str; p <= end; p++)
1691c84f3f3cSopenharmony_ci			if (gmatchx(p, pat, false)) {
1692c84f3f3cSopenharmony_ci trimsub_match:
1693c84f3f3cSopenharmony_ci				record_match(p);
1694c84f3f3cSopenharmony_ci				strndupx(end, str, p - str, ATEMP);
1695c84f3f3cSopenharmony_ci				return (end);
1696c84f3f3cSopenharmony_ci			}
1697c84f3f3cSopenharmony_ci		break;
1698c84f3f3cSopenharmony_ci	}
1699c84f3f3cSopenharmony_ci
1700c84f3f3cSopenharmony_ci	/* no match, return string */
1701c84f3f3cSopenharmony_ci	return (str);
1702c84f3f3cSopenharmony_ci}
1703c84f3f3cSopenharmony_ci
1704c84f3f3cSopenharmony_ci/*
1705c84f3f3cSopenharmony_ci * glob
1706c84f3f3cSopenharmony_ci * Name derived from V6's /etc/glob, the program that expanded filenames.
1707c84f3f3cSopenharmony_ci */
1708c84f3f3cSopenharmony_ci
1709c84f3f3cSopenharmony_ci/* XXX cp not const 'cause slashes are temporarily replaced with NULs... */
1710c84f3f3cSopenharmony_cistatic void
1711c84f3f3cSopenharmony_ciglob(char *cp, XPtrV *wp, bool markdirs)
1712c84f3f3cSopenharmony_ci{
1713c84f3f3cSopenharmony_ci	int oldsize = XPsize(*wp);
1714c84f3f3cSopenharmony_ci
1715c84f3f3cSopenharmony_ci	if (glob_str(cp, wp, markdirs) == 0)
1716c84f3f3cSopenharmony_ci		XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
1717c84f3f3cSopenharmony_ci	else
1718c84f3f3cSopenharmony_ci		qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize,
1719c84f3f3cSopenharmony_ci		    sizeof(void *), ascpstrcmp);
1720c84f3f3cSopenharmony_ci}
1721c84f3f3cSopenharmony_ci
1722c84f3f3cSopenharmony_ci#define GF_NONE		0
1723c84f3f3cSopenharmony_ci#define GF_EXCHECK	BIT(0)		/* do existence check on file */
1724c84f3f3cSopenharmony_ci#define GF_GLOBBED	BIT(1)		/* some globbing has been done */
1725c84f3f3cSopenharmony_ci#define GF_MARKDIR	BIT(2)		/* add trailing / to directories */
1726c84f3f3cSopenharmony_ci
1727c84f3f3cSopenharmony_ci/*
1728c84f3f3cSopenharmony_ci * Apply file globbing to cp and store the matching files in wp. Returns
1729c84f3f3cSopenharmony_ci * the number of matches found.
1730c84f3f3cSopenharmony_ci */
1731c84f3f3cSopenharmony_ciint
1732c84f3f3cSopenharmony_ciglob_str(char *cp, XPtrV *wp, bool markdirs)
1733c84f3f3cSopenharmony_ci{
1734c84f3f3cSopenharmony_ci	int oldsize = XPsize(*wp);
1735c84f3f3cSopenharmony_ci	XString xs;
1736c84f3f3cSopenharmony_ci	char *xp;
1737c84f3f3cSopenharmony_ci
1738c84f3f3cSopenharmony_ci	Xinit(xs, xp, 256, ATEMP);
1739c84f3f3cSopenharmony_ci	globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE);
1740c84f3f3cSopenharmony_ci	Xfree(xs, xp);
1741c84f3f3cSopenharmony_ci
1742c84f3f3cSopenharmony_ci	return (XPsize(*wp) - oldsize);
1743c84f3f3cSopenharmony_ci}
1744c84f3f3cSopenharmony_ci
1745c84f3f3cSopenharmony_cistatic void
1746c84f3f3cSopenharmony_ciglobit(XString *xs,	/* dest string */
1747c84f3f3cSopenharmony_ci    char **xpp,		/* ptr to dest end */
1748c84f3f3cSopenharmony_ci    char *sp,		/* source path */
1749c84f3f3cSopenharmony_ci    XPtrV *wp,		/* output list */
1750c84f3f3cSopenharmony_ci    int check)		/* GF_* flags */
1751c84f3f3cSopenharmony_ci{
1752c84f3f3cSopenharmony_ci	char *np;		/* next source component */
1753c84f3f3cSopenharmony_ci	char *xp = *xpp;
1754c84f3f3cSopenharmony_ci	char *se;
1755c84f3f3cSopenharmony_ci	char odirsep;
1756c84f3f3cSopenharmony_ci
1757c84f3f3cSopenharmony_ci	/* This to allow long expansions to be interrupted */
1758c84f3f3cSopenharmony_ci	intrcheck();
1759c84f3f3cSopenharmony_ci
1760c84f3f3cSopenharmony_ci	if (sp == NULL) {
1761c84f3f3cSopenharmony_ci		/* end of source path */
1762c84f3f3cSopenharmony_ci		/*
1763c84f3f3cSopenharmony_ci		 * We only need to check if the file exists if a pattern
1764c84f3f3cSopenharmony_ci		 * is followed by a non-pattern (eg, foo*x/bar; no check
1765c84f3f3cSopenharmony_ci		 * is needed for foo* since the match must exist) or if
1766c84f3f3cSopenharmony_ci		 * any patterns were expanded and the markdirs option is set.
1767c84f3f3cSopenharmony_ci		 * Symlinks make things a bit tricky...
1768c84f3f3cSopenharmony_ci		 */
1769c84f3f3cSopenharmony_ci		if ((check & GF_EXCHECK) ||
1770c84f3f3cSopenharmony_ci		    ((check & GF_MARKDIR) && (check & GF_GLOBBED))) {
1771c84f3f3cSopenharmony_ci#define stat_check()	(stat_done ? stat_done : (stat_done = \
1772c84f3f3cSopenharmony_ci			    stat(Xstring(*xs, xp), &statb) < 0 ? -1 : 1))
1773c84f3f3cSopenharmony_ci			struct stat lstatb, statb;
1774c84f3f3cSopenharmony_ci			/* -1: failed, 1 ok, 0 not yet done */
1775c84f3f3cSopenharmony_ci			int stat_done = 0;
1776c84f3f3cSopenharmony_ci
1777c84f3f3cSopenharmony_ci			if (mksh_lstat(Xstring(*xs, xp), &lstatb) < 0)
1778c84f3f3cSopenharmony_ci				return;
1779c84f3f3cSopenharmony_ci			/*
1780c84f3f3cSopenharmony_ci			 * special case for systems which strip trailing
1781c84f3f3cSopenharmony_ci			 * slashes from regular files (eg, /etc/passwd/).
1782c84f3f3cSopenharmony_ci			 * SunOS 4.1.3 does this...
1783c84f3f3cSopenharmony_ci			 */
1784c84f3f3cSopenharmony_ci			if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) &&
1785c84f3f3cSopenharmony_ci			    mksh_cdirsep(xp[-1]) && !S_ISDIR(lstatb.st_mode) &&
1786c84f3f3cSopenharmony_ci			    (!S_ISLNK(lstatb.st_mode) ||
1787c84f3f3cSopenharmony_ci			    stat_check() < 0 || !S_ISDIR(statb.st_mode)))
1788c84f3f3cSopenharmony_ci				return;
1789c84f3f3cSopenharmony_ci			/*
1790c84f3f3cSopenharmony_ci			 * Possibly tack on a trailing / if there isn't already
1791c84f3f3cSopenharmony_ci			 * one and if the file is a directory or a symlink to a
1792c84f3f3cSopenharmony_ci			 * directory
1793c84f3f3cSopenharmony_ci			 */
1794c84f3f3cSopenharmony_ci			if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) &&
1795c84f3f3cSopenharmony_ci			    xp > Xstring(*xs, xp) && !mksh_cdirsep(xp[-1]) &&
1796c84f3f3cSopenharmony_ci			    (S_ISDIR(lstatb.st_mode) ||
1797c84f3f3cSopenharmony_ci			    (S_ISLNK(lstatb.st_mode) && stat_check() > 0 &&
1798c84f3f3cSopenharmony_ci			    S_ISDIR(statb.st_mode)))) {
1799c84f3f3cSopenharmony_ci				*xp++ = '/';
1800c84f3f3cSopenharmony_ci				*xp = '\0';
1801c84f3f3cSopenharmony_ci			}
1802c84f3f3cSopenharmony_ci		}
1803c84f3f3cSopenharmony_ci		strndupx(np, Xstring(*xs, xp), Xlength(*xs, xp), ATEMP);
1804c84f3f3cSopenharmony_ci		XPput(*wp, np);
1805c84f3f3cSopenharmony_ci		return;
1806c84f3f3cSopenharmony_ci	}
1807c84f3f3cSopenharmony_ci
1808c84f3f3cSopenharmony_ci	if (xp > Xstring(*xs, xp))
1809c84f3f3cSopenharmony_ci		*xp++ = '/';
1810c84f3f3cSopenharmony_ci	while (mksh_cdirsep(*sp)) {
1811c84f3f3cSopenharmony_ci		Xcheck(*xs, xp);
1812c84f3f3cSopenharmony_ci		*xp++ = *sp++;
1813c84f3f3cSopenharmony_ci	}
1814c84f3f3cSopenharmony_ci	np = mksh_sdirsep(sp);
1815c84f3f3cSopenharmony_ci	if (np != NULL) {
1816c84f3f3cSopenharmony_ci		se = np;
1817c84f3f3cSopenharmony_ci		/* don't assume '/', can be multiple kinds */
1818c84f3f3cSopenharmony_ci		odirsep = *np;
1819c84f3f3cSopenharmony_ci		*np++ = '\0';
1820c84f3f3cSopenharmony_ci	} else {
1821c84f3f3cSopenharmony_ci		odirsep = '\0'; /* keep gcc quiet */
1822c84f3f3cSopenharmony_ci		se = strnul(sp);
1823c84f3f3cSopenharmony_ci	}
1824c84f3f3cSopenharmony_ci
1825c84f3f3cSopenharmony_ci
1826c84f3f3cSopenharmony_ci	/*
1827c84f3f3cSopenharmony_ci	 * Check if sp needs globbing - done to avoid pattern checks for strings
1828c84f3f3cSopenharmony_ci	 * containing MAGIC characters, open [s without the matching close ],
1829c84f3f3cSopenharmony_ci	 * etc. (otherwise opendir() will be called which may fail because the
1830c84f3f3cSopenharmony_ci	 * directory isn't readable - if no globbing is needed, only execute
1831c84f3f3cSopenharmony_ci	 * permission should be required (as per POSIX)).
1832c84f3f3cSopenharmony_ci	 */
1833c84f3f3cSopenharmony_ci	if (!has_globbing(sp)) {
1834c84f3f3cSopenharmony_ci		XcheckN(*xs, xp, se - sp + 1);
1835c84f3f3cSopenharmony_ci		debunk(xp, sp, Xnleft(*xs, xp));
1836c84f3f3cSopenharmony_ci		xp = strnul(xp);
1837c84f3f3cSopenharmony_ci		*xpp = xp;
1838c84f3f3cSopenharmony_ci		globit(xs, xpp, np, wp, check);
1839c84f3f3cSopenharmony_ci	} else {
1840c84f3f3cSopenharmony_ci		DIR *dirp;
1841c84f3f3cSopenharmony_ci		struct dirent *d;
1842c84f3f3cSopenharmony_ci		char *name;
1843c84f3f3cSopenharmony_ci		size_t len, prefix_len;
1844c84f3f3cSopenharmony_ci
1845c84f3f3cSopenharmony_ci		/* xp = *xpp;	copy_non_glob() may have re-alloc'd xs */
1846c84f3f3cSopenharmony_ci		*xp = '\0';
1847c84f3f3cSopenharmony_ci		prefix_len = Xlength(*xs, xp);
1848c84f3f3cSopenharmony_ci		dirp = opendir(prefix_len ? Xstring(*xs, xp) : Tdot);
1849c84f3f3cSopenharmony_ci		if (dirp == NULL)
1850c84f3f3cSopenharmony_ci			goto Nodir;
1851c84f3f3cSopenharmony_ci		while ((d = readdir(dirp)) != NULL) {
1852c84f3f3cSopenharmony_ci			name = d->d_name;
1853c84f3f3cSopenharmony_ci			if (name[0] == '.' &&
1854c84f3f3cSopenharmony_ci			    (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
1855c84f3f3cSopenharmony_ci				/* always ignore . and .. */
1856c84f3f3cSopenharmony_ci				continue;
1857c84f3f3cSopenharmony_ci			if ((*name == '.' && *sp != '.') ||
1858c84f3f3cSopenharmony_ci			    !gmatchx(name, sp, true))
1859c84f3f3cSopenharmony_ci				continue;
1860c84f3f3cSopenharmony_ci
1861c84f3f3cSopenharmony_ci			len = strlen(d->d_name) + 1;
1862c84f3f3cSopenharmony_ci			XcheckN(*xs, xp, len);
1863c84f3f3cSopenharmony_ci			memcpy(xp, name, len);
1864c84f3f3cSopenharmony_ci			*xpp = xp + len - 1;
1865c84f3f3cSopenharmony_ci			globit(xs, xpp, np, wp, (check & GF_MARKDIR) |
1866c84f3f3cSopenharmony_ci			    GF_GLOBBED | (np ? GF_EXCHECK : GF_NONE));
1867c84f3f3cSopenharmony_ci			xp = Xstring(*xs, xp) + prefix_len;
1868c84f3f3cSopenharmony_ci		}
1869c84f3f3cSopenharmony_ci		closedir(dirp);
1870c84f3f3cSopenharmony_ci Nodir:
1871c84f3f3cSopenharmony_ci		;
1872c84f3f3cSopenharmony_ci	}
1873c84f3f3cSopenharmony_ci
1874c84f3f3cSopenharmony_ci	if (np != NULL)
1875c84f3f3cSopenharmony_ci		*--np = odirsep;
1876c84f3f3cSopenharmony_ci}
1877c84f3f3cSopenharmony_ci
1878c84f3f3cSopenharmony_ci/* remove MAGIC from string */
1879c84f3f3cSopenharmony_cichar *
1880c84f3f3cSopenharmony_cidebunk(char *dp, const char *sp, size_t dlen)
1881c84f3f3cSopenharmony_ci{
1882c84f3f3cSopenharmony_ci	char *d;
1883c84f3f3cSopenharmony_ci	const char *s;
1884c84f3f3cSopenharmony_ci
1885c84f3f3cSopenharmony_ci	if ((s = cstrchr(sp, MAGIC))) {
1886c84f3f3cSopenharmony_ci		if (s - sp >= (ssize_t)dlen)
1887c84f3f3cSopenharmony_ci			return (dp);
1888c84f3f3cSopenharmony_ci		memmove(dp, sp, s - sp);
1889c84f3f3cSopenharmony_ci		for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++)
1890c84f3f3cSopenharmony_ci			if (!ISMAGIC(*s) || !(*++s & 0x80) ||
1891c84f3f3cSopenharmony_ci			    !ctype(*s & 0x7F, C_PATMO | C_SPC))
1892c84f3f3cSopenharmony_ci				*d++ = *s;
1893c84f3f3cSopenharmony_ci			else {
1894c84f3f3cSopenharmony_ci				/* extended pattern operators: *+?@! */
1895c84f3f3cSopenharmony_ci				if ((*s & 0x7f) != ' ')
1896c84f3f3cSopenharmony_ci					*d++ = *s & 0x7f;
1897c84f3f3cSopenharmony_ci				if (d - dp < (ssize_t)dlen)
1898c84f3f3cSopenharmony_ci					*d++ = '(';
1899c84f3f3cSopenharmony_ci			}
1900c84f3f3cSopenharmony_ci		*d = '\0';
1901c84f3f3cSopenharmony_ci	} else if (dp != sp)
1902c84f3f3cSopenharmony_ci		strlcpy(dp, sp, dlen);
1903c84f3f3cSopenharmony_ci	return (dp);
1904c84f3f3cSopenharmony_ci}
1905c84f3f3cSopenharmony_ci
1906c84f3f3cSopenharmony_ci/*
1907c84f3f3cSopenharmony_ci * Check if p is an unquoted name, possibly followed by a / or :. If so
1908c84f3f3cSopenharmony_ci * puts the expanded version in *dcp,dp and returns a pointer in p just
1909c84f3f3cSopenharmony_ci * past the name, otherwise returns 0.
1910c84f3f3cSopenharmony_ci */
1911c84f3f3cSopenharmony_cistatic const char *
1912c84f3f3cSopenharmony_cimaybe_expand_tilde(const char *p, XString *dsp, char **dpp, bool isassign)
1913c84f3f3cSopenharmony_ci{
1914c84f3f3cSopenharmony_ci	XString ts;
1915c84f3f3cSopenharmony_ci	char *dp = *dpp;
1916c84f3f3cSopenharmony_ci	char *tp;
1917c84f3f3cSopenharmony_ci	const char *r;
1918c84f3f3cSopenharmony_ci
1919c84f3f3cSopenharmony_ci	Xinit(ts, tp, 16, ATEMP);
1920c84f3f3cSopenharmony_ci	/* : only for DOASNTILDE form */
1921c84f3f3cSopenharmony_ci	while (p[0] == CHAR && /* not cdirsep */ p[1] != '/' &&
1922c84f3f3cSopenharmony_ci	    (!isassign || p[1] != ':')) {
1923c84f3f3cSopenharmony_ci		Xcheck(ts, tp);
1924c84f3f3cSopenharmony_ci		*tp++ = p[1];
1925c84f3f3cSopenharmony_ci		p += 2;
1926c84f3f3cSopenharmony_ci	}
1927c84f3f3cSopenharmony_ci	*tp = '\0';
1928c84f3f3cSopenharmony_ci	r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ?
1929c84f3f3cSopenharmony_ci	    do_tilde(Xstring(ts, tp)) : NULL;
1930c84f3f3cSopenharmony_ci	Xfree(ts, tp);
1931c84f3f3cSopenharmony_ci	if (r) {
1932c84f3f3cSopenharmony_ci		while (*r) {
1933c84f3f3cSopenharmony_ci			Xcheck(*dsp, dp);
1934c84f3f3cSopenharmony_ci			if (ISMAGIC(*r))
1935c84f3f3cSopenharmony_ci				*dp++ = MAGIC;
1936c84f3f3cSopenharmony_ci			*dp++ = *r++;
1937c84f3f3cSopenharmony_ci		}
1938c84f3f3cSopenharmony_ci		*dpp = dp;
1939c84f3f3cSopenharmony_ci		r = p;
1940c84f3f3cSopenharmony_ci	}
1941c84f3f3cSopenharmony_ci	return (r);
1942c84f3f3cSopenharmony_ci}
1943c84f3f3cSopenharmony_ci
1944c84f3f3cSopenharmony_ci/*
1945c84f3f3cSopenharmony_ci * tilde expansion
1946c84f3f3cSopenharmony_ci *
1947c84f3f3cSopenharmony_ci * based on a version by Arnold Robbins
1948c84f3f3cSopenharmony_ci */
1949c84f3f3cSopenharmony_cichar *
1950c84f3f3cSopenharmony_cido_tilde(char *cp)
1951c84f3f3cSopenharmony_ci{
1952c84f3f3cSopenharmony_ci	char *dp = null;
1953c84f3f3cSopenharmony_ci#ifndef MKSH_NOPWNAM
1954c84f3f3cSopenharmony_ci	bool do_simplify = true;
1955c84f3f3cSopenharmony_ci#endif
1956c84f3f3cSopenharmony_ci
1957c84f3f3cSopenharmony_ci	if (cp[0] == '\0')
1958c84f3f3cSopenharmony_ci		dp = str_val(global("HOME"));
1959c84f3f3cSopenharmony_ci	else if (cp[0] == '+' && cp[1] == '\0')
1960c84f3f3cSopenharmony_ci		dp = str_val(global(TPWD));
1961c84f3f3cSopenharmony_ci	else if (ksh_isdash(cp))
1962c84f3f3cSopenharmony_ci		dp = str_val(global(TOLDPWD));
1963c84f3f3cSopenharmony_ci#ifndef MKSH_NOPWNAM
1964c84f3f3cSopenharmony_ci	else {
1965c84f3f3cSopenharmony_ci		dp = homedir(cp);
1966c84f3f3cSopenharmony_ci		do_simplify = false;
1967c84f3f3cSopenharmony_ci	}
1968c84f3f3cSopenharmony_ci#endif
1969c84f3f3cSopenharmony_ci
1970c84f3f3cSopenharmony_ci	/* if parameters aren't set, don't expand ~ */
1971c84f3f3cSopenharmony_ci	if (dp == NULL || dp == null)
1972c84f3f3cSopenharmony_ci		return (NULL);
1973c84f3f3cSopenharmony_ci
1974c84f3f3cSopenharmony_ci	/* simplify parameters as if cwd upon entry */
1975c84f3f3cSopenharmony_ci#ifndef MKSH_NOPWNAM
1976c84f3f3cSopenharmony_ci	if (do_simplify)
1977c84f3f3cSopenharmony_ci#endif
1978c84f3f3cSopenharmony_ci	  {
1979c84f3f3cSopenharmony_ci		strdupx(dp, dp, ATEMP);
1980c84f3f3cSopenharmony_ci		simplify_path(dp);
1981c84f3f3cSopenharmony_ci	}
1982c84f3f3cSopenharmony_ci	return (dp);
1983c84f3f3cSopenharmony_ci}
1984c84f3f3cSopenharmony_ci
1985c84f3f3cSopenharmony_ci#ifndef MKSH_NOPWNAM
1986c84f3f3cSopenharmony_ci/*
1987c84f3f3cSopenharmony_ci * map userid to user's home directory.
1988c84f3f3cSopenharmony_ci * note that 4.3's getpw adds more than 6K to the shell,
1989c84f3f3cSopenharmony_ci * and the YP version probably adds much more.
1990c84f3f3cSopenharmony_ci * we might consider our own version of getpwnam() to keep the size down.
1991c84f3f3cSopenharmony_ci */
1992c84f3f3cSopenharmony_cistatic char *
1993c84f3f3cSopenharmony_cihomedir(char *name)
1994c84f3f3cSopenharmony_ci{
1995c84f3f3cSopenharmony_ci	struct tbl *ap;
1996c84f3f3cSopenharmony_ci
1997c84f3f3cSopenharmony_ci	ap = ktenter(&homedirs, name, hash(name));
1998c84f3f3cSopenharmony_ci	if (!(ap->flag & ISSET)) {
1999c84f3f3cSopenharmony_ci		struct passwd *pw;
2000c84f3f3cSopenharmony_ci
2001c84f3f3cSopenharmony_ci		pw = getpwnam(name);
2002c84f3f3cSopenharmony_ci		if (pw == NULL)
2003c84f3f3cSopenharmony_ci			return (NULL);
2004c84f3f3cSopenharmony_ci		strdupx(ap->val.s, pw->pw_dir, APERM);
2005c84f3f3cSopenharmony_ci		ap->flag |= DEFINED|ISSET|ALLOC;
2006c84f3f3cSopenharmony_ci	}
2007c84f3f3cSopenharmony_ci	return (ap->val.s);
2008c84f3f3cSopenharmony_ci}
2009c84f3f3cSopenharmony_ci#endif
2010c84f3f3cSopenharmony_ci
2011c84f3f3cSopenharmony_cistatic void
2012c84f3f3cSopenharmony_cialt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
2013c84f3f3cSopenharmony_ci{
2014c84f3f3cSopenharmony_ci	unsigned int count = 0;
2015c84f3f3cSopenharmony_ci	char *brace_start, *brace_end, *comma = NULL;
2016c84f3f3cSopenharmony_ci	char *field_start;
2017c84f3f3cSopenharmony_ci	char *p = exp_start;
2018c84f3f3cSopenharmony_ci
2019c84f3f3cSopenharmony_ci	/* search for open brace */
2020c84f3f3cSopenharmony_ci	while ((p = strchr(p, MAGIC)) && ord(p[1]) != ORD('{' /*}*/))
2021c84f3f3cSopenharmony_ci		p += 2;
2022c84f3f3cSopenharmony_ci	brace_start = p;
2023c84f3f3cSopenharmony_ci
2024c84f3f3cSopenharmony_ci	/* find matching close brace, if any */
2025c84f3f3cSopenharmony_ci	if (p) {
2026c84f3f3cSopenharmony_ci		comma = NULL;
2027c84f3f3cSopenharmony_ci		count = 1;
2028c84f3f3cSopenharmony_ci		p += 2;
2029c84f3f3cSopenharmony_ci		while (*p && count) {
2030c84f3f3cSopenharmony_ci			if (ISMAGIC(*p++)) {
2031c84f3f3cSopenharmony_ci				if (ord(*p) == ORD('{' /*}*/))
2032c84f3f3cSopenharmony_ci					++count;
2033c84f3f3cSopenharmony_ci				else if (ord(*p) == ORD(/*{*/ '}'))
2034c84f3f3cSopenharmony_ci					--count;
2035c84f3f3cSopenharmony_ci				else if (*p == ',' && count == 1)
2036c84f3f3cSopenharmony_ci					comma = p;
2037c84f3f3cSopenharmony_ci				++p;
2038c84f3f3cSopenharmony_ci			}
2039c84f3f3cSopenharmony_ci		}
2040c84f3f3cSopenharmony_ci	}
2041c84f3f3cSopenharmony_ci	/* no valid expansions... */
2042c84f3f3cSopenharmony_ci	if (!p || count != 0) {
2043c84f3f3cSopenharmony_ci		/*
2044c84f3f3cSopenharmony_ci		 * Note that given a{{b,c} we do not expand anything (this is
2045c84f3f3cSopenharmony_ci		 * what AT&T ksh does. This may be changed to do the {b,c}
2046c84f3f3cSopenharmony_ci		 * expansion. }
2047c84f3f3cSopenharmony_ci		 */
2048c84f3f3cSopenharmony_ci		if (fdo & DOGLOB)
2049c84f3f3cSopenharmony_ci			glob(start, wp, tobool(fdo & DOMARKDIRS));
2050c84f3f3cSopenharmony_ci		else
2051c84f3f3cSopenharmony_ci			XPput(*wp, debunk(start, start, end - start));
2052c84f3f3cSopenharmony_ci		return;
2053c84f3f3cSopenharmony_ci	}
2054c84f3f3cSopenharmony_ci	brace_end = p;
2055c84f3f3cSopenharmony_ci	if (!comma) {
2056c84f3f3cSopenharmony_ci		alt_expand(wp, start, brace_end, end, fdo);
2057c84f3f3cSopenharmony_ci		return;
2058c84f3f3cSopenharmony_ci	}
2059c84f3f3cSopenharmony_ci
2060c84f3f3cSopenharmony_ci	/* expand expression */
2061c84f3f3cSopenharmony_ci	field_start = brace_start + 2;
2062c84f3f3cSopenharmony_ci	count = 1;
2063c84f3f3cSopenharmony_ci	for (p = brace_start + 2; p != brace_end; p++) {
2064c84f3f3cSopenharmony_ci		if (ISMAGIC(*p)) {
2065c84f3f3cSopenharmony_ci			if (ord(*++p) == ORD('{' /*}*/))
2066c84f3f3cSopenharmony_ci				++count;
2067c84f3f3cSopenharmony_ci			else if ((ord(*p) == ORD(/*{*/ '}') && --count == 0) ||
2068c84f3f3cSopenharmony_ci			    (*p == ',' && count == 1)) {
2069c84f3f3cSopenharmony_ci				char *news;
2070c84f3f3cSopenharmony_ci				int l1, l2, l3;
2071c84f3f3cSopenharmony_ci
2072c84f3f3cSopenharmony_ci				/*
2073c84f3f3cSopenharmony_ci				 * addition safe since these operate on
2074c84f3f3cSopenharmony_ci				 * one string (separate substrings)
2075c84f3f3cSopenharmony_ci				 */
2076c84f3f3cSopenharmony_ci				l1 = brace_start - start;
2077c84f3f3cSopenharmony_ci				l2 = (p - 1) - field_start;
2078c84f3f3cSopenharmony_ci				l3 = end - brace_end;
2079c84f3f3cSopenharmony_ci				news = alloc(l1 + l2 + l3 + 1, ATEMP);
2080c84f3f3cSopenharmony_ci				memcpy(news, start, l1);
2081c84f3f3cSopenharmony_ci				memcpy(news + l1, field_start, l2);
2082c84f3f3cSopenharmony_ci				memcpy(news + l1 + l2, brace_end, l3);
2083c84f3f3cSopenharmony_ci				news[l1 + l2 + l3] = '\0';
2084c84f3f3cSopenharmony_ci				alt_expand(wp, news, news + l1,
2085c84f3f3cSopenharmony_ci				    news + l1 + l2 + l3, fdo);
2086c84f3f3cSopenharmony_ci				field_start = p + 1;
2087c84f3f3cSopenharmony_ci			}
2088c84f3f3cSopenharmony_ci		}
2089c84f3f3cSopenharmony_ci	}
2090c84f3f3cSopenharmony_ci	return;
2091c84f3f3cSopenharmony_ci}
2092c84f3f3cSopenharmony_ci
2093c84f3f3cSopenharmony_ci/* helper function due to setjmp/longjmp woes */
2094c84f3f3cSopenharmony_cistatic char *
2095c84f3f3cSopenharmony_civalsub(struct op *t, Area *ap)
2096c84f3f3cSopenharmony_ci{
2097c84f3f3cSopenharmony_ci	char * volatile cp = NULL;
2098c84f3f3cSopenharmony_ci	struct tbl * volatile vp = NULL;
2099c84f3f3cSopenharmony_ci
2100c84f3f3cSopenharmony_ci	newenv(E_FUNC);
2101c84f3f3cSopenharmony_ci	newblock();
2102c84f3f3cSopenharmony_ci	if (ap)
2103c84f3f3cSopenharmony_ci		vp = local(TREPLY, false);
2104c84f3f3cSopenharmony_ci	if (!kshsetjmp(e->jbuf))
2105c84f3f3cSopenharmony_ci		execute(t, XXCOM | XERROK, NULL);
2106c84f3f3cSopenharmony_ci	if (vp)
2107c84f3f3cSopenharmony_ci		strdupx(cp, str_val(vp), ap);
2108c84f3f3cSopenharmony_ci	quitenv(NULL);
2109c84f3f3cSopenharmony_ci
2110c84f3f3cSopenharmony_ci	return (cp);
2111c84f3f3cSopenharmony_ci}
2112