xref: /trunk/main/soltools/cpp/_macro.c (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 #ifdef _MSC_VER
23 #   define _POSIX_
24 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef __hpux
29 #   define _HPUX_SOURCE
30 #endif
31 #if defined(__IBMC__) || defined(__EMX__)
32 #   define PATH_MAX _MAX_PATH
33 #endif
34 #include <limits.h>
35 
36 /*  The Microsoft C runtime used to define PATH_MAX in <limits.h> when _POSIX_
37     was defined, which is what the block at the top of this file is for.  The
38     UCRT -- VS2015 and every toolset since -- no longer does, and _POSIX_ no
39     longer selects anything, so PATH_MAX simply is not declared and the arrays
40     below become zero-sized.
41 
42     512 is the value the old runtime supplied, and it is kept rather than
43     substituting _MAX_PATH (260, as the __IBMC__ branch above does): the
44     buffers here hold an include directory concatenated with a file name, and
45     260 is not enough for that inside this tree's own output directories.  The
46     uses are all bounds-checked against sizeof, so the size is a limit rather
47     than a hazard -- but narrowing it would silently start rejecting includes
48     that resolve today.
49 
50     Inert wherever <limits.h> supplies a PATH_MAX of its own, VC9 included.  */
51 #ifndef PATH_MAX
52 #   define PATH_MAX 512
53 #endif
54 
55 #include "cpp.h"
56 
57 #define NCONCAT 16384
58 
59 /*
60  * do a macro definition.  tp points to the name being defined in the line
61  */
62 void
dodefine(Tokenrow * trp)63     dodefine(Tokenrow * trp)
64 {
65     Token *tp;
66     Nlist *np;
67     Source *s;
68     Tokenrow *def, *args;
69     static uchar location[(PATH_MAX + 8) * NINC], *cp;
70 
71     tp = trp->tp + 1;
72     if (tp >= trp->lp || tp->type != NAME)
73     {
74         error(ERROR, "#defined token is not a name");
75         return;
76     }
77     np = lookup(tp, 1);
78     if (np->flag & ISUNCHANGE)
79     {
80         error(ERROR, "#defined token %t can't be redefined", tp);
81         return;
82     }
83     /* collect arguments */
84     tp += 1;
85     args = NULL;
86     if (tp < trp->lp && tp->type == LP && tp->wslen == 0)
87     {
88         /* macro with args */
89         int narg = 0;
90 
91         tp += 1;
92         args = new(Tokenrow);
93         maketokenrow(2, args);
94         if (tp->type != RP)
95         {
96             int err = 0;
97 
98             for (;;)
99             {
100                 Token *atp;
101 
102                 if (tp->type != NAME)
103                 {
104                     err++;
105                     break;
106                 }
107                 if (narg >= args->max)
108                     growtokenrow(args);
109                 for (atp = args->bp; atp < args->lp; atp++)
110                     if (atp->len == tp->len
111                         && strncmp((char *) atp->t, (char *) tp->t, tp->len) == 0)
112                         error(ERROR, "Duplicate macro argument");
113                 *args->lp++ = *tp;
114                 narg++;
115                 tp += 1;
116                 if (tp->type == RP)
117                     break;
118                 if (tp->type != COMMA)
119                 {
120                     err++;
121                     break;
122                 }
123                 tp += 1;
124             }
125             if (err)
126             {
127                 error(ERROR, "Syntax error in macro parameters");
128                 return;
129             }
130         }
131         tp += 1;
132     }
133     trp->tp = tp;
134     if (((trp->lp) - 1)->type == NL)
135         trp->lp -= 1;
136     def = normtokenrow(trp);
137     if (np->flag & ISDEFINED)
138     {
139         if (comparetokens(def, np->vp)
140             || (np->ap == NULL) != (args == NULL)
141             || (np->ap && comparetokens(args, np->ap)))
142         {
143             if ( np->loc )
144                 error(ERROR,
145                     "Macro redefinition of %t (already defined at %s)",
146                     trp->bp + 2, np->loc);
147             else
148                 error(ERROR,
149                     "Macro redefinition of %t (already defined at %s)",
150                     trp->bp + 2, "commandline" );
151         }
152     }
153     if (args)
154     {
155         Tokenrow *tap;
156 
157         tap = normtokenrow(args);
158         dofree(args->bp);
159         dofree(args);
160         args = tap;
161     }
162     np->ap = args;
163     np->vp = def;
164     np->flag |= ISDEFINED;
165 
166     /* build location string of macro definition */
167     for (cp = location, s = cursource; s; s = s->next)
168         if (*s->filename)
169         {
170             if (cp != location)
171                 *cp++ = ' ';
172             sprintf((char *)cp, "%s:%d", s->filename, s->line);
173             cp += strlen((char *)cp);
174         }
175 
176     np->loc = newstring(location, strlen((char *)location), 0);
177 
178     if (Mflag)
179     {
180         if (np->ap)
181             error(INFO, "Macro definition of %s(%r) [%r]", np->name, np->ap, np->vp);
182         else
183             error(INFO, "Macro definition of %s [%r]", np->name, np->vp);
184     }
185 }
186 
187 /*
188  * Definition received via -D or -U
189  */
190 void
doadefine(Tokenrow * trp,int type)191     doadefine(Tokenrow * trp, int type)
192 {
193     Nlist *np;
194     static uchar onestr[2] = "1";
195     static Token onetoken[1] = {{NUMBER, 0, 0, 1, onestr, 0}};
196     static Tokenrow onetr = {onetoken, onetoken, onetoken + 1, 1};
197 
198     trp->tp = trp->bp;
199     if (type == 'U')
200     {
201         if (trp->lp - trp->tp != 2 || trp->tp->type != NAME)
202             goto syntax;
203         if ((np = lookup(trp->tp, 0)) == NULL)
204             return;
205         np->flag &= ~ISDEFINED;
206         return;
207     }
208 
209     if (type == 'A')
210     {
211         if (trp->tp >= trp->lp || trp->tp->type != NAME)
212             goto syntax;
213         trp->tp->type = ARCHITECTURE;
214         np = lookup(trp->tp, 1);
215         np->flag |= ISARCHITECTURE;
216         trp->tp += 1;
217         if (trp->tp >= trp->lp || trp->tp->type == END)
218         {
219             np->vp = &onetr;
220             return;
221         }
222         else
223             error(FATAL, "Illegal -A argument %r", trp);
224     }
225 
226     if (trp->tp >= trp->lp || trp->tp->type != NAME)
227         goto syntax;
228     np = lookup(trp->tp, 1);
229     np->flag |= ISDEFINED;
230     trp->tp += 1;
231     if (trp->tp >= trp->lp || trp->tp->type == END)
232     {
233         np->vp = &onetr;
234         return;
235     }
236     if (trp->tp->type != ASGN)
237         goto syntax;
238     trp->tp += 1;
239     if ((trp->lp - 1)->type == END)
240         trp->lp -= 1;
241     np->vp = normtokenrow(trp);
242     return;
243 syntax:
244     error(FATAL, "Illegal -D or -U argument %r", trp);
245 }
246 
247 
248 
249 /*
250  * Do macro expansion in a row of tokens.
251  * Flag is NULL if more input can be gathered.
252  */
253 void
expandrow(Tokenrow * trp,char * flag)254     expandrow(Tokenrow * trp, char *flag)
255 {
256     Token * tp;
257     Nlist * np;
258 
259     MacroValidatorList  validators;
260     mvl_init(&validators);
261     /* Sets all token-identifiers to 0 because tokens may not be initialised (never use C!) */
262     tokenrow_zeroTokenIdentifiers(trp);
263 
264     if (flag)
265         setsource(flag, -1, -1, "", 0);
266     for (tp = trp->tp; tp < trp->lp;)
267     {
268         mvl_check(&validators, tp);
269 
270         if (tp->type != NAME
271             || quicklook(tp->t[0], tp->len > 1 ? tp->t[1] : 0) == 0
272             || (np = lookup(tp, 0)) == NULL
273             || (np->flag & (ISDEFINED | ISMAC)) == 0
274             || (np->flag & ISACTIVE) != 0)
275         {
276             tp++;
277             continue;
278         }
279         trp->tp = tp;
280         if (np->val == KDEFINED)
281         {
282             tp->type = DEFINED;
283             if ((tp + 1) < trp->lp && (tp + 1)->type == NAME)
284                 (tp + 1)->type = NAME1;
285             else
286                 if ((tp + 3) < trp->lp && (tp + 1)->type == LP
287                     && (tp + 2)->type == NAME && (tp + 3)->type == RP)
288                     (tp + 2)->type = NAME1;
289                 else
290                     error(ERROR, "Incorrect syntax for `defined'");
291             tp++;
292             continue;
293         }
294         else
295             if (np->val == KMACHINE)
296             {
297                 if (((tp - 1) >= trp->bp) && ((tp - 1)->type == SHARP))
298                 {
299                     tp->type = ARCHITECTURE;
300                     if ((tp + 1) < trp->lp && (tp + 1)->type == NAME)
301                         (tp + 1)->type = NAME2;
302                     else
303                         if ((tp + 3) < trp->lp && (tp + 1)->type == LP
304                             && (tp + 2)->type == NAME && (tp + 3)->type == RP)
305                             (tp + 2)->type = NAME2;
306                         else
307                             error(ERROR, "Incorrect syntax for `#machine'");
308                 }
309                 tp++;
310                 continue;
311             }
312 
313         if (np->flag & ISMAC)
314             builtin(trp, np->val);
315         else
316             expand(trp, np, &validators);
317         tp = trp->tp;
318     }   // end for
319     if (flag)
320         unsetsource();
321 
322     mvl_destruct(&validators);
323 }
324 
325 /*
326  * Expand the macro whose name is np, at token trp->tp, in the tokenrow.
327  * Return trp->tp at the first token next to be expanded
328  * (ordinarily the beginning of the expansion)
329  * I.e.: the same position as before!
330  * Only one expansion is performed, then we return to the expandrow()
331  * loop and start at same position.
332  */
333 void
expand(Tokenrow * trp,Nlist * np,MacroValidatorList * pValidators)334     expand(Tokenrow * trp, Nlist * np, MacroValidatorList * pValidators)
335 {
336 //  Token * pOldNextTp;
337     Tokenrow ntr;
338     int ntokc, narg, i;
339     Tokenrow *atr[NARG + 1];
340 
341     if (Mflag == 2)
342     {
343         if (np->ap)
344             error(INFO, "Macro expansion of %t with %s(%r)", trp->tp, np->name, np->ap);
345         else
346             error(INFO, "Macro expansion of %t with %s", trp->tp, np->name);
347     }
348 
349     copytokenrow(&ntr, np->vp);         /* copy macro value */
350     if (np->ap == NULL)                 /* parameterless */
351         ntokc = 1;
352     else
353     {
354         ntokc = gatherargs(trp, atr, &narg);
355         if (narg < 0)
356         {                               /* not actually a call (no '(') */
357             trp->tp++;
358             return;
359         }
360         if (narg != rowlen(np->ap))
361         {
362             error(ERROR, "Disagreement in number of macro arguments");
363             trp->tp += ntokc;
364             return;
365         }
366 
367         /** If gatherargs passed a macro validating token, this token
368             must become valid here.
369             trp->tp+0 was checked in expandrow(), so we dont need to do it
370             again here:
371         */
372         for (i = 1; i < ntokc; i++)
373         {
374             mvl_check(pValidators,trp->tp+i);
375         }
376 
377         substargs(np, &ntr, atr);       /* put args into replacement */
378         for (i = 0; i < narg; i++)
379         {
380             dofree(atr[i]->bp);
381             dofree(atr[i]);
382         }
383     }
384 
385 /* old
386     np->flag |= ISACTIVE;
387 */
388 
389 /* rh
390 */
391     doconcat(&ntr);                     /* execute ## operators */
392     ntr.tp = ntr.bp;
393     makespace(&ntr, trp->tp);
394 
395 /* old
396 //  expandrow(&ntr, "<expand>");
397 //  insertrow(trp, ntokc, &ntr);
398 //  dofree(ntr.bp);
399 //  np->flag &= ~ISACTIVE;
400 */
401 
402 /* NP
403         // Replace macro by its value:
404 */
405 //  pOldNextTp = trp->tp+ntokc;
406     tokenrow_zeroTokenIdentifiers(&ntr);
407     insertrow(trp, ntokc, &ntr);
408         /* Reassign old macro validators:
409         */
410 //  mvl_move(pValidators, trp->tp - pOldNextTp);
411 
412         /* add validator for just invalidated macro:
413         */
414     np->flag |= ISACTIVE;
415     if (trp->tp != trp->lp)
416     {   /* tp is a valid pointer: */
417         mvl_add(pValidators,np,trp->tp);
418     }
419     else
420     {   /* tp is == lp, therefore does not point to valid memory: */
421         mvl_add(pValidators,np,0);
422     }
423         /* reset trp->tp to original position:
424         */
425     trp->tp -= ntr.lp - ntr.bp;         /* so the result will be tested for macros from the same position again */
426 
427     dofree(ntr.bp);
428 
429     return;
430 }
431 
432 /*
433  * Gather an arglist, starting in trp with tp pointing at the macro name.
434  * Return total number of tokens passed, stash number of args found.
435  * trp->tp is not changed relative to the tokenrow.
436  */
437 int
gatherargs(Tokenrow * trp,Tokenrow ** atr,int * narg)438     gatherargs(Tokenrow * trp, Tokenrow ** atr, int *narg)
439 {
440     int parens = 1;
441     int ntok = 0;
442     Token *bp, *lp;
443     Tokenrow ttr;
444     int ntokp;
445     int needspace;
446 
447     *narg = -1;                         /* means that there is no macro
448                                          * call */
449     /* look for the ( */
450     for (;;)
451     {
452         trp->tp++;
453         ntok++;
454         if (trp->tp >= trp->lp)
455         {
456             gettokens(trp, 0);
457             if ((trp->lp - 1)->type == END)
458             {
459                 trp->lp -= 1;
460                 trp->tp -= ntok;
461                 return ntok;
462             }
463         }
464         if (trp->tp->type == LP)
465             break;
466         if (trp->tp->type != NL)
467             return ntok;
468     }
469     *narg = 0;
470     ntok++;
471     ntokp = ntok;
472     trp->tp++;
473     /* search for the terminating ), possibly extending the row */
474     needspace = 0;
475     while (parens > 0)
476     {
477         if (trp->tp >= trp->lp)
478             gettokens(trp, 0);
479         if (needspace)
480         {
481             needspace = 0;
482             /* makespace(trp); [rh] */
483         }
484         if (trp->tp->type == END)
485         {
486             trp->lp -= 1;
487             trp->tp -= ntok;
488             error(ERROR, "EOF in macro arglist");
489             return ntok;
490         }
491         if (trp->tp->type == NL)
492         {
493             trp->tp += 1;
494             adjustrow(trp, -1);
495             trp->tp -= 1;
496             /* makespace(trp); [rh] */
497             needspace = 1;
498             continue;
499         }
500         if (trp->tp->type == LP)
501             parens++;
502         else
503             if (trp->tp->type == RP)
504                 parens--;
505         trp->tp++;
506         ntok++;
507     }
508     trp->tp -= ntok;
509     /* Now trp->tp won't move underneath us */
510     lp = bp = trp->tp + ntokp;
511     for (; parens >= 0; lp++)
512     {
513         if (lp->type == LP)
514         {
515             parens++;
516             continue;
517         }
518         if (lp->type == RP)
519             parens--;
520         if (lp->type == DSHARP)
521             lp->type = DSHARP1;         /* ## not special in arg */
522         if ((lp->type == COMMA && parens == 0) ||
523                 ( parens < 0 && ((lp - 1)->type != LP)))
524         {
525             if (*narg >= NARG - 1)
526                 error(FATAL, "Sorry, too many macro arguments");
527             ttr.bp = ttr.tp = bp;
528             ttr.lp = lp;
529             atr[(*narg)++] = normtokenrow(&ttr);
530             bp = lp + 1;
531         }
532     }
533     return ntok;
534 }
535 
536 /*
537  * substitute the argument list into the replacement string
538  *  This would be simple except for ## and #
539  */
540 void
substargs(Nlist * np,Tokenrow * rtr,Tokenrow ** atr)541     substargs(Nlist * np, Tokenrow * rtr, Tokenrow ** atr)
542 {
543     Tokenrow tatr;
544     Token *tp;
545     int ntok, argno;
546 
547     for (rtr->tp = rtr->bp; rtr->tp < rtr->lp;)
548     {
549         if (rtr->tp->type == SHARP)
550         {                               /* string operator */
551             tp = rtr->tp;
552             rtr->tp += 1;
553             if ((argno = lookuparg(np, rtr->tp)) < 0)
554             {
555                 error(ERROR, "# not followed by macro parameter");
556                 continue;
557             }
558             ntok = 1 + (rtr->tp - tp);
559             rtr->tp = tp;
560             insertrow(rtr, ntok, stringify(atr[argno]));
561             continue;
562         }
563         if (rtr->tp->type == NAME
564             && (argno = lookuparg(np, rtr->tp)) >= 0)
565         {
566             if (((rtr->tp + 1) < rtr->lp && (rtr->tp + 1)->type == DSHARP)
567                 || (rtr->tp != rtr->bp  && (rtr->tp - 1)->type == DSHARP))
568             {
569                 copytokenrow(&tatr, atr[argno]);
570                 makespace(&tatr, rtr->tp);
571                 insertrow(rtr, 1, &tatr);
572                 dofree(tatr.bp);
573             }
574             else
575             {
576                 copytokenrow(&tatr, atr[argno]);
577                 makespace(&tatr, rtr->tp);
578                 expandrow(&tatr, "<macro>");
579                 insertrow(rtr, 1, &tatr);
580                 dofree(tatr.bp);
581             }
582             continue;
583         }
584         rtr->tp++;
585     }
586 }
587 
588 /*
589  * Evaluate the ## operators in a tokenrow
590  */
591 void
doconcat(Tokenrow * trp)592     doconcat(Tokenrow * trp)
593 {
594     Token *ltp, *ntp;
595     Tokenrow ntr;
596     int len;
597 
598     for (trp->tp = trp->bp; trp->tp < trp->lp; trp->tp++)
599     {
600         if (trp->tp->type == DSHARP1)
601             trp->tp->type = DSHARP;
602         else
603             if (trp->tp->type == DSHARP)
604             {
605                 int  i;
606                 char tt[NCONCAT];
607 
608                 ltp = trp->tp - 1;
609                 ntp = trp->tp + 1;
610 
611                 if (ltp < trp->bp || ntp >= trp->lp)
612                 {
613                     error(ERROR, "## occurs at border of replacement");
614                     continue;
615                 }
616 
617                 ntp = ltp;
618                 i   = 1;
619                 len = 0;
620 
621                 do
622                 {
623                     if (len + ntp->len + ntp->wslen > sizeof(tt))
624                     {
625                         error(ERROR, "## string concatination buffer overrun");
626                         break;
627                     }
628 
629                     if (ntp != trp->tp + 1)
630                     {
631                         strncpy((char *) tt + len, (char *) ntp->t - ntp->wslen,
632                                 ntp->len + ntp->wslen);
633                         len += ntp->len + ntp->wslen;
634                     }
635                     else    // Leerzeichen um ## herum entfernen:
636                     {
637                         strncpy((char *) tt + len, (char *) ntp->t, ntp->len);
638                         len += ntp->len;
639                     }
640 
641                     ntp = trp->tp + i;
642                     i++;
643                 }
644                 while (ntp < trp->lp);
645 
646                 tt[len] = '\0';
647                 setsource("<##>", -1, -1, tt, 0);
648                 maketokenrow(3, &ntr);
649                 gettokens(&ntr, 1);
650                 unsetsource();
651                 if (ntr.bp->type == UNCLASS)
652                     error(WARNING, "Bad token %r produced by ##", &ntr);
653                 while ((ntr.lp-1)->len == 0 && ntr.lp != ntr.bp)
654                     ntr.lp--;
655 
656                 doconcat(&ntr);
657                 trp->tp = ltp;
658                 makespace(&ntr, ltp);
659                 insertrow(trp, ntp - ltp, &ntr);
660                 dofree(ntr.bp);
661                 trp->tp--;
662             }
663     }
664 }
665 
666 /*
667  * tp is a potential parameter name of macro mac;
668  * look it up in mac's arglist, and if found, return the
669  * corresponding index in the argname array.  Return -1 if not found.
670  */
671 int
lookuparg(Nlist * mac,Token * tp)672     lookuparg(Nlist * mac, Token * tp)
673 {
674     Token *ap;
675 
676     if (tp->type != NAME || mac->ap == NULL)
677         return -1;
678     for (ap = mac->ap->bp; ap < mac->ap->lp; ap++)
679     {
680         if (ap->len == tp->len && strncmp((char *) ap->t, (char *) tp->t, ap->len) == 0)
681             return ap - mac->ap->bp;
682     }
683     return -1;
684 }
685 
686 /*
687  * Return a quoted version of the tokenrow (from # arg)
688  */
689 #define STRLEN  512
690 Tokenrow *
stringify(Tokenrow * vp)691     stringify(Tokenrow * vp)
692 {
693     static Token t = {STRING, 0, 0, 0, NULL, 0};
694     static Tokenrow tr = {&t, &t, &t + 1, 1};
695     Token *tp;
696     uchar s[STRLEN];
697     uchar *sp = s, *cp;
698     int i, instring;
699 
700     *sp++ = '"';
701     for (tp = vp->bp; tp < vp->lp; tp++)
702     {
703         instring = tp->type == STRING || tp->type == CCON;
704         if (sp + 2 * tp->len + tp->wslen  >= &s[STRLEN - 10])
705         {
706             error(ERROR, "Stringified macro arg is too long");
707             break;
708         }
709 
710         // Change by np 31.10.2001, #93725 - begin
711         if ( tp->wslen > 0 )
712         *sp++ = ' ';
713         // change end.
714 
715         for (i = 0, cp = tp->t; (unsigned int)i < tp->len; i++)
716         {
717             if (instring && (*cp == '"' || *cp == '\\'))
718                 *sp++ = '\\';
719             *sp++ = *cp++;
720         }
721     }
722     *sp++ = '"';
723     *sp = '\0';
724     sp = s;
725     t.len = strlen((char *) sp);
726     t.t = newstring(sp, t.len, 0);
727     return &tr;
728 }
729 
730 /*
731  * expand a builtin name
732  */
733 void
builtin(Tokenrow * trp,int biname)734     builtin(Tokenrow * trp, int biname)
735 {
736     char *op;
737     Token *tp;
738     Source *s;
739 
740     tp = trp->tp;
741     trp->tp++;
742     /* need to find the real source */
743     s = cursource;
744     while (s && s->fd == -1)
745         s = s->next;
746     if (s == NULL)
747         s = cursource;
748     /* most are strings */
749     tp->type = STRING;
750     if (tp->wslen)
751     {
752         *outptr++ = ' ';
753         tp->wslen = 1;
754     }
755     op = outptr;
756     *op++ = '"';
757     switch (biname)
758     {
759 
760         case KLINENO:
761             tp->type = NUMBER;
762             op = outnum(op - 1, s->line);
763             break;
764 
765         case KFILE:
766             {
767                 char *src = s->filename;
768 
769                 while ((*op++ = *src++) != 0)
770                     if (src[-1] == '\\')
771                         *op++ = '\\';
772                 op--;
773                 break;
774             }
775 
776         case KDATE:
777             strncpy(op, curtime + 4, 7);
778             strncpy(op + 7, curtime + 20, 4);
779             op += 11;
780             break;
781 
782         case KTIME:
783             strncpy(op, curtime + 11, 8);
784             op += 8;
785             break;
786 
787         default:
788             error(ERROR, "cpp botch: unknown internal macro");
789             return;
790     }
791     if (tp->type == STRING)
792         *op++ = '"';
793     tp->t = (uchar *) outptr;
794     tp->len = op - outptr;
795     outptr = op;
796 }
797