VirtualBox

source: kBuild/trunk/src/ash/parser.c@ 1223

Last change on this file since 1223 was 809, checked in by bird, 18 years ago

Solaris + cleanup.

  • Property svn:eol-style set to native
File size: 35.2 KB
Line 
1/* $NetBSD: parser.c,v 1.59 2005/03/21 20:10:29 dsl Exp $ */
2
3/*-
4 * Copyright (c) 1991, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Kenneth Almquist.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#ifdef HAVE_SYS_CDEFS_H
36#include <sys/cdefs.h>
37#endif
38#ifndef lint
39#if 0
40static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95";
41#else
42__RCSID("$NetBSD: parser.c,v 1.59 2005/03/21 20:10:29 dsl Exp $");
43#endif
44#endif /* not lint */
45
46#include <stdlib.h>
47#ifdef __sun__
48#include <iso/limits_iso.h>
49#endif
50
51#include "shell.h"
52#include "parser.h"
53#include "nodes.h"
54#include "expand.h" /* defines rmescapes() */
55#include "eval.h" /* defines commandname */
56#include "redir.h" /* defines copyfd() */
57#include "syntax.h"
58#include "options.h"
59#include "input.h"
60#include "output.h"
61#include "var.h"
62#include "error.h"
63#include "memalloc.h"
64#include "mystring.h"
65#include "alias.h"
66#include "show.h"
67#ifndef SMALL
68#include "myhistedit.h"
69#endif
70
71/*
72 * Shell command parser.
73 */
74
75#define EOFMARKLEN 79
76
77/* values returned by readtoken */
78#include "token.h"
79
80#define OPENBRACE '{'
81#define CLOSEBRACE '}'
82
83
84struct heredoc {
85 struct heredoc *next; /* next here document in list */
86 union node *here; /* redirection node */
87 char *eofmark; /* string indicating end of input */
88 int striptabs; /* if set, strip leading tabs */
89};
90
91
92
93static int noalias = 0; /* when set, don't handle aliases */
94struct heredoc *heredoclist; /* list of here documents to read */
95int parsebackquote; /* nonzero if we are inside backquotes */
96int doprompt; /* if set, prompt the user */
97int needprompt; /* true if interactive and at start of line */
98int lasttoken; /* last token read */
99MKINIT int tokpushback; /* last token pushed back */
100char *wordtext; /* text of last word returned by readtoken */
101MKINIT int checkkwd; /* 1 == check for kwds, 2 == also eat newlines */
102struct nodelist *backquotelist;
103union node *redirnode;
104struct heredoc *heredoc;
105int quoteflag; /* set if (part of) last token was quoted */
106int startlinno; /* line # where last token started */
107
108
109STATIC union node *list(int);
110STATIC union node *andor(void);
111STATIC union node *pipeline(void);
112STATIC union node *command(void);
113STATIC union node *simplecmd(union node **, union node *);
114STATIC union node *makename(void);
115STATIC void parsefname(void);
116STATIC void parseheredoc(void);
117STATIC int peektoken(void);
118STATIC int readtoken(void);
119STATIC int xxreadtoken(void);
120STATIC int readtoken1(int, char const *, char *, int);
121STATIC int noexpand(char *);
122STATIC void synexpect(int) __attribute__((__noreturn__));
123STATIC void synerror(const char *) __attribute__((__noreturn__));
124STATIC void setprompt(int);
125
126
127/*
128 * Read and parse a command. Returns NEOF on end of file. (NULL is a
129 * valid parse tree indicating a blank line.)
130 */
131
132union node *
133parsecmd(int interact)
134{
135 int t;
136
137 tokpushback = 0;
138 doprompt = interact;
139 if (doprompt)
140 setprompt(1);
141 else
142 setprompt(0);
143 needprompt = 0;
144 t = readtoken();
145 if (t == TEOF)
146 return NEOF;
147 if (t == TNL)
148 return NULL;
149 tokpushback++;
150 return list(1);
151}
152
153
154STATIC union node *
155list(int nlflag)
156{
157 union node *n1, *n2, *n3;
158 int tok;
159
160 checkkwd = 2;
161 if (nlflag == 0 && tokendlist[peektoken()])
162 return NULL;
163 n1 = NULL;
164 for (;;) {
165 n2 = andor();
166 tok = readtoken();
167 if (tok == TBACKGND) {
168 if (n2->type == NCMD || n2->type == NPIPE) {
169 n2->ncmd.backgnd = 1;
170 } else if (n2->type == NREDIR) {
171 n2->type = NBACKGND;
172 } else {
173 n3 = (union node *)stalloc(sizeof (struct nredir));
174 n3->type = NBACKGND;
175 n3->nredir.n = n2;
176 n3->nredir.redirect = NULL;
177 n2 = n3;
178 }
179 }
180 if (n1 == NULL) {
181 n1 = n2;
182 }
183 else {
184 n3 = (union node *)stalloc(sizeof (struct nbinary));
185 n3->type = NSEMI;
186 n3->nbinary.ch1 = n1;
187 n3->nbinary.ch2 = n2;
188 n1 = n3;
189 }
190 switch (tok) {
191 case TBACKGND:
192 case TSEMI:
193 tok = readtoken();
194 /* fall through */
195 case TNL:
196 if (tok == TNL) {
197 parseheredoc();
198 if (nlflag)
199 return n1;
200 } else {
201 tokpushback++;
202 }
203 checkkwd = 2;
204 if (tokendlist[peektoken()])
205 return n1;
206 break;
207 case TEOF:
208 if (heredoclist)
209 parseheredoc();
210 else
211 pungetc(); /* push back EOF on input */
212 return n1;
213 default:
214 if (nlflag)
215 synexpect(-1);
216 tokpushback++;
217 return n1;
218 }
219 }
220}
221
222
223
224STATIC union node *
225andor(void)
226{
227 union node *n1, *n2, *n3;
228 int t;
229
230 n1 = pipeline();
231 for (;;) {
232 if ((t = readtoken()) == TAND) {
233 t = NAND;
234 } else if (t == TOR) {
235 t = NOR;
236 } else {
237 tokpushback++;
238 return n1;
239 }
240 n2 = pipeline();
241 n3 = (union node *)stalloc(sizeof (struct nbinary));
242 n3->type = t;
243 n3->nbinary.ch1 = n1;
244 n3->nbinary.ch2 = n2;
245 n1 = n3;
246 }
247}
248
249
250
251STATIC union node *
252pipeline(void)
253{
254 union node *n1, *n2, *pipenode;
255 struct nodelist *lp, *prev;
256 int negate;
257
258 negate = 0;
259 TRACE(("pipeline: entered\n"));
260 while (readtoken() == TNOT)
261 negate = !negate;
262 tokpushback++;
263 n1 = command();
264 if (readtoken() == TPIPE) {
265 pipenode = (union node *)stalloc(sizeof (struct npipe));
266 pipenode->type = NPIPE;
267 pipenode->npipe.backgnd = 0;
268 lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
269 pipenode->npipe.cmdlist = lp;
270 lp->n = n1;
271 do {
272 prev = lp;
273 lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
274 lp->n = command();
275 prev->next = lp;
276 } while (readtoken() == TPIPE);
277 lp->next = NULL;
278 n1 = pipenode;
279 }
280 tokpushback++;
281 if (negate) {
282 n2 = (union node *)stalloc(sizeof (struct nnot));
283 n2->type = NNOT;
284 n2->nnot.com = n1;
285 return n2;
286 } else
287 return n1;
288}
289
290
291
292STATIC union node *
293command(void)
294{
295 union node *n1, *n2;
296 union node *ap, **app;
297 union node *cp, **cpp;
298 union node *redir, **rpp;
299 int t, negate = 0;
300
301 checkkwd = 2;
302 redir = NULL;
303 n1 = NULL;
304 rpp = &redir;
305
306 /* Check for redirection which may precede command */
307 while (readtoken() == TREDIR) {
308 *rpp = n2 = redirnode;
309 rpp = &n2->nfile.next;
310 parsefname();
311 }
312 tokpushback++;
313
314 while (readtoken() == TNOT) {
315 TRACE(("command: TNOT recognized\n"));
316 negate = !negate;
317 }
318 tokpushback++;
319
320 switch (readtoken()) {
321 case TIF:
322 n1 = (union node *)stalloc(sizeof (struct nif));
323 n1->type = NIF;
324 n1->nif.test = list(0);
325 if (readtoken() != TTHEN)
326 synexpect(TTHEN);
327 n1->nif.ifpart = list(0);
328 n2 = n1;
329 while (readtoken() == TELIF) {
330 n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
331 n2 = n2->nif.elsepart;
332 n2->type = NIF;
333 n2->nif.test = list(0);
334 if (readtoken() != TTHEN)
335 synexpect(TTHEN);
336 n2->nif.ifpart = list(0);
337 }
338 if (lasttoken == TELSE)
339 n2->nif.elsepart = list(0);
340 else {
341 n2->nif.elsepart = NULL;
342 tokpushback++;
343 }
344 if (readtoken() != TFI)
345 synexpect(TFI);
346 checkkwd = 1;
347 break;
348 case TWHILE:
349 case TUNTIL: {
350 int got;
351 n1 = (union node *)stalloc(sizeof (struct nbinary));
352 n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
353 n1->nbinary.ch1 = list(0);
354 if ((got=readtoken()) != TDO) {
355TRACE(("expecting DO got %s %s\n", tokname[got], got == TWORD ? wordtext : ""));
356 synexpect(TDO);
357 }
358 n1->nbinary.ch2 = list(0);
359 if (readtoken() != TDONE)
360 synexpect(TDONE);
361 checkkwd = 1;
362 break;
363 }
364 case TFOR:
365 if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
366 synerror("Bad for loop variable");
367 n1 = (union node *)stalloc(sizeof (struct nfor));
368 n1->type = NFOR;
369 n1->nfor.var = wordtext;
370 if (readtoken() == TWORD && ! quoteflag && equal(wordtext, "in")) {
371 app = &ap;
372 while (readtoken() == TWORD) {
373 n2 = (union node *)stalloc(sizeof (struct narg));
374 n2->type = NARG;
375 n2->narg.text = wordtext;
376 n2->narg.backquote = backquotelist;
377 *app = n2;
378 app = &n2->narg.next;
379 }
380 *app = NULL;
381 n1->nfor.args = ap;
382 if (lasttoken != TNL && lasttoken != TSEMI)
383 synexpect(-1);
384 } else {
385 static char argvars[5] = {CTLVAR, VSNORMAL|VSQUOTE,
386 '@', '=', '\0'};
387 n2 = (union node *)stalloc(sizeof (struct narg));
388 n2->type = NARG;
389 n2->narg.text = argvars;
390 n2->narg.backquote = NULL;
391 n2->narg.next = NULL;
392 n1->nfor.args = n2;
393 /*
394 * Newline or semicolon here is optional (but note
395 * that the original Bourne shell only allowed NL).
396 */
397 if (lasttoken != TNL && lasttoken != TSEMI)
398 tokpushback++;
399 }
400 checkkwd = 2;
401 if ((t = readtoken()) == TDO)
402 t = TDONE;
403 else if (t == TBEGIN)
404 t = TEND;
405 else
406 synexpect(-1);
407 n1->nfor.body = list(0);
408 if (readtoken() != t)
409 synexpect(t);
410 checkkwd = 1;
411 break;
412 case TCASE:
413 n1 = (union node *)stalloc(sizeof (struct ncase));
414 n1->type = NCASE;
415 if (readtoken() != TWORD)
416 synexpect(TWORD);
417 n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg));
418 n2->type = NARG;
419 n2->narg.text = wordtext;
420 n2->narg.backquote = backquotelist;
421 n2->narg.next = NULL;
422 while (readtoken() == TNL);
423 if (lasttoken != TWORD || ! equal(wordtext, "in"))
424 synerror("expecting \"in\"");
425 cpp = &n1->ncase.cases;
426 noalias = 1;
427 checkkwd = 2, readtoken();
428 do {
429 *cpp = cp = (union node *)stalloc(sizeof (struct nclist));
430 cp->type = NCLIST;
431 app = &cp->nclist.pattern;
432 for (;;) {
433 *app = ap = (union node *)stalloc(sizeof (struct narg));
434 ap->type = NARG;
435 ap->narg.text = wordtext;
436 ap->narg.backquote = backquotelist;
437 if (checkkwd = 2, readtoken() != TPIPE)
438 break;
439 app = &ap->narg.next;
440 readtoken();
441 }
442 ap->narg.next = NULL;
443 noalias = 0;
444 if (lasttoken != TRP) {
445 synexpect(TRP);
446 }
447 cp->nclist.body = list(0);
448
449 checkkwd = 2;
450 if ((t = readtoken()) != TESAC) {
451 if (t != TENDCASE) {
452 noalias = 0;
453 synexpect(TENDCASE);
454 } else {
455 noalias = 1;
456 checkkwd = 2;
457 readtoken();
458 }
459 }
460 cpp = &cp->nclist.next;
461 } while(lasttoken != TESAC);
462 noalias = 0;
463 *cpp = NULL;
464 checkkwd = 1;
465 break;
466 case TLP:
467 n1 = (union node *)stalloc(sizeof (struct nredir));
468 n1->type = NSUBSHELL;
469 n1->nredir.n = list(0);
470 n1->nredir.redirect = NULL;
471 if (readtoken() != TRP)
472 synexpect(TRP);
473 checkkwd = 1;
474 break;
475 case TBEGIN:
476 n1 = list(0);
477 if (readtoken() != TEND)
478 synexpect(TEND);
479 checkkwd = 1;
480 break;
481 /* Handle an empty command like other simple commands. */
482 case TSEMI:
483 /*
484 * An empty command before a ; doesn't make much sense, and
485 * should certainly be disallowed in the case of `if ;'.
486 */
487 if (!redir)
488 synexpect(-1);
489 case TAND:
490 case TOR:
491 case TNL:
492 case TEOF:
493 case TWORD:
494 case TRP:
495 tokpushback++;
496 n1 = simplecmd(rpp, redir);
497 goto checkneg;
498 default:
499 synexpect(-1);
500 /* NOTREACHED */
501 }
502
503 /* Now check for redirection which may follow command */
504 while (readtoken() == TREDIR) {
505 *rpp = n2 = redirnode;
506 rpp = &n2->nfile.next;
507 parsefname();
508 }
509 tokpushback++;
510 *rpp = NULL;
511 if (redir) {
512 if (n1->type != NSUBSHELL) {
513 n2 = (union node *)stalloc(sizeof (struct nredir));
514 n2->type = NREDIR;
515 n2->nredir.n = n1;
516 n1 = n2;
517 }
518 n1->nredir.redirect = redir;
519 }
520
521checkneg:
522 if (negate) {
523 n2 = (union node *)stalloc(sizeof (struct nnot));
524 n2->type = NNOT;
525 n2->nnot.com = n1;
526 return n2;
527 }
528 else
529 return n1;
530}
531
532
533STATIC union node *
534simplecmd(union node **rpp, union node *redir)
535{
536 union node *args, **app;
537 union node **orig_rpp = rpp;
538 union node *n = NULL, *n2;
539 int negate = 0;
540
541 /* If we don't have any redirections already, then we must reset */
542 /* rpp to be the address of the local redir variable. */
543 if (redir == 0)
544 rpp = &redir;
545
546 args = NULL;
547 app = &args;
548 /*
549 * We save the incoming value, because we need this for shell
550 * functions. There can not be a redirect or an argument between
551 * the function name and the open parenthesis.
552 */
553 orig_rpp = rpp;
554
555 while (readtoken() == TNOT) {
556 TRACE(("command: TNOT recognized\n"));
557 negate = !negate;
558 }
559 tokpushback++;
560
561 for (;;) {
562 if (readtoken() == TWORD) {
563 n = (union node *)stalloc(sizeof (struct narg));
564 n->type = NARG;
565 n->narg.text = wordtext;
566 n->narg.backquote = backquotelist;
567 *app = n;
568 app = &n->narg.next;
569 } else if (lasttoken == TREDIR) {
570 *rpp = n = redirnode;
571 rpp = &n->nfile.next;
572 parsefname(); /* read name of redirection file */
573 } else if (lasttoken == TLP && app == &args->narg.next
574 && rpp == orig_rpp) {
575 /* We have a function */
576 if (readtoken() != TRP)
577 synexpect(TRP);
578#ifdef notdef
579 if (! goodname(n->narg.text))
580 synerror("Bad function name");
581#endif
582 n->type = NDEFUN;
583 n->narg.next = command();
584 goto checkneg;
585 } else {
586 tokpushback++;
587 break;
588 }
589 }
590 *app = NULL;
591 *rpp = NULL;
592 n = (union node *)stalloc(sizeof (struct ncmd));
593 n->type = NCMD;
594 n->ncmd.backgnd = 0;
595 n->ncmd.args = args;
596 n->ncmd.redirect = redir;
597
598checkneg:
599 if (negate) {
600 n2 = (union node *)stalloc(sizeof (struct nnot));
601 n2->type = NNOT;
602 n2->nnot.com = n;
603 return n2;
604 }
605 else
606 return n;
607}
608
609STATIC union node *
610makename(void)
611{
612 union node *n;
613
614 n = (union node *)stalloc(sizeof (struct narg));
615 n->type = NARG;
616 n->narg.next = NULL;
617 n->narg.text = wordtext;
618 n->narg.backquote = backquotelist;
619 return n;
620}
621
622void fixredir(union node *n, const char *text, int err)
623 {
624 TRACE(("Fix redir %s %d\n", text, err));
625 if (!err)
626 n->ndup.vname = NULL;
627
628 if (is_digit(text[0]) && text[1] == '\0')
629 n->ndup.dupfd = digit_val(text[0]);
630 else if (text[0] == '-' && text[1] == '\0')
631 n->ndup.dupfd = -1;
632 else {
633
634 if (err)
635 synerror("Bad fd number");
636 else
637 n->ndup.vname = makename();
638 }
639}
640
641
642STATIC void
643parsefname(void)
644{
645 union node *n = redirnode;
646
647 if (readtoken() != TWORD)
648 synexpect(-1);
649 if (n->type == NHERE) {
650 struct heredoc *here = heredoc;
651 struct heredoc *p;
652 int i;
653
654 if (quoteflag == 0)
655 n->type = NXHERE;
656 TRACE(("Here document %d\n", n->type));
657 if (here->striptabs) {
658 while (*wordtext == '\t')
659 wordtext++;
660 }
661 if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
662 synerror("Illegal eof marker for << redirection");
663 rmescapes(wordtext);
664 here->eofmark = wordtext;
665 here->next = NULL;
666 if (heredoclist == NULL)
667 heredoclist = here;
668 else {
669 for (p = heredoclist ; p->next ; p = p->next);
670 p->next = here;
671 }
672 } else if (n->type == NTOFD || n->type == NFROMFD) {
673 fixredir(n, wordtext, 0);
674 } else {
675 n->nfile.fname = makename();
676 }
677}
678
679
680/*
681 * Input any here documents.
682 */
683
684STATIC void
685parseheredoc(void)
686{
687 struct heredoc *here;
688 union node *n;
689
690 while (heredoclist) {
691 here = heredoclist;
692 heredoclist = here->next;
693 if (needprompt) {
694 setprompt(2);
695 needprompt = 0;
696 }
697 readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
698 here->eofmark, here->striptabs);
699 n = (union node *)stalloc(sizeof (struct narg));
700 n->narg.type = NARG;
701 n->narg.next = NULL;
702 n->narg.text = wordtext;
703 n->narg.backquote = backquotelist;
704 here->here->nhere.doc = n;
705 }
706}
707
708STATIC int
709peektoken(void)
710{
711 int t;
712
713 t = readtoken();
714 tokpushback++;
715 return (t);
716}
717
718STATIC int
719readtoken(void)
720{
721 int t;
722 int savecheckkwd = checkkwd;
723#ifdef DEBUG
724 int alreadyseen = tokpushback;
725#endif
726 struct alias *ap;
727
728 top:
729 t = xxreadtoken();
730
731 if (checkkwd) {
732 /*
733 * eat newlines
734 */
735 if (checkkwd == 2) {
736 checkkwd = 0;
737 while (t == TNL) {
738 parseheredoc();
739 t = xxreadtoken();
740 }
741 } else
742 checkkwd = 0;
743 /*
744 * check for keywords and aliases
745 */
746 if (t == TWORD && !quoteflag)
747 {
748 const char *const *pp;
749
750 for (pp = parsekwd; *pp; pp++) {
751 if (**pp == *wordtext && equal(*pp, wordtext))
752 {
753 lasttoken = t = pp -
754 parsekwd + KWDOFFSET;
755 TRACE(("keyword %s recognized\n", tokname[t]));
756 goto out;
757 }
758 }
759 if(!noalias &&
760 (ap = lookupalias(wordtext, 1)) != NULL) {
761 pushstring(ap->val, strlen(ap->val), ap);
762 checkkwd = savecheckkwd;
763 goto top;
764 }
765 }
766out:
767 checkkwd = (t == TNOT) ? savecheckkwd : 0;
768 }
769#ifdef DEBUG
770 if (!alreadyseen)
771 TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
772 else
773 TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
774#endif
775 return (t);
776}
777
778
779/*
780 * Read the next input token.
781 * If the token is a word, we set backquotelist to the list of cmds in
782 * backquotes. We set quoteflag to true if any part of the word was
783 * quoted.
784 * If the token is TREDIR, then we set redirnode to a structure containing
785 * the redirection.
786 * In all cases, the variable startlinno is set to the number of the line
787 * on which the token starts.
788 *
789 * [Change comment: here documents and internal procedures]
790 * [Readtoken shouldn't have any arguments. Perhaps we should make the
791 * word parsing code into a separate routine. In this case, readtoken
792 * doesn't need to have any internal procedures, but parseword does.
793 * We could also make parseoperator in essence the main routine, and
794 * have parseword (readtoken1?) handle both words and redirection.]
795 */
796
797#define RETURN(token) return lasttoken = token
798
799STATIC int
800xxreadtoken(void)
801{
802 int c;
803
804 if (tokpushback) {
805 tokpushback = 0;
806 return lasttoken;
807 }
808 if (needprompt) {
809 setprompt(2);
810 needprompt = 0;
811 }
812 startlinno = plinno;
813 for (;;) { /* until token or start of word found */
814 c = pgetc_macro();
815 if (c == ' ' || c == '\t')
816 continue; /* quick check for white space first */
817 switch (c) {
818 case ' ': case '\t':
819 continue;
820 case '#':
821 while ((c = pgetc()) != '\n' && c != PEOF);
822 pungetc();
823 continue;
824 case '\\':
825 if (pgetc() == '\n') {
826 startlinno = ++plinno;
827 if (doprompt)
828 setprompt(2);
829 else
830 setprompt(0);
831 continue;
832 }
833 pungetc();
834 goto breakloop;
835 case '\n':
836 plinno++;
837 needprompt = doprompt;
838 RETURN(TNL);
839 case PEOF:
840 RETURN(TEOF);
841 case '&':
842 if (pgetc() == '&')
843 RETURN(TAND);
844 pungetc();
845 RETURN(TBACKGND);
846 case '|':
847 if (pgetc() == '|')
848 RETURN(TOR);
849 pungetc();
850 RETURN(TPIPE);
851 case ';':
852 if (pgetc() == ';')
853 RETURN(TENDCASE);
854 pungetc();
855 RETURN(TSEMI);
856 case '(':
857 RETURN(TLP);
858 case ')':
859 RETURN(TRP);
860 default:
861 goto breakloop;
862 }
863 }
864breakloop:
865 return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
866#undef RETURN
867}
868
869
870
871/*
872 * If eofmark is NULL, read a word or a redirection symbol. If eofmark
873 * is not NULL, read a here document. In the latter case, eofmark is the
874 * word which marks the end of the document and striptabs is true if
875 * leading tabs should be stripped from the document. The argument firstc
876 * is the first character of the input token or document.
877 *
878 * Because C does not have internal subroutines, I have simulated them
879 * using goto's to implement the subroutine linkage. The following macros
880 * will run code that appears at the end of readtoken1.
881 */
882
883#define CHECKEND() {goto checkend; checkend_return:;}
884#define PARSEREDIR() {goto parseredir; parseredir_return:;}
885#define PARSESUB() {goto parsesub; parsesub_return:;}
886#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
887#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
888#define PARSEARITH() {goto parsearith; parsearith_return:;}
889
890/*
891 * Keep track of nested doublequotes in dblquote and doublequotep.
892 * We use dblquote for the first 32 levels, and we expand to a malloc'ed
893 * region for levels above that. Usually we never need to malloc.
894 * This code assumes that an int is 32 bits. We don't use uint32_t,
895 * because the rest of the code does not.
896 */
897#define ISDBLQUOTE() ((varnest < 32) ? (dblquote & (1 << varnest)) : \
898 (dblquotep[(varnest / 32) - 1] & (1 << (varnest % 32))))
899
900#define SETDBLQUOTE() \
901 if (varnest < 32) \
902 dblquote |= (1 << varnest); \
903 else \
904 dblquotep[(varnest / 32) - 1] |= (1 << (varnest % 32))
905
906#define CLRDBLQUOTE() \
907 if (varnest < 32) \
908 dblquote &= ~(1 << varnest); \
909 else \
910 dblquotep[(varnest / 32) - 1] &= ~(1 << (varnest % 32))
911
912STATIC int
913readtoken1(int firstc, char const *syntax, char *eofmark, int striptabs)
914{
915 int c = firstc;
916 char *out;
917 int len;
918 char line[EOFMARKLEN + 1];
919 struct nodelist *bqlist;
920 int quotef;
921 int *dblquotep = NULL;
922 size_t maxnest = 32;
923 int dblquote;
924 int varnest; /* levels of variables expansion */
925 int arinest; /* levels of arithmetic expansion */
926 int parenlevel; /* levels of parens in arithmetic */
927 int oldstyle;
928 char const *prevsyntax; /* syntax before arithmetic */
929#if __GNUC__
930 /* Avoid longjmp clobbering */
931 (void) &maxnest;
932 (void) &dblquotep;
933 (void) &out;
934 (void) &quotef;
935 (void) &dblquote;
936 (void) &varnest;
937 (void) &arinest;
938 (void) &parenlevel;
939 (void) &oldstyle;
940 (void) &prevsyntax;
941 (void) &syntax;
942#endif
943
944 startlinno = plinno;
945 dblquote = 0;
946 varnest = 0;
947 if (syntax == DQSYNTAX) {
948 SETDBLQUOTE();
949 }
950 quotef = 0;
951 bqlist = NULL;
952 arinest = 0;
953 parenlevel = 0;
954
955 STARTSTACKSTR(out);
956 loop: { /* for each line, until end of word */
957#if ATTY
958 if (c == '\034' && doprompt
959 && attyset() && ! equal(termval(), "emacs")) {
960 attyline();
961 if (syntax == BASESYNTAX)
962 return readtoken();
963 c = pgetc();
964 goto loop;
965 }
966#endif
967 CHECKEND(); /* set c to PEOF if at end of here document */
968 for (;;) { /* until end of line or end of word */
969 CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
970 switch(syntax[c]) {
971 case CNL: /* '\n' */
972 if (syntax == BASESYNTAX)
973 goto endword; /* exit outer loop */
974 USTPUTC(c, out);
975 plinno++;
976 if (doprompt)
977 setprompt(2);
978 else
979 setprompt(0);
980 c = pgetc();
981 goto loop; /* continue outer loop */
982 case CWORD:
983 USTPUTC(c, out);
984 break;
985 case CCTL:
986 if (eofmark == NULL || ISDBLQUOTE())
987 USTPUTC(CTLESC, out);
988 USTPUTC(c, out);
989 break;
990 case CBACK: /* backslash */
991 c = pgetc();
992 if (c == PEOF) {
993 USTPUTC('\\', out);
994 pungetc();
995 break;
996 }
997 if (c == '\n') {
998 if (doprompt)
999 setprompt(2);
1000 else
1001 setprompt(0);
1002 break;
1003 }
1004 quotef = 1;
1005 if (ISDBLQUOTE() && c != '\\' &&
1006 c != '`' && c != '$' &&
1007 (c != '"' || eofmark != NULL))
1008 USTPUTC('\\', out);
1009 if (SQSYNTAX[c] == CCTL)
1010 USTPUTC(CTLESC, out);
1011 else if (eofmark == NULL) {
1012 USTPUTC(CTLQUOTEMARK, out);
1013 USTPUTC(c, out);
1014 if (varnest != 0)
1015 USTPUTC(CTLQUOTEEND, out);
1016 break;
1017 }
1018 USTPUTC(c, out);
1019 break;
1020 case CSQUOTE:
1021 if (syntax != SQSYNTAX) {
1022 if (eofmark == NULL)
1023 USTPUTC(CTLQUOTEMARK, out);
1024 quotef = 1;
1025 syntax = SQSYNTAX;
1026 break;
1027 }
1028 if (eofmark != NULL && arinest == 0 &&
1029 varnest == 0) {
1030 /* Ignore inside quoted here document */
1031 USTPUTC(c, out);
1032 break;
1033 }
1034 /* End of single quotes... */
1035 if (arinest)
1036 syntax = ARISYNTAX;
1037 else {
1038 syntax = BASESYNTAX;
1039 if (varnest != 0)
1040 USTPUTC(CTLQUOTEEND, out);
1041 }
1042 break;
1043 case CDQUOTE:
1044 if (eofmark != NULL && arinest == 0 &&
1045 varnest == 0) {
1046 /* Ignore inside here document */
1047 USTPUTC(c, out);
1048 break;
1049 }
1050 quotef = 1;
1051 if (arinest) {
1052 if (ISDBLQUOTE()) {
1053 syntax = ARISYNTAX;
1054 CLRDBLQUOTE();
1055 } else {
1056 syntax = DQSYNTAX;
1057 SETDBLQUOTE();
1058 USTPUTC(CTLQUOTEMARK, out);
1059 }
1060 break;
1061 }
1062 if (eofmark != NULL)
1063 break;
1064 if (ISDBLQUOTE()) {
1065 if (varnest != 0)
1066 USTPUTC(CTLQUOTEEND, out);
1067 syntax = BASESYNTAX;
1068 CLRDBLQUOTE();
1069 } else {
1070 syntax = DQSYNTAX;
1071 SETDBLQUOTE();
1072 USTPUTC(CTLQUOTEMARK, out);
1073 }
1074 break;
1075 case CVAR: /* '$' */
1076 PARSESUB(); /* parse substitution */
1077 break;
1078 case CENDVAR: /* CLOSEBRACE */
1079 if (varnest > 0 && !ISDBLQUOTE()) {
1080 varnest--;
1081 USTPUTC(CTLENDVAR, out);
1082 } else {
1083 USTPUTC(c, out);
1084 }
1085 break;
1086 case CLP: /* '(' in arithmetic */
1087 parenlevel++;
1088 USTPUTC(c, out);
1089 break;
1090 case CRP: /* ')' in arithmetic */
1091 if (parenlevel > 0) {
1092 USTPUTC(c, out);
1093 --parenlevel;
1094 } else {
1095 if (pgetc() == ')') {
1096 if (--arinest == 0) {
1097 USTPUTC(CTLENDARI, out);
1098 syntax = prevsyntax;
1099 if (syntax == DQSYNTAX)
1100 SETDBLQUOTE();
1101 else
1102 CLRDBLQUOTE();
1103 } else
1104 USTPUTC(')', out);
1105 } else {
1106 /*
1107 * unbalanced parens
1108 * (don't 2nd guess - no error)
1109 */
1110 pungetc();
1111 USTPUTC(')', out);
1112 }
1113 }
1114 break;
1115 case CBQUOTE: /* '`' */
1116 PARSEBACKQOLD();
1117 break;
1118 case CEOF:
1119 goto endword; /* exit outer loop */
1120 default:
1121 if (varnest == 0)
1122 goto endword; /* exit outer loop */
1123 USTPUTC(c, out);
1124 }
1125 c = pgetc_macro();
1126 }
1127 }
1128endword:
1129 if (syntax == ARISYNTAX)
1130 synerror("Missing '))'");
1131 if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL)
1132 synerror("Unterminated quoted string");
1133 if (varnest != 0) {
1134 startlinno = plinno;
1135 /* { */
1136 synerror("Missing '}'");
1137 }
1138 USTPUTC('\0', out);
1139 len = out - stackblock();
1140 out = stackblock();
1141 if (eofmark == NULL) {
1142 if ((c == '>' || c == '<')
1143 && quotef == 0
1144 && len <= 2
1145 && (*out == '\0' || is_digit(*out))) {
1146 PARSEREDIR();
1147 return lasttoken = TREDIR;
1148 } else {
1149 pungetc();
1150 }
1151 }
1152 quoteflag = quotef;
1153 backquotelist = bqlist;
1154 grabstackblock(len);
1155 wordtext = out;
1156 if (dblquotep != NULL)
1157 ckfree(dblquotep);
1158 return lasttoken = TWORD;
1159/* end of readtoken routine */
1160
1161
1162
1163/*
1164 * Check to see whether we are at the end of the here document. When this
1165 * is called, c is set to the first character of the next input line. If
1166 * we are at the end of the here document, this routine sets the c to PEOF.
1167 */
1168
1169checkend: {
1170 if (eofmark) {
1171 if (striptabs) {
1172 while (c == '\t')
1173 c = pgetc();
1174 }
1175 if (c == *eofmark) {
1176 if (pfgets(line, sizeof line) != NULL) {
1177 char *p, *q;
1178
1179 p = line;
1180 for (q = eofmark + 1 ; *q && *p == *q ; p++, q++);
1181 if (*p == '\n' && *q == '\0') {
1182 c = PEOF;
1183 plinno++;
1184 needprompt = doprompt;
1185 } else {
1186 pushstring(line, strlen(line), NULL);
1187 }
1188 }
1189 }
1190 }
1191 goto checkend_return;
1192}
1193
1194
1195/*
1196 * Parse a redirection operator. The variable "out" points to a string
1197 * specifying the fd to be redirected. The variable "c" contains the
1198 * first character of the redirection operator.
1199 */
1200
1201parseredir: {
1202 char fd = *out;
1203 union node *np;
1204
1205 np = (union node *)stalloc(sizeof (struct nfile));
1206 if (c == '>') {
1207 np->nfile.fd = 1;
1208 c = pgetc();
1209 if (c == '>')
1210 np->type = NAPPEND;
1211 else if (c == '|')
1212 np->type = NCLOBBER;
1213 else if (c == '&')
1214 np->type = NTOFD;
1215 else {
1216 np->type = NTO;
1217 pungetc();
1218 }
1219 } else { /* c == '<' */
1220 np->nfile.fd = 0;
1221 switch (c = pgetc()) {
1222 case '<':
1223 if (sizeof (struct nfile) != sizeof (struct nhere)) {
1224 np = (union node *)stalloc(sizeof (struct nhere));
1225 np->nfile.fd = 0;
1226 }
1227 np->type = NHERE;
1228 heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
1229 heredoc->here = np;
1230 if ((c = pgetc()) == '-') {
1231 heredoc->striptabs = 1;
1232 } else {
1233 heredoc->striptabs = 0;
1234 pungetc();
1235 }
1236 break;
1237
1238 case '&':
1239 np->type = NFROMFD;
1240 break;
1241
1242 case '>':
1243 np->type = NFROMTO;
1244 break;
1245
1246 default:
1247 np->type = NFROM;
1248 pungetc();
1249 break;
1250 }
1251 }
1252 if (fd != '\0')
1253 np->nfile.fd = digit_val(fd);
1254 redirnode = np;
1255 goto parseredir_return;
1256}
1257
1258
1259/*
1260 * Parse a substitution. At this point, we have read the dollar sign
1261 * and nothing else.
1262 */
1263
1264parsesub: {
1265 int subtype;
1266 int typeloc;
1267 int flags;
1268 char *p;
1269 static const char types[] = "}-+?=";
1270
1271 c = pgetc();
1272 if (c != '(' && c != OPENBRACE && !is_name(c) && !is_special(c)) {
1273 USTPUTC('$', out);
1274 pungetc();
1275 } else if (c == '(') { /* $(command) or $((arith)) */
1276 if (pgetc() == '(') {
1277 PARSEARITH();
1278 } else {
1279 pungetc();
1280 PARSEBACKQNEW();
1281 }
1282 } else {
1283 USTPUTC(CTLVAR, out);
1284 typeloc = out - stackblock();
1285 USTPUTC(VSNORMAL, out);
1286 subtype = VSNORMAL;
1287 if (c == OPENBRACE) {
1288 c = pgetc();
1289 if (c == '#') {
1290 if ((c = pgetc()) == CLOSEBRACE)
1291 c = '#';
1292 else
1293 subtype = VSLENGTH;
1294 }
1295 else
1296 subtype = 0;
1297 }
1298 if (is_name(c)) {
1299 do {
1300 STPUTC(c, out);
1301 c = pgetc();
1302 } while (is_in_name(c));
1303 } else if (is_digit(c)) {
1304 do {
1305 USTPUTC(c, out);
1306 c = pgetc();
1307 } while (is_digit(c));
1308 }
1309 else if (is_special(c)) {
1310 USTPUTC(c, out);
1311 c = pgetc();
1312 }
1313 else
1314badsub: synerror("Bad substitution");
1315
1316 STPUTC('=', out);
1317 flags = 0;
1318 if (subtype == 0) {
1319 switch (c) {
1320 case ':':
1321 flags = VSNUL;
1322 c = pgetc();
1323 /*FALLTHROUGH*/
1324 default:
1325 p = strchr(types, c);
1326 if (p == NULL)
1327 goto badsub;
1328 subtype = p - types + VSNORMAL;
1329 break;
1330 case '%':
1331 case '#':
1332 {
1333 int cc = c;
1334 subtype = c == '#' ? VSTRIMLEFT :
1335 VSTRIMRIGHT;
1336 c = pgetc();
1337 if (c == cc)
1338 subtype++;
1339 else
1340 pungetc();
1341 break;
1342 }
1343 }
1344 } else {
1345 pungetc();
1346 }
1347 if (ISDBLQUOTE() || arinest)
1348 flags |= VSQUOTE;
1349 *(stackblock() + typeloc) = subtype | flags;
1350 if (subtype != VSNORMAL) {
1351 varnest++;
1352 if (varnest >= maxnest) {
1353 dblquotep = ckrealloc(dblquotep, maxnest / 8);
1354 dblquotep[(maxnest / 32) - 1] = 0;
1355 maxnest += 32;
1356 }
1357 }
1358 }
1359 goto parsesub_return;
1360}
1361
1362
1363/*
1364 * Called to parse command substitutions. Newstyle is set if the command
1365 * is enclosed inside $(...); nlpp is a pointer to the head of the linked
1366 * list of commands (passed by reference), and savelen is the number of
1367 * characters on the top of the stack which must be preserved.
1368 */
1369
1370parsebackq: {
1371 struct nodelist **nlpp;
1372 int savepbq;
1373 union node *n;
1374 char *volatile str;
1375 struct jmploc jmploc;
1376 struct jmploc *volatile savehandler;
1377 int savelen;
1378 int saveprompt;
1379#ifdef __GNUC__
1380 (void) &saveprompt;
1381#endif
1382
1383 savepbq = parsebackquote;
1384 if (setjmp(jmploc.loc)) {
1385 if (str)
1386 ckfree(str);
1387 parsebackquote = 0;
1388 handler = savehandler;
1389 longjmp(handler->loc, 1);
1390 }
1391 INTOFF;
1392 str = NULL;
1393 savelen = out - stackblock();
1394 if (savelen > 0) {
1395 str = ckmalloc(savelen);
1396 memcpy(str, stackblock(), savelen);
1397 }
1398 savehandler = handler;
1399 handler = &jmploc;
1400 INTON;
1401 if (oldstyle) {
1402 /* We must read until the closing backquote, giving special
1403 treatment to some slashes, and then push the string and
1404 reread it as input, interpreting it normally. */
1405 char *pout;
1406 int pc;
1407 int psavelen;
1408 char *pstr;
1409
1410
1411 STARTSTACKSTR(pout);
1412 for (;;) {
1413 if (needprompt) {
1414 setprompt(2);
1415 needprompt = 0;
1416 }
1417 switch (pc = pgetc()) {
1418 case '`':
1419 goto done;
1420
1421 case '\\':
1422 if ((pc = pgetc()) == '\n') {
1423 plinno++;
1424 if (doprompt)
1425 setprompt(2);
1426 else
1427 setprompt(0);
1428 /*
1429 * If eating a newline, avoid putting
1430 * the newline into the new character
1431 * stream (via the STPUTC after the
1432 * switch).
1433 */
1434 continue;
1435 }
1436 if (pc != '\\' && pc != '`' && pc != '$'
1437 && (!ISDBLQUOTE() || pc != '"'))
1438 STPUTC('\\', pout);
1439 break;
1440
1441 case '\n':
1442 plinno++;
1443 needprompt = doprompt;
1444 break;
1445
1446 case PEOF:
1447 startlinno = plinno;
1448 synerror("EOF in backquote substitution");
1449 break;
1450
1451 default:
1452 break;
1453 }
1454 STPUTC(pc, pout);
1455 }
1456done:
1457 STPUTC('\0', pout);
1458 psavelen = pout - stackblock();
1459 if (psavelen > 0) {
1460 pstr = grabstackstr(pout);
1461 setinputstring(pstr, 1);
1462 }
1463 }
1464 nlpp = &bqlist;
1465 while (*nlpp)
1466 nlpp = &(*nlpp)->next;
1467 *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
1468 (*nlpp)->next = NULL;
1469 parsebackquote = oldstyle;
1470
1471 if (oldstyle) {
1472 saveprompt = doprompt;
1473 doprompt = 0;
1474 }
1475
1476 n = list(0);
1477
1478 if (oldstyle)
1479 doprompt = saveprompt;
1480 else {
1481 if (readtoken() != TRP)
1482 synexpect(TRP);
1483 }
1484
1485 (*nlpp)->n = n;
1486 if (oldstyle) {
1487 /*
1488 * Start reading from old file again, ignoring any pushed back
1489 * tokens left from the backquote parsing
1490 */
1491 popfile();
1492 tokpushback = 0;
1493 }
1494 while (stackblocksize() <= savelen)
1495 growstackblock();
1496 STARTSTACKSTR(out);
1497 if (str) {
1498 memcpy(out, str, savelen);
1499 STADJUST(savelen, out);
1500 INTOFF;
1501 ckfree(str);
1502 str = NULL;
1503 INTON;
1504 }
1505 parsebackquote = savepbq;
1506 handler = savehandler;
1507 if (arinest || ISDBLQUOTE())
1508 USTPUTC(CTLBACKQ | CTLQUOTE, out);
1509 else
1510 USTPUTC(CTLBACKQ, out);
1511 if (oldstyle)
1512 goto parsebackq_oldreturn;
1513 else
1514 goto parsebackq_newreturn;
1515}
1516
1517/*
1518 * Parse an arithmetic expansion (indicate start of one and set state)
1519 */
1520parsearith: {
1521
1522 if (++arinest == 1) {
1523 prevsyntax = syntax;
1524 syntax = ARISYNTAX;
1525 USTPUTC(CTLARI, out);
1526 if (ISDBLQUOTE())
1527 USTPUTC('"',out);
1528 else
1529 USTPUTC(' ',out);
1530 } else {
1531 /*
1532 * we collapse embedded arithmetic expansion to
1533 * parenthesis, which should be equivalent
1534 */
1535 USTPUTC('(', out);
1536 }
1537 goto parsearith_return;
1538}
1539
1540} /* end of readtoken */
1541
1542
1543
1544#ifdef mkinit
1545RESET {
1546 tokpushback = 0;
1547 checkkwd = 0;
1548}
1549#endif
1550
1551/*
1552 * Returns true if the text contains nothing to expand (no dollar signs
1553 * or backquotes).
1554 */
1555
1556STATIC int
1557noexpand(char *text)
1558{
1559 char *p;
1560 char c;
1561
1562 p = text;
1563 while ((c = *p++) != '\0') {
1564 if (c == CTLQUOTEMARK)
1565 continue;
1566 if (c == CTLESC)
1567 p++;
1568 else if (BASESYNTAX[(int)c] == CCTL)
1569 return 0;
1570 }
1571 return 1;
1572}
1573
1574
1575/*
1576 * Return true if the argument is a legal variable name (a letter or
1577 * underscore followed by zero or more letters, underscores, and digits).
1578 */
1579
1580int
1581goodname(char *name)
1582 {
1583 char *p;
1584
1585 p = name;
1586 if (! is_name(*p))
1587 return 0;
1588 while (*++p) {
1589 if (! is_in_name(*p))
1590 return 0;
1591 }
1592 return 1;
1593}
1594
1595
1596/*
1597 * Called when an unexpected token is read during the parse. The argument
1598 * is the token that is expected, or -1 if more than one type of token can
1599 * occur at this point.
1600 */
1601
1602STATIC void
1603synexpect(int token)
1604{
1605 char msg[64];
1606
1607 if (token >= 0) {
1608 fmtstr(msg, 64, "%s unexpected (expecting %s)",
1609 tokname[lasttoken], tokname[token]);
1610 } else {
1611 fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
1612 }
1613 synerror(msg);
1614 /* NOTREACHED */
1615}
1616
1617
1618STATIC void
1619synerror(const char *msg)
1620{
1621 if (commandname)
1622 outfmt(&errout, "%s: %d: ", commandname, startlinno);
1623 outfmt(&errout, "Syntax error: %s\n", msg);
1624 error((char *)NULL);
1625 /* NOTREACHED */
1626}
1627
1628STATIC void
1629setprompt(int which)
1630{
1631 whichprompt = which;
1632
1633#ifndef SMALL
1634 if (!el)
1635#endif
1636 out2str(getprompt(NULL));
1637}
1638
1639/*
1640 * called by editline -- any expansions to the prompt
1641 * should be added here.
1642 */
1643const char *
1644getprompt(void *unused)
1645 {
1646 switch (whichprompt) {
1647 case 0:
1648 return "";
1649 case 1:
1650 return ps1val();
1651 case 2:
1652 return ps2val();
1653 default:
1654 return "<internal prompt error>";
1655 }
1656}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette