]>
Commit | Line | Data |
---|---|---|
c90f71dd RD |
1 | /******************************************************************************* |
2 | * Simplified Wrapper and Interface Generator (SWIG) | |
3 | * | |
4 | * Author : David Beazley | |
5 | * | |
6 | * Department of Computer Science | |
7 | * University of Chicago | |
8 | * 1100 E 58th Street | |
9 | * Chicago, IL 60637 | |
10 | * beazley@cs.uchicago.edu | |
11 | * | |
12 | * Please read the file LICENSE for the copyright and terms by which SWIG | |
13 | * can be used and distributed. | |
14 | *******************************************************************************/ | |
15 | /*********************************************************************** | |
16 | * $Header$ | |
17 | * | |
18 | * main.cxx | |
19 | * | |
20 | * The main program. | |
21 | * | |
22 | ***********************************************************************/ | |
23 | ||
24 | #define WRAP | |
25 | ||
26 | #include "internal.h" | |
27 | #include "ascii.h" | |
28 | #include "latex.h" | |
29 | #include "html.h" | |
30 | #include "nodoc.h" | |
31 | #include <time.h> | |
32 | #include <stdlib.h> | |
33 | #include <stdio.h> | |
34 | #include <ctype.h> | |
35 | ||
36 | class SwigException {}; | |
37 | ||
38 | static char *usage = "\ | |
39 | \nDocumentation Options\n\ | |
40 | -dascii - ASCII documentation.\n\ | |
41 | -dhtml - HTML documentation.\n\ | |
42 | -dlatex - LaTeX documentation.\n\ | |
43 | -dnone - No documentation.\n\n\ | |
44 | General Options\n\ | |
45 | -c - Produce raw wrapper code (omit support code)\n\ | |
46 | -c++ - Enable C++ processing\n\ | |
47 | -ci - Check a file into the SWIG library\n\ | |
48 | -co - Check a file out of the SWIG library\n\ | |
49 | -d docfile - Set name of the documentation file.\n\ | |
50 | -Dsymbol - Define a symbol (for conditional compilation)\n\ | |
51 | -I<dir> - Look for SWIG files in <dir>\n\ | |
52 | -l<ifile> - Include SWIG library file.\n\ | |
53 | -make_default - Create default constructors/destructors\n\ | |
54 | -nocomment - Ignore all comments (for documentation).\n\ | |
55 | -o outfile - Set name of the output file.\n\ | |
56 | -objc - Enable Objective C processing\n\ | |
57 | -stat - Print statistics\n\ | |
58 | -strict n - Set pointer type-checking strictness\n\ | |
59 | -swiglib - Report location of SWIG library and exit\n\ | |
60 | -t typemap_file - Use a typemap file.\n\ | |
61 | -v - Run in verbose mode\n\ | |
62 | -version - Print SWIG version number\n\ | |
63 | -help - This output.\n\n"; | |
64 | ||
65 | //----------------------------------------------------------------- | |
66 | // main() | |
67 | // | |
68 | // Main program. Initializes the files and starts the parser. | |
69 | //----------------------------------------------------------------- | |
70 | ||
71 | char infilename[256]; | |
72 | char filename[256]; | |
73 | char fn_header[256]; | |
74 | char fn_wrapper[256]; | |
75 | char fn_init[256]; | |
76 | char output_dir[512]; | |
77 | ||
78 | #ifdef MACSWIG | |
79 | FILE *swig_log; | |
80 | #endif | |
81 | ||
82 | char *SwigLib; | |
83 | ||
002b1ea6 RD |
84 | #ifndef MSDOS |
85 | char** __argv; | |
86 | int __argc; | |
87 | #endif | |
c90f71dd RD |
88 | |
89 | int SWIG_main(int argc, char *argv[], Language *l, Documentation *d) { | |
90 | ||
91 | int i; | |
92 | char *c; | |
93 | extern FILE *LEX_in; | |
94 | extern void add_directory(char *); | |
95 | extern char *get_time(); | |
96 | char temp[512]; | |
97 | char infile[512]; | |
98 | ||
99 | char *outfile_name = 0; | |
100 | extern int add_iname(char *); | |
101 | int help = 0; | |
102 | int ignorecomments = 0; | |
103 | int checkout = 0; | |
104 | int checkin = 0; | |
105 | char *typemap_file = 0; | |
106 | char *includefiles[256]; | |
107 | int includecount = 0; | |
108 | extern void check_suffix(char *); | |
109 | extern void scanner_file(FILE *); | |
110 | ||
111 | #ifdef MACSWIG | |
112 | try { | |
113 | #endif | |
114 | ||
115 | f_wrappers = 0; | |
116 | f_init = 0; | |
117 | f_header = 0; | |
118 | ||
002b1ea6 RD |
119 | #ifndef MSDOS |
120 | __argc = argc; | |
121 | __argv = argv; | |
122 | #endif | |
c90f71dd RD |
123 | lang = l; |
124 | doc = d; | |
125 | Status = 0; | |
126 | TypeStrict = 2; // Very strict type checking | |
127 | Verbose = 0; | |
128 | char *doc_file = 0; | |
129 | ||
130 | DataType::init_typedef(); // Initialize the type handler | |
131 | ||
132 | // Set up some default symbols (available in both SWIG interface files | |
133 | // and C files) | |
134 | ||
135 | add_symbol("SWIG",0,0); // Define the SWIG symbol | |
136 | #ifdef MACSWIG | |
137 | add_symbol("SWIGMAC",0,0); | |
138 | #endif | |
139 | #ifdef SWIGWIN32 | |
140 | add_symbol("SWIGWIN32",0,0); | |
141 | #endif | |
142 | ||
143 | strcpy(LibDir, getSwigLib()); | |
144 | SwigLib = copy_string(LibDir); // Make a copy of the real library location | |
145 | #ifdef MACSWIG | |
146 | sprintf(temp,"%s:config", LibDir); | |
147 | add_directory(temp); | |
148 | add_directory(":swig_lib:config"); | |
149 | add_directory(LibDir); | |
150 | add_directory(":swig_lib"); | |
151 | #else | |
152 | sprintf(temp,"%s/config", LibDir); | |
153 | add_directory(temp); | |
154 | add_directory("./swig_lib/config"); | |
155 | add_directory(LibDir); | |
156 | add_directory("./swig_lib"); | |
157 | sprintf(InitName,"init_wrap"); | |
158 | #endif | |
159 | ||
160 | sprintf(InitName,"init_wrap"); | |
161 | ||
162 | // Get options | |
163 | for (i = 1; i < argc; i++) { | |
164 | if (argv[i]) { | |
165 | if (strncmp(argv[i],"-I",2) == 0) { | |
166 | // Add a new directory search path | |
167 | includefiles[includecount++] = copy_string(argv[i]+2); | |
168 | mark_arg(i); | |
169 | } else if (strncmp(argv[i],"-D",2) == 0) { | |
170 | // Create a symbol | |
171 | add_symbol(argv[i]+2, (DataType *) 0, (char *) 0); | |
172 | mark_arg(i); | |
173 | } else if (strcmp(argv[i],"-strict") == 0) { | |
174 | if (argv[i+1]) { | |
175 | TypeStrict = atoi(argv[i+1]); | |
176 | mark_arg(i); | |
177 | mark_arg(i+1); | |
178 | i++; | |
179 | } else { | |
180 | arg_error(); | |
181 | } | |
182 | } else if ((strcmp(argv[i],"-verbose") == 0) || (strcmp(argv[i],"-v") == 0)) { | |
183 | Verbose = 1; | |
184 | mark_arg(i); | |
185 | } else if (strcmp(argv[i],"-dascii") == 0) { | |
186 | doc = new ASCII; | |
187 | mark_arg(i); | |
188 | } else if (strcmp(argv[i],"-dnone") == 0) { | |
189 | doc = new NODOC; | |
190 | mark_arg(i); | |
191 | } else if (strcmp(argv[i],"-dhtml") == 0) { | |
192 | doc = new HTML; | |
193 | mark_arg(i); | |
194 | } else if (strcmp(argv[i],"-dlatex") == 0) { | |
195 | doc = new LATEX; | |
196 | mark_arg(i); | |
197 | } else if (strcmp(argv[i],"-nocomment") == 0) { | |
198 | ignorecomments = 1; | |
199 | mark_arg(i); | |
200 | } else if (strcmp(argv[i],"-stat") == 0) { | |
201 | Stats=1; | |
202 | mark_arg(i); | |
203 | } else if (strcmp(argv[i],"-c++") == 0) { | |
204 | CPlusPlus=1; | |
205 | mark_arg(i); | |
206 | } else if (strcmp(argv[i],"-objc") == 0) { | |
207 | ObjC = 1; | |
208 | mark_arg(i); | |
209 | } else if (strcmp(argv[i],"-c") == 0) { | |
210 | NoInclude=1; | |
211 | mark_arg(i); | |
212 | } else if (strcmp(argv[i],"-make_default") == 0) { | |
213 | GenerateDefault = 1; | |
214 | mark_arg(i); | |
215 | } else if (strcmp(argv[i],"-swiglib") == 0) { | |
216 | printf("%s\n", LibDir); | |
217 | SWIG_exit(0); | |
218 | } else if (strcmp(argv[i],"-o") == 0) { | |
219 | mark_arg(i); | |
220 | if (argv[i+1]) { | |
221 | outfile_name = copy_string(argv[i+1]); | |
222 | mark_arg(i+1); | |
223 | i++; | |
224 | } else { | |
225 | arg_error(); | |
226 | } | |
227 | } else if (strcmp(argv[i],"-d") == 0) { | |
228 | mark_arg(i); | |
229 | if (argv[i+1]) { | |
230 | doc_file = copy_string(argv[i+1]); | |
231 | mark_arg(i+1); | |
232 | i++; | |
233 | } else { | |
234 | arg_error(); | |
235 | } | |
236 | } else if (strcmp(argv[i],"-t") == 0) { | |
237 | mark_arg(i); | |
238 | if (argv[i+1]) { | |
239 | typemap_file = copy_string(argv[i+1]); | |
240 | mark_arg(i+1); | |
241 | i++; | |
242 | } else { | |
243 | arg_error(); | |
244 | } | |
245 | } else if (strcmp(argv[i],"-version") == 0) { | |
246 | fprintf(stderr,"\nSWIG Version %d.%d %s\n", SWIG_MAJOR_VERSION, | |
247 | SWIG_MINOR_VERSION, SWIG_SPIN); | |
248 | fprintf(stderr,"Copyright (c) 1995-98\n"); | |
249 | fprintf(stderr,"University of Utah and the Regents of the University of California\n"); | |
250 | fprintf(stderr,"\nCompiled with %s\n", SWIG_CC); | |
251 | SWIG_exit(0); | |
252 | } else if (strncmp(argv[i],"-l",2) == 0) { | |
253 | // Add a new directory search path | |
254 | library_add(argv[i]+2); | |
255 | mark_arg(i); | |
256 | } else if (strcmp(argv[i],"-co") == 0) { | |
257 | checkout = 1; | |
258 | mark_arg(i); | |
259 | } else if (strcmp(argv[i],"-ci") == 0) { | |
260 | checkin = 1; | |
261 | mark_arg(i); | |
262 | } else if (strcmp(argv[i],"-help") == 0) { | |
263 | fputs(usage,stderr); | |
264 | mark_arg(i); | |
265 | help = 1; | |
266 | } | |
267 | } | |
268 | } | |
269 | ||
270 | while (includecount > 0) { | |
271 | add_directory(includefiles[--includecount]); | |
272 | } | |
273 | ||
274 | // Create a new documentation handler | |
275 | ||
276 | if (doc == 0) doc = new ASCII; | |
277 | ||
278 | // Open up a comment handler | |
279 | ||
280 | comment_handler = new CommentHandler(); | |
281 | comment_handler->parse_args(argc,argv); | |
282 | if (ignorecomments) comment_handler->style("ignore",0); | |
283 | ||
284 | // Create a new documentation entry | |
285 | ||
286 | doctitle = new DocTitle("",0); | |
287 | doctitle->parse_args(argc,argv); | |
288 | doc_entry = doctitle; | |
289 | ||
290 | // Handle documentation module options | |
291 | ||
292 | doc->parse_args(argc,argv); | |
293 | ||
294 | // Parse language dependent options | |
295 | ||
296 | lang->parse_args(argc,argv); | |
297 | ||
298 | if (help) SWIG_exit(0); // Exit if we're in help mode | |
299 | ||
300 | // Check all of the options to make sure we're cool. | |
301 | ||
302 | check_options(); | |
303 | ||
304 | // If we made it this far, looks good. go for it.... | |
305 | ||
306 | // Create names of temporary files that are created | |
307 | ||
308 | sprintf(infilename,"%s", argv[argc-1]); | |
309 | input_file = new char[strlen(infilename)+1]; | |
310 | strcpy(input_file, infilename); | |
311 | ||
312 | // If the user has requested to check out a file, handle that | |
313 | ||
314 | if (checkout) { | |
315 | int stat; | |
316 | char *outfile = input_file; | |
317 | if (outfile_name) | |
318 | outfile = outfile_name; | |
319 | stat = checkout_file(input_file,outfile); | |
320 | if (!stat) { | |
321 | fprintf(stderr,"%s checked out from the SWIG library\n",input_file); | |
322 | } else { | |
323 | FILE * f = fopen(input_file,"r"); | |
324 | if (f) { | |
325 | fprintf(stderr,"Unable to check-out %s. File already exists.\n", input_file); | |
326 | fclose(f); | |
327 | } else { | |
328 | fprintf(stderr,"Unable to check-out %s\n", input_file); | |
329 | } | |
330 | } | |
331 | } else if (checkin) { | |
332 | // Try to check-in a file to the SWIG library | |
333 | int stat; | |
334 | char *outname = input_file; | |
335 | if (outfile_name) | |
336 | outname = outfile_name; | |
337 | stat = checkin_file(SwigLib, LibDir, input_file, outname); | |
338 | if (!stat) { | |
339 | fprintf(stderr,"%s checked-in to %s/%s/%s\n", input_file, SwigLib, LibDir, outname); | |
340 | } else { | |
341 | fprintf(stderr,"Unable to check-in %s to %s/%s\n", input_file, SwigLib, LibDir); | |
342 | } | |
343 | } else { | |
344 | doctitle->file = copy_string(input_file); | |
345 | doctitle->line_number = -1000; | |
346 | doctitle->end_line = -1000; | |
347 | ||
348 | // Check the suffix for a .c file. If so, we're going to | |
349 | // declare everything we see as "extern" | |
350 | ||
351 | check_suffix(infilename); | |
352 | ||
353 | // Strip off suffix | |
354 | ||
355 | c = infilename + strlen(infilename); | |
356 | while (c != infilename) { | |
357 | if (*c == '.') { | |
358 | *c = 0; | |
359 | break; | |
360 | } else { | |
361 | c--; | |
362 | } | |
363 | } | |
364 | ||
365 | if (!outfile_name) { | |
366 | sprintf(fn_header,"%s_wrap.c",infilename); | |
367 | strcpy(infile,infilename); | |
368 | strcpy(output_dir,""); | |
369 | } else { | |
370 | sprintf(fn_header,"%s",outfile_name); | |
371 | // Try to identify the output directory | |
372 | char *cc = outfile_name; | |
373 | char *lastc = outfile_name; | |
374 | while (*cc) { | |
375 | #ifdef MACSWIG | |
376 | if (*cc == ':') lastc = cc+1; | |
377 | #else | |
378 | if (*cc == '/') lastc = cc+1; | |
379 | #endif | |
380 | cc++; | |
381 | } | |
382 | cc = outfile_name; | |
383 | char *dd = output_dir; | |
384 | while (cc != lastc) { | |
385 | *dd = *cc; | |
386 | dd++; | |
387 | cc++; | |
388 | } | |
389 | *dd = 0; | |
390 | // Patch up the input filename | |
391 | cc = infilename + strlen(infilename); | |
392 | while (cc != infilename) { | |
393 | #ifdef MACSWIG | |
394 | if (*cc == ':') { | |
395 | cc++; | |
396 | break; | |
397 | } | |
398 | #else | |
399 | if (*cc == '/') { | |
400 | cc++; | |
401 | break; | |
402 | } | |
403 | #endif | |
404 | cc--; | |
405 | } | |
406 | strcpy(infile,cc); | |
407 | } | |
408 | ||
409 | sprintf(fn_wrapper,"%s%s_wrap.wrap",output_dir,infile); | |
410 | sprintf(fn_init,"%s%s_wrap.init",output_dir,infile); | |
411 | ||
412 | sprintf(title,"%s", fn_header); | |
413 | ||
414 | // Open up files | |
415 | ||
416 | if ((f_input = fopen(input_file,"r")) == 0) { | |
417 | // Okay. File wasn't found right away. Let's see if we can | |
418 | // extract it from the SWIG library instead. | |
419 | if ((checkout_file(input_file,input_file)) == -1) { | |
420 | fprintf(stderr,"Unable to open %s\n", input_file); | |
421 | SWIG_exit(0); | |
422 | } else { | |
423 | // Successfully checked out a file from the library, print a warning and | |
424 | // continue | |
425 | checkout = 1; | |
426 | fprintf(stderr,"%s checked out from the SWIG library.\n",input_file); | |
427 | if ((f_input = fopen(input_file,"r")) == 0) { | |
428 | fprintf(stderr,"Unable to open %s\n", input_file); | |
429 | SWIG_exit(0); | |
430 | } | |
431 | } | |
432 | } | |
433 | ||
434 | // Add to the include list | |
435 | ||
436 | add_iname(infilename); | |
437 | ||
438 | // Initialize the scanner | |
439 | ||
440 | LEX_in = f_input; | |
441 | scanner_file(LEX_in); | |
442 | ||
443 | // printf("fn_header = %s\n", fn_header); | |
444 | // printf("fn_wrapper = %s\n", fn_wrapper); | |
445 | // printf("fn_init = %s\n", fn_init); | |
446 | ||
447 | if((f_header = fopen(fn_header,"w")) == 0) { | |
448 | fprintf(stderr,"Unable to open %s\n", fn_header); | |
449 | exit(0); | |
450 | } | |
451 | if((f_wrappers = fopen(fn_wrapper,"w")) == 0) { | |
452 | fprintf(stderr,"Unable to open %s\n",fn_wrapper); | |
453 | exit(0); | |
454 | } | |
455 | if ((f_init = fopen(fn_init,"w")) == 0) { | |
456 | fprintf(stderr,"Unable to open %s\n",fn_init); | |
457 | exit(0); | |
458 | } | |
459 | ||
460 | // Open up documentation | |
461 | ||
462 | if (doc_file) { | |
463 | doc->init(doc_file); | |
464 | } else { | |
465 | doc_file = new char[strlen(infile)+strlen(output_dir)+8]; | |
466 | sprintf(doc_file,"%s%s_wrap",output_dir,infile); | |
467 | doc->init(doc_file); | |
468 | } | |
469 | ||
470 | // Set up the typemap for handling new return strings | |
471 | { | |
472 | DataType *temp_t = new DataType(T_CHAR); | |
473 | temp_t->is_pointer++; | |
474 | if (CPlusPlus) | |
475 | typemap_register("newfree",typemap_lang,temp_t,"","delete [] $source;\n",0); | |
476 | else | |
477 | typemap_register("newfree",typemap_lang,temp_t,"","free($source);\n",0); | |
478 | ||
479 | delete temp_t; | |
480 | } | |
481 | ||
482 | // Define the __cplusplus symbol | |
483 | if (CPlusPlus) | |
484 | add_symbol("__cplusplus",0,0); | |
485 | ||
486 | ||
487 | // Load up the typemap file if given | |
488 | ||
489 | if (typemap_file) { | |
490 | if (include_file(typemap_file) == -1) { | |
491 | fprintf(stderr,"Unable to locate typemap file %s. Aborting.\n", typemap_file); | |
492 | SWIG_exit(1); | |
493 | } | |
494 | } | |
495 | ||
496 | // If in Objective-C mode. Load in a configuration file | |
497 | ||
498 | if (ObjC) { | |
499 | // Add the 'id' object type as a void * | |
500 | /* DataType *t = new DataType(T_VOID); | |
501 | t->is_pointer = 1; | |
502 | t->implicit_ptr = 0; | |
503 | t->typedef_add("id"); | |
504 | delete t; | |
505 | */ | |
506 | } | |
507 | ||
508 | // Pass control over to the specific language interpreter | |
509 | ||
510 | lang->parse(); | |
511 | ||
512 | fclose(f_wrappers); | |
513 | fclose(f_init); | |
514 | ||
515 | swig_append(fn_wrapper,f_header); | |
516 | swig_append(fn_init,f_header); | |
517 | ||
518 | fclose(f_header); | |
519 | ||
520 | // Print out documentation. Due to tree-like nature of documentation, | |
521 | // printing out the title prints out everything. | |
522 | ||
523 | while(doctitle) { | |
524 | doctitle->output(doc); | |
525 | doctitle = doctitle->next; | |
526 | } | |
527 | ||
528 | doc->close(); | |
529 | ||
530 | // Remove temporary files | |
531 | ||
532 | remove(fn_wrapper); | |
533 | remove(fn_init); | |
534 | ||
535 | // If only producing documentation, remove the wrapper file as well | |
536 | ||
537 | if (DocOnly) | |
538 | remove(fn_header); | |
539 | ||
540 | // Check for undefined types that were used. | |
541 | ||
542 | if (Verbose) | |
543 | type_undefined_check(); | |
544 | ||
545 | if (Stats) { | |
546 | fprintf(stderr,"Wrapped %d functions\n", Stat_func); | |
547 | fprintf(stderr,"Wrapped %d variables\n", Stat_var); | |
548 | fprintf(stderr,"Wrapped %d constants\n", Stat_const); | |
549 | type_undefined_check(); | |
550 | } | |
551 | ||
552 | if (checkout) { | |
553 | // File was checked out from the SWIG library. Remove it now | |
554 | remove(input_file); | |
555 | } | |
556 | } | |
557 | #ifdef MACSWIG | |
558 | fclose(swig_log); | |
559 | } catch (SwigException) { | |
560 | fclose(swig_log); | |
561 | } | |
562 | #else | |
563 | exit(error_count); | |
564 | #endif | |
565 | return(error_count); | |
566 | } | |
567 | ||
568 | // -------------------------------------------------------------------------- | |
569 | // SWIG_exit() | |
570 | // | |
571 | // Fatal parser error. Exit and cleanup | |
572 | // -------------------------------------------------------------------------- | |
573 | ||
574 | void SWIG_exit(int) { | |
575 | ||
576 | if (f_wrappers) { | |
577 | fclose(f_wrappers); | |
578 | remove(fn_wrapper); | |
579 | } | |
580 | if (f_header) { | |
581 | fclose(f_header); | |
582 | remove(fn_header); | |
583 | } | |
584 | if (f_init) { | |
585 | fclose(f_init); | |
586 | remove(fn_init); | |
587 | } | |
588 | #ifndef MACSWIG | |
589 | exit(1); | |
590 | #else | |
591 | throw SwigException(); | |
592 | #endif | |
593 | } | |
594 | ||
595 | ||
596 | // -------------------------------------------------------------------------- | |
597 | // swig_pragma(char *name, char *value) | |
598 | // | |
599 | // Handle pragma directives. Not many supported right now | |
600 | // -------------------------------------------------------------------------- | |
601 | ||
602 | void swig_pragma(char *name, char *value) { | |
603 | ||
604 | if (strcmp(name,"make_default") == 0) { | |
605 | GenerateDefault = 1; | |
606 | } | |
607 | if (strcmp(name,"no_default") == 0) { | |
608 | GenerateDefault = 0; | |
609 | } | |
610 | if (strcmp(name,"objc_new") == 0) { | |
611 | objc_construct = copy_string(value); | |
612 | } | |
613 | if (strcmp(name,"objc_delete") == 0) { | |
614 | objc_destruct = copy_string(value); | |
615 | } | |
616 | } | |
617 | ||
618 | ||
619 | ||
620 | char* getSwigLib() { | |
621 | char* c; | |
622 | char* rv; | |
623 | ||
624 | // Check for SWIG_LIB environment variable | |
625 | if ((c = getenv("SWIG_LIB")) != (char *) 0) { | |
626 | rv = c; | |
627 | } else { | |
628 | #ifdef SWIG_LIB | |
629 | rv = SWIG_LIB; | |
630 | #else | |
631 | // use executable location | |
632 | static char path[256]; | |
633 | char* last; | |
634 | strcpy(path, __argv[0]); | |
635 | last = strrchr(path, '/'); | |
636 | if (! last) last = strrchr(path, '\\'); | |
637 | if (last) | |
638 | strcpy(last+1, "swig_lib"); | |
639 | else | |
640 | strcpy(path, "swig_lib"); | |
641 | rv = path; | |
642 | #endif | |
643 | } | |
644 | printf("Using swig lib at: %s\n", rv); | |
645 | return rv; | |
646 | } | |
647 |