1 /*
2 * This file is part of DGD, https://github.com/dworkin/dgd
3 * Copyright (C) 1993-2010 Dworkin B.V.
4 * Copyright (C) 2010-2013 DGD Authors (see the commit log for details)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (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 Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 # include "dgd.h"
21 # include "str.h"
22 # include "array.h"
23 # include "object.h"
24 # include "xfloat.h"
25 # include "data.h"
26 # include "interpret.h"
27 # include "control.h"
28 # include "table.h"
29 # include <math.h>
30
31 # define EXTENSION_MAJOR 0
32 # define EXTENSION_MINOR 6
33
34
35 /*
36 * NAME: ext->frame_object()
37 * DESCRIPTION: return the current object
38 */
ext_frame_object(frame * f)39 static object *ext_frame_object(frame *f)
40 {
41 return (f->lwobj == NULL) ? OBJW(f->oindex) : NULL;
42 }
43
44 /*
45 * NAME: ext->frame_dataspace()
46 * DESCRIPTION: return the current dataspace
47 */
ext_frame_dataspace(frame * f)48 static dataspace *ext_frame_dataspace(frame *f)
49 {
50 return f->data;
51 }
52
53 /*
54 * NAME: ext->frame_arg()
55 * DESCRIPTION: return the given argument
56 */
ext_frame_arg(frame * f,int nargs,int arg)57 static value *ext_frame_arg(frame *f, int nargs, int arg)
58 {
59 return f->sp + nargs - arg - 1;
60 }
61
62 /*
63 * NAME: ext->frame_atomic()
64 * DESCRIPTION: running atomically?
65 */
ext_frame_atomic(frame * f)66 static int ext_frame_atomic(frame *f)
67 {
68 return (f->level != 0);
69 }
70
71 /*
72 * NAME: ext->frame_push_int()
73 * DESCRIPTION: push a number on the stack
74 */
ext_frame_push_int(frame * f,Int i)75 Int ext_frame_push_int(frame *f, Int i)
76 {
77 (--f->sp)->type = T_INT;
78 return f->sp->u.number = i;
79 }
80
81 /*
82 * NAME: ext->frame_push_lvalue()
83 * DESCRIPTION: push an lvalue on the stack
84 */
ext_frame_push_lvalue(frame * f,value * v,int t)85 void ext_frame_push_lvalue(frame *f, value *v, int t)
86 {
87 (--f->sp)->type = T_LVALUE;
88 f->sp->oindex = t;
89 f->sp->u.lval = v;
90 }
91
92 /*
93 * NAME: ext->frame_push_lvclass()
94 * DESCRIPTION: push an lvalue class on the stack
95 */
ext_frame_push_lvclass(frame * f,Int t)96 void ext_frame_push_lvclass(frame *f, Int t)
97 {
98 f->lip->type = T_INT;
99 (f->lip++)->u.number = t;
100 }
101
102 /*
103 * NAME: ext->frame_pop_int()
104 * DESCRIPTION: pop a number from the stack
105 */
ext_frame_pop_int(frame * f)106 Int ext_frame_pop_int(frame *f)
107 {
108 return (f->sp++)->u.number;
109 }
110
111 /*
112 * NAME: ext->frame_pop_truthval()
113 * DESCRIPTION: pop a truth value from the stack
114 */
ext_frame_pop_truthval(frame * f)115 int ext_frame_pop_truthval(frame *f)
116 {
117 switch (f->sp->type) {
118 case T_NIL:
119 f->sp++;
120 return FALSE;
121
122 case T_INT:
123 return (f->sp++)->u.number != 0;
124
125 case T_FLOAT:
126 f->sp++;
127 return !VFLT_ISZERO(f->sp - 1);
128
129 case T_STRING:
130 str_del(f->sp->u.string);
131 break;
132
133 case T_ARRAY:
134 case T_MAPPING:
135 case T_LWOBJECT:
136 arr_del(f->sp->u.array);
137 break;
138 }
139 f->sp++;
140 return TRUE;
141 }
142
143 /*
144 * NAME: ext->frame_store()
145 * DESCRIPTION: store a value
146 */
ext_frame_store(frame * f)147 void ext_frame_store(frame *f)
148 {
149 i_store(f);
150 --f->sp;
151 f->sp[0] = f->sp[-1];
152 }
153
154 /*
155 * NAME: ext->frame_store_int()
156 * DESCRIPTION: store an integer value
157 */
ext_frame_store_int(frame * f)158 Int ext_frame_store_int(frame *f)
159 {
160 i_store(f);
161 return f->sp[-2].u.number;
162 }
163
164 /*
165 * NAME: ext->frame_kfun()
166 * DESCRIPTION: call a kernel function
167 */
ext_frame_kfun(frame * f,int n)168 void ext_frame_kfun(frame *f, int n)
169 {
170 register kfunc *kf;
171
172 kf = &kftab[n];
173 if (PROTO_CLASS(kf->proto) & C_TYPECHECKED) {
174 i_typecheck(f, (frame *) NULL, kf->name, "kfun", kf->proto,
175 PROTO_NARGS(kf->proto), TRUE);
176 }
177 n = (*kf->func)(f, PROTO_NARGS(kf->proto), kf);
178 if (n != 0) {
179 error("Bad argument %d for kfun %s", n, kf->name);
180 }
181 }
182
183 /*
184 * NAME: ext->frame_kfun_arg()
185 * DESCRIPTION: call a kernel function with variable # of arguments
186 */
ext_frame_kfun_arg(frame * f,int n,int nargs)187 void ext_frame_kfun_arg(frame *f, int n, int nargs)
188 {
189 register kfunc *kf;
190
191 kf = &kftab[n];
192 if (PROTO_CLASS(kf->proto) & C_TYPECHECKED) {
193 i_typecheck(f, (frame *) NULL, kf->name, "kfun", kf->proto, nargs,
194 TRUE);
195 }
196 n = (*kf->func)(f, nargs, kf);
197 if (n != 0) {
198 error("Bad argument %d for kfun %s", n, kf->name);
199 }
200 }
201
202 /*
203 * NAME: ext->value_type()
204 * DESCRIPTION: return the type of a value
205 */
ext_value_type(value * val)206 static int ext_value_type(value *val)
207 {
208 return val->type;
209 }
210
211 /*
212 * NAME: ext->value_nil()
213 * DESCRIPTION: return nil
214 */
ext_value_nil()215 static value *ext_value_nil()
216 {
217 return &nil_value;
218 }
219
220 /*
221 * NAME: ext->value_temp()
222 * DESCRIPTION: return a scratch value
223 */
ext_value_temp(dataspace * data)224 static value *ext_value_temp(dataspace *data)
225 {
226 static value temp;
227
228 UNREFERENCED_PARAMETER(data);
229 return &temp;
230 }
231
232 /*
233 * NAME: ext->int_getval()
234 * DESCRIPTION: retrieve an int from a value
235 */
ext_int_getval(value * val)236 static Int ext_int_getval(value *val)
237 {
238 return val->u.number;
239 }
240
241 /*
242 * NAME: ext->int_putval()
243 * DESCRIPTION: store an int in a value
244 */
ext_int_putval(value * val,Int i)245 static void ext_int_putval(value *val, Int i)
246 {
247 PUT_INTVAL(val, i);
248 }
249
250 /*
251 * NAME: ext->int_div()
252 * DESCRIPTION: perform integer division
253 */
ext_int_div(Int i,Int d)254 Int ext_int_div(Int i, Int d)
255 {
256 if (d == 0) {
257 error("Division by zero");
258 }
259 if ((i | d) < 0) {
260 Int r;
261
262 r = ((Uint) ((i < 0) ? -i : i)) / ((Uint) ((d < 0) ? -d : d));
263 return ((i ^ d) < 0) ? -r : r;
264 }
265 return ((Uint) i) / ((Uint) d);
266 }
267
268 /*
269 * NAME: ext->int_mod()
270 * DESCRIPTION: perform integer modulus
271 */
ext_int_mod(Int i,Int d)272 Int ext_int_mod(Int i, Int d)
273 {
274 if (d == 0) {
275 error("Modulus by zero");
276 }
277 if (d < 0) {
278 d = -d;
279 }
280 if (i < 0) {
281 return - (Int) (((Uint) -i) % ((Uint) d));
282 }
283 return ((Uint) i) % ((Uint) d);
284 }
285
286 /*
287 * NAME: ext->int_lshift()
288 * DESCRIPTION: perform left shift
289 */
ext_int_lshift(Int i,Int shift)290 Int ext_int_lshift(Int i, Int shift)
291 {
292 if ((shift & ~31) != 0) {
293 if (shift < 0) {
294 error("Negative left shift");
295 }
296 return 0;
297 }
298 return (Uint) i << shift;
299 }
300
301 /*
302 * NAME: ext->int_rshift()
303 * DESCRIPTION: perform right shift
304 */
ext_int_rshift(Int i,Int shift)305 Int ext_int_rshift(Int i, Int shift)
306 {
307 if ((shift & ~31) != 0) {
308 if (shift < 0) {
309 error("Negative right shift");
310 }
311 return 0;
312 }
313 return (Uint) i >> shift;
314 }
315
316 # ifndef NOFLOAT
317 /*
318 * NAME: ext->float_getval()
319 * DESCRIPTION: retrieve a float from a value
320 */
ext_float_getval(value * val)321 static long double ext_float_getval(value *val)
322 {
323 xfloat flt;
324 register double d;
325
326 GET_FLT(val, flt);
327 if ((flt.high | flt.low) == 0) {
328 return 0.0;
329 } else {
330 d = ldexp((double) (0x10 | (flt.high & 0xf)), 32);
331 d = ldexp(d + flt.low, ((flt.high >> 4) & 0x7ff) - 1023 - 36);
332 return (long double) ((flt.high >> 15) ? -d : d);
333 }
334 }
335
336 /*
337 * NAME: ext->float_putval()
338 * DESCRIPTION: store a float in a value
339 */
ext_float_putval(value * val,long double ld)340 static void ext_float_putval(value *val, long double ld)
341 {
342 double d;
343 xfloat flt;
344 bool sign;
345 int e;
346
347 d = (double) ld;
348 if (d == 0.0) {
349 flt.high = 0;
350 flt.low = 0;
351 } else {
352 sign = (d < 0.0);
353 d = ldexp(frexp(fabs(d), &e), 5);
354 flt.high = ((unsigned short) sign << 15) | ((e - 1 + 1023) << 4) |
355 ((unsigned short) d & 0xf);
356 flt.low = (Uint) (ldexp(d - (int) d, 32) + 0.5);
357 }
358 PUT_FLTVAL(val, flt);
359 }
360 # endif
361
362 /*
363 * NAME: ext->string_getval()
364 * DESCRIPTION: retrieve a string from a value
365 */
ext_string_getval(value * val)366 static string *ext_string_getval(value *val)
367 {
368 return val->u.string;
369 }
370
371 /*
372 * NAME: ext->string_putval()
373 * DESCRIPTION: store a string in a value
374 */
ext_string_putval(value * val,string * str)375 static void ext_string_putval(value *val, string *str)
376 {
377 PUT_STRVAL_NOREF(val, str);
378 }
379
380 /*
381 * NAME: ext->string_new()
382 * DESCRIPTION: create a new string
383 */
ext_string_new(dataspace * data,char * text,int len)384 static string *ext_string_new(dataspace *data, char *text, int len)
385 {
386 UNREFERENCED_PARAMETER(data);
387 return str_new(text, len);
388 }
389
390 /*
391 * NAME: ext->string_text()
392 * DESCRIPTION: return string text
393 */
ext_string_text(string * str)394 static char *ext_string_text(string *str)
395 {
396 return str->text;
397 }
398
399 /*
400 * NAME: ext->string_length()
401 * DESCRIPTION: return string length
402 */
ext_string_length(string * str)403 static int ext_string_length(string *str)
404 {
405 return str->len;
406 }
407
408 /*
409 * NAME: ext->object_putval()
410 * DESCRIPTION: store an object in a value
411 */
ext_object_putval(value * val,object * obj)412 static void ext_object_putval(value *val, object *obj)
413 {
414 PUT_OBJVAL(val, obj);
415 }
416
417 /*
418 * NAME: ext->object_name()
419 * DESCRIPTION: store the name of an object
420 */
ext_object_name(frame * f,object * obj,char * buf)421 static char *ext_object_name(frame *f, object *obj, char *buf)
422 {
423 UNREFERENCED_PARAMETER(f);
424 return o_name(buf, obj);
425 }
426
427 /*
428 * NAME: ext->object_isspecial()
429 * DESCRIPTION: return TRUE if the given object is special, FALSE otherwise
430 */
ext_object_isspecial(object * obj)431 static int ext_object_isspecial(object *obj)
432 {
433 return ((obj->flags & O_SPECIAL) != 0);
434 }
435
436 /*
437 * NAME: ext->object_ismarked()
438 * DESCRIPTION: return TRUE if the given object is marked, FALSE otherwise
439 */
ext_object_ismarked(object * obj)440 static int ext_object_ismarked(object *obj)
441 {
442 return ((obj->flags & O_SPECIAL) == O_SPECIAL);
443 }
444
445 /*
446 * NAME: ext->object_mark()
447 * DESCRIPTION: mark the given object
448 */
ext_object_mark(object * obj)449 static void ext_object_mark(object *obj)
450 {
451 obj->flags |= O_SPECIAL;
452 }
453
454 /*
455 * NAME: ext->object_unmark()
456 * DESCRIPTION: unmark the given object
457 */
ext_object_unmark(object * obj)458 static void ext_object_unmark(object *obj)
459 {
460 obj->flags &= ~O_SPECIAL;
461 }
462
463 /*
464 * NAME: ext->array_getval()
465 * DESCRIPTION: retrieve an array from a value
466 */
ext_array_getval(value * val)467 static array *ext_array_getval(value *val)
468 {
469 return val->u.array;
470 }
471
472 /*
473 * NAME: ext->array_putval()
474 * DESCRIPTION: store an array in a value
475 */
ext_array_putval(value * val,array * a)476 static void ext_array_putval(value *val, array *a)
477 {
478 PUT_ARRVAL_NOREF(val, a);
479 }
480
481 /*
482 * NAME: ext->array_new()
483 * DESCRIPTION: create a new array
484 */
ext_array_new(dataspace * data,int size)485 static array *ext_array_new(dataspace *data, int size)
486 {
487 return arr_ext_new(data, size);
488 }
489
490 /*
491 * NAME: ext->array_index()
492 * DESCRIPTION: return an array element
493 */
ext_array_index(array * a,int i)494 static value *ext_array_index(array *a, int i)
495 {
496 return &d_get_elts(a)[i];
497 }
498
499 /*
500 * NAME: ext->array_assign()
501 * DESCRIPTION: assign a value to an array element
502 */
ext_array_assign(dataspace * data,array * a,int i,value * val)503 static void ext_array_assign(dataspace *data, array *a, int i, value *val)
504 {
505 d_assign_elt(data, a, &d_get_elts(a)[i], val);
506 }
507
508 /*
509 * NAME: ext->array_size()
510 * DESCRIPTION: return the size of an array
511 */
ext_array_size(array * a)512 static int ext_array_size(array *a)
513 {
514 return a->size;
515 }
516
517 /*
518 * NAME: ext->mapping_putval()
519 * DESCRIPTION: store a mapping in a value
520 */
ext_mapping_putval(value * val,array * m)521 static void ext_mapping_putval(value *val, array *m)
522 {
523 PUT_MAPVAL_NOREF(val, m);
524 }
525
526 /*
527 * NAME: ext->mapping_new()
528 * DESCRIPTION: create a new mapping
529 */
ext_mapping_new(dataspace * data)530 static array *ext_mapping_new(dataspace *data)
531 {
532 return map_new(data, 0);
533 }
534
535 /*
536 * NAME: ext->mapping_index()
537 * DESCRIPTION: return a value from a mapping
538 */
ext_mapping_index(array * m,value * idx)539 static value *ext_mapping_index(array *m, value *idx)
540 {
541 return map_index(m->primary->data, m, idx, (value *) NULL, (value *) NULL);
542 }
543
544 /*
545 * NAME: ext->mapping_assign()
546 * DESCRIPTION: assign to a mapping value
547 */
ext_mapping_assign(dataspace * data,array * m,value * idx,value * val)548 static void ext_mapping_assign(dataspace *data, array *m, value *idx,
549 value *val)
550 {
551 map_index(data, m, idx, val, (value *) NULL);
552 }
553
554 /*
555 * NAME: ext->mapping_enum()
556 * DESCRIPTION: return the nth enumerated index
557 */
ext_mapping_enum(array * m,int i)558 static value *ext_mapping_enum(array *m, int i)
559 {
560 map_compact(m->primary->data, m);
561 return &d_get_elts(m)[i];
562 }
563
564 /*
565 * NAME: ext->mapping_size()
566 * DESCRIPTION: return the size of a mapping
567 */
ext_mapping_size(array * m)568 static int ext_mapping_size(array *m)
569 {
570 return map_size(m->primary->data, m);
571 }
572
573 /*
574 * NAME: ext->runtime_error()
575 * DESCRIPTION: handle an error at runtime
576 */
ext_runtime_error(frame * f,char * mesg)577 static void ext_runtime_error(frame *f, char *mesg)
578 {
579 UNREFERENCED_PARAMETER(f);
580 error(mesg);
581 }
582
583 /*
584 * NAME: ext->runtime_rlimits()
585 * DESCRIPTION: handle rlimits
586 */
ext_runtime_rlimits(frame * f)587 void ext_runtime_rlimits(frame *f)
588 {
589 if (f->sp[1].type != T_INT) {
590 error("Bad rlimits depth type");
591 }
592 if (f->sp->type != T_INT) {
593 error("Bad rlimits ticks type");
594 }
595 f->sp += 2;
596
597 i_new_rlimits(f, f->sp[-1].u.number, f->sp[-2].u.number);
598 }
599
600 /*
601 * NAME: ext->runtime_rswitch()
602 * DESCRIPTION: handle a range switch
603 */
ext_runtime_rswitch(Int i,Int * tab,int h)604 int ext_runtime_rswitch(Int i, Int *tab, int h)
605 {
606 register int l, m;
607 register Int *t;
608
609 l = 0;
610 do {
611 m = (l + h) >> 1;
612 t = tab + (m << 1);
613 if (i < *t++) {
614 h = m; /* search in lower half */
615 } else if (i > *t) {
616 l = m + 1; /* search in upper half */
617 } else {
618 return m + 1; /* found */
619 }
620 } while (l < h);
621
622 return 0; /* not found */
623 }
624
625 /*
626 * NAME: ext->runtime_sswitch()
627 * DESCRIPTION: handle a str switch
628 */
ext_runtime_sswitch(frame * f,char * tab,int h)629 int ext_runtime_sswitch(frame *f, char *tab, int h)
630 {
631 register int l, m, c;
632 register char *t;
633 register string *s;
634 value *v;
635
636 v = f->sp++;
637 if (VAL_NIL(v)) {
638 return (tab[0] == 0);
639 } else if (v->type != T_STRING) {
640 i_del_value(v);
641 return 0;
642 }
643
644 s = v->u.string;
645 if (*tab++ == 0) {
646 tab -= 3;
647 l = 1;
648 } else {
649 l = 0;
650 }
651
652 do {
653 m = (l + h) >> 1;
654 t = tab + 3 * m;
655 c = str_cmp(s, d_get_strconst(f->p_ctrl, t[0],
656 (UCHAR(t[1]) << 8) + UCHAR(t[2])));
657 if (c == 0) {
658 str_del(s);
659 return m + 1; /* found */
660 } else if (c < 0) {
661 h = m; /* search in lower half */
662 } else {
663 l = m + 1; /* search in upper half */
664 }
665 } while (l < h);
666
667 str_del(s);
668 return 0; /* not found */
669 }
670
671 /*
672 * NAME: ext->dgd()
673 * DESCRIPTION: initialize extension interface
674 */
ext_dgd(char * module)675 bool ext_dgd(char *module)
676 {
677 voidf *ext_ext[1];
678 voidf *ext_frame[4];
679 voidf *ext_data[2];
680 voidf *ext_value[3];
681 voidf *ext_int[2];
682 voidf *ext_float[2];
683 voidf *ext_string[5];
684 voidf *ext_object[6];
685 voidf *ext_array[6];
686 voidf *ext_mapping[7];
687 voidf *ext_runtime[1];
688 voidf **ftabs[11];
689 int sizes[11];
690 int (*init) (int, int, voidf**[], int[]);
691
692 init = (int (*) (int, int, voidf**[], int[])) P_dload(module, "ext_init");
693 if (init == NULL) {
694 return FALSE;
695 }
696
697 ext_ext[0] = (voidf *) &kf_ext_kfun;
698 ext_frame[0] = (voidf *) &ext_frame_object;
699 ext_frame[1] = (voidf *) &ext_frame_dataspace;
700 ext_frame[2] = (voidf *) &ext_frame_arg;
701 ext_frame[3] = (voidf *) &ext_frame_atomic;
702 ext_data[0] = (voidf *) &d_get_extravar;
703 ext_data[1] = (voidf *) &d_set_extravar;
704 ext_value[0] = (voidf *) &ext_value_type;
705 ext_value[1] = (voidf *) &ext_value_nil;
706 ext_value[2] = (voidf *) &ext_value_temp;
707 ext_int[0] = (voidf *) &ext_int_getval;
708 ext_int[1] = (voidf *) &ext_int_putval;
709 # ifndef NOFLOAT
710 ext_float[0] = (voidf *) &ext_float_getval;
711 ext_float[1] = (voidf *) &ext_float_putval;
712 # else
713 ext_float[0] = (voidf *) NULL;
714 ext_float[1] = (voidf *) NULL;
715 # endif
716 ext_string[0] = (voidf *) &ext_string_getval;
717 ext_string[1] = (voidf *) &ext_string_putval;
718 ext_string[2] = (voidf *) &ext_string_new;
719 ext_string[3] = (voidf *) &ext_string_text;
720 ext_string[4] = (voidf *) &ext_string_length;
721 ext_object[0] = (voidf *) &ext_object_putval;
722 ext_object[1] = (voidf *) &ext_object_name;
723 ext_object[2] = (voidf *) &ext_object_isspecial;
724 ext_object[3] = (voidf *) &ext_object_ismarked;
725 ext_object[4] = (voidf *) &ext_object_mark;
726 ext_object[5] = (voidf *) &ext_object_unmark;
727 ext_array[0] = (voidf *) &ext_array_getval;
728 ext_array[1] = (voidf *) &ext_array_putval;
729 ext_array[2] = (voidf *) &ext_array_new;
730 ext_array[3] = (voidf *) &ext_array_index;
731 ext_array[4] = (voidf *) &ext_array_assign;
732 ext_array[5] = (voidf *) &ext_array_size;
733 ext_mapping[0] = (voidf *) &ext_array_getval;
734 ext_mapping[1] = (voidf *) &ext_mapping_putval;
735 ext_mapping[2] = (voidf *) &ext_mapping_new;
736 ext_mapping[3] = (voidf *) &ext_mapping_index;
737 ext_mapping[4] = (voidf *) &ext_mapping_assign;
738 ext_mapping[5] = (voidf *) &ext_mapping_enum;
739 ext_mapping[6] = (voidf *) &ext_mapping_size;
740 ext_runtime[0] = (voidf *) &ext_runtime_error;
741
742 ftabs[ 0] = ext_ext; sizes[ 0] = 1;
743 ftabs[ 1] = ext_frame; sizes[ 1] = 4;
744 ftabs[ 2] = ext_data; sizes[ 2] = 2;
745 ftabs[ 3] = ext_value; sizes[ 3] = 3;
746 ftabs[ 4] = ext_int; sizes[ 4] = 2;
747 ftabs[ 5] = ext_float; sizes[ 5] = 2;
748 ftabs[ 6] = ext_string; sizes[ 6] = 5;
749 ftabs[ 7] = ext_object; sizes[ 7] = 6;
750 ftabs[ 8] = ext_array; sizes[ 8] = 6;
751 ftabs[ 9] = ext_mapping; sizes[ 9] = 7;
752 ftabs[10] = ext_runtime; sizes[10] = 1;
753
754 if (!init(EXTENSION_MAJOR, EXTENSION_MINOR, ftabs, sizes)) {
755 fatal("incompatible runtime extension");
756 }
757 return TRUE;
758 }
759