1/* Muscle table manager for Bison.
2
3 Copyright (C) 2001-2015, 2018-2019 Free Software Foundation, Inc.
4
5 This file is part of Bison, the GNU Compiler Compiler.
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20#include <config.h>
21#include "system.h"
22
23#include <hash.h>
24
25#include "complain.h"
26#include "files.h"
27#include "fixits.h"
28#include "getargs.h"
29#include "muscle-tab.h"
30#include "quote.h"
31
32muscle_kind
33muscle_kind_new (char const *k)
34{
35 if (STREQ (k, "code"))
36 return muscle_code;
37 else if (STREQ (k, "keyword"))
38 return muscle_keyword;
39 else if (STREQ (k, "string"))
40 return muscle_string;
41 abort ();
42}
43
44char const *
45muscle_kind_string (muscle_kind k)
46{
47 switch (k)
48 {
49 case muscle_code: return "code";
50 case muscle_keyword: return "keyword";
51 case muscle_string: return "string";
52 }
53 abort ();
54}
55
56
57/* A key-value pair, along with storage that can be reclaimed when
58 this pair is no longer needed. */
59typedef struct
60{
61 char const *key;
62 char const *value;
63 char *storage;
64 muscle_kind kind;
65} muscle_entry;
66
67
68/* The name of muscle for the %define variable VAR (corresponding to
69 FIELD, if defined). */
70static uniqstr
71muscle_name (char const *var, char const *field)
72{
73 if (field)
74 return UNIQSTR_CONCAT ("percent_define_", field, "(", var, ")");
75 else
76 return UNIQSTR_CONCAT ("percent_define(", var, ")");
77}
78
79/* An obstack used to create some entries. */
80struct obstack muscle_obstack;
81
82/* Initial capacity of muscles hash table. */
83#define HT_INITIAL_CAPACITY 257
84
85static struct hash_table *muscle_table = NULL;
86
87static bool
88hash_compare_muscles (void const *x, void const *y)
89{
90 muscle_entry const *m1 = x;
91 muscle_entry const *m2 = y;
92 return STREQ (m1->key, m2->key);
93}
94
95static size_t
96hash_muscle (const void *x, size_t tablesize)
97{
98 muscle_entry const *m = x;
99 return hash_string (m->key, tablesize);
100}
101
102/* Create a fresh muscle name KEY, and insert in the hash table. */
103static void *
104muscle_entry_new (char const *key)
105{
106 muscle_entry *res = xmalloc (sizeof *res);
107 res->key = key;
108 res->value = NULL;
109 res->storage = NULL;
110 if (!hash_insert (muscle_table, res))
111 xalloc_die ();
112 return res;
113}
114
115static void
116muscle_entry_free (void *entry)
117{
118 muscle_entry *mentry = entry;
119 free (mentry->storage);
120 free (mentry);
121}
122
123void
124muscle_init (void)
125{
126 /* Initialize the muscle obstack. */
127 obstack_init (&muscle_obstack);
128
129 muscle_table = hash_xinitialize (HT_INITIAL_CAPACITY, NULL, hash_muscle,
130 hash_compare_muscles, muscle_entry_free);
131
132 /* Version and input file. */
133 MUSCLE_INSERT_STRING ("version", VERSION);
134}
135
136
137void
138muscle_free (void)
139{
140 hash_free (muscle_table);
141 obstack_free (&muscle_obstack, NULL);
142}
143
144/* Look for the muscle named KEY. Return NULL if does not exist. */
145static
146muscle_entry *
147muscle_lookup (char const *key)
148{
149 muscle_entry probe;
150 probe.key = key;
151 return hash_lookup (muscle_table, &probe);
152}
153
154
155void
156muscle_insert (char const *key, char const *value)
157{
158 muscle_entry *entry = muscle_lookup (key);
159 if (entry)
160 free (entry->storage);
161 else
162 /* First insertion in the hash. */
163 entry = muscle_entry_new (key);
164 entry->value = value;
165 entry->storage = NULL;
166}
167
168
169/* Append VALUE to the current value of KEY. If KEY did not already
170 exist, create it. Use MUSCLE_OBSTACK. De-allocate the previously
171 associated value. Copy VALUE and SEPARATOR. If VALUE does not end
172 with TERMINATOR, append one. */
173
174static void
175muscle_grow (const char *key, const char *val,
176 const char *separator, const char *terminator)
177{
178 muscle_entry *entry = muscle_lookup (key);
179 if (entry)
180 {
181 obstack_sgrow (&muscle_obstack, entry->value);
182 obstack_sgrow (&muscle_obstack, separator);
183 free (entry->storage);
184 }
185 else
186 entry = muscle_entry_new (key);
187
188 obstack_sgrow (&muscle_obstack, val);
189
190 size_t vals = strlen (val);
191 size_t terms = strlen (terminator);
192 if (terms <= vals
193 && STRNEQ (val + vals - terms, terminator))
194 obstack_sgrow (&muscle_obstack, terminator);
195
196 {
197 char const *new_val = obstack_finish0 (&muscle_obstack);
198 entry->value = entry->storage = xstrdup (new_val);
199 obstack_free (&muscle_obstack, new_val);
200 }
201}
202
203/*------------------------------------------------------------------.
204| Using muscle_grow, append a synchronization line for the location |
205| LOC to the current value of KEY. |
206`------------------------------------------------------------------*/
207
208static void
209muscle_syncline_grow (char const *key, location loc)
210{
211 obstack_printf (&muscle_obstack, "]b4_syncline(%d, ", loc.start.line);
212 obstack_quote (&muscle_obstack,
213 quotearg_style (c_quoting_style, loc.start.file));
214 obstack_sgrow (&muscle_obstack, ")dnl\n[");
215 char const *extension = obstack_finish0 (&muscle_obstack);
216 muscle_grow (key, extension, "", "");
217 obstack_free (&muscle_obstack, extension);
218}
219
220/*------------------------------------------------------------------.
221| Append VALUE to the current value of KEY, using muscle_grow. But |
222| in addition, issue a synchronization line for the location LOC |
223| using muscle_syncline_grow. |
224`------------------------------------------------------------------*/
225
226void
227muscle_code_grow (const char *key, const char *val, location loc)
228{
229 muscle_syncline_grow (key, loc);
230 muscle_grow (key, val, "", "\n");
231}
232
233
234void
235muscle_pair_list_grow (const char *muscle,
236 const char *a1, const char *a2)
237{
238 obstack_sgrow (&muscle_obstack, "[");
239 obstack_quote (&muscle_obstack, a1);
240 obstack_sgrow (&muscle_obstack, ", ");
241 obstack_quote (&muscle_obstack, a2);
242 obstack_sgrow (&muscle_obstack, "]");
243 char const *pair = obstack_finish0 (&muscle_obstack);
244 muscle_grow (muscle, pair, ",\n", "");
245 obstack_free (&muscle_obstack, pair);
246}
247
248
249char const *
250muscle_find_const (char const *key)
251{
252 muscle_entry *entry = muscle_lookup (key);
253 return entry ? entry->value : NULL;
254}
255
256
257char *
258muscle_find (char const *key)
259{
260 muscle_entry *entry = muscle_lookup (key);
261 if (entry)
262 {
263 aver (entry->value == entry->storage);
264 return entry->storage;
265 }
266 return NULL;
267}
268
269
270/* In the format 'file_name:line.column', append BOUND to MUSCLE. Use
271 digraphs for special characters in the file name. */
272
273static void
274muscle_boundary_grow (char const *key, boundary bound)
275{
276 obstack_sgrow (&muscle_obstack, "[[");
277 obstack_escape (&muscle_obstack, bound.file);
278 obstack_printf (&muscle_obstack, ":%d.%d@@%d]]", bound.line, bound.column, bound.byte);
279 char const *extension = obstack_finish0 (&muscle_obstack);
280 muscle_grow (key, extension, "", "");
281 obstack_free (&muscle_obstack, extension);
282}
283
284
285void
286muscle_location_grow (char const *key, location loc)
287{
288 muscle_boundary_grow (key, loc.start);
289 muscle_grow (key, "", ", ", "");
290 muscle_boundary_grow (key, loc.end);
291}
292
293#define COMMON_DECODE(Value) \
294 case '$': \
295 ++(Value); aver (*(Value) == '['); \
296 ++(Value); aver (*(Value) == ']'); \
297 ++(Value); aver (*(Value) == '['); \
298 obstack_sgrow (&muscle_obstack, "$"); \
299 break; \
300 case '@': \
301 switch (*++(Value)) \
302 { \
303 case '@': obstack_sgrow (&muscle_obstack, "@" ); break; \
304 case '{': obstack_sgrow (&muscle_obstack, "[" ); break; \
305 case '}': obstack_sgrow (&muscle_obstack, "]" ); break; \
306 default: aver (false); break; \
307 } \
308 break; \
309 default: \
310 obstack_1grow (&muscle_obstack, *(Value)); \
311 break;
312
313/* Reverse of obstack_escape. */
314static char *
315string_decode (char const *key)
316{
317 char const *value = muscle_find_const (key);
318 if (!value)
319 return NULL;
320 do {
321 switch (*value)
322 {
323 COMMON_DECODE (value)
324 case '[':
325 case ']':
326 aver (false);
327 break;
328 }
329 } while (*value++);
330 char const *value_decoded = obstack_finish (&muscle_obstack);
331 char *res = xstrdup (value_decoded);
332 obstack_free (&muscle_obstack, value_decoded);
333 return res;
334}
335
336/* Reverse of muscle_location_grow. */
337static location
338location_decode (char const *value)
339{
340 aver (value);
341 aver (*value == '[');
342 ++value; aver (*value == '[');
343 location loc;
344 while (*++value)
345 switch (*value)
346 {
347 COMMON_DECODE (value)
348 case '[':
349 aver (false);
350 break;
351 case ']':
352 ++value; aver (*value == ']');
353 char *boundary_str = obstack_finish0 (&muscle_obstack);
354 switch (*++value)
355 {
356 case ',':
357 boundary_set_from_string (&loc.start, boundary_str);
358 obstack_free (&muscle_obstack, boundary_str);
359 ++value; aver (*value == ' ');
360 ++value; aver (*value == '[');
361 ++value; aver (*value == '[');
362 break;
363 case '\0':
364 boundary_set_from_string (&loc.end, boundary_str);
365 obstack_free (&muscle_obstack, boundary_str);
366 return loc;
367 break;
368 default:
369 aver (false);
370 break;
371 }
372 break;
373 }
374 aver (false);
375 return loc;
376}
377
378void
379muscle_user_name_list_grow (char const *key, char const *user_name,
380 location loc)
381{
382 muscle_grow (key, "[[[[", ",", "");
383 muscle_grow (key, user_name, "", "");
384 muscle_grow (key, "]], ", "", "");
385 muscle_location_grow (key, loc);
386 muscle_grow (key, "]]", "", "");
387}
388
389
390/** Return an allocated string that represents the %define directive
391 that performs the assignment.
392
393 @param assignment "VAR", or "VAR=VAL".
394 @param value default value if VAL \a assignment has no '='.
395
396 For instance:
397 "foo", NULL => "%define foo"
398 "foo", "baz" => "%define foo baz"
399 "foo=bar", NULL => "%define foo bar"
400 "foo=bar", "baz" => "%define foo bar"
401 "foo=", NULL => "%define foo"
402 "foo=", "baz" => "%define foo"
403 */
404
405static
406char *
407define_directive (char const *assignment,
408 muscle_kind kind,
409 char const *value)
410{
411 char *eq = strchr (assignment, '=');
412 char const *fmt
413 = eq || !value || !*value ? "%%define %s"
414 : kind == muscle_code ? "%%define %s {%s}"
415 : kind == muscle_string ? "%%define %s \"%s\""
416 : "%%define %s %s";
417 char *res = xmalloc (strlen (fmt) + strlen (assignment)
418 + (value ? strlen (value) : 0));
419 sprintf (res, fmt, assignment, value);
420 eq = strchr (res, '=');
421 if (eq)
422 *eq = eq[1] ? ' ' : '\0';
423 return res;
424}
425
426/** If the \a variable name is obsolete, return the name to use,
427 * otherwise \a variable. If the \a value is obsolete, update it too.
428 *
429 * Allocates the returned value if needed, otherwise the returned
430 * value is exactly \a variable. */
431static
432char const *
433muscle_percent_variable_update (char const *variable,
434 muscle_kind kind,
435 char const **value,
436 char **old, char **upd)
437{
438 typedef struct
439 {
440 const char *obsolete;
441 const char *updated;
442 muscle_kind kind;
443 } conversion_type;
444 const conversion_type conversion[] =
445 {
446 { "%error-verbose", "parse.error=verbose", muscle_keyword },
447 { "%error_verbose", "parse.error=verbose", muscle_keyword },
448 { "abstract", "api.parser.abstract", muscle_keyword },
449 { "annotations", "api.parser.annotations", muscle_code },
450 { "api.push_pull", "api.push-pull", muscle_keyword },
451 { "api.tokens.prefix", "api.token.prefix", muscle_code },
452 { "extends", "api.parser.extends", muscle_keyword },
453 { "final", "api.parser.final", muscle_keyword },
454 { "implements", "api.parser.implements", muscle_keyword },
455 { "lex_symbol", "api.token.constructor", -1 },
456 { "location_type", "api.location.type", muscle_code },
457 { "lr.default-reductions", "lr.default-reduction", muscle_keyword },
458 { "lr.keep-unreachable-states", "lr.keep-unreachable-state", muscle_keyword },
459 { "lr.keep_unreachable_states", "lr.keep-unreachable-state", muscle_keyword },
460 { "namespace", "api.namespace", muscle_code },
461 { "parser_class_name", "api.parser.class", muscle_code },
462 { "public", "api.parser.public", muscle_keyword },
463 { "strictfp", "api.parser.strictfp", muscle_keyword },
464 { "stype", "api.value.type", -1 },
465 { "variant=", "api.value.type=variant", -1 },
466 { "variant=true", "api.value.type=variant", -1 },
467 { NULL, NULL, -1, }
468 };
469
470 for (conversion_type const *c = conversion; c->obsolete; ++c)
471 {
472 char const *eq = strchr (c->obsolete, '=');
473 if (eq
474 ? (!strncmp (c->obsolete, variable, eq - c->obsolete)
475 && STREQ (eq + 1, *value))
476 : STREQ (c->obsolete, variable))
477 {
478 /* Generate the deprecation warning. */
479 *old = c->obsolete[0] == '%'
480 ? xstrdup (c->obsolete)
481 : define_directive (c->obsolete, kind, *value);
482 *upd = define_directive (c->updated, c->kind, *value);
483 /* Update the variable and its value. */
484 {
485 char *res = xstrdup (c->updated);
486 char *eq2 = strchr (res, '=');
487 if (eq2)
488 {
489 *eq2 = '\0';
490 *value = eq2 + 1;
491 }
492 return res;
493 }
494 }
495 }
496 return variable;
497}
498
499void
500muscle_percent_define_insert (char const *var, location variable_loc,
501 muscle_kind kind,
502 char const *value,
503 muscle_percent_define_how how)
504{
505 /* Backward compatibility. */
506 char *old = NULL;
507 char *upd = NULL;
508 char const *variable
509 = muscle_percent_variable_update (var, kind,
510 &value, &old, &upd);
511 uniqstr name = muscle_name (variable, NULL);
512 uniqstr loc_name = muscle_name (variable, "loc");
513 uniqstr syncline_name = muscle_name (variable, "syncline");
514 uniqstr how_name = muscle_name (variable, "how");
515 uniqstr kind_name = muscle_name (variable, "kind");
516
517 /* Command-line options are processed before the grammar file. */
518 bool warned = false;
519 if (how == MUSCLE_PERCENT_DEFINE_GRAMMAR_FILE)
520 {
521 char const *current_value = muscle_find_const (name);
522 if (current_value)
523 {
524 muscle_percent_define_how how_old
525 = atoi (muscle_find_const (how_name));
526 if (how_old == MUSCLE_PERCENT_DEFINE_F)
527 goto end;
528 unsigned i = 0;
529 /* If assigning the same value, make it a warning. */
530 warnings warn = STREQ (value, current_value) ? Wother : complaint;
531 complain_indent (&variable_loc, warn, &i,
532 _("%%define variable %s redefined"),
533 quote (variable));
534 i += SUB_INDENT;
535 location loc = muscle_percent_define_get_loc (variable);
536 complain_indent (&loc, warn, &i, _("previous definition"));
537 fixits_register (&variable_loc, "");
538 warned = true;
539 }
540 }
541
542 if (!warned && old && upd)
543 deprecated_directive (&variable_loc, old, upd);
544
545 MUSCLE_INSERT_STRING (name, value);
546 muscle_insert (loc_name, "");
547 muscle_location_grow (loc_name, variable_loc);
548 muscle_insert (syncline_name, "");
549 muscle_syncline_grow (syncline_name, variable_loc);
550 muscle_user_name_list_grow ("percent_define_user_variables", variable,
551 variable_loc);
552 MUSCLE_INSERT_INT (how_name, how);
553 MUSCLE_INSERT_STRING (kind_name, muscle_kind_string (kind));
554 end:
555 free (old);
556 free (upd);
557 if (variable != var)
558 free ((char *) variable);
559}
560
561/* This is used for backward compatibility, e.g., "%define api.pure"
562 supersedes "%pure-parser". */
563void
564muscle_percent_define_ensure (char const *variable, location loc,
565 bool value)
566{
567 uniqstr name = muscle_name (variable, NULL);
568 char const *val = value ? "" : "false";
569
570 /* Don't complain is VARIABLE is already defined, but be sure to set
571 its value to VAL. */
572 if (!muscle_find_const (name)
573 || muscle_percent_define_flag_if (variable) != value)
574 muscle_percent_define_insert (variable, loc, muscle_keyword, val,
575 MUSCLE_PERCENT_DEFINE_GRAMMAR_FILE);
576}
577
578/* Mark %define VARIABLE as used. */
579static void
580muscle_percent_define_use (char const *variable)
581{
582 muscle_insert (muscle_name (variable, "bison_variables"), "");
583}
584
585/* The value of %define variable VARIABLE (corresponding to FIELD, if
586 defined). Do not register as used, but diagnose unset variables. */
587
588static
589char const *
590muscle_percent_define_get_raw (char const *variable, char const *field)
591{
592 uniqstr name = muscle_name (variable, field);
593 char const *res = muscle_find_const (name);
594 if (!res)
595 complain (NULL, fatal, _("%s: undefined %%define variable %s"),
596 "muscle_percent_define_get_raw", quote (variable));
597 return res;
598}
599
600char *
601muscle_percent_define_get (char const *variable)
602{
603 uniqstr name = muscle_name (variable, NULL);
604 char *value = string_decode (name);
605 if (!value)
606 value = xstrdup ("");
607 muscle_percent_define_use (variable);
608 return value;
609}
610
611/* The kind of VARIABLE. An error if undefined. */
612static muscle_kind
613muscle_percent_define_get_kind (char const *variable)
614{
615 return muscle_kind_new (muscle_percent_define_get_raw (variable, "kind"));
616}
617
618/* Check the kind of VARIABLE. An error if undefined. */
619static void
620muscle_percent_define_check_kind (char const *variable, muscle_kind kind)
621{
622 if (muscle_percent_define_get_kind (variable) != kind)
623 {
624 location loc = muscle_percent_define_get_loc (variable);
625 switch (kind)
626 {
627 case muscle_code:
628 complain (&loc, Wdeprecated,
629 "%%define variable '%s' requires '{...}' values",
630 variable);
631 break;
632 case muscle_keyword:
633 complain (&loc, Wdeprecated,
634 "%%define variable '%s' requires keyword values",
635 variable);
636 break;
637 case muscle_string:
638 complain (&loc, Wdeprecated,
639 "%%define variable '%s' requires '\"...\"' values",
640 variable);
641 break;
642 }
643 }
644}
645
646
647location
648muscle_percent_define_get_loc (char const *variable)
649{
650 return location_decode (muscle_percent_define_get_raw (variable, "loc"));
651}
652
653char const *
654muscle_percent_define_get_syncline (char const *variable)
655{
656 return muscle_percent_define_get_raw (variable, "syncline");
657}
658
659bool
660muscle_percent_define_ifdef (char const *variable)
661{
662 if (muscle_find_const (muscle_name (variable, NULL)))
663 {
664 muscle_percent_define_use (variable);
665 return true;
666 }
667 else
668 return false;
669}
670
671bool
672muscle_percent_define_flag_if (char const *variable)
673{
674 uniqstr invalid_boolean_name = muscle_name (variable, "invalid_boolean");
675 bool res = false;
676
677 if (muscle_percent_define_ifdef (variable))
678 {
679 char *value = muscle_percent_define_get (variable);
680 muscle_percent_define_check_kind (variable, muscle_keyword);
681 if (value[0] == '\0' || STREQ (value, "true"))
682 res = true;
683 else if (STREQ (value, "false"))
684 res = false;
685 else if (!muscle_find_const (invalid_boolean_name))
686 {
687 muscle_insert (invalid_boolean_name, "");
688 location loc = muscle_percent_define_get_loc (variable);
689 complain (&loc, complaint,
690 _("invalid value for %%define Boolean variable %s"),
691 quote (variable));
692 }
693 free (value);
694 }
695 else
696 complain (NULL, fatal, _("%s: undefined %%define variable %s"),
697 "muscle_percent_define_flag", quote (variable));
698
699 return res;
700}
701
702void
703muscle_percent_define_default (char const *variable, char const *value)
704{
705 uniqstr name = muscle_name (variable, NULL);
706 if (!muscle_find_const (name))
707 {
708 MUSCLE_INSERT_STRING (name, value);
709 MUSCLE_INSERT_STRING (muscle_name (variable, "kind"), "keyword");
710 {
711 uniqstr loc_name = muscle_name (variable, "loc");
712 location loc;
713 loc.start.file = "<default value>";
714 loc.start.line = -1;
715 loc.start.column = -1;
716 loc.start.byte = -1;
717 loc.end = loc.start;
718 muscle_insert (loc_name, "");
719 muscle_location_grow (loc_name, loc);
720 }
721 muscle_insert (muscle_name (variable, "syncline"), "");
722 }
723}
724
725void
726muscle_percent_define_check_values (char const * const *values)
727{
728 for (; *values; ++values)
729 {
730 char const * const *variablep = values;
731 uniqstr name = muscle_name (*variablep, NULL);
732 char *value = string_decode (name);
733 muscle_percent_define_check_kind (*variablep, muscle_keyword);
734 if (value)
735 {
736 for (++values; *values; ++values)
737 if (STREQ (value, *values))
738 break;
739 if (!*values)
740 {
741 location loc = muscle_percent_define_get_loc (*variablep);
742 unsigned i = 0;
743 complain_indent (&loc, complaint, &i,
744 _("invalid value for %%define variable %s: %s"),
745 quote (*variablep), quote_n (1, value));
746 i += SUB_INDENT;
747 for (values = variablep + 1; *values; ++values)
748 complain_indent (&loc, complaint | no_caret | silent, &i,
749 _("accepted value: %s"), quote (*values));
750 }
751 else
752 while (*values)
753 ++values;
754 free (value);
755 }
756 else
757 complain (NULL, fatal, _("%s: undefined %%define variable %s"),
758 "muscle_percent_define_check_values", quote (*variablep));
759 }
760}
761
762void
763muscle_percent_code_grow (char const *qualifier, location qualifier_loc,
764 char const *code, location code_loc)
765{
766 char const *name = UNIQSTR_CONCAT ("percent_code(", qualifier, ")");
767 muscle_code_grow (name, code, code_loc);
768 muscle_user_name_list_grow ("percent_code_user_qualifiers", qualifier,
769 qualifier_loc);
770}
771
772
773/*------------------------------------------------.
774| Output the definition of ENTRY as a m4_define. |
775`------------------------------------------------*/
776
777static inline bool
778muscle_m4_output (muscle_entry *entry, FILE *out)
779{
780 fprintf (out,
781 "m4_define([b4_%s],\n"
782 "[[%s]])\n\n\n", entry->key, entry->value);
783 return true;
784}
785
786static bool
787muscle_m4_output_processor (void *entry, void *out)
788{
789 return muscle_m4_output (entry, out);
790}
791
792
793void
794muscles_m4_output (FILE *out)
795{
796 hash_do_for_each (muscle_table, muscle_m4_output_processor, out);
797}
798