1 /***********************************************************************
2  *                                                                      *
3  *               This software is part of the ast package               *
4  *          Copyright (c) 1982-2014 AT&T Intellectual Property          *
5  *                      and is licensed under the                       *
6  *                 Eclipse Public License, Version 1.0                  *
7  *                    by AT&T Intellectual Property                     *
8  *                                                                      *
9  *                A copy of the License is available at                 *
10  *          http://www.eclipse.org/org/documents/epl-v10.html           *
11  *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12  *                                                                      *
13  *              Information and Software Systems Research               *
14  *                            AT&T Research                             *
15  *                           Florham Park NJ                            *
16  *                                                                      *
17  *                    David Korn <dgkorn@gmail.com>                     *
18  *                                                                      *
19  ***********************************************************************/
20 #include "config_ast.h"  // IWYU pragma: keep
21 
22 #include <stdbool.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 
27 #include "ast.h"
28 #include "ast_assert.h"
29 #include "builtins.h"
30 #include "cdt.h"
31 #include "defs.h"
32 #include "error.h"
33 #include "name.h"
34 #include "path.h"
35 #include "sfio.h"
36 #include "stk.h"
37 #include "variables.h"
38 
39 static_fn void assign(Namval_t *, const void *, nvflag_t, Namfun_t *);
40 
41 const Nvdisc_op_t DISC_OP_NOOP = {DISC_OP_NOOP_val};
42 const Nvdisc_op_t DISC_OP_FIRST = {DISC_OP_FIRST_val};
43 const Nvdisc_op_t DISC_OP_LAST = {DISC_OP_LAST_val};
44 const Nvdisc_op_t DISC_OP_POP = {DISC_OP_POP_val};
45 const Nvdisc_op_t DISC_OP_CLONE = {DISC_OP_CLONE_val};
46 
nv_compare(Dt_t * dict,void * sp,void * dp,Dtdisc_t * disc)47 int nv_compare(Dt_t *dict, void *sp, void *dp, Dtdisc_t *disc) {
48     UNUSED(dict);
49     UNUSED(disc);
50 
51     if (sp == dp) return 0;
52     return strcmp((char *)sp, (char *)dp);
53 }
54 
55 //
56 // Call the next getval function in the chain.
57 //
nv_getv(Namval_t * np,Namfun_t * nfp)58 char *nv_getv(Namval_t *np, Namfun_t *nfp) {
59     Shell_t *shp = sh_ptr(np);
60     Namfun_t *fp;
61     char *cp;
62 
63     fp = nfp;
64     if (nfp && !nv_local) fp = nfp = nfp->next;
65     nv_local = false;
66     for (; fp; fp = fp->next) {
67         if (!fp->disc || (!fp->disc->getnum && !fp->disc->getval)) continue;
68         if (!nv_isattr(np, NV_NODISC) || fp == (Namfun_t *)nv_arrayptr(np)) break;
69     }
70     if (fp && fp->disc->getval) {
71         cp = (*fp->disc->getval)(np, fp);
72     } else if (fp && fp->disc->getnum) {
73         sfprintf(shp->strbuf, "%.*Lg", 12, (*fp->disc->getnum)(np, fp));
74         cp = sfstruse(shp->strbuf);
75     } else {
76         nv_local = true;
77         cp = nv_getval(np);
78     }
79     return cp;
80 }
81 
82 //
83 // Call the next getnum function in the chain.
84 //
nv_getn(Namval_t * np,Namfun_t * nfp)85 Sfdouble_t nv_getn(Namval_t *np, Namfun_t *nfp) {
86     Namfun_t *fp;
87     Sfdouble_t d = 0;
88     Shell_t *shp = sh_ptr(np);
89     char *str;
90 
91     fp = nfp;
92     if (nfp && !nv_local) fp = nfp = nfp->next;
93     nv_local = false;
94     for (; fp; fp = fp->next) {
95         if (!fp->disc || (!fp->disc->getnum && !fp->disc->getval)) continue;
96         if (!fp->disc->getnum && nv_isattr(np, NV_INTEGER)) continue;
97         if (!nv_isattr(np, NV_NODISC) || fp == (Namfun_t *)nv_arrayptr(np)) break;
98     }
99     if (fp && fp->disc && fp->disc->getnum) {
100         d = (*fp->disc->getnum)(np, fp);
101     } else if (nv_isattr(np, NV_INTEGER)) {
102         nv_local = true;
103         d = nv_getnum(np);
104     } else {
105         if (fp && fp->disc && fp->disc->getval) {
106             str = (*fp->disc->getval)(np, fp);
107         } else {
108             str = nv_getv(np, fp ? fp : nfp);
109         }
110         if (str && *str) {
111             if (nv_isattr(np, NV_LJUST | NV_RJUST) ||
112                 (*str == '0' && !(str[1] == 'x' || str[1] == 'X'))) {
113                 while (*str == '0') str++;
114             }
115             d = sh_arith(shp, str);
116         }
117     }
118     return d;
119 }
120 
121 //
122 // Call the next assign function in the chain.
123 //
nv_putv(Namval_t * np,const void * value,nvflag_t flags,Namfun_t * nfp)124 void nv_putv(Namval_t *np, const void *value, nvflag_t flags, Namfun_t *nfp) {
125     Namfun_t *fp, *fpnext;
126     Namarr_t *ap;
127 
128     fp = nfp;
129     if (nfp && !nv_local) fp = nfp = nfp->next;
130     nv_local = false;
131     if (flags & NV_NODISC) fp = NULL;
132     for (; fp; fp = fpnext) {
133         fpnext = fp->next;
134         if (!fp->disc || !fp->disc->putval) {
135             if (!value && (!(ap = nv_arrayptr(np)) || ap->nelem == 0)) {
136                 if (fp->disc || !(fp->nofree & 1)) nv_disc(np, fp, DISC_OP_POP);
137                 if (!(fp->nofree & 1)) free(fp);
138             }
139             continue;
140         }
141         if (!nv_isattr(np, NV_NODISC) || fp == (Namfun_t *)nv_arrayptr(np)) break;
142     }
143     if (!value && nv_isflag(flags, NV_TYPE) && fp && fp->disc->putval == assign) fp = NULL;
144     if (fp && fp->disc->putval) {
145         (*fp->disc->putval)(np, value, flags, fp);
146     } else {
147         nv_local = true;
148         if (value) {
149             nv_putval(np, value, flags);
150         } else {
151             _nv_unset(np, flags & (NV_RDONLY | NV_EXPORT));
152         }
153     }
154 }
155 
156 #define LOOKUPS 0
157 #define ASSIGN 1
158 #define APPEND 2
159 #define UNASSIGN 3
160 #define LOOKUPN 4
161 
162 struct vardisc {
163     Namfun_t namfun;
164     Namval_t *disc[5];
165 };
166 
167 struct blocked {
168     struct blocked *next;
169     Namval_t *np;
170     int flags;
171     void *sub;
172     int isub;
173 };
174 
175 static struct blocked *blist;
176 
177 #define isblocked(bp, type) ((bp)->flags & (1 << (type)))
178 #define block(bp, type) ((bp)->flags |= (1 << (type)))
179 #define unblock(bp, type) ((bp)->flags &= ~(1 << (type)))
180 
181 //
182 // Returns pointer to blocking structure.
183 //
block_info(Namval_t * np,struct blocked * pp)184 static_fn struct blocked *block_info(Namval_t *np, struct blocked *pp) {
185     struct blocked *bp;
186     void *sub = NULL;
187     int isub = 0;
188 
189     if (nv_isarray(np) && (isub = nv_aindex(np)) < 0) {
190         sub = nv_associative(np, NULL, ASSOC_OP_CURRENT);
191     }
192     for (bp = blist; bp; bp = bp->next) {
193         if (bp->np == np && bp->sub == sub && bp->isub == isub) return bp;
194     }
195     if (pp) {
196         pp->np = np;
197         pp->flags = 0;
198         pp->isub = isub;
199         pp->sub = sub;
200         pp->next = blist;
201         blist = pp;
202     }
203     return pp;
204 }
205 
block_done(struct blocked * bp)206 static_fn void block_done(struct blocked *bp) {
207     blist = bp = bp->next;
208     if (bp && (bp->isub >= 0 || bp->sub)) {
209         nv_putsub(bp->np, bp->sub, (bp->isub < 0 ? 0 : bp->isub), ARRAY_SETSUB);
210     }
211 }
212 
213 //
214 // Free discipline if no more discipline functions.
215 //
chktfree(Namval_t * np,struct vardisc * vp)216 static_fn void chktfree(Namval_t *np, struct vardisc *vp) {
217     int n;
218 
219     for (n = 0; n < sizeof(vp->disc) / sizeof(*vp->disc); n++) {
220         if (vp->disc[n]) break;
221     }
222     if (n >= sizeof(vp->disc) / sizeof(*vp->disc)) {
223         // No disc left so pop.
224         Namfun_t *fp;
225         if ((fp = nv_stack(np, NULL)) && !(fp->nofree & 1)) free(fp);
226     }
227 }
228 
229 //
230 // This function performs an assignment disc on the given node <np>.
231 //
assign(Namval_t * np,const void * val,nvflag_t flags,Namfun_t * handle)232 static_fn void assign(Namval_t *np, const void *val, nvflag_t flags, Namfun_t *handle) {
233     Shell_t *shp = sh_ptr(np);
234     int type = (flags & NV_APPEND) ? APPEND : ASSIGN;
235     struct vardisc *vp = (struct vardisc *)handle;
236     Namval_t *nq = vp->disc[type];
237     struct blocked block, *bp = block_info(np, &block);
238     Namval_t node;
239     struct Value *up = FETCH_VT(np->nvalue, up);
240     Namval_t *tp, *nr;
241 
242     if (val && (tp = nv_type(np)) &&
243         (nr = nv_open(val, shp->var_tree, NV_VARNAME | NV_ARRAY | NV_NOADD | NV_NOFAIL)) &&
244         tp == nv_type(nr)) {
245         char *sub = nv_getsub(np);
246         _nv_unset(np, 0);
247         if (sub) {
248             nv_putsub(np, sub, 0, ARRAY_ADD);
249             nv_putval(np, nv_getval(nr), 0);
250         } else {
251             nv_clone(nr, np, 0);
252         }
253         goto done;
254     }
255     if (val || isblocked(bp, type)) {
256         if (!nq || isblocked(bp, type)) {
257             nv_putv(np, val, flags, handle);
258             goto done;
259         }
260         node = *VAR_sh_value;
261         if (!nv_isnull(VAR_sh_value)) {
262             nv_onattr(VAR_sh_value, NV_NOFREE);
263             _nv_unset(VAR_sh_value, 0);
264         }
265         if (flags & NV_INTEGER) {
266             nv_onattr(VAR_sh_value,
267                       (flags & (NV_LONG | NV_DOUBLE | NV_EXPNOTE | NV_HEXFLOAT | NV_SHORT)));
268         }
269         nv_putval(VAR_sh_value, val, (flags & NV_INTEGER) ? flags : NV_NOFREE);
270     } else {
271         nq = vp->disc[type = UNASSIGN];
272     }
273     if (nq && !isblocked(bp, type)) {
274         int bflag = 0;
275         block(bp, type);
276         if (type == APPEND && (bflag = !isblocked(bp, LOOKUPS))) block(bp, LOOKUPS);
277         sh_fun(shp, nq, np, NULL);
278         unblock(bp, type);
279         if (bflag) unblock(bp, LOOKUPS);
280         if (!vp->disc[type]) chktfree(np, vp);
281     }
282     if (nv_isarray(np)) STORE_VT(np->nvalue, up, up);
283     if (val) {
284         char *cp;
285         Sfdouble_t d;
286         if (nv_isnull(VAR_sh_value)) {
287             cp = NULL;
288         } else if (flags & NV_INTEGER) {
289             d = nv_getnum(VAR_sh_value);
290             cp = (char *)(&d);
291             flags |= (NV_LONG | NV_DOUBLE);
292             flags &= ~NV_SHORT;
293         } else {
294             cp = nv_getval(VAR_sh_value);
295         }
296         if (cp) nv_putv(np, cp, flags | NV_RDONLY, handle);
297         _nv_unset(VAR_sh_value, 0);
298         // Restore everything but the nvlink field.
299         memcpy(&VAR_sh_value->nvname, &node.nvname, sizeof(node) - sizeof(node.nvlink));
300     } else if (sh_isstate(shp, SH_INIT) || np == VAR_sh_fun) {
301         // Don't free functions during reinitialization.
302         nv_putv(np, val, flags, handle);
303     } else if (!nq || !isblocked(bp, type)) {
304         Dt_t *root = sh_subfuntree(shp, true);
305         Namval_t *pp = NULL;
306         int n;
307 
308         block(bp, type);
309         if (!nv_isattr(np, NV_MINIMAL)) pp = np->nvenv;
310         nv_putv(np, val, flags, handle);
311         if (!nv_isarray(np) || array_isempty(np)) nv_disc(np, handle, DISC_OP_POP);
312         if (shp->subshell) goto done;
313         if (pp && nv_isarray(pp)) goto done;
314         if (nv_isarray(np) && !array_isempty(np)) goto done;
315         for (n = 0; n < sizeof(vp->disc) / sizeof(*vp->disc); n++) {
316             if ((nq = vp->disc[n]) && !nv_isattr(nq, NV_NOFREE)) {
317                 _nv_unset(nq, 0);
318                 dtdelete(root, nq);
319             }
320         }
321         unblock(bp, type);
322         if (!(handle->nofree & 1)) free(handle);
323     }
324 done:
325     if (bp == &block) block_done(bp);
326     if (nq && FETCH_VT(nq->nvalue, rp)->running == 1) {
327         FETCH_VT(nq->nvalue, rp)->running = 0;
328         _nv_unset(nq, 0);
329     }
330 }
331 
332 //
333 // This function executes a lookup disc and then performs the lookup on the given node <np>.
334 //
lookup(Namval_t * np,int type,Sfdouble_t * dp,Namfun_t * handle)335 static_fn char *lookup(Namval_t *np, int type, Sfdouble_t *dp, Namfun_t *handle) {
336     Shell_t *shp = sh_ptr(np);
337     struct vardisc *vp = (struct vardisc *)handle;
338     struct blocked block, *bp = block_info(np, &block);
339     Namval_t *nq = vp->disc[type];
340     char *cp = NULL;
341     Namval_t node;
342     struct Value *up = FETCH_VT(np->nvalue, up);
343 
344     if (nq && !isblocked(bp, type)) {
345         node = *VAR_sh_value;
346         if (!nv_isnull(VAR_sh_value)) {
347             nv_onattr(VAR_sh_value, NV_NOFREE);
348             _nv_unset(VAR_sh_value, 0);
349         }
350         if (type == LOOKUPN) {
351             nv_onattr(VAR_sh_value, NV_DOUBLE | NV_INTEGER);
352             nv_setsize(VAR_sh_value, 10);
353         }
354         block(bp, type);
355         // Make sure nv_setdisc doesn't invalidate `vp` by freeing it.
356         // See https://github.com/att/ast/issues/1268
357         block(bp, UNASSIGN);
358         sh_fun(shp, nq, np, NULL);
359         unblock(bp, UNASSIGN);
360         unblock(bp, type);
361         if (!vp->disc[type]) chktfree(np, vp);
362         if (type == LOOKUPN) {
363             cp = (char *)FETCH_VT(VAR_sh_value->nvalue, const_cp);
364             *dp = nv_getnum(VAR_sh_value);
365         } else if ((cp = nv_getval(VAR_sh_value))) {
366             cp = stkcopy(stkstd, cp);
367         }
368         _nv_unset(VAR_sh_value, NV_RDONLY);
369         if (!nv_isnull(&node)) {
370             // Restore everything but the nvlink field.
371             memcpy(&VAR_sh_value->nvname, &node.nvname, sizeof(node) - sizeof(node.nvlink));
372         }
373     }
374     if (nv_isarray(np)) STORE_VT(np->nvalue, up, up);
375     if (!cp) {
376         if (type == LOOKUPS) {
377             cp = nv_getv(np, handle);
378         } else {
379             *dp = nv_getn(np, handle);
380         }
381     }
382     if (bp == &block) block_done(bp);
383     if (nq) {
384         struct Ufunction *rp = FETCH_VT(nq->nvalue, rp);
385         if (rp && rp->running == 1) {
386             rp->running = 0;
387             _nv_unset(nq, 0);
388         }
389     }
390     return cp;
391 }
392 
lookups(Namval_t * np,Namfun_t * handle)393 static_fn char *lookups(Namval_t *np, Namfun_t *handle) {
394     return lookup(np, LOOKUPS, NULL, handle);
395 }
396 
lookupn(Namval_t * np,Namfun_t * handle)397 static_fn Sfdouble_t lookupn(Namval_t *np, Namfun_t *handle) {
398     Sfdouble_t d;
399     lookup(np, LOOKUPN, &d, handle);
400     return d;
401 }
402 
403 //
404 // Set disc on given <event> to <action>.
405 // If action==np, the current disc is returned.
406 // A null return value indicates that no <event> is known for <np>.
407 // If <event> is NULL, then return the event name after <action>.
408 // If <event> is NULL, and <action> is NULL, return the first event.
409 //
nv_setdisc(Namval_t * np,const void * event,Namval_t * action,Namfun_t * fp)410 char *nv_setdisc(Namval_t *np, const void *event, Namval_t *action, Namfun_t *fp) {
411     struct vardisc *vp = (struct vardisc *)np->nvfun;
412     int type;
413     char *empty = "";
414 
415     while (vp) {
416         if (vp->namfun.disc && (vp->namfun.disc->setdisc || vp->namfun.disc->putval == assign)) {
417             break;
418         }
419         vp = (struct vardisc *)vp->namfun.next;
420     }
421     if (vp && !vp->namfun.disc) vp = 0;
422     if (np == (Namval_t *)fp) {
423         const char *name;
424         int getname = 0;
425         // Top level call, check for get/set.
426         if (!event) {
427             if (!action) return (char *)nv_discnames[0];
428             getname = 1;
429             event = (char *)action;
430         }
431         for (type = 0; (name = nv_discnames[type]); type++) {
432             if (strcmp(event, name) == 0) break;
433         }
434         if (getname) {
435             event = 0;
436             if (name && !(name = nv_discnames[++type])) action = 0;
437         }
438         if (!name) {
439             for (fp = &vp->namfun; fp; fp = fp->next) {
440                 if (fp->disc && fp->disc->setdisc) {
441                     return (*fp->disc->setdisc)(np, event, action, fp);
442                 }
443             }
444         } else if (getname) {
445             return (char *)name;
446         }
447     }
448     if (!fp) return NULL;
449     if (np != (Namval_t *)fp) {
450         // Not the top level.
451         while ((fp = fp->next)) {
452             if (fp->disc && fp->disc->setdisc) return (*fp->disc->setdisc)(np, event, action, fp);
453         }
454         return NULL;
455     }
456     // Handle GET/SET/APPEND/UNSET disc.
457     if (vp && vp->namfun.disc->putval != assign) vp = NULL;
458     if (!vp) {
459         Namdisc_t *dp;
460         if (action == np) return (char *)action;
461         vp = calloc(1, sizeof(struct vardisc) + sizeof(Namdisc_t));
462         dp = (Namdisc_t *)(vp + 1);
463         vp->namfun.disc = dp;
464         memset(dp, 0, sizeof(*dp));
465         dp->dsize = sizeof(struct vardisc);
466         dp->putval = assign;
467         if (nv_isarray(np) && !nv_arrayptr(np)) nv_putsub(np, NULL, 1, 0);
468         nv_stack(np, &vp->namfun);
469     }
470     if (action == np) {
471         action = vp->disc[type];
472         empty = NULL;
473     } else if (action) {
474         // The cast is because `vp->namfun.disc` is const. Why are we modifying a const struct?
475         Namdisc_t *dp = (Namdisc_t *)vp->namfun.disc;
476         if (type == LOOKUPS) {
477             dp->getval = lookups;
478         } else if (type == LOOKUPN) {
479             dp->getnum = lookupn;
480         }
481         vp->disc[type] = action;
482     } else {
483         action = vp->disc[type];
484         vp->disc[type] = NULL;
485         struct blocked *bp = block_info(np, NULL);
486         if (!bp || !isblocked(bp, UNASSIGN)) chktfree(np, vp);
487     }
488     return action ? (char *)action : empty;
489 }
490 
491 //
492 // Set disc on given <event> to <action>.
493 // If action==np, the current disc is returned.
494 // A null return value indicates that no <event> is known for <np>.
495 // If <event> is NULL, then return the event name after <action>.
496 // If <event> is NULL, and <action> is NULL, return the first event.
497 //
setdisc(Namval_t * np,const void * vp,Namval_t * action,Namfun_t * fp)498 static_fn char *setdisc(Namval_t *np, const void *vp, Namval_t *action, Namfun_t *fp) {
499     const char *event = vp;
500     Nambfun_t *bp = (Nambfun_t *)fp;
501     int type, getname = 0;
502     const char *name;
503     const char **discnames = bp->bnames;
504 
505     // Top level call, check for discipline match.
506     if (!event) {
507         if (!action) return (char *)discnames[0];
508         getname = 1;
509         event = (char *)action;
510     }
511     for (type = 0; (name = discnames[type]); type++) {
512         if (strcmp(event, name) == 0) break;
513     }
514     if (getname) {
515         event = 0;
516         if (name && !(name = discnames[++type])) action = 0;
517     }
518     if (!name) {
519         return nv_setdisc(np, event, action, fp);
520     } else if (getname) {
521         return (char *)name;
522     }
523     // Handle the disciplines.
524     if (action == np) {
525         action = bp->bltins[type];
526     } else if (action) {
527         Namval_t *tp = nv_type(np);
528         if (tp && (np = bp->bltins[type]) && nv_isattr(np, NV_STATICF)) {
529             errormsg(SH_DICT, ERROR_exit(1), e_staticfun, name, tp->nvname);
530         }
531         bp->bltins[type] = action;
532     } else {
533         action = bp->bltins[type];
534         bp->bltins[type] = 0;
535     }
536     return (char *)action;
537 }
538 
putdisc(Namval_t * np,const void * val,nvflag_t flag,Namfun_t * fp)539 static_fn void putdisc(Namval_t *np, const void *val, nvflag_t flag, Namfun_t *fp) {
540     Shell_t *shp = sh_ptr(np);
541     nv_putv(np, val, flag, fp);
542     if (!val && !(flag & NV_NOFREE)) {
543         Nambfun_t *vp = (Nambfun_t *)fp;
544         int i;
545         for (i = 0; vp->bnames[i]; i++) {
546             Namval_t *mp;
547             mp = vp->bltins[i];
548             if (mp && !nv_isattr(mp, NV_NOFREE) && is_abuiltin(mp)) {
549                 if (mp->nvfun && !nv_isattr(mp, NV_NOFREE)) free(mp->nvfun);
550                 dtdelete(shp->bltin_tree, mp);
551                 free(mp);
552             }
553         }
554         nv_disc(np, fp, DISC_OP_POP);
555         if (!(fp->nofree & 1)) free(fp);
556     }
557 }
558 
559 static const Namdisc_t Nv_bdisc = {.dsize = 0, .putval = putdisc, .setdisc = setdisc};
560 
nv_clone_disc(Namfun_t * fp,nvflag_t flags)561 Namfun_t *nv_clone_disc(Namfun_t *fp, nvflag_t flags) {
562     Namfun_t *nfp;
563     int size;
564 
565     if (!fp->disc && !fp->next && (fp->nofree & 1)) return fp;
566     if (!(size = fp->dsize) && (!fp->disc || !(size = fp->disc->dsize))) size = sizeof(Namfun_t);
567     nfp = calloc(1, size);
568     memcpy(nfp, fp, size);
569     nfp->nofree &= ~1;
570     nfp->nofree |= (flags & NV_RDONLY) ? 1 : 0;
571     return nfp;
572 }
573 
nv_adddisc(Namval_t * np,const char ** names,Namval_t ** funs)574 bool nv_adddisc(Namval_t *np, const char **names, Namval_t **funs) {
575     Nambfun_t *vp;
576     int n = 0;
577     const char **av = names;
578 
579     if (av) {
580         while (*av++) n++;
581     }
582     vp = calloc(1, sizeof(Nambfun_t) + n * sizeof(Namval_t *));
583     vp->fun.dsize = sizeof(Nambfun_t) + n * sizeof(Namval_t *);
584     vp->fun.nofree |= 2;
585     vp->num = n;
586     if (funs) {
587         memcpy(vp->bltins, funs, n * sizeof(Namval_t *));
588     } else {
589         while (n >= 0) vp->bltins[n--] = 0;
590     }
591     vp->fun.disc = &Nv_bdisc;
592     vp->bnames = names;
593     nv_stack(np, &vp->fun);
594     return true;
595 }
596 
597 //
598 // Push, pop, clone, or reorder disciplines onto node <np>.
599 // <op> can be one of
600 //    DISC_OP_NOOP:   ???
601 //    DISC_OP_FIRST:  Move or push <fp> to top of the stack or delete top.
602 //    DISC_OP_LAST:   Move or push <fp> to bottom of stack or delete last.
603 //    DISC_OP_POP:    Delete <fp> from top of the stack.
604 //    DISC_OP_CLONE:  Replace <fp> with a copy created my malloc() and return it.
605 //
nv_disc(Namval_t * np,Namfun_t * fp,Nvdisc_op_t op)606 Namfun_t *nv_disc(Namval_t *np, Namfun_t *fp, Nvdisc_op_t op) {
607     Shell_t *shp = sh_ptr(np);
608     Namfun_t *lp, **lpp;
609 
610     if (nv_isref(np)) return NULL;
611     if (op.val == DISC_OP_CLONE_val && !fp) return NULL;
612 
613     if (fp) {
614         fp->subshell = shp->subshell;
615         if ((lp = np->nvfun) == fp) {
616             if (op.val == DISC_OP_CLONE_val) {
617                 lp = nv_clone_disc(fp, 0);
618                 np->nvfun = lp;
619                 return lp;
620             }
621             if (op.val == DISC_OP_FIRST_val || op.val == DISC_OP_NOOP_val) return fp;
622             np->nvfun = lp->next;
623             if (op.val == DISC_OP_POP_val) return fp;
624             if (op.val == DISC_OP_LAST_val && (lp->next == 0 || lp->next->disc == 0)) return fp;
625         }
626         // See if <fp> is on the list already.
627         lpp = &np->nvfun;
628         if (lp) {
629             while (lp->next && lp->next->disc) {
630                 if (lp->next == fp) {
631                     if (op.val == DISC_OP_LAST_val && fp->next == 0) return fp;
632                     if (op.val == DISC_OP_CLONE_val) {
633                         fp = nv_clone_disc(fp, 0);
634                         lp->next = fp;
635                         return fp;
636                     }
637                     lp->next = fp->next;
638                     if (op.val == DISC_OP_POP_val) return fp;
639                     if (op.val != DISC_OP_LAST_val) break;
640                 }
641                 lp = lp->next;
642             }
643             if (op.val == DISC_OP_LAST_val && lp->disc) lpp = &lp->next;
644         }
645         if (op.val == DISC_OP_POP_val) return NULL;
646         // Push.
647         nv_offattr(np, NV_NODISC);
648         if (op.val == DISC_OP_LAST_val) {
649             if (lp && !lp->disc) {
650                 fp->next = lp;
651             } else {
652                 fp->next = NULL;
653             }
654         } else {
655             if ((fp->nofree & 1) && *lpp) fp = nv_clone_disc(fp, 0);
656             fp->next = *lpp;
657         }
658         *lpp = fp;
659     } else {
660         if (op.val == DISC_OP_FIRST_val) {
661             return np->nvfun;
662         } else if (op.val == DISC_OP_LAST_val) {
663             for (lp = np->nvfun; lp; fp = lp, lp = lp->next) {
664                 ;  // empty loop
665             }
666         } else if ((fp = np->nvfun)) {
667             np->nvfun = fp->next;
668         }
669     }
670     return fp;
671 }
672 
673 //
674 // Returns discipline pointer if discipline with specified functions is on the discipline stack.
675 //
nv_hasdisc(const Namval_t * np,const Namdisc_t * dp)676 Namfun_t *nv_hasdisc(const Namval_t *np, const Namdisc_t *dp) {
677     Namfun_t *fp;
678 
679     for (fp = np->nvfun; fp; fp = fp->next) {
680         if (fp->disc == dp) return fp;
681     }
682     return 0;
683 }
684 
685 struct notify {
686     Namfun_t namfun;
687     char **ptr;
688 };
689 
put_notify(Namval_t * np,const void * val,nvflag_t flags,Namfun_t * fp)690 static_fn void put_notify(Namval_t *np, const void *val, nvflag_t flags, Namfun_t *fp) {
691     struct notify *pp = (struct notify *)fp;
692 
693     nv_putv(np, val, flags, fp);
694     nv_stack(np, fp);
695     nv_stack(np, NULL);
696     *pp->ptr = NULL;
697     if (!(fp->nofree & 1)) free(fp);
698 }
699 
700 static const Namdisc_t notify_disc = {.dsize = 0, .putval = put_notify};
701 
nv_unsetnotify(Namval_t * np,char ** addr)702 bool nv_unsetnotify(Namval_t *np, char **addr) {
703     Namfun_t *fp;
704 
705     for (fp = np->nvfun; fp; fp = fp->next) {
706         if (fp->disc->putval == put_notify && ((struct notify *)fp)->ptr == addr) {
707             nv_stack(np, fp);
708             nv_stack(np, NULL);
709             if (!(fp->nofree & 1)) free(fp);
710             return true;
711         }
712     }
713     return false;
714 }
715 
nv_setnotify(Namval_t * np,char ** addr)716 bool nv_setnotify(Namval_t *np, char **addr) {
717     struct notify *pp = calloc(1, sizeof(struct notify));
718 
719     pp->ptr = addr;
720     pp->namfun.disc = &notify_disc;
721     nv_stack(np, &pp->namfun);
722     return true;
723 }
724 
newnode(const char * name)725 static_fn void *newnode(const char *name) {
726     size_t s = strlen(name) + 1;
727     Namval_t *np = calloc(1, sizeof(Namval_t) + s);
728     np->nvname = (char *)np + sizeof(Namval_t);
729     memcpy(np->nvname, name, s);
730     return np;
731 }
732 
733 //
734 // Clone a numeric value.
735 //
num_clone(Namval_t * np,void * val)736 static_fn void *num_clone(Namval_t *np, void *val) {
737     int size;
738     void *nval;
739 
740     if (!val) return 0;
741     if (nv_isattr(np, NV_DOUBLE) == NV_DOUBLE) {
742         if (nv_isattr(np, NV_LONG)) {
743             size = sizeof(Sfdouble_t);
744         } else if (nv_isattr(np, NV_SHORT)) {
745             size = sizeof(float);
746         } else {
747             size = sizeof(double);
748         }
749     } else {
750         if (nv_isattr(np, NV_LONG)) {
751             size = sizeof(Sflong_t);
752         } else if (nv_isattr(np, NV_SHORT)) {
753             if (nv_isattr(np, NV_INT16P | NV_DOUBLE) == NV_INT16P) {
754                 size = sizeof(short);
755             } else {
756                 return FETCH_VT(np->nvalue, ip);
757             }
758         } else {
759             size = sizeof(int32_t);
760         }
761     }
762     if (!(nval = malloc(size))) return 0;
763     memcpy(nval, val, size);
764     return nval;
765 }
766 
clone_all_disc(Namval_t * np,Namval_t * mp,nvflag_t flags)767 void clone_all_disc(Namval_t *np, Namval_t *mp, nvflag_t flags) {
768     Namfun_t *fp, **mfp = &mp->nvfun, *nfp, *fpnext;
769 
770     for (fp = np->nvfun; fp; fp = fpnext) {
771         fpnext = fp->next;
772         if (!fpnext && (flags & NV_COMVAR) && fp->disc && fp->disc->namef) return;
773         if ((fp->nofree & 2) && (flags & NV_NODISC)) nfp = NULL;
774         if (fp->disc && fp->disc->clonef) {
775             nfp = (*fp->disc->clonef)(np, mp, flags, fp);
776         } else if (flags & NV_MOVE) {
777             nfp = fp;
778         } else {
779             nfp = nv_clone_disc(fp, flags);
780         }
781         if (!nfp) continue;
782         nfp->next = NULL;
783         *mfp = nfp;
784         mfp = &nfp->next;
785     }
786 }
787 
788 //
789 // Clone <mp> from <np> flags can be one of the following:
790 // NV_APPEND - append <np> onto <mp>
791 // NV_MOVE - move <np> to <mp>
792 // NV_NOFREE - mark the new node as nofree
793 // NV_NODISC - discplines with funs non-zero will not be copied
794 // NV_COMVAR - cloning a compound variable
795 //
nv_clone(Namval_t * np,Namval_t * mp,nvflag_t flags)796 int nv_clone(Namval_t *np, Namval_t *mp, nvflag_t flags) {
797     Namfun_t *fp, *fpnext;
798     const char *val = FETCH_VT(mp->nvalue, const_cp);
799     nvflag_t flag = mp->nvflag;
800     size_t size = nv_size(mp);
801 
802     mp->nvshell = np->nvshell;
803     for (fp = mp->nvfun; fp; fp = fpnext) {
804         fpnext = fp->next;
805         if (!fpnext && (flags & NV_COMVAR) && fp->disc && fp->disc->namef) break;
806         if (!(fp->nofree & 1)) free(fp);
807     }
808     mp->nvfun = fp;
809     fp = np->nvfun;
810     if (fp) {
811         Shell_t *shp = np->nvshell;
812         Namval_t *last_table = shp->last_table;
813         if (nv_isattr(mp, NV_EXPORT | NV_MINIMAL) == (NV_EXPORT | NV_MINIMAL)) {
814             mp->nvenv = NULL;
815             nv_offattr(mp, NV_MINIMAL);
816         }
817         if (!(flags & NV_COMVAR) && !nv_isattr(np, NV_MINIMAL) && np->nvenv &&
818             !(nv_isattr(mp, NV_MINIMAL))) {
819             mp->nvenv = np->nvenv;
820         }
821         mp->nvflag &= NV_MINIMAL;
822         mp->nvflag |= np->nvflag & ~(NV_ARRAY | NV_MINIMAL | NV_NOFREE);
823         flag = mp->nvflag;
824         clone_all_disc(np, mp, flags);
825         shp->last_table = last_table;
826     }
827     if (nv_isflag(flags, NV_APPEND)) return 1;
828     if (nv_size(mp) == size) nv_setsize(mp, nv_size(np));
829     if (mp->nvflag == flag) {
830         nv_setattr(mp, (np->nvflag & ~(NV_MINIMAL)) | (mp->nvflag & NV_MINIMAL));
831     }
832     if (nv_isattr(np, NV_EXPORT)) mp->nvflag |= (np->nvflag & NV_MINIMAL);
833     if (FETCH_VT(mp->nvalue, const_cp) == val && !nv_isattr(np, NV_INTEGER)) {
834         if (FETCH_VT(np->nvalue, const_cp) && FETCH_VT(np->nvalue, const_cp) != Empty &&
835             nv_isflag(flags, NV_COMVAR) && !nv_isflag(flags, NV_MOVE)) {
836             const char *cp = FETCH_VT(np->nvalue, const_cp);
837             if (size) {
838                 STORE_VT(mp->nvalue, const_cp, memdup(cp, size));
839             } else {
840                 STORE_VT(mp->nvalue, const_cp, strdup(cp));
841             }
842             nv_offattr(mp, NV_NOFREE);
843         } else if (np->nvfun || !nv_isattr(np, NV_ARRAY)) {
844             const char *cp = FETCH_VT(np->nvalue, const_cp);
845             STORE_VT(mp->nvalue, const_cp, cp);
846             if (!cp) nv_offattr(mp, NV_NOFREE);
847         }
848     }
849     mp->nvshell = np->nvshell;
850     if (nv_isflag(flags, NV_MOVE)) {
851         if (nv_isattr(np, NV_INTEGER)) STORE_VT(mp->nvalue, ip, FETCH_VT(np->nvalue, ip));
852         np->nvfun = NULL;
853         STORE_VT(np->nvalue, const_cp, NULL);
854         if (!nv_isattr(np, NV_MINIMAL) || nv_isattr(mp, NV_EXPORT)) {
855             mp->nvenv = np->nvenv;
856             if (nv_isattr(np, NV_MINIMAL)) {
857                 np->nvenv = NULL;
858                 nv_setattr(np, NV_EXPORT);
859             } else {
860                 nv_setattr(np, 0);
861             }
862         } else {
863             np->nvflag &= NV_MINIMAL;
864         }
865         return 1;
866     } else if (nv_isflag(flags, NV_ARRAY) && !nv_isattr(np, NV_MINIMAL)) {
867         mp->nvenv = np->nvenv;
868     }
869     if (nv_isattr(np, NV_INTEGER) && FETCH_VT(mp->nvalue, ip) != FETCH_VT(np->nvalue, ip) &&
870         FETCH_VT(np->nvalue, const_cp) != Empty) {
871         STORE_VT(mp->nvalue, ip, (int *)num_clone(np, FETCH_VT(np->nvalue, ip)));
872         nv_offattr(mp, NV_NOFREE);
873     } else if (nv_isflag(flags, NV_NOFREE) && !nv_arrayptr(np)) {
874         nv_onattr(np, NV_NOFREE);
875     }
876     return 1;
877 }
878 
879 //
880 // The following discipline is for copy-on-write semantics.
881 //
clone_getv(Namval_t * np,Namfun_t * handle)882 static_fn char *clone_getv(Namval_t *np, Namfun_t *handle) {
883     UNUSED(handle);
884 
885     struct Namval *np2 = FETCH_VT(np->nvalue, np);
886     return np2 ? nv_getval(np2) : NULL;
887 }
888 
clone_getn(Namval_t * np,Namfun_t * handle)889 static_fn Sfdouble_t clone_getn(Namval_t *np, Namfun_t *handle) {
890     UNUSED(handle);
891 
892     struct Namval *np2 = FETCH_VT(np->nvalue, np);
893     return np2 ? nv_getnum(np2) : 0.0;
894 }
895 
896 // TODO: Figure out if this function can be removed else fix it.
897 //
898 // Coverity CID#253864 points out that the `if (val)` test implies that `val` might be NULL in the
899 // call to `nvputval()` which will cause the NULL to be dereferenced. I instrumented the code and
900 // this is never called (at least as a consequence of any unit test).
901 //
902 // Either the `if (val)` is never false or the `nv_putval()` should also be predicated on it being
903 // non-NULL.
clone_putv(Namval_t * np,const void * val,nvflag_t flags,Namfun_t * handle)904 static_fn void clone_putv(Namval_t *np, const void *val, nvflag_t flags, Namfun_t *handle) {
905     UNUSED(handle);
906     Shell_t *shp = sh_ptr(np);
907     Namfun_t *dp = nv_stack(np, NULL);
908     Namval_t *mp = FETCH_VT(np->nvalue, np);
909     if (!shp->subshell) free(dp);
910     if (val) nv_clone(mp, np, NV_NOFREE);
911     STORE_VT(np->nvalue, const_cp, NULL);
912     assert(val);
913     nv_putval(np, val, flags);
914 }
915 
916 static const Namdisc_t clone_disc = {
917     .dsize = 0, .putval = clone_putv, .getval = clone_getv, .getnum = clone_getn};
918 
nv_mkclone(Namval_t * mp)919 Namval_t *nv_mkclone(Namval_t *mp) {
920     Shell_t *shp = sh_ptr(mp);
921     Namval_t *np;
922     Namfun_t *dp;
923     np = calloc(1, sizeof(Namval_t));
924     nv_setattr(np, mp->nvflag);
925     np->nvsize = mp->nvsize;
926     np->nvname = mp->nvname;
927     np->nvshell = mp->nvshell;
928     STORE_VT(np->nvalue, np, mp);
929     nv_setattr(np, mp->nvflag);
930     dp = calloc(1, sizeof(Namfun_t));
931     dp->disc = &clone_disc;
932     nv_stack(np, dp);
933     dtinsert(nv_dict(shp->namespace), np);
934     return np;
935 }
936 
nv_search(const char * name,Dt_t * root,nvflag_t mode)937 Namval_t *nv_search(const char *name, Dt_t *root, nvflag_t mode) {
938     Shell_t *shp = sh_getinterp();
939     Dt_t *dp = NULL;
940 
941     if (mode & NV_NOSCOPE) dp = dtview(root, 0);
942     if (*name == '.' && root == shp->var_tree && !dp) root = shp->var_base;
943 
944     Namval_t *np = dtmatch(root, name);
945     if (!np && (mode & NV_ADD)) {
946         if (shp->namespace && !(mode & NV_NOSCOPE) && root == shp->var_tree) {
947             root = nv_dict(shp->namespace);
948         } else if (!dp && !(mode & NV_NOSCOPE)) {
949             Dt_t *next;
950             while ((next = dtvnext(root))) root = next;
951         }
952         np = dtinsert(root, newnode(name));
953         np->nvshell = shp;
954     }
955     if (dp) dtview(root, dp);
956     return np;
957 }
958 
959 // This is a variant of nv_search() that takes an existing Namval_t* rather than a char* to the var
960 // name of interest.
nv_search_namval(const Namval_t * mp,Dt_t * root,nvflag_t mode)961 Namval_t *nv_search_namval(const Namval_t *mp, Dt_t *root, nvflag_t mode) {
962     Shell_t *shp = sh_getinterp();
963     Dt_t *dp = NULL;
964     const char *name = NULL;
965 
966     if (nv_isflag(mode, NV_NOSCOPE)) dp = dtview(root, 0);
967 
968     Namval_t *np = dtsearch(root, mp);
969     if (!np && nv_isflag(mode, NV_ADD)) {
970         name = nv_name(mp);
971         if (shp->namespace && !nv_isflag(mode, NV_NOSCOPE) && root == shp->var_tree) {
972             root = nv_dict(shp->namespace);
973         } else if (!dp && !nv_isflag(mode, NV_NOSCOPE)) {
974             Dt_t *next;
975             while ((next = dtvnext(root))) root = next;
976         }
977         np = dtinsert(root, newnode(name));
978         np->nvshell = shp;
979     }
980     if (dp) dtview(root, dp);
981     return np;
982 }
983 
984 //
985 // Finds function or builtin for given name and the discipline variable. if var!=0 the variable
986 // pointer is returned and the built-in name is put onto the stack at the current offset. Otherwise,
987 // a pointer to the builtin (variable or type) is returned and var contains the pointer to the
988 // variable. If last==0 and first component of name is a reference, nv_bfsearch() will return 0.
989 //
nv_bfsearch(const char * name,Dt_t * root,Namval_t ** var,char ** last)990 Namval_t *nv_bfsearch(const char *name, Dt_t *root, Namval_t **var, char **last) {
991     Shell_t *shp = sh_getinterp();
992     int c, offset = stktell(shp->stk);
993     char *sp, *cp = NULL;
994     Namval_t *np, *nq;
995     char *dname = NULL;
996 
997     if (var) *var = 0;
998     // Check for . in the name before =.
999     for (sp = (char *)name + 1; *sp; sp++) {
1000         if (*sp == '=') return 0;
1001         if (*sp == '[') {
1002             while (*sp == '[') {
1003                 sp = nv_endsubscript(NULL, (char *)sp, 0, shp);
1004                 if (sp[-1] != ']') return 0;
1005             }
1006             if (*sp == 0) break;
1007             if (*sp != '.') return 0;
1008             cp = sp;
1009         } else if (*sp == '.') {
1010             cp = sp;
1011         }
1012     }
1013     if (!cp) {
1014         if (!var) return 0;
1015         if (shp->namespace) {
1016             sfprintf(shp->strbuf, "%s.%s%c", nv_name(shp->namespace), name, 0);
1017             np = nv_search(sfstruse(shp->strbuf), root, 0);
1018             if (np) return np;
1019         }
1020         return nv_search(name, root, 0);
1021     }
1022     sfputr(shp->stk, name, 0);
1023     dname = cp + 1;
1024     cp = stkptr(shp->stk, offset) + (cp - name);
1025     if (last) *last = cp;
1026     c = *cp;
1027     *cp = 0;
1028     nq = nv_open(stkptr(shp->stk, offset), shp->var_tree, NV_VARNAME | NV_NOADD | NV_NOFAIL);
1029     *cp = c;
1030     if (!nq) {
1031         np = NULL;
1032         goto done;
1033     }
1034     if (!var) {
1035         np = nq;
1036         goto done;
1037     }
1038     *var = nq;
1039     if (c == '[') nv_endsubscript(nq, cp, NV_NOADD, nq->nvshell);
1040     stkseek(shp->stk, offset);
1041     if (nv_istable(nq)) {
1042         Namval_t *nsp = shp->namespace;
1043         if (last == 0) return nv_search(name, root, 0);
1044         shp->namespace = NULL;
1045         sfputr(shp->stk, nv_name(nq), -1);
1046         shp->namespace = nsp;
1047         sfputr(shp->stk, dname - 1, 0);
1048         np = nv_search(stkptr(shp->stk, offset), root, 0);
1049         stkseek(shp->stk, offset);
1050         return np;
1051     }
1052     while (nv_isarray(nq) && !nv_isattr(nq, NV_MINIMAL | NV_EXPORT) && nq->nvenv &&
1053            nv_isarray(nq->nvenv)) {
1054         nq = nq->nvenv;
1055     }
1056     return (Namval_t *)nv_setdisc(nq, dname, nq, (Namfun_t *)nq);
1057 done:
1058     stkseek(shp->stk, offset);
1059     return np;
1060 }
1061 
1062 //
1063 // Add or replace built-in version of command corresponding to <path>. The <bltin> argument is a
1064 // pointer to the built-in. If <extra>==builtin_delete, the built-in will be deleted. If
1065 // <extra>==builtin_disable, the built-in will be disabled. Special builtins cannot be
1066 // added or deleted return failure. The return value for adding builtins is a pointer to the node or
1067 // NULL on failure.  For delete NULL means success and the node that cannot be deleted is returned
1068 // on failure.
1069 //
sh_addbuiltin(Shell_t * shp,const char * path,Shbltin_f bltin,void * extra)1070 Namval_t *sh_addbuiltin(Shell_t *shp, const char *path, Shbltin_f bltin, void *extra) {
1071     const char *name;
1072     char *cp;
1073     Namval_t *np, *nq = NULL;
1074     int offset = stktell(shp->stk);
1075 
1076     if (extra == builtin_delete) {
1077         name = path;
1078     } else if ((name = path_basename(path)) == path &&
1079                bltin != FETCH_VT(SYSTYPESET->nvalue, shbltinp) &&
1080                (nq = nv_bfsearch(name, shp->bltin_tree, NULL, &cp))) {
1081         path = name = stkptr(shp->stk, offset);
1082     } else if (shp->bltin_dir && extra != builtin_delete) {
1083         sfputr(shp->stk, shp->bltin_dir, '/');
1084         sfputr(shp->stk, name, 0);
1085         path = stkptr(shp->stk, offset);
1086     }
1087     np = nv_search(name, shp->bltin_tree, 0);
1088     if (np) {
1089         // Exists without a path.
1090         stkseek(shp->stk, offset);
1091         if (extra == builtin_delete) {
1092             if (nv_isattr(np, BLT_SPC)) {
1093                 errormsg(SH_DICT, ERROR_exit(1), "Cannot delete: %s%s", name, is_spcbuiltin);
1094             }
1095             if (np->nvfun && !nv_isattr(np, NV_NOFREE)) free(np->nvfun);
1096             dtdelete(shp->bltin_tree, np);
1097             return NULL;
1098         } else if (extra == builtin_disable) {
1099             nv_onattr(np, BLT_DISABLE);
1100             return NULL;
1101         }
1102         if (!bltin) return np;
1103     } else {
1104         for (np = dtfirst(shp->bltin_tree); np; np = dtnext(shp->bltin_tree, np)) {
1105             if (!strcmp(name, path_basename(nv_name(np)))) {
1106                 // Exists probably with different path so delete it.
1107                 if (strcmp(path, nv_name(np))) {
1108                     if (nv_isattr(np, BLT_SPC)) return np;
1109                     if (!bltin) bltin = FETCH_VT(np->nvalue, shbltinp);
1110                     if (extra == builtin_delete) {
1111                         dtdelete(shp->bltin_tree, np);
1112                         return NULL;
1113                     }
1114                     np = NULL;
1115                 }
1116                 break;
1117             }
1118         }
1119     }
1120     if (!np && !(np = nv_search(path, shp->bltin_tree, bltin ? NV_ADD : 0))) return NULL;
1121     stkseek(shp->stk, offset);
1122     if (nv_isattr(np, BLT_SPC)) {
1123         if (extra) np->nvfun = extra;
1124         return np;
1125     }
1126     np->nvenv = NULL;
1127     np->nvfun = NULL;
1128     if (bltin) {
1129         STORE_VT(np->nvalue, shbltinp, bltin);
1130         nv_onattr(np, NV_BLTIN | NV_NOFREE);
1131         np->nvfun = extra;
1132     }
1133     if (nq) {
1134         cp = nv_setdisc(nq, cp + 1, np, (Namfun_t *)nq);
1135         nv_close(nq);
1136         if (!cp) errormsg(SH_DICT, ERROR_exit(1), e_baddisc, name);
1137     }
1138     if (extra == builtin_delete) return NULL;
1139     return np;
1140 }
1141 
1142 struct table {
1143     Namfun_t fun;
1144     Namval_t *parent;
1145     Shell_t *shp;
1146     Dt_t *dict;
1147 };
1148 
next_table(Namval_t * np,Dt_t * root,Namfun_t * fp)1149 static_fn Namval_t *next_table(Namval_t *np, Dt_t *root, Namfun_t *fp) {
1150     struct table *tp = (struct table *)fp;
1151     if (root) return dtnext(root, np);
1152     return dtfirst(tp->dict);
1153 }
1154 
create_table(Namval_t * np,const void * name,nvflag_t flags,Namfun_t * fp)1155 static_fn Namval_t *create_table(Namval_t *np, const void *name, nvflag_t flags, Namfun_t *fp) {
1156     struct table *tp = (struct table *)fp;
1157     tp->shp->last_table = np;
1158     return nv_create(name, tp->dict, flags, fp);
1159 }
1160 
clone_table(Namval_t * np,Namval_t * mp,nvflag_t flags,Namfun_t * fp)1161 static_fn Namfun_t *clone_table(Namval_t *np, Namval_t *mp, nvflag_t flags, Namfun_t *fp) {
1162     struct table *tp = (struct table *)fp;
1163     struct table *ntp = (struct table *)nv_clone_disc(fp, 0);
1164     Dt_t *oroot = tp->dict;
1165     Dt_t *nroot = dtopen(&_Nvdisc, Dtoset);
1166     assert(nroot);
1167 
1168     dtuserdata(nroot, dtuserdata(oroot, 0, 0), 1);
1169     memcpy(ntp, fp, sizeof(struct table));
1170     ntp->dict = nroot;
1171     ntp->parent = nv_lastdict(mp->nvshell);
1172     for (np = dtfirst(oroot); np; np = dtnext(oroot, np)) {
1173         mp = dtinsert(nroot, newnode(np->nvname));
1174         mp->nvshell = dtuserdata(nroot, 0, 0);
1175         nv_clone(np, mp, flags);
1176     }
1177     return &ntp->fun;
1178 }
1179 
1180 struct adata {
1181     Shell_t *sh;
1182     Namval_t *tp;
1183     char *mapname;
1184     char **argnam;
1185     int attsize;
1186     char *attval;
1187 };
1188 
delete_fun(Namval_t * np,void * data)1189 static_fn void delete_fun(Namval_t *np, void *data) {
1190     Shell_t *shp = ((struct adata *)data)->sh;
1191     nv_delete(np, shp->fun_tree, NV_NOFREE);
1192 }
1193 
put_table(Namval_t * np,const void * val,nvflag_t flags,Namfun_t * fp)1194 static_fn void put_table(Namval_t *np, const void *val, nvflag_t flags, Namfun_t *fp) {
1195     Dt_t *root = ((struct table *)fp)->dict;
1196     Namval_t *nq, *mp;
1197     Namarr_t *ap;
1198     struct adata data;
1199 
1200     if (val) {
1201         nv_putv(np, val, flags, fp);
1202         return;
1203     }
1204     if (nv_isarray(np) && (ap = nv_arrayptr(np)) && array_elem(ap)) return;
1205     memset(&data, 0, sizeof(data));
1206     data.mapname = nv_name(np);
1207     data.sh = ((struct table *)fp)->shp;
1208     nv_scan(data.sh->fun_tree, delete_fun, &data, NV_FUNCTION, NV_FUNCTION | NV_NOSCOPE);
1209     dtview(root, 0);
1210     for (mp = dtfirst(root); mp; mp = nq) {
1211         _nv_unset(mp, flags);
1212         nq = dtnext(root, mp);
1213         dtdelete(root, mp);
1214         free(mp);
1215     }
1216     dtclose(root);
1217     if (!(fp->nofree & 1)) free(fp);
1218     np->nvfun = NULL;
1219 }
1220 
1221 //
1222 // Return space separated list of names of variables in given tree.
1223 //
get_table(Namval_t * np,Namfun_t * fp)1224 static_fn char *get_table(Namval_t *np, Namfun_t *fp) {
1225     Dt_t *root = ((struct table *)fp)->dict;
1226     static Sfio_t *out;
1227     int first = 1;
1228     Dt_t *base = dtview(root, 0);
1229 
1230     if (out) {
1231         sfseek(out, (Sfoff_t)0, SEEK_SET);
1232     } else {
1233         out = sfnew(NULL, NULL, -1, -1, SF_WRITE | SF_STRING);
1234     }
1235     for (np = dtfirst(root); np; np = dtnext(root, np)) {
1236         if (!nv_isnull(np) || np->nvfun || nv_isattr(np, ~NV_NOFREE)) {
1237             if (!first) {
1238                 sfputc(out, ' ');
1239             } else {
1240                 first = 0;
1241             }
1242             sfputr(out, np->nvname, -1);
1243         }
1244     }
1245     sfputc(out, 0);
1246     if (base) dtview(root, base);
1247     return (char *)out->data;
1248 }
1249 
1250 static const Namdisc_t table_disc = {.dsize = sizeof(struct table),
1251                                      .putval = put_table,
1252                                      .getval = get_table,
1253                                      .createf = create_table,
1254                                      .clonef = clone_table,
1255                                      .nextf = next_table};
1256 
nv_parent(const Namval_t * np)1257 Namval_t *nv_parent(const Namval_t *np) {
1258     struct table *tp = (struct table *)nv_hasdisc(np, &table_disc);
1259     if (tp) return tp->parent;
1260     return NULL;
1261 }
1262 
nv_dict(Namval_t * np)1263 Dt_t *nv_dict(Namval_t *np) {
1264     Shell_t *shp = sh_ptr(np);
1265     struct table *tp = (struct table *)nv_hasdisc(np, &table_disc);
1266     if (tp) return tp->dict;
1267     np = shp->last_table;
1268 #if 1
1269     if (np) {
1270         tp = (struct table *)nv_hasdisc(np, &table_disc);
1271         if (tp) return tp->dict;
1272     }
1273 #else
1274     while (np) {
1275         tp = (struct table *)nv_hasdisc(np, &table_disc);
1276         if (tp) return tp->dict;
1277         np = nv_create(np, NULL, NV_FIRST, NULL);
1278     }
1279 #endif
1280     return shp->var_tree;
1281 }
1282 
nv_istable(const Namval_t * np)1283 bool nv_istable(const Namval_t *np) { return nv_hasdisc(np, &table_disc) != 0; }
1284 
1285 //
1286 // Create a mountable name-value pair tree.
1287 //
nv_mount(Namval_t * np,const char * name,Dt_t * dict)1288 Namval_t *nv_mount(Namval_t *np, const char *name, Dt_t *dict) {
1289     Namval_t *mp, *pp;
1290     struct table *tp;
1291 
1292     dtuserdata(dict, sh_ptr(np), 1);
1293     if (nv_hasdisc(np, &table_disc)) {
1294         pp = np;
1295     } else {
1296         pp = nv_lastdict(np->nvshell);
1297     }
1298     tp = calloc(1, sizeof(struct table));
1299     if (name) {
1300         Namfun_t *fp = pp->nvfun;
1301         mp = (*fp->disc->createf)(pp, name, 0, fp);
1302     } else {
1303         mp = np;
1304     }
1305     nv_offattr(mp, NV_TABLE);
1306     if (!nv_isnull(mp)) _nv_unset(mp, NV_RDONLY);
1307     tp->shp = sh_ptr(np);
1308     tp->dict = dict;
1309     tp->parent = pp;
1310     tp->fun.disc = &table_disc;
1311     nv_disc(mp, &tp->fun, DISC_OP_FIRST);
1312     return mp;
1313 }
1314 
nv_discfun(Nvdiscfun_op_t op)1315 const Namdisc_t *nv_discfun(Nvdiscfun_op_t op) {
1316     if (op == DISCFUN_ADD) return &Nv_bdisc;
1317     if (op == DISCFUN_RESTRICT) return &RESTRICTED_disc;
1318     abort();
1319 }
1320 
nv_hasget(Namval_t * np)1321 bool nv_hasget(Namval_t *np) {
1322     Namfun_t *fp;
1323 
1324     for (fp = np->nvfun; fp; fp = fp->next) {
1325         if (!fp->disc || (!fp->disc->getnum && !fp->disc->getval)) continue;
1326         return true;
1327     }
1328     return false;
1329 }
1330 
sh_fsearch(Shell_t * shp,const char * fname,nvflag_t add)1331 Namval_t *sh_fsearch(Shell_t *shp, const char *fname, nvflag_t add) {
1332     Stk_t *stkp = shp->stk;
1333     int offset = stktell(stkp);
1334 
1335     sfputr(stkp, nv_name(shp->namespace), '.');
1336     sfputr(stkp, fname, 0);
1337     fname = stkptr(stkp, offset);
1338     Dt_t *funtree = sh_subfuntree(shp, nv_isflag(add, NV_ADD));
1339     return nv_search(fname, funtree, add);
1340 }
1341