r8260: added an init based registration system for the generated ejs rpc code, so
[jelmer/samba4-debian.git] / source / build / pidl / ejs.pm
1 ###################################################
2 # EJS function wrapper generator
3 # Copyright jelmer@samba.org 2005
4 # Copyright Andrew Tridgell 2005
5 # released under the GNU GPL
6
7 package EjsClient;
8
9 use strict;
10 use pidl::typelist;
11
12 my($res);
13 my %constants;
14
15 my $tabs = "";
16 sub pidl($)
17 {
18         my $d = shift;
19         if ($d) {
20                 $res .= $tabs;
21                 $res .= $d;
22         }
23         $res .= "\n";
24 }
25
26 sub indent()
27 {
28         $tabs .= "\t";
29 }
30
31 sub deindent()
32 {
33         $tabs = substr($tabs, 0, -1);
34 }
35
36 # this should probably be in ndr.pm
37 sub GenerateStructEnv($)
38 {
39         my $x = shift;
40         my %env;
41
42         foreach my $e (@{$x->{ELEMENTS}}) {
43                 if ($e->{NAME}) {
44                         $env{$e->{NAME}} = "r->$e->{NAME}";
45                 }
46         }
47
48         $env{"this"} = "r";
49
50         return \%env;
51 }
52
53 sub GenerateFunctionInEnv($)
54 {
55         my $fn = shift;
56         my %env;
57
58         foreach my $e (@{$fn->{ELEMENTS}}) {
59                 if (grep (/in/, @{$e->{DIRECTION}})) {
60                         $env{$e->{NAME}} = "r->in.$e->{NAME}";
61                 }
62         }
63
64         return \%env;
65 }
66
67 sub GenerateFunctionOutEnv($)
68 {
69         my $fn = shift;
70         my %env;
71
72         foreach my $e (@{$fn->{ELEMENTS}}) {
73                 if (grep (/out/, @{$e->{DIRECTION}})) {
74                         $env{$e->{NAME}} = "r->out.$e->{NAME}";
75                 } elsif (grep (/in/, @{$e->{DIRECTION}})) {
76                         $env{$e->{NAME}} = "r->in.$e->{NAME}";
77                 }
78         }
79
80         return \%env;
81 }
82
83 sub get_pointer_to($)
84 {
85         my $var_name = shift;
86         
87         if ($var_name =~ /^\*(.*)$/) {
88                 return $1;
89         } elsif ($var_name =~ /^\&(.*)$/) {
90                 return "&($var_name)";
91         } else {
92                 return "&$var_name";
93         }
94 }
95
96 sub get_value_of($)
97 {
98         my $var_name = shift;
99
100         if ($var_name =~ /^\&(.*)$/) {
101                 return $1;
102         } else {
103                 return "*$var_name";
104         }
105 }
106
107 #####################################################################
108 # work out is a parse function should be declared static or not
109 sub fn_prefix($)
110 {
111         my $fn = shift;
112
113         return "" if (util::has_property($fn, "public"));
114         return "static ";
115 }
116
117 ###########################
118 # pull a scalar element
119 sub EjsPullScalar($$$$$)
120 {
121         my ($e, $l, $var, $name, $env) = @_;
122
123         return if (util::has_property($e, "value"));
124
125         $var = get_pointer_to($var);
126         # have to handle strings specially :(
127         if ($e->{TYPE} eq "string") {
128                 $var = get_pointer_to($var);
129         }
130         pidl "NDR_CHECK(ejs_pull_$e->{TYPE}(ejs, v, $name, $var));";
131 }
132
133 ###########################
134 # pull a pointer element
135 sub EjsPullPointer($$$$$)
136 {
137         my ($e, $l, $var, $name, $env) = @_;
138         pidl "if (ejs_pull_null(ejs, v, $name)) {";
139         indent;
140         pidl "$var = NULL;";
141         deindent;
142         pidl "} else {";
143         indent;
144         pidl "EJS_ALLOC(ejs, $var);";
145         $var = get_value_of($var);              
146         EjsPullElement($e, Ndr::GetNextLevel($e, $l), $var, $name, $env);
147         deindent;
148         pidl "}";
149 }
150
151 ###########################
152 # pull a string element
153 sub EjsPullString($$$$$)
154 {
155         my ($e, $l, $var, $name, $env) = @_;
156         $var = get_pointer_to($var);
157         pidl "NDR_CHECK(ejs_pull_string(ejs, v, $name, $var));";
158 }
159
160
161 ###########################
162 # pull an arrar element
163 sub EjsPullArray($$$$$)
164 {
165         my ($e, $l, $var, $name, $env) = @_;
166         my $length = util::ParseExpr($l->{LENGTH_IS}, $env);
167         my $pl = Ndr::GetPrevLevel($e, $l);
168         if ($pl && $pl->{TYPE} eq "POINTER") {
169                 $var = get_pointer_to($var);
170         }
171         my $avar = $var . "[i]";
172         pidl "{";
173         indent;
174         pidl "uint32_t i;";
175         if (!$l->{IS_FIXED}) {
176                 pidl "EJS_ALLOC_N(ejs, $var, $length);";
177         }
178         pidl "for (i=0;i<$length;i++) {";
179         indent;
180         pidl "char *id = talloc_asprintf(ejs, \"%s.%u\", $name, i);";
181         EjsPullElement($e, Ndr::GetNextLevel($e, $l), $avar, "id", $env);
182         pidl "talloc_free(id);";
183         deindent;
184         pidl "}";
185         pidl "ejs_push_uint32(ejs, v, $name \".length\", &i);";
186         deindent;
187         pidl "}";
188 }
189
190 ###########################
191 # pull a switch element
192 sub EjsPullSwitch($$$$$)
193 {
194         my ($e, $l, $var, $name, $env) = @_;
195         my $switch_var = util::ParseExpr($l->{SWITCH_IS}, $env);
196         pidl "ejs_set_switch(ejs, $switch_var);";
197         EjsPullElement($e, Ndr::GetNextLevel($e, $l), $var, $name, $env);
198 }
199
200 ###########################
201 # pull a structure element
202 sub EjsPullElement($$$$$)
203 {
204         my ($e, $l, $var, $name, $env) = @_;
205         if (util::has_property($e, "charset")) {
206                 EjsPullString($e, $l, $var, $name, $env);
207         } elsif ($l->{TYPE} eq "ARRAY") {
208                 EjsPullArray($e, $l, $var, $name, $env);
209         } elsif ($l->{TYPE} eq "DATA") {
210                 EjsPullScalar($e, $l, $var, $name, $env);
211         } elsif (($l->{TYPE} eq "POINTER")) {
212                 EjsPullPointer($e, $l, $var, $name, $env);
213         } elsif (($l->{TYPE} eq "SWITCH")) {
214                 EjsPullSwitch($e, $l, $var, $name, $env);
215         } else {
216                 pidl "return ejs_panic(ejs, \"unhandled pull type $l->{TYPE}\");";
217         }
218 }
219
220 #############################################
221 # pull a structure/union element at top level
222 sub EjsPullElementTop($$)
223 {
224         my $e = shift;
225         my $env = shift;
226         my $l = $e->{LEVELS}[0];
227         my $var = util::ParseExpr($e->{NAME}, $env);
228         my $name = "\"$e->{NAME}\"";
229         EjsPullElement($e, $l, $var, $name, $env);
230 }
231
232 ###########################
233 # pull a struct
234 sub EjsStructPull($$)
235 {
236         my $name = shift;
237         my $d = shift;
238         my $env = GenerateStructEnv($d);
239         pidl fn_prefix($d);
240         pidl "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, struct $name *r)\n{";
241         indent;
242         pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, name));";
243         foreach my $e (@{$d->{ELEMENTS}}) {
244                 EjsPullElementTop($e, $env);
245         }
246         pidl "return NT_STATUS_OK;";
247         deindent;
248         pidl "}\n";
249 }
250
251 ###########################
252 # pull a union
253 sub EjsUnionPull($$)
254 {
255         my $name = shift;
256         my $d = shift;
257         my $have_default = 0;
258         my $env = GenerateStructEnv($d);
259         pidl fn_prefix($d);
260         pidl "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, union $name *r)\n{";
261         indent;
262         pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, name));";
263         pidl "switch (ejs->switch_var) {";
264         indent;
265         foreach my $e (@{$d->{ELEMENTS}}) {
266                 if ($e->{CASE} eq "default") {
267                         $have_default = 1;
268                 }
269                 pidl "$e->{CASE}:";
270                 indent;
271                 if ($e->{TYPE} ne "EMPTY") {
272                         EjsPullElementTop($e, $env);
273                 }
274                 pidl "break;";
275                 deindent;
276         }
277         if (! $have_default) {
278                 pidl "default:";
279                 indent;
280                 pidl "return ejs_panic(ejs, \"Bad switch value\");";
281                 deindent;
282         }
283         deindent;
284         pidl "}";
285         pidl "return NT_STATUS_OK;";
286         deindent;
287         pidl "}";
288 }
289
290 ###########################
291 # pull a enum
292 sub EjsEnumPull($$)
293 {
294         my $name = shift;
295         my $d = shift;
296         pidl fn_prefix($d);
297         pidl "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, enum $name *r)\n{";
298         indent;
299         pidl "unsigned e;";
300         pidl "NDR_CHECK(ejs_pull_enum(ejs, v, name, &e));";
301         pidl "*r = e;";
302         pidl "return NT_STATUS_OK;";
303         deindent;
304         pidl "}\n";
305 }
306
307 ###########################
308 # pull a bitmap
309 sub EjsBitmapPull($$)
310 {
311         my $name = shift;
312         my $d = shift;
313         my $type_fn = $d->{BASE_TYPE};
314         my($type_decl) = typelist::mapType($d->{BASE_TYPE});
315         pidl fn_prefix($d);
316         pidl "NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, $type_decl *r)\n{";
317         indent;
318         pidl "return ejs_pull_$type_fn(ejs, v, name, r);";
319         deindent;
320         pidl "}";
321 }
322
323
324 ###########################
325 # generate a structure pull
326 sub EjsTypedefPull($)
327 {
328         my $d = shift;
329         return if (util::has_property($d, "noejs"));
330         if ($d->{DATA}->{TYPE} eq 'STRUCT') {
331                 EjsStructPull($d->{NAME}, $d->{DATA});
332         } elsif ($d->{DATA}->{TYPE} eq 'UNION') {
333                 EjsUnionPull($d->{NAME}, $d->{DATA});
334         } elsif ($d->{DATA}->{TYPE} eq 'ENUM') {
335                 EjsEnumPull($d->{NAME}, $d->{DATA});
336         } elsif ($d->{DATA}->{TYPE} eq 'BITMAP') {
337                 EjsBitmapPull($d->{NAME}, $d->{DATA});
338         } else {
339                 warn "Unhandled pull typedef $d->{NAME} of type $d->{DATA}->{TYPE}";
340         }
341 }
342
343 #####################
344 # generate a function
345 sub EjsPullFunction($)
346 {
347         my $d = shift;
348         my $env = GenerateFunctionInEnv($d);
349         my $name = $d->{NAME};
350
351         pidl "\nstatic NTSTATUS ejs_pull_$name(struct ejs_rpc *ejs, struct MprVar *v, struct $name *r)";
352         pidl "{";
353         indent;
354         pidl "NDR_CHECK(ejs_pull_struct_start(ejs, &v, \"input\"));";
355
356         foreach my $e (@{$d->{ELEMENTS}}) {
357                 next unless (grep(/in/, @{$e->{DIRECTION}}));
358                 EjsPullElementTop($e, $env);
359         }
360
361         pidl "return NT_STATUS_OK;";
362         deindent;
363         pidl "}\n";
364 }
365
366
367 ###########################
368 # push a scalar element
369 sub EjsPushScalar($$$$$)
370 {
371         my ($e, $l, $var, $name, $env) = @_;
372         $var = get_pointer_to($var);
373         pidl "NDR_CHECK(ejs_push_$e->{TYPE}(ejs, v, $name, $var));";
374 }
375
376 ###########################
377 # push a string element
378 sub EjsPushString($$$$$)
379 {
380         my ($e, $l, $var, $name, $env) = @_;
381         pidl "NDR_CHECK(ejs_push_string(ejs, v, $name, $var));";
382 }
383
384 ###########################
385 # push a pointer element
386 sub EjsPushPointer($$$$$)
387 {
388         my ($e, $l, $var, $name, $env) = @_;
389         pidl "if (NULL == $var) {";
390         indent;
391         pidl "NDR_CHECK(ejs_push_null(ejs, v, $name));";
392         deindent;
393         pidl "} else {";
394         indent;
395         $var = get_value_of($var);              
396         EjsPushElement($e, Ndr::GetNextLevel($e, $l), $var, $name, $env);
397         deindent;
398         pidl "}";
399 }
400
401 ###########################
402 # push a switch element
403 sub EjsPushSwitch($$$$$)
404 {
405         my ($e, $l, $var, $name, $env) = @_;
406         my $switch_var = util::ParseExpr($l->{SWITCH_IS}, $env);
407         pidl "ejs_set_switch(ejs, $switch_var);";
408         EjsPushElement($e, Ndr::GetNextLevel($e, $l), $var, $name, $env);
409 }
410
411
412 ###########################
413 # push an arrar element
414 sub EjsPushArray($$$$$)
415 {
416         my ($e, $l, $var, $name, $env) = @_;
417         my $length = util::ParseExpr($l->{LENGTH_IS}, $env);
418         my $pl = Ndr::GetPrevLevel($e, $l);
419         if ($pl && $pl->{TYPE} eq "POINTER") {
420                 $var = get_pointer_to($var);
421         }
422         my $avar = $var . "[i]";
423         pidl "{";
424         indent;
425         pidl "uint32_t i;";
426         pidl "for (i=0;i<$length;i++) {";
427         indent;
428         pidl "const char *id = talloc_asprintf(ejs, \"%s.%u\", $name, i);";
429         EjsPushElement($e, Ndr::GetNextLevel($e, $l), $avar, "id", $env);
430         deindent;
431         pidl "}";
432         pidl "ejs_push_uint32(ejs, v, $name \".length\", &i);";
433         deindent;
434         pidl "}";
435 }
436
437 ################################
438 # push a structure/union element
439 sub EjsPushElement($$$$$)
440 {
441         my ($e, $l, $var, $name, $env) = @_;
442         if (util::has_property($e, "charset")) {
443                 EjsPushString($e, $l, $var, $name, $env);
444         } elsif ($l->{TYPE} eq "ARRAY") {
445                 EjsPushArray($e, $l, $var, $name, $env);
446         } elsif ($l->{TYPE} eq "DATA") {
447                 EjsPushScalar($e, $l, $var, $name, $env);
448         } elsif (($l->{TYPE} eq "POINTER")) {
449                 EjsPushPointer($e, $l, $var, $name, $env);
450         } elsif (($l->{TYPE} eq "SWITCH")) {
451                 EjsPushSwitch($e, $l, $var, $name, $env);
452         } else {
453                 pidl "return ejs_panic(ejs, \"unhandled push type $l->{TYPE}\");";
454         }
455 }
456
457 #############################################
458 # push a structure/union element at top level
459 sub EjsPushElementTop($$)
460 {
461         my $e = shift;
462         my $env = shift;
463         my $l = $e->{LEVELS}[0];
464         my $var = util::ParseExpr($e->{NAME}, $env);
465         my $name = "\"$e->{NAME}\"";
466         EjsPushElement($e, $l, $var, $name, $env);
467 }
468
469 ###########################
470 # push a struct
471 sub EjsStructPush($$)
472 {
473         my $name = shift;
474         my $d = shift;
475         my $env = GenerateStructEnv($d);
476         pidl fn_prefix($d);
477         pidl "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const struct $name *r)\n{";
478         indent;
479         pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, name));";
480         foreach my $e (@{$d->{ELEMENTS}}) {
481                 EjsPushElementTop($e, $env);
482         }
483         pidl "return NT_STATUS_OK;";
484         deindent;
485         pidl "}\n";
486 }
487
488 ###########################
489 # push a union
490 sub EjsUnionPush($$)
491 {
492         my $name = shift;
493         my $d = shift;
494         my $have_default = 0;
495         my $env = GenerateStructEnv($d);
496         pidl fn_prefix($d);
497         pidl "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const union $name *r)\n{";
498         indent;
499         pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, name));";
500         pidl "switch (ejs->switch_var) {";
501         indent;
502         foreach my $e (@{$d->{ELEMENTS}}) {
503                 if ($e->{CASE} eq "default") {
504                         $have_default = 1;
505                 }
506                 pidl "$e->{CASE}:";
507                 indent;
508                 if ($e->{TYPE} ne "EMPTY") {
509                         EjsPushElementTop($e, $env);
510                 }
511                 pidl "break;";
512                 deindent;
513         }
514         if (! $have_default) {
515                 pidl "default:";
516                 indent;
517                 pidl "return ejs_panic(ejs, \"Bad switch value\");";
518                 deindent;
519         }
520         deindent;
521         pidl "}";
522         pidl "return NT_STATUS_OK;";
523         deindent;
524         pidl "}";
525 }
526
527 ###########################
528 # push a enum
529 sub EjsEnumPush($$)
530 {
531         my $name = shift;
532         my $d = shift;
533         my $v = 0;
534         # put the enum elements in the constants array
535         foreach my $e (@{$d->{ELEMENTS}}) {
536                 my $el = $e;
537                 chomp $el;
538                 if ($el =~ /^(.*)=\s*(.*)\s*$/) {
539                         $el = $1;
540                         $v = $2;
541                 }
542                 $constants{$el} = $v;
543                 $v++;
544         }
545         pidl fn_prefix($d);
546         pidl "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const enum $name *r)\n{";
547         indent;
548         pidl "unsigned e = *r;";
549         pidl "NDR_CHECK(ejs_push_enum(ejs, v, name, &e));";
550         pidl "return NT_STATUS_OK;";
551         deindent;
552         pidl "}\n";
553 }
554
555 ###########################
556 # push a bitmap
557 sub EjsBitmapPush($$)
558 {
559         my $name = shift;
560         my $d = shift;
561         my $type_fn = $d->{BASE_TYPE};
562         my($type_decl) = typelist::mapType($d->{BASE_TYPE});
563         # put the bitmap elements in the constants array
564         foreach my $e (@{$d->{ELEMENTS}}) {
565                 if ($e =~ /^(\w*)\s*(.*)\s*$/) {
566                         my $bname = $1;
567                         my $v = $2;
568                         $constants{$bname} = $v;
569                 }
570         }
571         pidl fn_prefix($d);
572         pidl "NTSTATUS ejs_push_$name(struct ejs_rpc *ejs, struct MprVar *v, const char *name, const $type_decl *r)\n{";
573         indent;
574         pidl "return ejs_push_$type_fn(ejs, v, name, r);";
575         deindent;
576         pidl "}";
577 }
578
579
580 ###########################
581 # generate a structure push
582 sub EjsTypedefPush($)
583 {
584         my $d = shift;
585         return if (util::has_property($d, "noejs"));
586         if ($d->{DATA}->{TYPE} eq 'STRUCT') {
587                 EjsStructPush($d->{NAME}, $d->{DATA});
588         } elsif ($d->{DATA}->{TYPE} eq 'UNION') {
589                 EjsUnionPush($d->{NAME}, $d->{DATA});
590         } elsif ($d->{DATA}->{TYPE} eq 'ENUM') {
591                 EjsEnumPush($d->{NAME}, $d->{DATA});
592         } elsif ($d->{DATA}->{TYPE} eq 'BITMAP') {
593                 EjsBitmapPush($d->{NAME}, $d->{DATA});
594         } else {
595                 warn "Unhandled push typedef $d->{NAME} of type $d->{DATA}->{TYPE}";
596         }
597 }
598
599
600 #####################
601 # generate a function
602 sub EjsPushFunction($)
603 {
604         my $d = shift;
605         my $env = GenerateFunctionOutEnv($d);
606
607         pidl "\nstatic NTSTATUS ejs_push_$d->{NAME}(struct ejs_rpc *ejs, struct MprVar *v, const struct $d->{NAME} *r)";
608         pidl "{";
609         indent;
610         pidl "NDR_CHECK(ejs_push_struct_start(ejs, &v, \"output\"));";
611
612         foreach my $e (@{$d->{ELEMENTS}}) {
613                 next unless (grep(/out/, @{$e->{DIRECTION}}));
614                 EjsPushElementTop($e, $env);
615         }
616
617         pidl "return NT_STATUS_OK;";
618         deindent;
619         pidl "}\n";
620 }
621
622
623 #################################
624 # generate a ejs mapping function
625 sub EjsFunction($)
626 {
627         my $d = shift;
628         my $name = $d->{NAME};
629
630         pidl "static int ejs_$name(int eid, int argc, struct MprVar **argv)";
631         pidl "{";
632         indent;
633         pidl "return ejs_rpc_call(eid, argc, argv, \"$name\", (ejs_pull_function_t)ejs_pull_$name, (ejs_push_function_t)ejs_push_$name);";
634         deindent;
635         pidl "}\n";
636 }
637
638 ###################
639 # handle a constant
640 sub EjsConst($)
641 {
642     my $const = shift;
643     $constants{$const->{NAME}} = $const->{VALUE};
644 }
645
646 #####################################################################
647 # parse the interface definitions
648 sub EjsInterface($)
649 {
650         my($interface) = shift;
651         my @fns = ();
652         my $name = $interface->{NAME};
653
654         %constants = ();
655
656         foreach my $d (@{$interface->{TYPEDEFS}}) {
657                 EjsTypedefPush($d);
658                 EjsTypedefPull($d);
659         }
660
661         foreach my $d (@{$interface->{FUNCTIONS}}) {
662                 next if not defined($d->{OPNUM});
663                 
664                 EjsPullFunction($d);
665                 EjsPushFunction($d);
666                 EjsFunction($d);
667
668                 push (@fns, $d->{NAME});
669         }
670
671         foreach my $d (@{$interface->{CONSTS}}) {
672                 EjsConst($d);
673         }
674
675         pidl "void setup_ejs_$name(void)";
676         pidl "{";
677         indent;
678         foreach (@fns) {
679                 pidl "ejsDefineCFunction(-1, \"dcerpc_$_\", ejs_$_, NULL, MPR_VAR_SCRIPT_HANDLE);";
680         }
681         deindent;
682         pidl "}\n";
683
684         pidl "void setup_ejs_constants_$name(int eid)";
685         pidl "{";
686         indent;
687         foreach my $v (keys %constants) {
688                 my $value = $constants{$v};
689                 if (substr($value, 0, 1) eq "\"") {
690                         pidl "ejs_set_constant_string(eid, \"$v\", $value);";
691                 } else {
692                         pidl "ejs_set_constant_int(eid, \"$v\", $value);";
693                 }
694         }
695         deindent;
696         pidl "}\n";
697
698         pidl "NTSTATUS ejs_init_$name(void)";
699         pidl "{";
700         indent;
701         pidl "return smbcalls_register_ejs(\"$name\", setup_ejs_$name, setup_ejs_constants_$name);";
702         deindent;
703         pidl "}";
704 }
705
706 #####################################################################
707 # parse a parsed IDL into a C header
708 sub Parse($$)
709 {
710     my($ndr,$hdr) = @_;
711     
712     my $ejs_hdr = $hdr;
713     $ejs_hdr =~ s/.h$/_ejs.h/;
714     $res = "";
715     pidl "
716 /* EJS wrapper functions auto-generated by pidl */
717 #include \"includes.h\"
718 #include \"lib/ejs/ejs.h\"
719 #include \"scripting/ejs/ejsrpc.h\"
720 #include \"librpc/gen_ndr/ndr_misc_ejs.h\"
721 #include \"$hdr\"
722 #include \"$ejs_hdr\"
723
724 ";
725     foreach my $x (@{$ndr}) {
726             if ($x->{TYPE} eq "INTERFACE") {
727                     ($x->{TYPE} eq "INTERFACE") && EjsInterface($x);
728             }
729     }
730
731     return $res;
732 }
733
734 1;