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