]> git.sur5r.net Git - cc65/blob - src/grc65/grc65.c
Removed -f option. Just always overwrite files - as the other tools do.
[cc65] / src / grc65 / grc65.c
1 /* GEOS resource compiler
2
3    by Maciej 'YTM/Elysium' Witkowiak
4
5    see GEOSLib documentation for license info
6 */
7
8 /* - make it work, then do it better
9    - more or less comments? it was hard to code, should be even harder to
10      understand =D
11    - add loadable icons feature (binary - 63 bytes)
12 */
13
14 /* - err, maybe free allocated memory, huh? (who cares, it's just a little prog...)
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <time.h>
23
24 /* common stuff */
25 #include "fname.h"
26 #include "abend.h"
27 #include "chartype.h"
28 #include "xmalloc.h"
29
30 /* I hope that no one will be able to create a .grc bigger than this... */
31 #define BLOODY_BIG_BUFFER 65000
32
33 /* there are no 6MB GEOS binaries... I hope! */
34 #define THIS_BUFFER_IS_SOOO_HUGE 6000000
35
36
37 struct menuitem {
38     char *name;
39     char *type;
40     char *target;
41     struct menuitem *next;
42 };
43
44 struct menu {
45     char *name;
46     int top, left;
47     int bot, right;
48     char *type;
49     struct menuitem *item;
50 };
51
52 struct appheader {
53     int year, month, day, hour, min;
54     int mode;
55     int dostype;
56     int geostype;
57     int structure;
58     char *dosname;
59     char *classname;
60     char *version;
61     char *author;
62     char *info;
63     char *icon;
64 };
65
66 const char *mainToken[] = {"MENU", "HEADER", "ICON", "DIALOG", "VLIR", ""};
67
68 const char *hdrFTypes[] = {"APPLICATION", "AUTO_EXEC", "DESK_ACC", "ASSEMBLY",
69                            "DISK_DEVICE", "PRINTER", "SYSTEM", ""};
70
71 const char *hdrFields[] = {"author", "info", "date", "dostype", "mode", "structure", "icon", ""};
72
73 const char *hdrDOSTp[] = {"seq", "SEQ", "prg", "PRG", "usr", "USR", ""};
74
75 const char *hdrStructTp[] = {"seq", "SEQ", "vlir", "VLIR", ""};
76
77 const char *hdrModes[] = {"any", "40only", "80only", "c64only", ""};
78
79 const int BSWTab[] = {0, 0x005, 0x007, 0x00b, 0x011, 0x017, 0x01d, 0x023,
80     0x025, 0x029, 0x02d, 0x033, 0x039, 0x03c, 0x041, 0x043, 0x04a, 0x04f,
81     0x052, 0x056, 0x05a, 0x05f, 0x063, 0x068, 0x06d, 0x072, 0x077, 0x079,
82     0x07c, 0x080, 0x084, 0x088, 0x08e, 0x094, 0x09a, 0x09f, 0x0a4, 0x0a9,
83     0x0ad, 0x0b1, 0x0b6, 0x0bc, 0x0be, 0x0c2, 0x0c8, 0x0cc, 0x0d4, 0x0da,
84     0x0e0, 0x0e5, 0x0eb, 0x0f0, 0x0f5, 0x0f9, 0x0fe, 0x104, 0x10c, 0x112,
85     0x118, 0x11e, 0x121, 0x129, 0x12c, 0x132, 0x13a, 0x13e, 0x143, 0x148,
86     0x14d, 0x152, 0x157, 0x15a, 0x15f, 0x164, 0x166, 0x168, 0x16d, 0x16f,
87     0x177, 0x17c, 0x182, 0x187, 0x18c, 0x18f, 0x193, 0x196, 0x19b, 0x1a1,
88     0x1a9, 0x1af, 0x1b4, 0x1ba, 0x1be, 0x1c0, 0x1c4, 0x1ca, 0x1d2, 0x1dd};
89
90 const unsigned char icon1[] = {255, 255, 255, 128,   0,   1, 128,   0,   1,
91                                128,   0,   1, 128,   0,   1, 128,   0,   1,
92                                128,   0,   1, 128,   0,   1, 128,   0,   1,
93                                128,   0,   1, 128,   0,   1, 128,   0,   1,
94                                128,   0,   1, 128,   0,   1, 128,   0,   1,
95                                128,   0,   1, 128,   0,   1, 128,   0,   1,
96                                128,   0,   1, 128,   0,   1, 255, 255, 255};
97
98 char *ProgName; /* for AbEnd, later remove and use common/cmdline.h */
99
100 char *outputCName = NULL, *outputSName = NULL;
101 FILE *outputCFile, *outputSFile;
102 int CFnum = 0, SFnum = 0;
103 char outputCMode[2] = "w";
104 char outputSMode[2] = "w";
105
106
107 void printUsage(void) {
108
109     printf("Usage: %s [options] file\n"
110            "Options:\n"
111            "\t-h, -?\t\tthis help\n"
112            "\t-o name\t\tname C output file\n"
113            "\t-s name\t\tname asm output file\n",
114            ProgName);
115 }
116
117
118 void printCHeader(void) {
119
120     fprintf(outputCFile,
121         "//\n"
122         "//\tThis file was generated by the GEOS Resource Compiler\n"
123         "//\n"
124         "//\tDO NOT EDIT! Any changes will be lost!\n"
125         "//\n"
126         "//\tEdit proper resource file instead.\n"
127         "//\n\n");
128 }
129
130
131 void printSHeader(void) {
132
133     fprintf(outputSFile,
134         ";\n"
135         ";\tThis file was generated by the GEOS Resource Compiler\n"
136         ";\n"
137         ";\tDO NOT EDIT! Any changes will be lost!\n"
138         ";\n"
139         ";\tEdit proper resource file instead.\n"
140         ";\n\n");
141 }
142
143
144 void openCFile(void) {
145
146     if ((outputCFile = fopen(outputCName,outputCMode)) == 0) {
147         AbEnd("can't open file %s for writing: %s\n", outputCName, strerror(errno));
148     }
149
150     if (CFnum == 0) {
151         outputCMode[0] = 'a';
152         printCHeader();
153         CFnum++;
154     }
155 }
156
157
158 void openSFile(void) {
159
160     if ((outputSFile = fopen(outputSName, outputSMode)) == 0) {
161         AbEnd("can't open file %s for writing: %s\n", outputSName, strerror(errno));
162     }
163
164     if (SFnum == 0) {
165         outputSMode[0] = 'a';
166         printSHeader();
167         SFnum++;
168     }
169 }
170
171
172 int findToken(const char **tokenTbl, const char *token) {
173
174     /* takes as input table of tokens and token, returns position in table or -1 if not found */
175     int a = 0;
176
177     while (strlen(tokenTbl[a]) != 0) {
178         if (strcmp(tokenTbl[a], token) == 0) break;
179         a++;
180     }
181
182     if (strlen(tokenTbl[a]) == 0) a = -1;
183     return a;
184 }
185
186
187 char *nextPhrase() {
188     return strtok(NULL, "\"");
189 }
190
191
192 char *nextWord() {
193     return strtok(NULL, " ");
194 }
195
196
197 void setLen(char *name, unsigned len) {
198     if (strlen(name) > len)
199         name[len] = '\0';
200 }
201
202
203 void fillOut(char *name, int len, char *filler) {
204
205     int a;
206
207     setLen(name, len);
208     fprintf(outputSFile, "\t.byte \"%s\"\n", name);
209     
210     a = strlen(name);
211     if (a < len) {
212         fprintf(outputSFile, "\t.res  (%i - %i), %s\n", len, a, filler);
213     }
214 }
215
216
217 char *bintos(unsigned char a, char out[7]) {
218
219     int i=0;
220
221     for (; i < 8; i++) {
222         out[7 - i] = ((a & 1) == 0) ? '0' : '1';
223         a = a >> 1;
224     }
225     out[i] = '\0';
226
227     return out;
228 }
229
230
231 int getNameSize(const char *word) {
232
233     /* count length of a word using BSW 9 font table */
234     int a = 0, i = 0;
235
236     while (word[i] != '\0') {
237         a += (BSWTab[word[i] - 31] - BSWTab[word[i] - 32]);
238         i++;
239     }
240
241     return a;
242 }
243
244
245 void DoMenu(void) {
246
247     int a, size, tmpsize, item = 0;
248     char *token;
249     char namebuff[255] = "";
250     struct menu myMenu;
251     struct menuitem *curItem, *newItem;
252
253     openCFile();
254
255     myMenu.name = nextWord();
256     myMenu.left = atoi(nextWord());
257     myMenu.top = atoi(nextWord());
258     myMenu.type = nextWord();
259
260     if (strcmp(nextWord(), "{") != 0) {
261         AbEnd("menu '%s' description has no opening bracket!\n", myMenu.name);
262     }
263     curItem = xmalloc(sizeof(struct menuitem));
264     myMenu.item = curItem;
265     do {
266         token = nextWord();
267         if (strcmp(token, "}") == 0) break;
268         if (token[strlen(token) - 1] != '"') {
269             strcpy(namebuff, token);
270             do {
271                 token = nextWord();
272                 strcat(namebuff, " ");
273                 strcat(namebuff, token);
274             } while (token[strlen(token) - 1] != '"');
275             token = xmalloc(strlen(namebuff));
276             strcpy(token, namebuff);
277         }
278         curItem->name = token;
279         curItem->type = nextWord();
280         curItem->target = nextWord();
281         newItem = xmalloc(sizeof(struct menuitem));
282         curItem->next = newItem;
283         curItem = newItem;
284         item++;
285         } while (strcmp(token, "}") != 0);
286     if (item == 0) AbEnd("menu '%s' has 0 items!\n", myMenu.name);
287     if (item > 31) AbEnd("menu '%s' has too many items!\n", myMenu.name);
288
289     curItem->next = NULL;
290
291     /* count menu sizes */
292     size = 0;
293     curItem = myMenu.item;
294     if (strstr(myMenu.type, "HORIZONTAL") != NULL) {
295         /* menu is HORIZONTAL, ysize=15, sum xsize of all items +~8?*/
296         myMenu.bot = myMenu.top + 15;
297         for (a = 0; a != item; a++) {
298             size += getNameSize(curItem->name);
299             curItem = curItem->next;
300         }
301     } else {
302         /* menu is VERTICAL, ysize=item*15, count largest xsize of all items +~8? */
303         myMenu.bot = myMenu.top + (14 * item);
304         for (a = 0; a != item; a++) {
305             tmpsize = getNameSize(curItem->name);
306             size = (size > tmpsize) ? size : tmpsize;
307             curItem = curItem->next;
308         }
309     }
310     myMenu.right = myMenu.left + size - 1;
311
312     curItem = myMenu.item;
313     for (a = 0; a != item; a++) {
314         /* print prototype only if MENU_ACTION or DYN_SUB_MENU are present in type */
315         if ((strstr(curItem->type, "MENU_ACTION") != NULL) || (strstr(curItem->type, "DYN_SUB_MENU") != NULL)) {
316             fprintf(outputCFile,
317                 "void %s (void);\n",
318                 curItem->target);
319         }
320         curItem=curItem->next;
321     }
322
323     fprintf(outputCFile,
324         "\n"
325         "const void %s = {\n"
326         "\t(char)%i, (char)%i,\n"
327         "\t(int)%i, (int)%i,\n"
328         "\t(char)(%i | %s),\n",
329         myMenu.name, myMenu.top, myMenu.bot, myMenu.left, myMenu.right, item, myMenu.type);
330
331     curItem = myMenu.item;
332     for (a = 0; a != item; a++) {
333         fprintf(outputCFile,
334             "\t%s, (char)%s, (int)",
335             curItem->name, curItem->type);
336         if ((strstr(curItem->type, "SUB_MENU") != NULL) && (strstr(curItem->type, "DYN_SUB_MENU") == NULL))
337             fprintf(outputCFile,
338                 "&");
339         fprintf(outputCFile,
340             "%s,\n",
341             curItem->target);
342         curItem = curItem->next;
343     }
344
345     fprintf(outputCFile,
346         "};\n\n");
347
348     if (fclose(outputCFile) != 0)
349         AbEnd("error closing %s: %s\n", outputCName, strerror(errno));
350 }
351
352
353 void DoHeader(void) {
354
355     time_t t;
356     struct tm *my_tm;
357
358     struct appheader myHead;
359     char *token;
360     char i1[9], i2[9], i3[9];
361     int a, b;
362
363     openSFile();
364
365     token = nextWord();
366
367     a = findToken(hdrFTypes, token);
368
369     switch (a) {
370         case 0:
371             myHead.geostype = 6;
372             break;
373         case 1:
374             myHead.geostype = 14;
375             break;
376         default:
377             AbEnd("filetype '%s' is not supported yet\n", token);
378     }
379
380     myHead.dosname = nextPhrase();
381     nextPhrase();
382     myHead.classname = nextPhrase();
383     nextPhrase();
384     myHead.version = nextPhrase();
385
386     /* put default values into myHead here */
387     myHead.author = "cc65";
388     myHead.info = "Program compiled with cc65 and GEOSLib.";
389     myHead.dostype = 128 + 3;
390     myHead.structure = 0;
391     myHead.mode = 0;
392     myHead.icon = NULL;
393
394     t = time(NULL);
395     my_tm = localtime(&t);
396
397     myHead.year = my_tm->tm_year;
398     myHead.month = my_tm->tm_mon+1;
399     myHead.day = my_tm->tm_mday;
400     myHead.hour = my_tm->tm_hour;
401     myHead.min = my_tm->tm_min;
402
403     if (strcmp(nextWord(), "{") != 0) {
404         AbEnd("header '%s' has no opening bracket!\n", myHead.dosname);
405     }
406
407     do {
408         token = nextWord();
409         if (strcmp(token, "}") == 0) break;
410         switch (a = findToken(hdrFields, token)) {
411             case -1:
412                 AbEnd("unknown field '%s' in header '%s'\n", token, myHead.dosname);
413                 break;
414             case 0: /* author */
415                 myHead.author = nextPhrase();
416                 break;
417             case 1: /* info */
418                 myHead.info = nextPhrase();
419                 break;
420             case 2: /* date */
421                 myHead.year = atoi(nextWord());
422                 myHead.month = atoi(nextWord());
423                 myHead.day = atoi(nextWord());
424                 myHead.hour = atoi(nextWord());
425                 myHead.min = atoi(nextWord());
426                 break;
427             case 3: /* dostype */
428                 switch (b = findToken(hdrDOSTp, nextWord())) {
429                     case -1:
430                         AbEnd("unknown dostype in header '%s'\n", myHead.dosname);
431                         break;
432                     default:
433                         myHead.dostype = b / 2 + 128 + 1;
434                         break;
435                 }
436                 break;
437             case 4: /* mode */
438                 switch (b = findToken(hdrModes, nextWord())) {
439                     case -1:
440                         AbEnd("unknown mode in header '%s'\n", myHead.dosname);
441                     case 0:
442                         myHead.mode = 0x40;
443                         break;
444                     case 1:
445                         myHead.mode = 0x00;
446                         break;
447                     case 2:
448                         myHead.mode = 0xc0;
449                         break;
450                     case 3:
451                         myHead.mode = 0x80;
452                         break;
453                 }
454                 break;
455             case 5: /* structure */
456                 switch (b = findToken(hdrStructTp, nextWord())) {
457                     case -1:
458                         AbEnd("unknown structure type in header '%s'\n", myHead.dosname);
459                     case 0:
460                     case 1:
461                         myHead.structure = 0;
462                         break;
463                     case 2:
464                     case 3:
465                         myHead.structure = 1;
466                         break;
467                 }
468                 break;
469             case 6: /* icon */
470                 myHead.icon = nextPhrase();
471                 break;
472         }
473
474     } while (strcmp(token, "}") != 0);
475
476     /* OK, all information is gathered, do flushout */
477
478     fprintf(outputSFile,
479         "\n"
480         "\t\t.segment \"DIRENTRY\"\n\n"
481         "\t.byte %i\n"
482         "\t.word 0\n", myHead.dostype);
483
484     fillOut(myHead.dosname, 16, "$a0");
485
486     fprintf(outputSFile,
487         "\t.word 0\n"
488         "\t.byte %i\n"
489         "\t.byte %i\n"
490         "\t.byte %i, %i, %i, %i, %i\n\n"
491         "\t.word 0\n"
492         "\t.byte \"PRG formatted GEOS file V1.0\"\n\n",
493         myHead.structure, myHead.geostype,
494         myHead.year, myHead.month, myHead.day, myHead.hour, myHead.min);
495
496     fprintf(outputSFile,
497         "\n"
498         "\t\t.segment \"FILEINFO\"\n\n"
499         "\t.import __VLIR0_START__, __STARTUP_RUN__\n\n"
500         "\t.byte 3, 21, 63 | $80\n");
501
502     if (myHead.icon != NULL) {
503         fprintf(outputSFile,
504             "\t.incbin \"%s\", 0, 63\n",
505             myHead.icon);
506     } else {
507         for (a = 0; a != 63; a = a + 3) {
508             fprintf(outputSFile,
509                 "\t.byte %%%s, %%%s, %%%s\n",
510                 bintos(icon1[a], i1), bintos(icon1[a+1], i2), bintos(icon1[a+2], i3));
511         }
512     }
513
514     fprintf(outputSFile,
515         "\t.byte %i, %i, %i\n"
516         "\t.word __VLIR0_START__, __VLIR0_START__ - 1, __STARTUP_RUN__\n\n",
517         myHead.dostype, myHead.geostype, myHead.structure);
518
519     fillOut(myHead.classname, 12, "$20");
520
521     fillOut(myHead.version, 4, "0");
522
523     fprintf(outputSFile,
524         "\t.byte 0, 0, 0\n"
525         "\t.byte %i\n\n", myHead.mode);
526
527     setLen(myHead.author, 62);
528     fprintf(outputSFile,
529         "\t.byte \"%s\"\n"
530         "\t.byte 0\n"
531         "\t.res  (63 - %i)\n\n",
532         myHead.author, (int)(strlen(myHead.author) + 1));
533
534     setLen(myHead.info, 95);
535     fprintf(outputSFile,
536         "\t.byte \"%s\"\n"
537         "\t.byte 0\n\n", myHead.info);
538
539     if (fclose (outputSFile) != 0)
540         AbEnd("error closing %s: %s\n", outputSName, strerror(errno));
541 }
542
543
544 void DoVLIR(void) {
545
546     char *token;
547     int record, lastrecord;
548     int vlirsize, vlirtable[127];
549
550     openSFile();
551
552     vlirsize = strtol(nextWord(), NULL, 0);
553
554     if (strcmp(nextWord(), "{") != 0) {
555         AbEnd ("VLIR description has no opening bracket!\n");
556     }
557
558     lastrecord = -1;
559     memset(vlirtable, 0, sizeof(vlirtable));
560
561     do {
562         token = nextWord();
563         if (strcmp(token, "}") == 0) break;
564
565         record = atoi(token);
566         if (record < 0 || record > 126) {
567             AbEnd("VLIR record %i is out of range 0-126.\n", record);
568         }
569         if (vlirtable[record] == 1) {
570             AbEnd("VLIR record %i is defined twice.\n", record);
571         }
572
573         vlirtable[record] = 1;
574         if (record > lastrecord) lastrecord = record;
575     } while (strcmp(token, "}") != 0);
576
577     if (lastrecord == -1) {
578         AbEnd("There must be at least one VLIR record.\n");
579     }
580     
581     /* always include record 0 */
582     vlirtable[0] = 1;
583
584     /* OK, all information is gathered, do flushout */
585
586     fprintf(outputSFile,
587         "\n"
588         "\t\t.segment \"RECORDS\"\n\n"
589         "\t.export __OVERLAYSIZE__ : absolute = $%04x\n\n",
590         vlirsize);
591
592     for (record = 0; record <= lastrecord; record++) {
593         if (vlirtable[record] == 1) {
594             fprintf(outputSFile,
595                 "\t.import __VLIR%i_START__, __VLIR%i_LAST__\n",
596                 record, record);
597         }
598     }
599     fprintf(outputSFile,
600         "\n");
601
602     for (record = 0; record <= lastrecord; record++) {
603         if (vlirtable[record] == 1) {
604             fprintf(outputSFile,
605                 "\t.byte .lobyte ((__VLIR%i_LAST__ - __VLIR%i_START__ - 1) /    254) + 1\n"
606                 "\t.byte .lobyte ((__VLIR%i_LAST__ - __VLIR%i_START__ - 1) .MOD 254) + 2\n",
607                 record, record, record, record);
608         } else {
609             fprintf(outputSFile,
610                 "\t.byte $00\n"
611                 "\t.byte $FF\n");
612         }
613     }
614     fprintf(outputSFile,
615         "\n");
616
617     if (fclose(outputSFile) != 0)
618         AbEnd("error closing %s: %s\n", outputSName, strerror(errno));
619
620     openCFile();
621
622     fprintf(outputCFile,
623         "extern void _OVERLAYADDR__;\n"
624         "extern void _OVERLAYSIZE__;\n\n"
625         "#define OVERLAY_ADDR (char*)   &_OVERLAYADDR__\n"
626         "#define OVERLAY_SIZE (unsigned)&_OVERLAYSIZE__\n\n");
627
628     if (fclose(outputCFile) != 0)
629         AbEnd("error closing %s: %s\n", outputCName, strerror(errno));
630 }
631
632
633 char *filterInput(FILE *F, char *tbl) {
634
635     /* loads file into buffer filtering it out */
636     int a, prevchar = -1, i = 0, bracket = 0, quote = 1;
637
638     while (1) {
639         a = getc(F);
640         if ((a == '\n') || (a == '\015')) a = ' ';
641         if (a == ',' && quote) a = ' ';
642         if (a == '\042') quote =! quote;
643         if (quote) {
644             if ((a == '{') || (a == '(')) bracket++;
645             if ((a == '}') || (a == ')')) bracket--;
646         }
647         if (a == EOF) {
648             tbl[i] = '\0';
649             xrealloc(tbl, i + 1);
650             break;
651         }
652         if (IsSpace(a)) {
653             if ((prevchar != ' ') && (prevchar != -1)) {
654                 tbl[i++] = ' ';
655                 prevchar = ' ';
656             }
657         } else {
658             if (a == ';' && quote) {
659                 do {
660                     a = getc(F);
661                 } while (a != '\n');
662                 fseek(F, -1, SEEK_CUR);
663             } else {
664                 tbl[i++] = a;
665                 prevchar = a;
666             }
667         }
668     }
669
670     if (bracket != 0) AbEnd("there are unclosed brackets!\n");
671
672     return tbl;
673 }
674
675
676 void processFile(const char *filename) {
677
678     FILE *F;
679
680     char *str;
681     char *token;
682
683     int head = 0; /* number of processed HEADER sections */
684     int vlir = 0; /* number of processed VLIR sections */
685
686     if ((F = fopen(filename, "r")) == 0) {
687         AbEnd("can't open file %s for reading: %s\n", filename, strerror(errno));
688     }
689
690     str = filterInput(F, xmalloc(BLOODY_BIG_BUFFER));
691
692     token = strtok(str, " ");
693
694     do {
695         if (str != NULL) {
696             switch (findToken(mainToken, token)) {
697                 case 0:
698                     DoMenu();
699                     break;
700                 case 1:
701                     if (++head != 1) {
702                         AbEnd("more than one HEADER section, aborting.\n");
703                     } else {
704                         DoHeader();
705                     }
706                     break;
707                 case 2: break; /* icon not implemented yet */
708                 case 3: break; /* dialog not implemented yet */
709                 case 4:
710                     if (++vlir != 1) {
711                         AbEnd("more than one VLIR section, aborting.\n");
712                     } else {
713                         DoVLIR();
714                     }
715                     break;
716                 default:
717                     AbEnd("unknown section %s.\n",token);
718                     break;
719             }
720         }
721         token = nextWord();
722     } while (token != NULL);
723 }
724
725
726 int main(int argc, char *argv[]) {
727
728     int ffile = 0, i = 1;
729
730     ProgName = argv[0];
731
732     while (i < argc) {
733         const char *arg = argv[i];
734
735         if (arg[0] == '-') {
736             switch (arg[1]) {
737                 case 'o':
738                     outputCName = argv[++i];
739                     break;
740                 case 's':
741                     outputSName = argv[++i];
742                     break;
743                 case 'h':
744                 case '?':
745                     printUsage();
746                     exit(EXIT_SUCCESS);
747                     break;
748                 default:
749                     AbEnd("unknown option %s\n", arg);
750             }
751         } else {
752             ffile++;
753
754             if (outputCName == NULL) outputCName = MakeFilename(arg, ".h");
755             if (outputSName == NULL) outputSName = MakeFilename(arg, ".s");
756
757             processFile(arg);
758         }
759
760         i++;
761     }
762
763     if (ffile == 0) AbEnd("no input file\n");
764
765     return EXIT_SUCCESS;
766 }