]> git.cameronkatri.com Git - mandoc.git/blob - mdoc_argv.c
Removed already-fixed FIXME.
[mandoc.git] / mdoc_argv.c
1 /* $Id: mdoc_argv.c,v 1.22 2009/08/19 14:44:35 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #include <sys/types.h>
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24
25 #include "libmdoc.h"
26
27 /*
28 * Routines to parse arguments of macros. Arguments follow the syntax
29 * of `-arg [val [valN...]]'. Arguments come in all types: quoted
30 * arguments, multiple arguments per value, no-value arguments, etc.
31 *
32 * There's no limit to the number or arguments that may be allocated.
33 */
34
35 #define ARGS_DELIM (1 << 1)
36 #define ARGS_TABSEP (1 << 2)
37
38 #define ARGV_NONE (1 << 0)
39 #define ARGV_SINGLE (1 << 1)
40 #define ARGV_MULTI (1 << 2)
41 #define ARGV_OPT_SINGLE (1 << 3)
42
43 #define MULTI_STEP 5
44
45 static int argv_a2arg(int, const char *);
46 static int args(struct mdoc *, int, int *,
47 char *, int, char **);
48 static int argv(struct mdoc *, int,
49 struct mdoc_argv *, int *, char *);
50 static int argv_single(struct mdoc *, int,
51 struct mdoc_argv *, int *, char *);
52 static int argv_opt_single(struct mdoc *, int,
53 struct mdoc_argv *, int *, char *);
54 static int argv_multi(struct mdoc *, int,
55 struct mdoc_argv *, int *, char *);
56
57 /* Per-argument flags. */
58
59 static int mdoc_argvflags[MDOC_ARG_MAX] = {
60 ARGV_NONE, /* MDOC_Split */
61 ARGV_NONE, /* MDOC_Nosplit */
62 ARGV_NONE, /* MDOC_Ragged */
63 ARGV_NONE, /* MDOC_Unfilled */
64 ARGV_NONE, /* MDOC_Literal */
65 ARGV_NONE, /* MDOC_File */
66 ARGV_SINGLE, /* MDOC_Offset */
67 ARGV_NONE, /* MDOC_Bullet */
68 ARGV_NONE, /* MDOC_Dash */
69 ARGV_NONE, /* MDOC_Hyphen */
70 ARGV_NONE, /* MDOC_Item */
71 ARGV_NONE, /* MDOC_Enum */
72 ARGV_NONE, /* MDOC_Tag */
73 ARGV_NONE, /* MDOC_Diag */
74 ARGV_NONE, /* MDOC_Hang */
75 ARGV_NONE, /* MDOC_Ohang */
76 ARGV_NONE, /* MDOC_Inset */
77 ARGV_MULTI, /* MDOC_Column */
78 ARGV_SINGLE, /* MDOC_Width */
79 ARGV_NONE, /* MDOC_Compact */
80 ARGV_OPT_SINGLE, /* MDOC_Std */
81 ARGV_NONE, /* MDOC_Filled */
82 ARGV_NONE, /* MDOC_Words */
83 ARGV_NONE, /* MDOC_Emphasis */
84 ARGV_NONE, /* MDOC_Symbolic */
85 ARGV_NONE /* MDOC_Symbolic */
86 };
87
88 static int mdoc_argflags[MDOC_MAX] = {
89 0, /* Ap */
90 0, /* Dd */
91 0, /* Dt */
92 0, /* Os */
93 0, /* Sh */
94 0, /* Ss */
95 ARGS_DELIM, /* Pp */
96 ARGS_DELIM, /* D1 */
97 ARGS_DELIM, /* Dl */
98 0, /* Bd */
99 0, /* Ed */
100 0, /* Bl */
101 0, /* El */
102 0, /* It */
103 ARGS_DELIM, /* Ad */
104 ARGS_DELIM, /* An */
105 ARGS_DELIM, /* Ar */
106 0, /* Cd */
107 ARGS_DELIM, /* Cm */
108 ARGS_DELIM, /* Dv */
109 ARGS_DELIM, /* Er */
110 ARGS_DELIM, /* Ev */
111 0, /* Ex */
112 ARGS_DELIM, /* Fa */
113 0, /* Fd */
114 ARGS_DELIM, /* Fl */
115 ARGS_DELIM, /* Fn */
116 ARGS_DELIM, /* Ft */
117 ARGS_DELIM, /* Ic */
118 0, /* In */
119 ARGS_DELIM, /* Li */
120 0, /* Nd */
121 ARGS_DELIM, /* Nm */
122 ARGS_DELIM, /* Op */
123 0, /* Ot */
124 ARGS_DELIM, /* Pa */
125 0, /* Rv */
126 ARGS_DELIM, /* St */
127 ARGS_DELIM, /* Va */
128 ARGS_DELIM, /* Vt */
129 ARGS_DELIM, /* Xr */
130 0, /* %A */
131 0, /* %B */
132 0, /* %D */
133 0, /* %I */
134 0, /* %J */
135 0, /* %N */
136 0, /* %O */
137 0, /* %P */
138 0, /* %R */
139 0, /* %T */
140 0, /* %V */
141 ARGS_DELIM, /* Ac */
142 0, /* Ao */
143 ARGS_DELIM, /* Aq */
144 ARGS_DELIM, /* At */
145 ARGS_DELIM, /* Bc */
146 0, /* Bf */
147 0, /* Bo */
148 ARGS_DELIM, /* Bq */
149 ARGS_DELIM, /* Bsx */
150 ARGS_DELIM, /* Bx */
151 0, /* Db */
152 ARGS_DELIM, /* Dc */
153 0, /* Do */
154 ARGS_DELIM, /* Dq */
155 ARGS_DELIM, /* Ec */
156 0, /* Ef */
157 ARGS_DELIM, /* Em */
158 0, /* Eo */
159 ARGS_DELIM, /* Fx */
160 ARGS_DELIM, /* Ms */
161 ARGS_DELIM, /* No */
162 ARGS_DELIM, /* Ns */
163 ARGS_DELIM, /* Nx */
164 ARGS_DELIM, /* Ox */
165 ARGS_DELIM, /* Pc */
166 ARGS_DELIM, /* Pf */
167 0, /* Po */
168 ARGS_DELIM, /* Pq */
169 ARGS_DELIM, /* Qc */
170 ARGS_DELIM, /* Ql */
171 0, /* Qo */
172 ARGS_DELIM, /* Qq */
173 0, /* Re */
174 0, /* Rs */
175 ARGS_DELIM, /* Sc */
176 0, /* So */
177 ARGS_DELIM, /* Sq */
178 0, /* Sm */
179 ARGS_DELIM, /* Sx */
180 ARGS_DELIM, /* Sy */
181 ARGS_DELIM, /* Tn */
182 ARGS_DELIM, /* Ux */
183 ARGS_DELIM, /* Xc */
184 0, /* Xo */
185 0, /* Fo */
186 0, /* Fc */
187 0, /* Oo */
188 ARGS_DELIM, /* Oc */
189 0, /* Bk */
190 0, /* Ek */
191 0, /* Bt */
192 0, /* Hf */
193 0, /* Fr */
194 0, /* Ud */
195 0, /* Lb */
196 ARGS_DELIM, /* Lp */
197 ARGS_DELIM, /* Lk */
198 ARGS_DELIM, /* Mt */
199 ARGS_DELIM, /* Brq */
200 0, /* Bro */
201 ARGS_DELIM, /* Brc */
202 0, /* %C */
203 0, /* Es */
204 0, /* En */
205 0, /* Dx */
206 0, /* %Q */
207 0, /* br */
208 0, /* sp */
209 };
210
211
212 /*
213 * Parse an argument from line text. This comes in the form of -key
214 * [value0...], which may either have a single mandatory value, at least
215 * one mandatory value, an optional single value, or no value.
216 */
217 int
218 mdoc_argv(struct mdoc *m, int line, int tok,
219 struct mdoc_arg **v, int *pos, char *buf)
220 {
221 char *p, sv;
222 struct mdoc_argv tmp;
223 struct mdoc_arg *arg;
224
225 if (0 == buf[*pos])
226 return(ARGV_EOLN);
227
228 assert(' ' != buf[*pos]);
229
230 /* Parse through to the first unescaped space. */
231
232 p = &buf[++(*pos)];
233
234 assert(*pos > 0);
235
236 /* LINTED */
237 while (buf[*pos]) {
238 if (' ' == buf[*pos])
239 if ('\\' != buf[*pos - 1])
240 break;
241 (*pos)++;
242 }
243
244 /* XXX - save zeroed byte, if not an argument. */
245
246 sv = 0;
247 if (buf[*pos]) {
248 sv = buf[*pos];
249 buf[(*pos)++] = 0;
250 }
251
252 (void)memset(&tmp, 0, sizeof(struct mdoc_argv));
253 tmp.line = line;
254 tmp.pos = *pos;
255
256 /* See if our token accepts the argument. */
257
258 if (MDOC_ARG_MAX == (tmp.arg = argv_a2arg(tok, p))) {
259 /* XXX - restore saved zeroed byte. */
260 if (sv)
261 buf[*pos - 1] = sv;
262 return(ARGV_WORD);
263 }
264
265 while (buf[*pos] && ' ' == buf[*pos])
266 (*pos)++;
267
268 if ( ! argv(m, line, &tmp, pos, buf))
269 return(ARGV_ERROR);
270
271 if (NULL == (arg = *v)) {
272 *v = calloc(1, sizeof(struct mdoc_arg));
273 if (NULL == *v) {
274 (void)mdoc_nerr(m, m->last, EMALLOC);
275 return(ARGV_ERROR);
276 }
277 arg = *v;
278 }
279
280 arg->argc++;
281 arg->argv = realloc(arg->argv, arg->argc *
282 sizeof(struct mdoc_argv));
283
284 if (NULL == arg->argv) {
285 (void)mdoc_nerr(m, m->last, EMALLOC);
286 return(ARGV_ERROR);
287 }
288
289 (void)memcpy(&arg->argv[(int)arg->argc - 1],
290 &tmp, sizeof(struct mdoc_argv));
291
292 return(ARGV_ARG);
293 }
294
295
296 void
297 mdoc_argv_free(struct mdoc_arg *p)
298 {
299 int i, j;
300
301 if (NULL == p)
302 return;
303
304 if (p->refcnt) {
305 --(p->refcnt);
306 if (p->refcnt)
307 return;
308 }
309 assert(p->argc);
310
311 /* LINTED */
312 for (i = 0; i < (int)p->argc; i++) {
313 if (0 == p->argv[i].sz)
314 continue;
315 /* LINTED */
316 for (j = 0; j < (int)p->argv[i].sz; j++)
317 free(p->argv[i].value[j]);
318
319 free(p->argv[i].value);
320 }
321
322 free(p->argv);
323 free(p);
324 }
325
326
327 int
328 mdoc_zargs(struct mdoc *m, int line, int *pos, char *buf, char **v)
329 {
330
331 return(args(m, line, pos, buf, 0, v));
332 }
333
334
335 int
336 mdoc_args(struct mdoc *m, int line,
337 int *pos, char *buf, int tok, char **v)
338 {
339 int fl, c, i;
340 struct mdoc_node *n;
341
342 fl = (0 == tok) ? 0 : mdoc_argflags[tok];
343
344 if (MDOC_It != tok)
345 return(args(m, line, pos, buf, fl, v));
346
347 /*
348 * The `It' macro is a special case, as it acquires parameters from its
349 * parent `Bl' context, specifically, we're concerned with -column.
350 */
351
352 for (n = m->last; n; n = n->parent)
353 if (MDOC_BLOCK == n->type && MDOC_Bl == n->tok)
354 break;
355
356 assert(n);
357 c = (int)(n->args ? n->args->argc : 0);
358 assert(c > 0);
359
360 /* LINTED */
361 for (i = 0; i < c; i++) {
362 if (MDOC_Column != n->args->argv[i].arg)
363 continue;
364 fl |= ARGS_TABSEP;
365 fl &= ~ARGS_DELIM;
366 break;
367 }
368
369 return(args(m, line, pos, buf, fl, v));
370 }
371
372
373 static int
374 args(struct mdoc *m, int line, int *pos,
375 char *buf, int fl, char **v)
376 {
377 int i;
378 char *p, *pp;
379
380 assert(*pos);
381 assert(' ' != buf[*pos]);
382
383 if (0 == buf[*pos])
384 return(ARGS_EOLN);
385
386 /*
387 * If the first character is a delimiter and we're to look for
388 * delimited strings, then pass down the buffer seeing if it
389 * follows the pattern of [[::delim::][ ]+]+.
390 */
391
392 if ((fl & ARGS_DELIM) && mdoc_iscdelim(buf[*pos])) {
393 for (i = *pos; buf[i]; ) {
394 if ( ! mdoc_iscdelim(buf[i]))
395 break;
396 i++;
397 if (0 == buf[i] || ' ' != buf[i])
398 break;
399 i++;
400 while (buf[i] && ' ' == buf[i])
401 i++;
402 }
403
404 /* FIXME: warn about trailing whitespace. */
405
406 if (0 == buf[i]) {
407 *v = &buf[*pos];
408 return(ARGS_PUNCT);
409 }
410 }
411
412 *v = &buf[*pos];
413
414 /*
415 * First handle TABSEP items, restricted to `Bl -column'. This
416 * ignores conventional token parsing and instead uses tabs or
417 * `Ta' macros to separate phrases. Phrases are parsed again
418 * for arguments at a later phase.
419 */
420
421 if (ARGS_TABSEP & fl) {
422 /* Scan ahead to tab (can't be escaped). */
423 p = strchr(*v, '\t');
424
425 /* Scan ahead to unescaped `Ta'. */
426 for (pp = *v; ; pp++) {
427 if (NULL == (pp = strstr(pp, "Ta")))
428 break;
429 if (pp > *v && ' ' != *(pp - 1))
430 continue;
431 if (' ' == *(pp + 2) || 0 == *(pp + 2))
432 break;
433 }
434
435 /*
436 * Adjust new-buffer position to be beyond delimiter
437 * mark (e.g., Ta -> end + 2).
438 */
439 if (p && pp) {
440 *pos += pp < p ? 2 : 1;
441 p = pp < p ? pp : p;
442 } else if (p && ! pp) {
443 *pos += 1;
444 } else if (pp && ! p) {
445 p = pp;
446 *pos += 2;
447 } else
448 p = strchr(*v, 0);
449
450 /* Whitespace check for eoln case... */
451 if (0 == *p && ' ' == *(p - 1))
452 if ( ! mdoc_pwarn(m, line, *pos, ETAILWS))
453 return(ARGS_ERROR);
454
455 *pos += (int)(p - *v);
456
457 /* Strip delimiter's preceding whitespace. */
458 pp = p - 1;
459 while (pp > *v && ' ' == *pp) {
460 if (pp > *v && '\\' == *(pp - 1))
461 break;
462 pp--;
463 }
464 *(pp + 1) = 0;
465
466 /* Strip delimiter's proceeding whitespace. */
467 for (pp = &buf[*pos]; ' ' == *pp; pp++, (*pos)++)
468 /* Skip ahead. */ ;
469
470 return(ARGS_PHRASE);
471 }
472
473 /*
474 * Process a quoted literal. A quote begins with a double-quote
475 * and ends with a double-quote NOT preceded by a double-quote.
476 * Whitespace is NOT involved in literal termination.
477 */
478
479 if ('\"' == buf[*pos]) {
480 *v = &buf[++(*pos)];
481
482 for ( ; buf[*pos]; (*pos)++) {
483 if ('\"' != buf[*pos])
484 continue;
485 if ('\"' != buf[*pos + 1])
486 break;
487 (*pos)++;
488 }
489
490 if (0 == buf[*pos]) {
491 if ( ! mdoc_pwarn(m, line, *pos, EQUOTTERM))
492 return(ARGS_ERROR);
493 return(ARGS_QWORD);
494 }
495
496 buf[(*pos)++] = 0;
497
498 if (0 == buf[*pos])
499 return(ARGS_QWORD);
500
501 while (' ' == buf[*pos])
502 (*pos)++;
503
504 if (0 == buf[*pos])
505 if ( ! mdoc_pwarn(m, line, *pos, ETAILWS))
506 return(ARGS_ERROR);
507
508 return(ARGS_QWORD);
509 }
510
511 /*
512 * A non-quoted term progresses until either the end of line or
513 * a non-escaped whitespace.
514 */
515
516 for ( ; buf[*pos]; (*pos)++)
517 if (' ' == buf[*pos] && '\\' != buf[*pos - 1])
518 break;
519
520 if (0 == buf[*pos])
521 return(ARGS_WORD);
522
523 buf[(*pos)++] = 0;
524
525 while (' ' == buf[*pos])
526 (*pos)++;
527
528 if (0 == buf[*pos])
529 if ( ! mdoc_pwarn(m, line, *pos, ETAILWS))
530 return(ARGS_ERROR);
531
532 return(ARGS_WORD);
533 }
534
535
536 static int
537 argv_a2arg(int tok, const char *argv)
538 {
539
540 /*
541 * Parse an argument identifier from its text. XXX - this
542 * should really be table-driven to clarify the code.
543 *
544 * If you add an argument to the list, make sure that you
545 * register it here with its one or more macros!
546 */
547
548 switch (tok) {
549 case (MDOC_An):
550 if (0 == strcmp(argv, "split"))
551 return(MDOC_Split);
552 else if (0 == strcmp(argv, "nosplit"))
553 return(MDOC_Nosplit);
554 break;
555
556 case (MDOC_Bd):
557 if (0 == strcmp(argv, "ragged"))
558 return(MDOC_Ragged);
559 else if (0 == strcmp(argv, "unfilled"))
560 return(MDOC_Unfilled);
561 else if (0 == strcmp(argv, "filled"))
562 return(MDOC_Filled);
563 else if (0 == strcmp(argv, "literal"))
564 return(MDOC_Literal);
565 else if (0 == strcmp(argv, "file"))
566 return(MDOC_File);
567 else if (0 == strcmp(argv, "offset"))
568 return(MDOC_Offset);
569 else if (0 == strcmp(argv, "compact"))
570 return(MDOC_Compact);
571 break;
572
573 case (MDOC_Bf):
574 if (0 == strcmp(argv, "emphasis"))
575 return(MDOC_Emphasis);
576 else if (0 == strcmp(argv, "literal"))
577 return(MDOC_Literal);
578 else if (0 == strcmp(argv, "symbolic"))
579 return(MDOC_Symbolic);
580 break;
581
582 case (MDOC_Bk):
583 if (0 == strcmp(argv, "words"))
584 return(MDOC_Words);
585 break;
586
587 case (MDOC_Bl):
588 if (0 == strcmp(argv, "bullet"))
589 return(MDOC_Bullet);
590 else if (0 == strcmp(argv, "dash"))
591 return(MDOC_Dash);
592 else if (0 == strcmp(argv, "hyphen"))
593 return(MDOC_Hyphen);
594 else if (0 == strcmp(argv, "item"))
595 return(MDOC_Item);
596 else if (0 == strcmp(argv, "enum"))
597 return(MDOC_Enum);
598 else if (0 == strcmp(argv, "tag"))
599 return(MDOC_Tag);
600 else if (0 == strcmp(argv, "diag"))
601 return(MDOC_Diag);
602 else if (0 == strcmp(argv, "hang"))
603 return(MDOC_Hang);
604 else if (0 == strcmp(argv, "ohang"))
605 return(MDOC_Ohang);
606 else if (0 == strcmp(argv, "inset"))
607 return(MDOC_Inset);
608 else if (0 == strcmp(argv, "column"))
609 return(MDOC_Column);
610 else if (0 == strcmp(argv, "width"))
611 return(MDOC_Width);
612 else if (0 == strcmp(argv, "offset"))
613 return(MDOC_Offset);
614 else if (0 == strcmp(argv, "compact"))
615 return(MDOC_Compact);
616 else if (0 == strcmp(argv, "nested"))
617 return(MDOC_Nested);
618 break;
619
620 case (MDOC_Rv):
621 /* FALLTHROUGH */
622 case (MDOC_Ex):
623 if (0 == strcmp(argv, "std"))
624 return(MDOC_Std);
625 break;
626 default:
627 break;
628 }
629
630 return(MDOC_ARG_MAX);
631 }
632
633
634 static int
635 argv_multi(struct mdoc *m, int line,
636 struct mdoc_argv *v, int *pos, char *buf)
637 {
638 int c;
639 char *p;
640
641 for (v->sz = 0; ; v->sz++) {
642 if ('-' == buf[*pos])
643 break;
644 c = args(m, line, pos, buf, 0, &p);
645 if (ARGS_ERROR == c)
646 return(0);
647 else if (ARGS_EOLN == c)
648 break;
649
650 if (0 == v->sz % MULTI_STEP) {
651 v->value = realloc(v->value,
652 (v->sz + MULTI_STEP) * sizeof(char *));
653 if (NULL == v->value) {
654 (void)mdoc_nerr(m, m->last, EMALLOC);
655 return(ARGV_ERROR);
656 }
657 }
658 if (NULL == (v->value[(int)v->sz] = strdup(p)))
659 return(mdoc_nerr(m, m->last, EMALLOC));
660 }
661
662 return(1);
663 }
664
665
666 static int
667 argv_opt_single(struct mdoc *m, int line,
668 struct mdoc_argv *v, int *pos, char *buf)
669 {
670 int c;
671 char *p;
672
673 if ('-' == buf[*pos])
674 return(1);
675
676 c = args(m, line, pos, buf, 0, &p);
677 if (ARGS_ERROR == c)
678 return(0);
679 if (ARGS_EOLN == c)
680 return(1);
681
682 v->sz = 1;
683 if (NULL == (v->value = calloc(1, sizeof(char *))))
684 return(mdoc_nerr(m, m->last, EMALLOC));
685 if (NULL == (v->value[0] = strdup(p)))
686 return(mdoc_nerr(m, m->last, EMALLOC));
687
688 return(1);
689 }
690
691
692 /*
693 * Parse a single, mandatory value from the stream.
694 */
695 static int
696 argv_single(struct mdoc *m, int line,
697 struct mdoc_argv *v, int *pos, char *buf)
698 {
699 int c, ppos;
700 char *p;
701
702 ppos = *pos;
703
704 c = args(m, line, pos, buf, 0, &p);
705 if (ARGS_ERROR == c)
706 return(0);
707 if (ARGS_EOLN == c)
708 return(mdoc_perr(m, line, ppos, EARGVAL));
709
710 v->sz = 1;
711 if (NULL == (v->value = calloc(1, sizeof(char *))))
712 return(mdoc_nerr(m, m->last, EMALLOC));
713 if (NULL == (v->value[0] = strdup(p)))
714 return(mdoc_nerr(m, m->last, EMALLOC));
715
716 return(1);
717 }
718
719
720 /*
721 * Determine rules for parsing arguments. Arguments can either accept
722 * no parameters, an optional single parameter, one parameter, or
723 * multiple parameters.
724 */
725 static int
726 argv(struct mdoc *mdoc, int line,
727 struct mdoc_argv *v, int *pos, char *buf)
728 {
729
730 v->sz = 0;
731 v->value = NULL;
732
733 switch (mdoc_argvflags[v->arg]) {
734 case (ARGV_SINGLE):
735 return(argv_single(mdoc, line, v, pos, buf));
736 case (ARGV_MULTI):
737 return(argv_multi(mdoc, line, v, pos, buf));
738 case (ARGV_OPT_SINGLE):
739 return(argv_opt_single(mdoc, line, v, pos, buf));
740 default:
741 /* ARGV_NONE */
742 break;
743 }
744
745 return(1);
746 }