1 /*
2
3 MPDM - Minimum Profit Data Manager
4 mpdm_x.c - Extended functions
5
6 ttcdt <dev@triptico.com> et al.
7
8 This software is released into the public domain.
9 NO WARRANTY. See file LICENSE for details.
10
11 */
12
13 #include "config.h"
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <locale.h>
19 #include <wchar.h>
20
21 #include "mpdm.h"
22
23 /** code **/
24
mpdm_new_x(mpdm_type_t type,const void * f,mpdm_t a)25 mpdm_t mpdm_new_x(mpdm_type_t type, const void *f, mpdm_t a)
26 {
27 mpdm_t r = NULL;
28
29 switch (type) {
30 case MPDM_TYPE_FUNCTION:
31 r = mpdm_new(type, f, 0);
32 break;
33
34 case MPDM_TYPE_PROGRAM:
35 r = mpdm_new(type, NULL, 0);
36
37 mpdm_push(r, MPDM_X(f));
38 mpdm_push(r, a);
39
40 break;
41
42 default:
43 r = NULL;
44 }
45
46 return r;
47 }
48
49
50 /**
51 * mpdm_is_true - Returns 1 if a value is true.
52 * @v: the value
53 *
54 * Returns 1 if @v is true. False values are: NULL, integers
55 * with value 0, reals with value 0.0, empty strings and the
56 * special string "0".
57 * The reference count is touched.
58 */
mpdm_is_true(mpdm_t v)59 int mpdm_is_true(mpdm_t v)
60 {
61 int r;
62
63 mpdm_ref(v);
64 r = mpdm_type_vc(v)->is_true(v);
65 mpdm_unref(v);
66
67 return r;
68 }
69
70
71 /**
72 * mpdm_bool - Returns a boolean value.
73 * @b: true or false
74 *
75 * Returns the stored values for TRUE or FALSE.
76 */
mpdm_bool(int b)77 mpdm_t mpdm_bool(int b)
78 {
79 return mpdm_get_wcs(mpdm_root(), b ? L"TRUE" : L"FALSE");
80 }
81
82
mpdm_count(mpdm_t v)83 int mpdm_count(mpdm_t v)
84 {
85 return mpdm_type_vc(v)->count(v);
86 }
87
88
89 /**
90 * mpdm_get_i - Gets an element by integer subscript.
91 * @s: the set
92 * @index: the subscript of the element
93 *
94 * Returns the element at @index of the set @s.
95 */
mpdm_get_i(const mpdm_t s,int index)96 mpdm_t mpdm_get_i(const mpdm_t s, int index)
97 {
98 return mpdm_type_vc(s)->get_i(s, index);
99 }
100
101
102 /**
103 * mpdm_get - Gets an element by index.
104 * @s: the set
105 * @index: the index
106 *
107 * Returns the element at @index of the set @s.
108 */
mpdm_get(const mpdm_t s,mpdm_t index)109 mpdm_t mpdm_get(const mpdm_t s, mpdm_t index)
110 {
111 return mpdm_type_vc(s)->get(s, index);
112 }
113
114
115 /**
116 * mpdm_del_i - Deletes an element by integer subscript.
117 * @s: the set
118 * @index: subscript of the element to be deleted
119 *
120 * Deletes the element at @index of the @s set.
121 */
mpdm_del_i(const mpdm_t s,int index)122 mpdm_t mpdm_del_i(const mpdm_t s, int index)
123 {
124 return mpdm_type_vc(s)->del_i(s, index);
125 }
126
127
mpdm_del(const mpdm_t s,mpdm_t index)128 mpdm_t mpdm_del(const mpdm_t s, mpdm_t index)
129 {
130 return mpdm_type_vc(s)->del(s, index);
131 }
132
133
mpdm_set_i(mpdm_t s,mpdm_t e,int index)134 mpdm_t mpdm_set_i(mpdm_t s, mpdm_t e, int index)
135 {
136 return mpdm_type_vc(s)->set_i(s, e, index);
137 }
138
139
mpdm_set(mpdm_t s,mpdm_t e,mpdm_t index)140 mpdm_t mpdm_set(mpdm_t s, mpdm_t e, mpdm_t index)
141 {
142 return mpdm_type_vc(s)->set(s, e, index);
143 }
144
145
146 /**
147 * mpdm_exec - Executes an executable value.
148 * @c: the code value
149 * @args: the arguments
150 * @ctxt: the context
151 *
152 * Executes an executable value. If @c is a scalar value, its data
153 * should be a pointer to a directly executable C function with a
154 * prototype of mpdm_t func(mpdm_t args, mpdm_t ctxt); if it's a multiple
155 * one, the first value's data should be a pointer to a directly executable
156 * C function with a prototype of
157 * mpdm_t func(mpdm_t b, mpdm_t args, mpdm_t ctxt) and
158 * the second value will be passed as the @b argument. This value is used
159 * to store bytecode or so when implementing virtual machines or compilers.
160 * The @ctxt is meant to be used as a special context to implement local
161 * symbol tables and such. Its meaning is free and can be NULL.
162 * If @c is a file descriptor, a line is read from it if the call has no
163 * arguments or otherwise all of them are written into it.
164 *
165 * Returns the return value of the code. If @c is NULL or not executable,
166 * returns NULL.
167 * [Value Management]
168 */
mpdm_exec(mpdm_t c,mpdm_t args,mpdm_t ctxt)169 mpdm_t mpdm_exec(mpdm_t c, mpdm_t args, mpdm_t ctxt)
170 {
171 mpdm_t r = NULL;
172
173 mpdm_ref(c);
174 mpdm_ref(args);
175 mpdm_ref(ctxt);
176
177 r = mpdm_type_vc(c)->exec(c, args, ctxt);
178
179 mpdm_ref(r);
180
181 mpdm_unref(ctxt);
182 mpdm_unref(args);
183 mpdm_unref(c);
184
185 return mpdm_unrefnd(r);
186 }
187
188
mpdm_exec_1(mpdm_t c,mpdm_t a1,mpdm_t ctxt)189 mpdm_t mpdm_exec_1(mpdm_t c, mpdm_t a1, mpdm_t ctxt)
190 {
191 mpdm_t a = MPDM_A(1);
192
193 mpdm_set_i(a, a1, 0);
194
195 return mpdm_exec(c, a, ctxt);
196 }
197
198
mpdm_exec_2(mpdm_t c,mpdm_t a1,mpdm_t a2,mpdm_t ctxt)199 mpdm_t mpdm_exec_2(mpdm_t c, mpdm_t a1, mpdm_t a2, mpdm_t ctxt)
200 {
201 mpdm_t a = MPDM_A(2);
202
203 mpdm_set_i(a, a1, 0);
204 mpdm_set_i(a, a2, 1);
205
206 return mpdm_exec(c, a, ctxt);
207 }
208
209
mpdm_exec_3(mpdm_t c,mpdm_t a1,mpdm_t a2,mpdm_t a3,mpdm_t ctxt)210 mpdm_t mpdm_exec_3(mpdm_t c, mpdm_t a1, mpdm_t a2, mpdm_t a3, mpdm_t ctxt)
211 {
212 mpdm_t a = MPDM_A(3);
213
214 mpdm_set_i(a, a1, 0);
215 mpdm_set_i(a, a2, 1);
216 mpdm_set_i(a, a3, 2);
217
218 return mpdm_exec(c, a, ctxt);
219 }
220
221
222 /**
223 * mpdm_iterator - Iterates through the content of a set.
224 * @set: the set (hash, array, file or scalar)
225 * @context: A pointer to an opaque context
226 * @v: a pointer to a value to store the key
227 * @i: a pointer to a value to store the index
228 *
229 * Iterates through the @set. If it's a hash, every value/index pair
230 * is returned on each call. If it's an array, @v contains the
231 * element and @i the index number on each call. If it's a file,
232 * @v contains the line read and @i the index number. Otherwise, it's
233 * assumed to be a string containing a numeral and @v and @i are filled
234 * with values from 0 to @set - 1 on each call.
235 *
236 * Any of @v and @i pointers can be NULL if the value is not of interest.
237 *
238 * The @context pointer to integer is opaque and should be
239 * initialized to zero on the first call.
240 *
241 * Returns 0 if no more data is left in @set.
242 * [Hashes]
243 * [Arrays]
244 */
mpdm_iterator(mpdm_t set,int * context,mpdm_t * v,mpdm_t * i)245 int mpdm_iterator(mpdm_t set, int *context, mpdm_t *v, mpdm_t *i)
246 {
247 int ret = 0;
248
249 mpdm_ref(set);
250 ret = mpdm_type_vc(set)->iterator(set, context, v, i);
251 mpdm_unrefnd(set);
252
253 return ret;
254 }
255
256
vc_default_map(mpdm_t set,mpdm_t filter,mpdm_t ctxt)257 mpdm_t vc_default_map(mpdm_t set, mpdm_t filter, mpdm_t ctxt)
258 {
259 mpdm_t r = MPDM_A(0);
260 int n = 0;
261 mpdm_t v, i;
262
263 while (mpdm_iterator(set, &n, &v, &i)) {
264 mpdm_t w = NULL;
265 mpdm_ref(v);
266 mpdm_ref(i);
267
268 switch (mpdm_type(filter)) {
269 case MPDM_TYPE_FUNCTION:
270 case MPDM_TYPE_PROGRAM:
271 w = mpdm_exec_2(filter, v, i, ctxt);
272 break;
273
274 case MPDM_TYPE_ARRAY:
275 case MPDM_TYPE_OBJECT:
276 w = mpdm_get(filter, v);
277 break;
278
279 case MPDM_TYPE_REGEX:
280 w = mpdm_regex(v, filter, 0);
281 break;
282
283 case MPDM_TYPE_STRING:
284 w = mpdm_fmt(filter, v);
285 break;
286
287 default:
288 w = v;
289 break;
290 }
291
292 mpdm_push(r, w);
293
294 mpdm_unref(i);
295 mpdm_unref(v);
296 }
297
298 return r;
299 }
300
301
mpdm_map(mpdm_t set,mpdm_t filter,mpdm_t ctxt)302 mpdm_t mpdm_map(mpdm_t set, mpdm_t filter, mpdm_t ctxt)
303 {
304 mpdm_t r;
305
306 mpdm_ref(set);
307 mpdm_ref(filter);
308 mpdm_ref(ctxt);
309
310 r = mpdm_type_vc(set)->map(set, filter, ctxt);
311
312 mpdm_unref(ctxt);
313 mpdm_unref(filter);
314 mpdm_unref(set);
315
316 return r;
317 }
318
319
mpdm_omap(mpdm_t set,mpdm_t filter,mpdm_t ctxt)320 mpdm_t mpdm_omap(mpdm_t set, mpdm_t filter, mpdm_t ctxt)
321 {
322 mpdm_t v, i;
323 mpdm_t out = MPDM_O();
324 int n = 0;
325
326 mpdm_ref(set);
327 mpdm_ref(filter);
328 mpdm_ref(ctxt);
329
330 out = MPDM_O();
331
332 while (mpdm_iterator(set, &n, &v, &i)) {
333 mpdm_t w = NULL;
334 mpdm_ref(i);
335 mpdm_ref(v);
336
337 switch (mpdm_type(filter)) {
338 case MPDM_TYPE_NULL:
339 /* invert hash */
340 w = MPDM_A(2);
341 mpdm_set_i(w, v, 0);
342 mpdm_set_i(w, i, 1);
343
344 break;
345
346 case MPDM_TYPE_FUNCTION:
347 case MPDM_TYPE_PROGRAM:
348 w = mpdm_exec_2(filter, v, i, ctxt);
349 break;
350
351 case MPDM_TYPE_ARRAY:
352 /* the set provides the values,
353 the filter array provides the indexes */
354 w = MPDM_A(2);
355 mpdm_set_i(w, v, 0);
356 mpdm_set_i(w, mpdm_get(filter, i), 1);
357
358 break;
359
360 case MPDM_TYPE_OBJECT:
361 w = mpdm_get(filter, v);
362 break;
363
364 case MPDM_TYPE_REGEX:
365 w = mpdm_regex(v, filter, 0);
366 break;
367
368 case MPDM_TYPE_STRING:
369 w = mpdm_fmt(filter, v);
370 break;
371
372 default:
373 break;
374 }
375
376 mpdm_ref(w);
377
378 /* if the filtered value is an array, it's a value/index pair */
379 if (mpdm_type(w) == MPDM_TYPE_ARRAY)
380 mpdm_set(out, mpdm_get_i(w, 0), mpdm_get_i(w, 1));
381 else
382 mpdm_set(out, w, i);
383
384 mpdm_unref(w);
385 mpdm_unref(v);
386 mpdm_unref(i);
387 }
388
389 mpdm_unref(ctxt);
390 mpdm_unref(filter);
391 mpdm_unref(set);
392
393 return out;
394 }
395
396
mpdm_grep(mpdm_t set,mpdm_t filter,mpdm_t ctxt)397 mpdm_t mpdm_grep(mpdm_t set, mpdm_t filter, mpdm_t ctxt)
398 {
399 mpdm_t out = NULL;
400
401 mpdm_ref(set);
402 mpdm_ref(filter);
403 mpdm_ref(ctxt);
404
405 if (set != NULL) {
406 mpdm_t v, i;
407 int n = 0;
408
409 out = mpdm_type(set) == MPDM_TYPE_OBJECT ? MPDM_O() : MPDM_A(0);
410
411 while (mpdm_iterator(set, &n, &v, &i)) {
412 mpdm_t w = NULL;
413 mpdm_ref(i);
414 mpdm_ref(v);
415
416 switch (mpdm_type(filter)) {
417 case MPDM_TYPE_FUNCTION:
418 case MPDM_TYPE_PROGRAM:
419 w = mpdm_exec_2(filter, v, i, ctxt);
420 break;
421
422 case MPDM_TYPE_REGEX:
423 case MPDM_TYPE_STRING:
424 w = mpdm_regex(v, filter, 0);
425 break;
426
427 case MPDM_TYPE_OBJECT:
428 w = mpdm_bool(mpdm_exists(filter, v));
429 break;
430
431 default:
432 break;
433 }
434
435 if (mpdm_is_true(w)) {
436 if (mpdm_type(out) == MPDM_TYPE_OBJECT)
437 mpdm_set(out, v, i);
438 else
439 mpdm_push(out, v);
440 }
441
442 mpdm_unref(v);
443 mpdm_unref(i);
444 }
445 }
446
447 mpdm_unref(ctxt);
448 mpdm_unref(filter);
449 mpdm_unref(set);
450
451 return out;
452 }
453
454
455 /**
456 * mpdm_join - Joins two values.
457 * @a: first value
458 * @b: second value
459 *
460 * Joins two values. If both are hashes, a new hash containing the
461 * pairs in @a overwritten with the keys in @b is returned; if both
462 * are arrays, a new array is returned with all elements in @a followed
463 * by all elements in b; if @a is an array and @b is a string,
464 * a new string is returned with all elements in @a joined using @b
465 * as a separator; and if @a is a hash and @b is a string, a new array
466 * is returned containing all pairs in @a joined using @b as a separator.
467 * [Arrays]
468 * [Hashes]
469 * [Strings]
470 */
mpdm_join(const mpdm_t a,const mpdm_t b)471 mpdm_t mpdm_join(const mpdm_t a, const mpdm_t b)
472 {
473 int n, c = 0;
474 mpdm_t r, v, i;
475
476 mpdm_ref(a);
477 mpdm_ref(b);
478
479 switch (mpdm_type(a)) {
480 case MPDM_TYPE_OBJECT:
481
482 switch (mpdm_type(b)) {
483 case MPDM_TYPE_OBJECT:
484 /* hash~hash -> hash */
485 r = MPDM_O();
486
487 n = 0;
488 while (mpdm_iterator(a, &n, &v, &i))
489 mpdm_set(r, v, i);
490 n = 0;
491 while (mpdm_iterator(b, &n, &v, &i))
492 mpdm_set(r, v, i);
493
494 break;
495
496 case MPDM_TYPE_ARRAY:
497 /* hash~array -> hash */
498 r = MPDM_O();
499
500 /* the array is a list of pairs */
501 for (n = 0; n < mpdm_size(b); n += 2)
502 mpdm_set(r, mpdm_get_i(b, n + 1), mpdm_get_i(b, n));
503
504 break;
505
506 case MPDM_TYPE_STRING:
507 /* hash~string -> array */
508 r = MPDM_A(mpdm_count(a));
509
510 n = 0;
511 while (mpdm_iterator(a, &n, &v, &i))
512 mpdm_set_i(r, mpdm_strcat(i, mpdm_strcat(b, v)), c++);
513
514 break;
515
516 default:
517 r = NULL;
518 break;
519 }
520
521 break;
522
523 case MPDM_TYPE_ARRAY:
524 case MPDM_TYPE_FILE:
525 switch (mpdm_type(b)) {
526 case MPDM_TYPE_ARRAY:
527 case MPDM_TYPE_FILE:
528 /* array~array -> array */
529 r = MPDM_A(0);
530
531 n = 0;
532 while (mpdm_iterator(a, &n, &v, NULL))
533 mpdm_push(r, v);
534 n = 0;
535 while (mpdm_iterator(b, &n, &v, NULL))
536 mpdm_push(r, v);
537
538 break;
539
540 case MPDM_TYPE_STRING:
541 case MPDM_TYPE_NULL:
542 /* array~string -> string */
543 r = mpdm_join_wcs(a, b ? mpdm_string(b) : NULL);
544
545 break;
546
547 default:
548 r = NULL;
549 break;
550 }
551
552 break;
553
554 case MPDM_TYPE_STRING:
555 /* string~string -> string */
556 r = mpdm_strcat(a, b);
557 break;
558
559 case MPDM_TYPE_INTEGER:
560 case MPDM_TYPE_REAL:
561 /* real~real -> sum! */
562 r = MPDM_R(mpdm_rval(a) + mpdm_rval(b));
563 break;
564
565 default:
566 r = NULL;
567 break;
568 }
569
570 mpdm_unref(b);
571 mpdm_unref(a);
572
573 return r;
574 }
575
576
577 mpdm_t mpdm_splice_a(const mpdm_t v, const mpdm_t i,
578 int offset, int del, mpdm_t *n, mpdm_t *d);
579 mpdm_t mpdm_splice_s(const mpdm_t v, const mpdm_t i,
580 int offset, int del, mpdm_t *n, mpdm_t *d);
581
mpdm_splice(const mpdm_t v,const mpdm_t i,int offset,int del,mpdm_t * n,mpdm_t * d)582 mpdm_t mpdm_splice(const mpdm_t v, const mpdm_t i,
583 int offset, int del, mpdm_t *n, mpdm_t *d)
584 {
585 mpdm_t r;
586
587 switch (mpdm_type(v)) {
588 case MPDM_TYPE_NULL:
589 case MPDM_TYPE_STRING:
590 r = mpdm_splice_s(v, i, offset, del, n, d);
591 break;
592
593 case MPDM_TYPE_ARRAY:
594 r = mpdm_splice_a(v, i, offset, del, n, d);
595 break;
596
597 default:
598 r = NULL;
599 break;
600 }
601
602 return r;
603 }
604
605
606 /**
607 * mpdm_cmp - Compares two values.
608 * @v1: the first value
609 * @v2: the second value
610 *
611 * Compares two values. If both has the MPDM_STRING flag set,
612 * a comparison using wcscoll() is returned; if both are arrays,
613 * the size is compared first and, if they have the same number
614 * elements, each one is compared; otherwise, a simple visual
615 * representation comparison is done.
616 * [Strings]
617 */
mpdm_cmp(const mpdm_t v1,const mpdm_t v2)618 int mpdm_cmp(const mpdm_t v1, const mpdm_t v2)
619 {
620 int r;
621
622 mpdm_ref(v1);
623 mpdm_ref(v2);
624
625 /* same values? */
626 if (v1 == v2)
627 r = 0;
628 else
629 if (v1 == NULL)
630 r = -1;
631 else
632 if (v2 == NULL)
633 r = 1;
634 else {
635 switch (mpdm_type(v1)) {
636 case MPDM_TYPE_NULL:
637 r = -1;
638 break;
639
640 case MPDM_TYPE_INTEGER:
641 r = mpdm_ival(v1) - mpdm_ival(v2);
642 break;
643
644 case MPDM_TYPE_REAL:
645 {
646 double d = mpdm_rval(v1) - mpdm_rval(v2);
647 r = d < 0.0 ? -1 : d > 0.0 ? 1 : 0;
648 }
649 break;
650
651 case MPDM_TYPE_ARRAY:
652 case MPDM_TYPE_OBJECT:
653 case MPDM_TYPE_PROGRAM:
654
655 if (mpdm_type(v2) == mpdm_type(v1)) {
656 /* if they are the same size, compare elements one by one */
657 if ((r = mpdm_size(v1) - mpdm_size(v2)) == 0) {
658 int n = 0;
659 mpdm_t v, i;
660
661 while (mpdm_iterator(v1, &n, &v, &i)) {
662 if ((r = mpdm_cmp(v, mpdm_get(v2, i))) != 0)
663 break;
664 }
665 }
666
667 break;
668 }
669
670 /* fallthrough */
671
672 default:
673 r = mpdm_cmp_wcs(v1, v2 ? mpdm_string(v2) : NULL);
674 break;
675 }
676 }
677
678 mpdm_unref(v2);
679 mpdm_unref(v1);
680
681 return r;
682 }
683
684
mpdm_multiply(mpdm_t v,mpdm_t i)685 mpdm_t mpdm_multiply(mpdm_t v, mpdm_t i)
686 {
687 mpdm_t r = NULL;
688
689 switch (mpdm_type(v)) {
690 case MPDM_TYPE_INTEGER:
691 case MPDM_TYPE_REAL:
692 r = MPDM_R(mpdm_rval(v) * mpdm_rval(i));
693 break;
694
695 case MPDM_TYPE_STRING:
696 /* replicate string */
697 {
698 int n = mpdm_ival(i);
699 wchar_t *ptr = NULL;
700 int z = 0;
701
702 while (n) {
703 ptr = mpdm_pokev(ptr, &z, v);
704 n--;
705 }
706
707 r = MPDM_NS(ptr, z);
708 }
709
710 break;
711
712 case MPDM_TYPE_ARRAY:
713 /* replicate an array */
714 {
715 int m, n, c;
716
717 c = mpdm_ival(i);
718 r = MPDM_A(c * mpdm_size(v));
719
720 for (n = 0; n < mpdm_size(v); n++) {
721 mpdm_t w = mpdm_get_i(v, n);
722
723 for (m = 0; m < c; m++)
724 mpdm_set_i(r, w, m * mpdm_size(v) + n);
725 }
726 }
727
728 break;
729
730 default:
731 break;
732 }
733
734 return r;
735 }
736
737
mpdm_substract(mpdm_t m,mpdm_t s)738 mpdm_t mpdm_substract(mpdm_t m, mpdm_t s)
739 {
740 int n;
741 mpdm_t v, i;
742 mpdm_t r = NULL;
743
744 switch (mpdm_type(m)) {
745 case MPDM_TYPE_INTEGER:
746 case MPDM_TYPE_REAL:
747 case MPDM_TYPE_STRING:
748 r = MPDM_R(mpdm_rval(m) - mpdm_rval(s));
749 break;
750
751 case MPDM_TYPE_ARRAY:
752 switch (mpdm_type(s)) {
753 case MPDM_TYPE_ARRAY:
754 r = MPDM_A(0);
755
756 for (n = 0; n < mpdm_size(m); n++) {
757 mpdm_t w = mpdm_get_i(m, n);
758
759 if (mpdm_seek(s, w, 1) == -1)
760 mpdm_push(r, w);
761 }
762
763 break;
764
765 case MPDM_TYPE_OBJECT:
766 r = MPDM_A(0);
767
768 for (n = 0; n < mpdm_size(m); n++) {
769 mpdm_t w = mpdm_get_i(m, n);
770
771 if (!mpdm_exists(s, w))
772 mpdm_push(r, w);
773 }
774
775 break;
776
777 default:
778 break;
779 }
780
781 break;
782
783 case MPDM_TYPE_OBJECT:
784 switch (mpdm_type(s)) {
785 case MPDM_TYPE_ARRAY:
786 r = MPDM_O();
787
788 n = 0;
789 while (mpdm_iterator(m, &n, &v, &i)) {
790 if (mpdm_seek(s, i, 1) == -1)
791 mpdm_set(r, v, i);
792 }
793
794 break;
795
796 case MPDM_TYPE_OBJECT:
797 r = MPDM_O();
798
799 n = 0;
800 while (mpdm_iterator(m, &n, &v, &i)) {
801 if (!mpdm_exists(s, i))
802 mpdm_set(r, v, i);
803 }
804
805 break;
806
807 default:
808 break;
809 }
810
811 break;
812
813 default:
814 break;
815 }
816
817 return r;
818 }
819