source: trunk/gsdl3/packages/mg/src/text/gs3_mg_passes.c@ 7440

Last change on this file since 7440 was 7440, checked in by kjdon, 20 years ago

some hacky changes - fix up under linux

  • Property svn:keywords set to Author Date Id Revision
File size: 17.7 KB
Line 
1/**************************************************************************
2 *
3 * mg_passes.c -- Driver for the various passes
4 * Copyright (C) 1994 Neil Sharman
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., 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 * $Id: gs3_mg_passes.c 7440 2004-05-26 02:39:57Z kjdon $
21 *
22 **************************************************************************/
23/* this needs to come first */
24#include "sysfuncs.h"
25
26#ifdef HAVE_MALLINFO
27# include <malloc.h>
28#endif
29
30#include "memlib.h"
31#include "messages.h"
32#include "timing.h"
33
34#include "longlong.h"
35#include "stemmer.h"
36
37
38#include "mg_files.h"
39#include "mg.h"
40#include "build.h"
41#include "text.h"
42
43#include "words.h"
44
45/*
46 $Log$
47 Revision 1.2 2004/05/26 02:39:57 kjdon
48 some hacky changes - fix up under linux
49
50 Revision 1.1 2004/05/25 03:30:12 kjdon
51 new mg passes for gs3. I thought I had commited this already
52
53 Revision 1.2 2004/04/25 23:01:18 kjdon
54 added a new -M option to mg_passes, allowing maxnumeric to be altered - made this change to keep gsdl3 mg inline with gsdl2 mg.
55
56 Revision 1.1 2003/02/20 21:18:24 mdewsnip
57 Addition of MG package for search and retrieval
58
59 Revision 1.3 2001/09/21 12:46:42 kjm18
60 updated mg to be in line with mg_1.3f. Now uses long long for some variables
61 to enable indexing of very large collections.
62
63 Revision 1.2 2001/06/12 23:23:42 jrm21
64 fixed a bug where mg_passes segfaults when trying to print the usage message.
65
66 Revision 1.1 1999/08/10 21:18:12 sjboddie
67 renamed mg-1.3d directory mg
68
69 Revision 1.3 1998/12/17 09:12:53 rjmcnab
70
71 Altered mg to process utf-8 encoded Unicode. The main changes
72 are in the parsing of the input, the casefolding, and the stemming.
73
74 Revision 1.2 1998/11/25 07:55:47 rjmcnab
75
76 Modified mg to that you can specify the stemmer you want
77 to use via a command line option. You specify it to
78 mg_passes during the build process. The number of the
79 stemmer that you used is stored within the inverted
80 dictionary header and the stemmed dictionary header so
81 the correct stemmer is used in later stages of building
82 and querying.
83
84 Revision 1.1 1998/11/17 09:35:13 rjmcnab
85 *** empty log message ***
86
87 * Revision 1.3 1994/10/20 03:56:57 tes
88 * I have rewritten the boolean query optimiser and abstracted out the
89 * components of the boolean query.
90 *
91 * Revision 1.2 1994/09/20 04:41:52 tes
92 * For version 1.1
93 *
94
95*/
96
97static char *RCSID = "$Id: gs3_mg_passes.c 7440 2004-05-26 02:39:57Z kjdon $";
98
99#define MAX_PASSES 5
100
101#define SPECIAL 1
102#define TEXT_PASS_1 2
103#define TEXT_PASS_2 4
104#define IVF_PASS_1 8
105#define IVF_PASS_2 16
106
107#define MIN_BUF 8192
108#define TERMRECORD '\002'
109
110unsigned long buf_size = 3 * 1024 * 1024; /* 3Mb */
111unsigned long invf_buffer_size = 5 * 1024 * 1024; /* 5Mb */
112unsigned long ChunkLimit = 0;
113char InvfLevel = 2;
114char SkipSGML = 0;
115char MakeWeights = 0;
116FILE *Comp_Stats = NULL;
117int comp_stat_point = 0;
118mg_ullong bytes_processed = 0;
119mg_ullong bytes_received = 0;
120int stemmer_num = 0; /* default to the lovin stemmer */
121int stem_method = 0;
122FILE * Trace;
123char * filename;
124unsigned long num_docs = 0;
125unsigned long block_bytes = 0;
126
127static char Passes = 0;
128static unsigned long trace = 0;
129static int Dump = 0;
130static char **files = NULL;
131static int num_files = 0;
132static char *trace_name = NULL;
133
134typedef struct pass_data
135 {
136 char *name;
137 int (*init) (char *);
138 int (*process) (u_char *, int);
139 int (*done) (char *);
140#ifdef HAVE_TIMES
141 clock_t init_time;
142 clock_t process_time;
143 clock_t done_time;
144#else
145 struct timeval init_time;
146 struct timeval process_time;
147 struct timeval done_time;
148#endif
149 }
150pass_data;
151
152#ifdef HAVE_TIMES
153#define NULL_TIMES 0, 0, 0
154#else
155#define NULL_TIMES {0, 0}, {0, 0}, {0, 0}
156#endif
157
158static pass_data PassData[MAX_PASSES] =
159{
160 {"special", init_special, process_special, done_special, NULL_TIMES},
161 {"text.pass1", init_text_1, process_text_1, done_text_1, NULL_TIMES},
162 {"text.pass2", init_text_2, process_text_2, done_text_2, NULL_TIMES},
163 {"ivf.pass1", init_ivf_1, process_ivf_1, done_ivf_1, NULL_TIMES},
164 {"ivf.pass2", init_ivf_2, process_ivf_2, done_ivf_2, NULL_TIMES},
165};
166
167static char *usage_str = "\nUSAGE:\n"
168" %s [-h] [-G] [-D] [-1|-2|-3] [-T1] [-T2] [-I1] [-I2] [-N1]\n"
169" %*s [-N2] [-W] [-S] [-b buffer-size] [-d dictionary-directory]\n"
170" %*s [-t trace-point Mb] [-m invf-memory] [-c chunk-limit]\n"
171" %*s [-n trace-name] [-C comp-stat-size] [-s stem_method]\n"
172" %*s [-a stemmer] [-M max-numeric] -f doc-collection-name\n";
173
174
175static void
176usage (char *err)
177{
178 if (err)
179 Message (err);
180 fprintf (stderr, usage_str, msg_prefix, strlen (msg_prefix), "",
181 strlen (msg_prefix), "",strlen (msg_prefix), "",
182 strlen (msg_prefix),"");
183 exit (1);
184}
185
186
187
188
189#if 0
190static char *
191str_comma (unsigned long u)
192{
193 static char buf[20];
194 unsigned long a, b, c, d;
195 a = u / 1000000000;
196 u -= a * 1000000000;
197 b = u / 1000000;
198 u -= b * 1000000;
199 c = u / 1000;
200 u -= c * 1000;
201 d = u;
202
203 if (a)
204 sprintf (buf, "%u,%03u,%03u,%03u", a, b, c, d);
205 else if (b)
206 sprintf (buf, "%u,%03u,%03u", b, c, d);
207 else if (c)
208 sprintf (buf, "%u,%03u", c, d);
209 else
210 sprintf (buf, "%u", d);
211 return (buf);
212}
213#endif
214
215
216
217/*
218 int
219 open_next_file (int in_fd)
220 {
221 if (in_fd > 0)
222 close (in_fd);
223 if (num_files == 0)
224 return (-1);
225 if ((in_fd = open (files[0], O_RDONLY)) == -1)
226 FatalError (1, "Cannot open %s", files[0]);
227 files++;
228 num_files--;
229 return (in_fd);
230 }
231*/
232
233void clear_variables() {
234
235 buf_size = 3 * 1024 * 1024; /* 3Mb */
236 invf_buffer_size = 5 * 1024 * 1024; /* 5Mb */
237 ChunkLimit = 0;
238 InvfLevel = 2;
239 SkipSGML = 0;
240 MakeWeights = 0;
241 Comp_Stats = NULL;
242 comp_stat_point = 0;
243 bytes_processed = 0;
244 bytes_received = 0;
245 stemmer_num = 0; /* default to the lovin stemmer */
246 stem_method = 0;
247 Trace = NULL;
248 filename = NULL;
249 num_docs = 0;
250 block_bytes = 0;
251
252 Passes = 0;
253 trace = 0;
254 Dump = 0;
255 files = NULL;
256 num_files = 0;
257 trace_name = NULL;
258
259
260}
261void set_invf_level(char level) {
262
263 switch (level) {
264 case '1':
265 InvfLevel = 1;
266 break;
267 case '2':
268 InvfLevel = 2;
269 break;
270 case '3':
271 InvfLevel = 3;
272 break;
273 }
274
275}
276void set_stem_options(char * stemmer, int method) {
277 stemmer_num = stemmernumber (stemmer);
278 printf("stemmer num set to %d\n", stemmer_num);
279 stem_method = method & STEMMER_MASK;
280
281}
282
283void set_path(char * filen) {
284 int len = strlen(filen);
285 if (filename) {
286 Xfree (filename);
287 filename = NULL;
288 }
289 filename = Xstrdup (filen);
290 // put this here for now
291 SkipSGML=0;
292 Dump=1;
293 trace = 512;
294 if (!trace_name)
295 trace_name = make_name (filename, TRACE_SUFFIX, NULL);
296 if (!(Trace = fopen (trace_name, "a")))
297 Message ("Unable to open \"%s\". No tracing will be done.", trace_name);
298 else
299 setbuf (Trace, NULL);
300
301}
302void set_paths(char * basep, char* filen) {
303 int len = strlen(filen);
304 if (filename) {
305 Xfree (filename);
306 filename = NULL;
307 }
308
309 filename = Xstrdup (filen);
310 set_basepath(basep);
311 // put this here for now
312 SkipSGML=0;
313 Dump=1;
314 trace = 512;
315 if (!trace_name)
316 trace_name = make_name (filename, TRACE_SUFFIX, NULL);
317 if (!(Trace = fopen (trace_name, "a")))
318 Message ("Unable to open \"%s\". No tracing will be done.", trace_name);
319 else
320 setbuf (Trace, NULL);
321
322
323}
324
325void add_pass (char pass_type, char pass_num) {
326
327 switch(pass_type) {
328 case 'S':
329 Passes |= SPECIAL;
330 break;
331 case 'I':
332 case 'N':
333 if (pass_num == '1')
334 Passes |= IVF_PASS_1;
335 else if (pass_num == '2')
336 Passes |= IVF_PASS_2;
337 else
338 fprintf(stderr, "Invalid pass number %c for pass type %c\n", pass_num, pass_type);
339 break;
340 case 'T':
341 if (pass_num == '1')
342 Passes |= TEXT_PASS_1;
343 else if (pass_num == '2')
344 Passes |= TEXT_PASS_2;
345 else
346 fprintf(stderr, "Invalid pass number %c for pass type %c\n", pass_num, pass_type);
347 break;
348 }
349
350}
351ProgTime StartTime, InitTime, ProcTime, DoneTime;
352
353void
354init_driver ()
355{
356 int pass;
357
358 GetTime (&StartTime);
359
360 for (pass = 0; pass < MAX_PASSES; pass++) {
361 if (Passes & (1 << pass)) {
362 pass_data *pd = &PassData[pass];
363#ifdef HAVE_TIMES
364 struct tms tims;
365 times (&tims);
366 pd->init_time -= tims.tms_utime + tims.tms_stime;
367#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
368 struct rusage ru;
369
370 getrusage (RUSAGE_SELF, &ru);
371 pd->init_time.tv_sec -= ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
372 pd->init_time.tv_usec -= ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
373#endif
374 if (pd->init (filename) == COMPERROR)
375 FatalError (1, "Error during init of \"%s\"", pd->name);
376
377#ifdef HAVE_TIMES
378 times (&tims);
379 pd->init_time += tims.tms_utime + tims.tms_stime;
380#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
381 getrusage (RUSAGE_SELF, &ru);
382 pd->init_time.tv_sec += ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
383 pd->init_time.tv_usec += ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
384 time_normalise (&pd->init_time);
385#endif
386 }
387 }
388 GetTime (&InitTime);
389}
390
391
392void process_document(u_char *buffer, int len) {
393 int pass;
394 bytes_processed += len;
395
396 printf("process doc, len=%d\n",len);
397#ifndef QUIET
398 if (!len)
399 Message ("Warning : Processing zero length document");
400#endif
401
402 for (pass = 0; pass < MAX_PASSES; pass++) {
403 if (Passes & (1 << pass))
404 {
405 register pass_data *pd = &PassData[pass];
406
407#ifdef HAVE_TIMES
408 struct tms tims;
409 times (&tims);
410 pd->process_time -= tims.tms_utime + tims.tms_stime;
411#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
412 struct rusage ru;
413 register struct timeval *tv = &pd->process_time;
414
415 getrusage (RUSAGE_SELF, &ru);
416 tv->tv_sec -= ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
417 tv->tv_usec -= ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
418#endif
419 if (pd->process ((u_char *) buffer, len) == COMPERROR)
420 {
421 Message ("Error during processing of \"%s\"", pd->name);
422 if (Dump || Trace)
423 {
424 int i;
425 FILE *f = Trace ? Trace : stderr;
426 fprintf (f, "-=- * -=- * -=- * -=- * -=- * -=- * -=-\n");
427 for (i = 0; i < len; i++)
428 {
429 char ch = buffer[i];
430 if (ch == '\1' || ch == '\2')
431 ch = '\n';
432 putc (ch, f);
433 }
434 fprintf (f, "-=- * -=- * -=- * -=- * -=- * -=- * -=-\n");
435 }
436 if (Trace)
437 fprintf (Trace, "%11" ULL_FS " bytes |%7lu docs | %s\n",
438 bytes_processed, num_docs,
439 ElapsedTime (&StartTime, NULL));
440 exit (1);
441 }
442#ifdef HAVE_TIMES
443 times (&tims);
444 pd->process_time += tims.tms_utime + tims.tms_stime;
445#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
446 getrusage (RUSAGE_SELF, &ru);
447 tv->tv_sec += ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
448 tv->tv_usec += ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
449#endif
450 }
451 }
452 num_docs++;
453 if (Trace)
454 {
455 block_bytes += len;
456 if (block_bytes >= trace)
457 {
458#ifdef HAVE_MALLINFO
459 struct mallinfo mi;
460 mi = mallinfo ();
461 block_bytes -= trace;
462 fprintf (Trace, "%11" ULL_FS " bytes |%7lu docs |%7.3f Mb | %s\n",
463 bytes_processed, num_docs, mi.arena / 1024.0 / 1024.0,
464 ElapsedTime (&StartTime, NULL));
465#else
466 block_bytes -= trace;
467 fprintf (Trace, "%11" ULL_FS " bytes |%7lu docs | %s\n",
468 bytes_processed, num_docs,
469 ElapsedTime (&StartTime, NULL));
470#endif
471 }
472 }
473}
474
475void finalise_driver() {
476 int pass;
477#ifndef HAVE_TIMES
478 for (pass = 0; pass < MAX_PASSES; pass++)
479 if (Passes & (1 << pass))
480 time_normalise (&PassData[pass].process_time);
481#endif
482
483 GetTime (&ProcTime);
484
485 for (pass = 0; pass < MAX_PASSES; pass++)
486 if (Passes & (1 << pass))
487 {
488 pass_data *pd = &PassData[pass];
489#ifdef HAVE_TIMES
490 struct tms tims;
491 times (&tims);
492 pd->done_time -= tims.tms_utime + tims.tms_stime;
493#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
494 struct rusage ru;
495
496 getrusage (RUSAGE_SELF, &ru);
497 pd->done_time.tv_sec -= ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
498 pd->done_time.tv_usec -= ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
499#endif
500 if (pd->done (filename) == COMPERROR)
501 FatalError (1, "Error during done of \"%s\"", pd->name);
502
503#ifdef HAVE_TIMES
504 times (&tims);
505 pd->done_time += tims.tms_utime + tims.tms_stime;
506#elif defined(HAVE_GETRUSAGE) /* [RPAP - Feb 97: WIN32 Port] */
507 getrusage (RUSAGE_SELF, &ru);
508 pd->done_time.tv_sec += ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
509 pd->done_time.tv_usec += ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
510 time_normalise (&pd->done_time);
511#endif
512 }
513 if (Trace)
514 {
515#ifdef HAVE_MALLINFO
516 struct mallinfo mi;
517 mi = mallinfo ();
518 fprintf (Trace, "%11" ULL_FS " bytes |%7lu docs |%7.3f Mb | %s\n",
519 bytes_processed, num_docs, mi.arena / 1024.0 / 1024.0,
520 ElapsedTime (&StartTime, NULL));
521#else
522 fprintf (Trace, "%11" ULL_FS " bytes |%7lu docs | %s\n",
523 bytes_processed, num_docs,
524 ElapsedTime (&StartTime, NULL));
525#endif
526 }
527
528 GetTime (&DoneTime);
529
530 Message ("");
531 Message ("%10s : init process done", "");
532 for (pass = 0; pass < MAX_PASSES; pass++)
533 if (Passes & (1 << pass))
534 {
535 pass_data *pd = &PassData[pass];
536 char it[15], pt[15], dt[15];
537#ifdef HAVE_TIMES
538 strcpy (it, cputime_string (pd->init_time));
539 strcpy (pt, cputime_string (pd->process_time));
540 strcpy (dt, cputime_string (pd->done_time));
541#else
542 strcpy (it, cputime_string (&pd->init_time));
543 strcpy (pt, cputime_string (&pd->process_time));
544 strcpy (dt, cputime_string (&pd->done_time));
545#endif
546 Message ("%-10s : %s %s %s", pd->name, it, pt, dt);
547 }
548 Message ("");
549 Message ("Init time : %s", ElapsedTime (&StartTime, &InitTime));
550 Message ("Process time : %s", ElapsedTime (&InitTime, &ProcTime));
551 Message ("Done time : %s", ElapsedTime (&ProcTime, &DoneTime));
552 Message ("Total time : %s", ElapsedTime (&StartTime, &DoneTime));
553 Message ("Documents : %u", num_docs);
554 Message ("Bytes received : %" ULL_FS, bytes_received);
555 Message ("Bytes processed : %" ULL_FS, bytes_processed);
556 Message ("Process Rate : %.1f kB per cpu second",
557 (double) bytes_processed / (ProcTime.CPUTime - InitTime.CPUTime) / 1024);
558 //free (buffer);
559}
560
561
562
563int main (int argc, char **argv)
564{
565 int ch, in_fd;
566
567 msg_prefix = argv[0];
568
569 opterr = 0;
570 while ((ch = getopt (argc, argv, "hC:WGSD123f:d:b:T:I:t:m:N:c:n:s:a:M:")) != -1)
571 {
572 switch (ch)
573 {
574 case 'G':
575 SkipSGML = 1;
576 break;
577 case 'S':
578 Passes |= SPECIAL;
579 break;
580 case '1':
581 InvfLevel = 1;
582 break;
583 case '2':
584 InvfLevel = 2;
585 break;
586 case '3':
587 InvfLevel = 3;
588 break;
589 case 'f':
590 filename = optarg;
591 break;
592 case 'n':
593 trace_name = optarg;
594 break;
595 case 'D':
596 Dump = 1;
597 break;
598 case 'W':
599 MakeWeights = 1;
600 break;
601 case 'd':
602 set_basepath (optarg);
603 break;
604 case 'a':
605 stemmer_num = stemmernumber (optarg);
606 break;
607 case 's':
608 stem_method = atoi (optarg) & STEMMER_MASK;
609 break;
610 case 'b':
611 buf_size = atoi (optarg) * 1024;
612 break;
613 case 'C':
614 comp_stat_point = atoi (optarg) * 1024;
615 break;
616 case 'c':
617 ChunkLimit = atoi (optarg);
618 break;
619 case 'm':
620 invf_buffer_size = (int) (atof (optarg) * 1024 * 1024);
621 break;
622 case 'I':
623 case 'N': /* N kept for compatability */
624 if (*optarg == '1')
625 Passes |= IVF_PASS_1;
626 else if (*optarg == '2')
627 Passes |= IVF_PASS_2;
628 else
629 usage ("Invalid pass number");
630 break;
631 case 'T':
632 if (*optarg == '1')
633 Passes |= TEXT_PASS_1;
634 else if (*optarg == '2')
635 Passes |= TEXT_PASS_2;
636 else
637 usage ("Invalid pass number");
638 break;
639 case 't':
640 trace = (unsigned long) (atof (optarg) * 1024 * 1024);
641 break;
642 case 'M':
643 SetEnv ("maxnumeric", optarg, NULL);
644 break;
645 case 'h':
646 case '?':
647 usage (NULL);
648 }
649 }
650
651 if (!filename || *filename == '\0')
652 FatalError (1, "A document collection name must be specified.");
653
654 if (buf_size < MIN_BUF)
655 FatalError (1, "The buffer size must exceed 1024 bytes.");
656
657 if ((Passes & (IVF_PASS_1 | IVF_PASS_2)) == (IVF_PASS_1 | IVF_PASS_2))
658 FatalError (1, "I1 and I2 cannot be done simultaneously.");
659
660 if ((Passes & (TEXT_PASS_1 | TEXT_PASS_2)) == (TEXT_PASS_1 | TEXT_PASS_2))
661 FatalError (1, "T1 and T2 cannot be done simultaneously.");
662
663 if (!Passes)
664 FatalError (1, "S, T1, T2, I1 or I2 must be specified.");
665
666 if (optind < argc)
667 {
668 if ((in_fd = open (argv[optind], O_RDONLY)) == -1)
669 FatalError (1, "Cannot open %s", argv[optind]);
670 files = &argv[optind + 1];
671 num_files = argc - (optind + 1);
672 }
673 else
674 in_fd = 0; /* stdin */
675
676
677 if (trace)
678 {
679 if (!trace_name)
680 trace_name = make_name (filename, TRACE_SUFFIX, NULL);
681 if (!(Trace = fopen (trace_name, "a")))
682 Message ("Unable to open \"%s\". No tracing will be done.", trace_name);
683 else
684 setbuf (Trace, NULL);
685 }
686 else
687 Trace = NULL;
688
689 if (comp_stat_point)
690 {
691 char *name = make_name (filename, COMPRESSION_STATS_SUFFIX, NULL);
692 if (!(Comp_Stats = fopen (name, "wb"))) /* [RPAP - Feb 97: WIN32 Port] */
693 Message ("Unable to open \"%s\". No comp. stats. will be generated.",
694 name);
695 }
696
697
698 if (Trace)
699 {
700 int i;
701 fprintf (Trace, "\n\n\t\t-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n");
702 for (i = 0; i < argc; i++)
703 fprintf (Trace, "%s ", argv[i]);
704 fprintf (Trace, "\n\n");
705 }
706
707 init_driver ();
708 /* here we have to do something to process docs from stdin */
709 finalise_driver();
710 if (Trace)
711 fclose (Trace);
712
713 if (Comp_Stats)
714 fclose (Comp_Stats);
715
716 return 0;
717}
Note: See TracBrowser for help on using the repository browser.