]> git.pond.sub.org Git - empserver/blob - src/lib/subs/nstr.c
(lookup): Rename lookup() to symbol_by_value(), make it public
[empserver] / src / lib / subs / nstr.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2005, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
23  *  related information and legal notices. It is expected that any future
24  *  projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  nstr.c: compile and execute the item selections on sectors
29  * 
30  *  Known contributors to this file:
31  *     Dave Pare, 1989
32  *     Steve McClure, 1997
33  *     Markus Armbruster, 2004
34  */
35
36 #include <limits.h>
37 #include "misc.h"
38 #include "file.h"
39 #include "match.h"
40 #include "nsc.h"
41 #include "prototypes.h"
42
43 static char *nstr_parse_val(char *, struct valstr *);
44 static int nstr_match_ca(struct valstr *, struct castr *);
45 static int nstr_match_val(struct valstr *, int, struct castr *, int);
46 static int nstr_string_ok(struct castr *ca, int idx);
47 static struct valstr *nstr_resolve_sel(struct valstr *, struct castr *);
48 static struct valstr *nstr_mkselval(struct valstr *, int, struct castr *);
49 static struct valstr *nstr_resolve_id(struct valstr *, struct castr *, int, int);
50 static int nstr_promote(int);
51
52
53 /*
54  * Compile conditions into array NP[LEN].
55  * Return number of conditions, or -1 on error.
56  * It is an error if there are more than LEN conditions.
57  * TYPE is the context type, a file type.
58  * STR is the condition string, in Empire syntax, without the leading
59  * '?'.
60  */
61 int
62 nstr_comp(struct nscstr *np, int len, int type, char *str)
63 {
64     struct castr *ca = ef_cadef(type);
65     char *cond;
66     char *tail;
67     int i;
68     struct nscstr dummy;
69     int lft_caidx, rgt_caidx;
70     int lft_val, rgt_val;
71     int lft_type, rgt_type;
72
73     cond = str;
74     for (i = 0; ; ++i, ++np) {
75         if (i >= len)
76             np = &dummy;
77
78         /* left operand */
79         tail = nstr_parse_val(cond, &np->lft);
80         lft_caidx = nstr_match_ca(&np->lft, ca);
81
82         /* operator */
83         if (*tail != '<' && *tail != '=' && *tail != '>' && *tail != '#') {
84             if (*tail)
85                 pr("%s -- expected condition operator\n", cond);
86             else
87                 pr("%s -- missing condition operator\n", cond);
88             return -1;
89         }
90         np->operator = *tail;
91         ++tail;
92
93         /* right operand */
94         tail = nstr_parse_val(tail, &np->rgt);
95         rgt_caidx = nstr_match_ca(&np->rgt, ca);
96
97         /*
98          * Resolve identifiers
99          *
100          * An identifier can name a selector or, if the other operand
101          * is a selector, a value for that.  The condition is
102          * ambiguous if both selector x value and value x selector are
103          * possible.  Example: n<n for sectors could mean newdes<n or
104          * n<newdes.
105          */
106         lft_val = nstr_match_val(&np->lft, type, ca, rgt_caidx);
107         rgt_val = nstr_match_val(&np->rgt, type, ca, lft_caidx);
108         /*
109          * if lft_val >= 0, then rhs names a selector and lhs names
110          * one of its values.  Likewise for rgt_val.
111          */
112         if (lft_val >= 0 && rgt_val >= 0) {
113             pr("%.*s -- condition ambiguous\n", (int)(tail-cond), cond);
114             return -1;
115         } else if (rgt_val >= 0) {
116             /* selector x value */
117             if (!nstr_resolve_sel(&np->lft, &ca[lft_caidx]))
118                 return -1;
119             nstr_mkselval(&np->rgt, rgt_val, &ca[lft_caidx]);
120         } else if (lft_val >= 0) {
121             /* value x selector */
122             nstr_mkselval(&np->lft, lft_val, &ca[rgt_caidx]);
123             if (!nstr_resolve_sel(&np->rgt, &ca[rgt_caidx]))
124                 return -1;
125         } else {
126             /*
127              * Neither side works as selector value; any identifiers
128              * must name selectors.
129              */
130             if (!nstr_resolve_id(&np->lft, ca, lft_caidx,
131                                  nstr_string_ok(ca, rgt_caidx)))
132                 return -1;
133             if (!nstr_resolve_id(&np->rgt, ca, rgt_caidx,
134                                  nstr_string_ok(ca, lft_caidx)))
135                 return -1;
136         }
137
138         /* find operator type, coerce operands */
139         lft_type = nstr_promote(np->lft.val_type);
140         rgt_type = nstr_promote(np->rgt.val_type);
141         np->optype = NSC_NOTYPE;
142         if (lft_type == NSC_TYPEID) {
143             if (!nstr_coerce_val(&np->rgt, NSC_TYPEID, str))
144                 np->optype = NSC_TYPEID;
145         } else if (rgt_type == NSC_TYPEID) {
146             if (!nstr_coerce_val(&np->lft, NSC_TYPEID, str))
147                 np->optype = NSC_TYPEID;
148         } else if (lft_type == NSC_STRING) {
149             if (!nstr_coerce_val(&np->rgt, NSC_STRING, str))
150                 np->optype = NSC_STRING;
151         } else if (rgt_type == NSC_STRING) {
152             if (!nstr_coerce_val(&np->lft, NSC_STRING, str))
153                 np->optype = NSC_STRING;
154         } else if (lft_type == NSC_DOUBLE) {
155             if (!nstr_coerce_val(&np->rgt, NSC_DOUBLE, str))
156                 np->optype = NSC_DOUBLE;
157         } else if (rgt_type == NSC_DOUBLE) {
158             if (!nstr_coerce_val(&np->lft, NSC_DOUBLE, str))
159                 np->optype = NSC_DOUBLE;
160         } else {
161             if (!nstr_coerce_val(&np->lft, NSC_LONG, str)
162                 && !nstr_coerce_val(&np->rgt, NSC_LONG, str))
163                 np->optype = NSC_LONG;
164         }
165         if (np->optype == NSC_NOTYPE)
166             return -1;
167
168         /* another condition? */
169         if (*tail == 0)
170             break;
171         if (*tail != '&') {
172             pr("%s -- expected `&'\n", cond);
173             return -1;
174         }
175         cond = tail + 1;
176     }
177
178     if (i >= len) {
179         /* could just return I and let caller gripe or enlarge buffer */
180         pr("%s -- too many conditions\n", str);
181         return -1;
182     }
183
184     return i + 1;
185 }
186
187 /* Like strcmp(S1, S2), but limit length of S1 to SZ1 and of S2 to SZ2.  */
188 static int
189 strnncmp(char *s1, size_t sz1, char *s2, size_t sz2)
190 {
191     int res;
192     if (sz1 == sz2) return strncmp(s1, s2, sz2);
193     if (sz1 < sz2) return -strnncmp(s2, sz2, s1, sz1);
194     res = strncmp(s1, s2, sz2);
195     return res ? res : s1[sz2];
196 }
197
198 #define EVAL(op, lft, rgt)                      \
199     ((op) == '<' ? (lft) < (rgt)                \
200      : (op) == '=' ? (lft) == (rgt)             \
201      : (op) == '>' ? (lft) > (rgt)              \
202      : (op) == '#' ? (lft) != (rgt)             \
203      : 0)
204
205 /*
206  * Evaluate compiled conditions in array NP[NCOND].
207  * Return non-zero iff they are all true.
208  * PTR points to a context object of the type that was used to compile
209  * the conditions.
210  */
211 int
212 nstr_exec(struct nscstr *np, int ncond, void *ptr)
213 {
214     int i, op, optype, cmp;
215     struct valstr lft, rgt;
216
217     for (i = 0; i < ncond; ++i) {
218         op = np[i].operator;
219         optype = np[i].optype;
220         if (np[i].lft.val_cat == NSC_NOCAT || np[i].rgt.val_cat == NSC_NOCAT)
221             return 0;
222         lft = np[i].lft;
223         nstr_exec_val(&lft, player->cnum, ptr, optype);
224         rgt = np[i].rgt;
225         nstr_exec_val(&rgt, player->cnum, ptr, optype);
226         switch (optype) {
227         case NSC_TYPEID:
228         case NSC_LONG:
229             if (!EVAL(op, lft.val_as.lng, rgt.val_as.lng))
230                 return 0;
231             break;
232         case NSC_DOUBLE:
233             if (!EVAL(op, lft.val_as.dbl, rgt.val_as.dbl))
234                 return 0;
235             break;
236         case NSC_STRING:
237             cmp = strnncmp(lft.val_as.str.base, lft.val_as.str.maxsz,
238                            rgt.val_as.str.base, rgt.val_as.str.maxsz);
239             if (!EVAL(op, cmp, 0))
240                 return 0;
241             break;
242         default:
243             CANT_HAPPEN("bad OPTYPE");
244             return 0;
245         }
246     }
247
248     return 1;
249 }
250
251 /*
252  * Parse a value in STR into VAL.
253  * Return a pointer to the first character after the value.
254  * Value is either evaluated (but not NSC_TYPEID) or an identifier.
255  */
256 static char *
257 nstr_parse_val(char *str, struct valstr *val)
258 {
259     long l;
260     double d;
261     char *tail, *tail2;
262
263     /* string */
264     if (str[0] == '\'') {
265         for (tail = str + 1; *tail && *tail != '\''; ++tail) ;
266         /* FIXME implement \ quoting */
267         val->val_type = NSC_STRING;
268         val->val_cat = NSC_VAL;
269         val->val_as.str.base = str + 1;
270         val->val_as.str.maxsz = tail - (str + 1);
271         if (*tail) ++tail;
272         return tail;
273     }
274
275     /* identifier */
276     if (isalpha(str[0])) {
277         for (tail = str+1; isalnum(*tail) || *tail == '_'; ++tail) ;
278         val->val_type = NSC_NOTYPE;
279         val->val_cat = NSC_ID;
280         val->val_as.str.base = str;
281         val->val_as.str.maxsz = tail - str;
282         return tail;
283     }
284
285     /* number */
286     l = strtol(str, &tail, 0);
287     d = strtod(str, &tail2);
288     if (tail2 > tail) {
289         val->val_type = NSC_DOUBLE;
290         val->val_cat = NSC_VAL;
291         val->val_as.dbl = d;
292         return tail2;
293     }
294     if (tail != str) {
295         val->val_type = NSC_LONG;
296         val->val_cat = NSC_VAL;
297         val->val_as.lng = l;
298         return tail;
299     }
300
301     /* funny character, interpret as identifier */
302     tail = str+1;
303     val->val_type = NSC_NOTYPE;
304     val->val_cat = NSC_ID;
305     val->val_as.str.base = str;
306     val->val_as.str.maxsz = tail - str;
307     return tail;
308 }
309
310 /*
311  * Match VAL in table of selector descriptors CA, return index.
312  * Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
313  * several.
314  * A VAL that is not an identifier doesn't match anything.  A null CA
315  * is considered empty.
316  */
317 static int
318 nstr_match_ca(struct valstr *val, struct castr *ca)
319 {
320     char id[32];
321
322     if (val->val_cat != NSC_ID || val->val_as.str.maxsz >= sizeof(id))
323         return M_NOTFOUND;
324
325     if (!ca)
326         return M_NOTFOUND;
327
328     memcpy(id, val->val_as.str.base, val->val_as.str.maxsz);
329     id[val->val_as.str.maxsz] = 0;
330
331     return stmtch(id, ca, offsetof(struct castr, ca_name),
332                   sizeof(struct castr));
333 }
334
335 /*
336  * Match VAL in a selector's values, return its (non-negative) value.
337  * TYPE is the context type, a file type.
338  * CA is ef_cadef(TYPE).  If it is null, then IDX must be negative.
339  * Match values of selector descriptor CA[IDX], provided IDX is not
340  * negative.
341  * Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
342  * several.
343  * TODO: This is just a stub and works only for NSC_TYPEID.
344  * Generalize: give struct castr enough info to find values, remove
345  * parameter `type'.
346  */
347 static int
348 nstr_match_val(struct valstr *val, int type, struct castr *ca, int idx)
349 {
350     char id[32];
351
352     if (val->val_cat != NSC_ID || val->val_as.str.maxsz >= sizeof(id))
353         return M_NOTFOUND;
354
355     if (idx < 0 || ca[idx].ca_type != NSC_TYPEID)
356         return M_NOTFOUND;
357
358     memcpy(id, val->val_as.str.base, val->val_as.str.maxsz);
359     id[val->val_as.str.maxsz] = 0;
360
361     return typematch(id, type);
362 }
363
364 /*
365  * Can CA[IDX] be compared to a string?
366  * Return 0 for negative IDX.
367  */
368 static int
369 nstr_string_ok(struct castr *ca, int idx)
370 {
371     return idx >= 0 && nstr_promote(ca[idx].ca_type) == NSC_STRING;
372 }
373
374 /*
375  * Change VAL to resolve identifier to selector or string.
376  * Return VAL on success, NULL on error.
377  * No change if VAL is not an identifier.  Otherwise, change it as
378  * follows.
379  * Error if IDX == M_NOTUNIQUE or IDX == M_NOTFOUND and !STRING_OK.
380  * Change into string if IDX == M_NOTFOUND and STRING_OK.
381  * Change into selector CA[IDX] if IDX >= 0.
382  */
383 static struct valstr *
384 nstr_resolve_id(struct valstr *val, struct castr *ca, int idx, int string_ok)
385 {
386     if (val->val_cat != NSC_ID)
387         return val;
388
389     if (idx == M_NOTUNIQUE) {
390         pr("%.*s -- ambiguous name\n",
391            (int)val->val_as.str.maxsz, val->val_as.str.base);
392         val->val_cat = NSC_NOCAT;
393         return NULL;
394     }
395
396     if (idx < 0) {
397         CANT_HAPPEN(idx != M_NOTFOUND);
398         if (!string_ok) {
399             pr("%.*s -- unknown name\n",
400                (int)val->val_as.str.maxsz, val->val_as.str.base);
401             val->val_cat = NSC_NOCAT;
402             return NULL;
403         }
404         /* interpret unbound identifier as string */
405         val->val_type = NSC_STRING;
406         val->val_cat = NSC_VAL;
407         return val;
408     }
409
410     return nstr_resolve_sel(val, &ca[idx]);
411 }
412
413 /*
414  * Change VAL to resolve identifier to selector CA.
415  * Return VAL on success, NULL if the player is denied access to the
416  * selector.
417  * VAL must be an identifier.
418  */
419 static struct valstr *
420 nstr_resolve_sel(struct valstr *val, struct castr *ca)
421 {
422     if (CANT_HAPPEN(val->val_cat != NSC_ID)) {
423         val->val_cat = NSC_NOCAT;
424         return val;
425     }
426
427     if ((ca->ca_flags & NSC_DEITY) && !player->god) {
428         pr("%.*s -- not accessible to mortals\n",
429            (int)val->val_as.str.maxsz, val->val_as.str.base);
430         val->val_cat = NSC_NOCAT;
431         return NULL;
432     }
433
434     val->val_type = ca->ca_type;
435     val->val_cat = NSC_OFF;
436     val->val_as.sym.off = ca->ca_off;
437     val->val_as.sym.len = ca->ca_len;
438     val->val_as.sym.idx = 0;
439     return val;
440 }
441
442 /*
443  * Initialize VAL to value SELVAL for selector CA, return VAL.
444  */
445 static struct valstr *
446 nstr_mkselval(struct valstr *val, int selval, struct castr *ca)
447 {
448     if (CANT_HAPPEN(ca->ca_type != NSC_TYPEID)) {
449         val->val_type = NSC_NOTYPE;
450         val->val_cat = NSC_NOCAT;
451         return val;
452     }
453
454     val->val_type = ca->ca_type;
455     val->val_cat = NSC_VAL;
456     val->val_as.lng = selval;
457     return val;
458 }
459
460 /*
461  * Compile a value in STR into VAL.
462  * Return a pointer to the first character after the value on success,
463  * NULL on error.
464  * TYPE is the context type, a file type.
465  * If STR names an array, VAL simply refers to the element with index
466  * zero.
467  */
468 char *
469 nstr_comp_val(char *str, struct valstr *val, int type)
470 {
471     struct castr *ca = ef_cadef(type);
472     char *tail = nstr_parse_val(str, val);
473     return nstr_resolve_id(val, ca, nstr_match_ca(val, ca), 0) ? tail : NULL;
474 }
475
476
477 /*
478  * Promote VALTYPE.
479  * If VALTYPE is an integer type, return NSC_LONG.
480  * If VALTYPE is a floating-point type, return NSC_DOUBLE.
481  * If VALTYPE is NSC_STRINGY, return NSC_STRING.
482  * If VALTYPE is NSC_NOTYPE, NSC_STRING or NSC_TYPEID, return VALTYPE.
483  */
484 static int
485 nstr_promote(int valtype)
486 {
487     switch (valtype) {
488     case NSC_NOTYPE:
489     case NSC_LONG:
490     case NSC_DOUBLE:
491     case NSC_STRING:
492     case NSC_TYPEID:
493         break;
494     case NSC_CHAR:
495     case NSC_UCHAR:
496     case NSC_SHORT:
497     case NSC_USHORT:
498     case NSC_INT:
499     case NSC_XCOORD:
500     case NSC_YCOORD:
501     case NSC_TIME:
502         valtype = NSC_LONG;
503         break;
504     case NSC_FLOAT:
505         valtype = NSC_DOUBLE;
506         break;
507     case NSC_STRINGY:
508         valtype = NSC_STRING;
509         break;
510     default:
511         CANT_HAPPEN("bad VALTYPE");
512         valtype = NSC_NOTYPE;
513     }
514     return valtype;
515 }
516
517 static int
518 cond_type_mismatch(char *str)
519 {
520     if (str)
521         pr("%s -- condition operand type mismatch\n", str);
522     return -1;
523 }
524
525 /*
526  * Coerce VAL to promoted value type TO.
527  * Return 0 on success, -1 on error.
528  * If VAL is evaluated, convert it, else only check.
529  * STR is the condition text to be used for error messages.  Suppress
530  * messages if it is a null pointer.
531  */
532 int
533 nstr_coerce_val(struct valstr *val, nsc_type to, char *str)
534 {
535     /* FIXME get rid of promotion?  */
536     nsc_type from = nstr_promote(val->val_type);
537
538     if (from == NSC_NOTYPE)
539         return 0;
540
541     if (from != to) {
542         switch (to) {
543         case NSC_TYPEID:
544             return cond_type_mismatch(str);
545         case NSC_STRING:
546             return cond_type_mismatch(str); /* FIXME implement */
547         case NSC_DOUBLE:
548             if (from == NSC_LONG) {
549                 if (val->val_cat == NSC_VAL)
550                     val->val_as.dbl = val->val_as.lng;
551             } else
552                 return cond_type_mismatch(str);
553             break;
554         case NSC_LONG:
555             return cond_type_mismatch(str);
556         default:
557             CANT_HAPPEN("bad TO argument");
558             to = from;
559         }
560     }
561
562     if (val->val_cat == NSC_VAL) {
563         /* coord literals don't occur, conversion not implemented */
564         CANT_HAPPEN(val->val_type == NSC_XCOORD
565                     || val->val_type == NSC_YCOORD);
566         val->val_type = to;
567     }
568
569     return 0;
570 }
571
572 /*
573  * Evaluate VAL.
574  * If VAL is symbolic, evaluate it into a promoted value type.
575  * Use coordinate system of country CNUM.
576  * PTR points to a context object of the type that was used to compile
577  * the value.
578  * Unless WANT is NSC_NOTYPE, coerce the value to promoted value type
579  * WANT.  VAL must be coercible.  That's the case if a previous
580  * nstr_coerce_val(VAL, WANT, STR) succeeded.
581  */
582 void
583 nstr_exec_val(struct valstr *val, natid cnum, void *ptr, nsc_type want)
584 {
585     char *memb_ptr;
586     nsc_type valtype;
587     int idx;
588
589     switch (val->val_cat) {
590     default:
591         CANT_HAPPEN("Bad VAL category");
592         /* fall through */
593     case NSC_VAL:
594         valtype = val->val_type;
595         break;
596     case NSC_OFF:
597         valtype = NSC_LONG;
598         memb_ptr = ptr;
599         memb_ptr += val->val_as.sym.off;
600         idx = val->val_as.sym.idx;
601         switch (val->val_type) {
602         case NSC_CHAR:
603             val->val_as.lng = ((signed char *)memb_ptr)[idx];
604             break;
605         case NSC_UCHAR:
606             val->val_as.lng = ((unsigned char *)memb_ptr)[idx];
607             break;
608         case NSC_SHORT:
609             val->val_as.lng = ((short *)memb_ptr)[idx];
610             break;
611         case NSC_USHORT:
612             val->val_as.lng = ((unsigned short *)memb_ptr)[idx];
613             break;
614         case NSC_INT:
615             val->val_as.lng = ((int *)memb_ptr)[idx];
616             break;
617         case NSC_LONG:
618             val->val_as.lng = ((long *)memb_ptr)[idx];
619             break;
620         case NSC_XCOORD:
621             val->val_as.lng = xrel(getnatp(cnum), ((short *)memb_ptr)[idx]);
622             break;
623         case NSC_YCOORD:
624             val->val_as.lng = yrel(getnatp(cnum), ((short *)memb_ptr)[idx]);
625             break;
626         case NSC_FLOAT:
627             val->val_as.dbl = ((float *)memb_ptr)[idx];
628             valtype = NSC_DOUBLE;
629             break;
630         case NSC_DOUBLE:
631             val->val_as.dbl = ((double *)memb_ptr)[idx];
632             valtype = NSC_DOUBLE;
633             break;
634         case NSC_STRINGY:
635             CANT_HAPPEN(idx);
636             val->val_as.str.maxsz = val->val_as.sym.len;
637             val->val_as.str.base = (char *)memb_ptr;
638             valtype = NSC_STRING;
639             break;
640         case NSC_STRING:
641             val->val_as.str.base = ((char **)memb_ptr)[idx];
642             val->val_as.str.maxsz = INT_MAX;
643             valtype = NSC_STRING;
644             break;
645         case NSC_TIME:
646             val->val_as.lng = ((time_t *)memb_ptr)[idx];
647             break;
648         case NSC_TYPEID:
649             val->val_as.lng = ((signed char *)memb_ptr)[idx];
650             valtype = NSC_TYPEID;
651             break;
652         default:
653             CANT_HAPPEN("Bad VAL type");
654             val->val_as.lng = 0;
655         }
656         val->val_cat = NSC_VAL;
657     }
658
659     if (valtype == want)
660         ;
661     else if (want == NSC_DOUBLE) {
662         if (valtype == NSC_LONG) {
663             valtype = want;
664             val->val_as.dbl = val->val_as.lng;
665         }
666     } else if (want == NSC_STRING)
667         CANT_HAPPEN("unimplemented WANT"); /* FIXME */
668
669     if (CANT_HAPPEN(valtype != want && want != NSC_NOTYPE)) {
670         valtype = want;
671         switch (want) {
672         case NSC_TYPEID:
673         case NSC_LONG: val->val_as.lng = 0; break;
674         case NSC_DOUBLE: val->val_as.dbl = 0.0; break;
675         case NSC_STRING: val->val_as.str.base = NULL; break;
676         default:
677             CANT_HAPPEN("bad WANT argument");
678         }
679     }
680
681     val->val_type = valtype;
682 }
683
684 char *
685 symbol_by_value(int key, struct symbol *table)
686 {
687     int i;
688
689     for (i = 0; table[i].name; i++)
690         if (key == table[i].value)
691             return table[i].name;
692
693     return NULL;
694 }