]> git.sur5r.net Git - cc65/blob - src/grc65/grc65.c
Fixed typo.
[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 int forceFlag = 0;
104 char outputCMode[2] = "w";
105 char outputSMode[2] = "w";
106
107
108 void printUsage(void) {
109
110     printf("Usage: %s [options] file\n"
111            "Options:\n"
112            "\t-h, -?\t\tthis help\n"
113            "\t-f\t\tforce writing files\n"
114            "\t-o name\t\tname C output file\n"
115            "\t-s name\t\tname asm output file\n",
116            ProgName);
117 }
118
119
120 void printCHeader(void) {
121
122     fprintf(outputCFile,
123         "//\n"
124         "//\tThis file was generated by the GEOS Resource Compiler\n"
125         "//\n"
126         "//\tDO NOT EDIT! Any changes will be lost!\n"
127         "//\n"
128         "//\tEdit proper resource file instead.\n"
129         "//\n\n");
130 }
131
132
133 void printSHeader(void) {
134
135     fprintf(outputSFile,
136         ";\n"
137         ";\tThis file was generated by the GEOS Resource Compiler\n"
138         ";\n"
139         ";\tDO NOT EDIT! Any changes will be lost!\n"
140         ";\n"
141         ";\tEdit proper resource file instead.\n"
142         ";\n\n");
143 }
144
145
146 void openCFile(void) {
147
148     if ((CFnum == 0) && (forceFlag == 0)) {
149         /* test if file exists already and no forcing*/
150         if ((outputCFile = fopen(outputCName,"r")) != 0)
151             AbEnd("file %s already exists, aborting\n", outputCName);
152     }
153
154     if ((outputCFile = fopen(outputCName,outputCMode)) == 0) {
155         AbEnd("can't open file %s for writing: %s\n", outputCName, strerror(errno));
156     }
157
158     if (CFnum == 0) {
159         outputCMode[0] = 'a';
160         printCHeader();
161         CFnum++;
162     }
163 }
164
165
166 void openSFile(void) {
167
168     if ((SFnum == 0) && (forceFlag == 0)) {
169         /* test if file exists already and no forcing*/
170         if ((outputSFile = fopen(outputSName,"r")) != 0)
171             AbEnd("file %s already exists, aborting\n", outputSName);
172     }
173
174     if ((outputSFile = fopen(outputSName, outputSMode)) == 0) {
175         AbEnd("can't open file %s for writing: %s\n", outputSName, strerror(errno));
176     }
177
178     if (SFnum == 0) {
179         outputSMode[0] = 'a';
180         printSHeader();
181         SFnum++;
182     }
183 }
184
185
186 int findToken(const char **tokenTbl, const char *token) {
187
188     /* takes as input table of tokens and token, returns position in table or -1 if not found */
189     int a = 0;
190
191     while (strlen(tokenTbl[a]) != 0) {
192         if (strcmp(tokenTbl[a], token) == 0) break;
193         a++;
194     }
195
196     if (strlen(tokenTbl[a]) == 0) a = -1;
197     return a;
198 }
199
200
201 char *nextPhrase() {
202     return strtok(NULL, "\"");
203 }
204
205
206 char *nextWord() {
207     return strtok(NULL, " ");
208 }
209
210
211 void setLen(char *name, unsigned len) {
212     if (strlen(name) > len)
213         name[len] = '\0';
214 }
215
216
217 void fillOut(char *name, int len, char *filler) {
218
219     int a;
220
221     setLen(name, len);
222     fprintf(outputSFile, "\t.byte \"%s\"\n", name);
223     
224     a = strlen(name);
225     if (a < len) {
226         fprintf(outputSFile, "\t.res  (%i - %i), %s\n", len, a, filler);
227     }
228 }
229
230
231 char *bintos(unsigned char a, char out[7]) {
232
233     int i=0;
234
235     for (; i < 8; i++) {
236         out[7 - i] = ((a & 1) == 0) ? '0' : '1';
237         a = a >> 1;
238     }
239     out[i] = '\0';
240
241     return out;
242 }
243
244
245 int getNameSize(const char *word) {
246
247     /* count length of a word using BSW 9 font table */
248     int a = 0, i = 0;
249
250     while (word[i] != '\0') {
251         a += (BSWTab[word[i] - 31] - BSWTab[word[i] - 32]);
252         i++;
253     }
254
255     return a;
256 }
257
258
259 void DoMenu(void) {
260
261     int a, size, tmpsize, item = 0;
262     char *token;
263     char namebuff[255] = "";
264     struct menu myMenu;
265     struct menuitem *curItem, *newItem;
266
267     openCFile();
268
269     myMenu.name = nextWord();
270     myMenu.left = atoi(nextWord());
271     myMenu.top = atoi(nextWord());
272     myMenu.type = nextWord();
273
274     if (strcmp(nextWord(), "{") != 0) {
275         AbEnd("menu '%s' description has no opening bracket!\n", myMenu.name);
276     }
277     curItem = xmalloc(sizeof(struct menuitem));
278     myMenu.item = curItem;
279     do {
280         token = nextWord();
281         if (strcmp(token, "}") == 0) break;
282         if (token[strlen(token) - 1] != '"') {
283             strcpy(namebuff, token);
284             do {
285                 token = nextWord();
286                 strcat(namebuff, " ");
287                 strcat(namebuff, token);
288             } while (token[strlen(token) - 1] != '"');
289             token = xmalloc(strlen(namebuff));
290             strcpy(token, namebuff);
291         }
292         curItem->name = token;
293         curItem->type = nextWord();
294         curItem->target = nextWord();
295         newItem = xmalloc(sizeof(struct menuitem));
296         curItem->next = newItem;
297         curItem = newItem;
298         item++;
299         } while (strcmp(token, "}") != 0);
300     if (item == 0) AbEnd("menu '%s' has 0 items!\n", myMenu.name);
301     if (item > 31) AbEnd("menu '%s' has too many items!\n", myMenu.name);
302
303     curItem->next = NULL;
304
305     /* count menu sizes */
306     size = 0;
307     curItem = myMenu.item;
308     if (strstr(myMenu.type, "HORIZONTAL") != NULL) {
309         /* menu is HORIZONTAL, ysize=15, sum xsize of all items +~8?*/
310         myMenu.bot = myMenu.top + 15;
311         for (a = 0; a != item; a++) {
312             size += getNameSize(curItem->name);
313             curItem = curItem->next;
314         }
315     } else {
316         /* menu is VERTICAL, ysize=item*15, count largest xsize of all items +~8? */
317         myMenu.bot = myMenu.top + (14 * item);
318         for (a = 0; a != item; a++) {
319             tmpsize = getNameSize(curItem->name);
320             size = (size > tmpsize) ? size : tmpsize;
321             curItem = curItem->next;
322         }
323     }
324     myMenu.right = myMenu.left + size - 1;
325
326     curItem = myMenu.item;
327     for (a = 0; a != item; a++) {
328         /* print prototype only if MENU_ACTION or DYN_SUB_MENU are present in type */
329         if ((strstr(curItem->type, "MENU_ACTION") != NULL) || (strstr(curItem->type, "DYN_SUB_MENU") != NULL)) {
330             fprintf(outputCFile,
331                 "void %s (void);\n",
332                 curItem->target);
333         }
334         curItem=curItem->next;
335     }
336
337     fprintf(outputCFile,
338         "\n"
339         "const void %s = {\n"
340         "\t(char)%i, (char)%i,\n"
341         "\t(int)%i, (int)%i,\n"
342         "\t(char)(%i | %s),\n",
343         myMenu.name, myMenu.top, myMenu.bot, myMenu.left, myMenu.right, item, myMenu.type);
344
345     curItem = myMenu.item;
346     for (a = 0; a != item; a++) {
347         fprintf(outputCFile,
348             "\t%s, (char)%s, (int)",
349             curItem->name, curItem->type);
350         if ((strstr(curItem->type, "SUB_MENU") != NULL) && (strstr(curItem->type, "DYN_SUB_MENU") == NULL))
351             fprintf(outputCFile,
352                 "&");
353         fprintf(outputCFile,
354             "%s,\n",
355             curItem->target);
356         curItem = curItem->next;
357     }
358
359     fprintf(outputCFile,
360         "};\n\n");
361
362     if (fclose(outputCFile) != 0)
363         AbEnd("error closing %s: %s\n", outputCName, strerror(errno));
364 }
365
366
367 void DoHeader(void) {
368
369     time_t t;
370     struct tm *my_tm;
371
372     struct appheader myHead;
373     char *token;
374     char i1[9], i2[9], i3[9];
375     int a, b;
376
377     openSFile();
378
379     token = nextWord();
380
381     a = findToken(hdrFTypes, token);
382
383     switch (a) {
384         case 0:
385             myHead.geostype = 6;
386             break;
387         case 1:
388             myHead.geostype = 14;
389             break;
390         default:
391             AbEnd("filetype '%s' is not supported yet\n", token);
392     }
393
394     myHead.dosname = nextPhrase();
395     nextPhrase();
396     myHead.classname = nextPhrase();
397     nextPhrase();
398     myHead.version = nextPhrase();
399
400     /* put default values into myHead here */
401     myHead.author = "cc65";
402     myHead.info = "Program compiled with cc65 and GEOSLib.";
403     myHead.dostype = 128 + 3;
404     myHead.structure = 0;
405     myHead.mode = 0;
406     myHead.icon = NULL;
407
408     t = time(NULL);
409     my_tm = localtime(&t);
410
411     myHead.year = my_tm->tm_year;
412     myHead.month = my_tm->tm_mon+1;
413     myHead.day = my_tm->tm_mday;
414     myHead.hour = my_tm->tm_hour;
415     myHead.min = my_tm->tm_min;
416
417     if (strcmp(nextWord(), "{") != 0) {
418         AbEnd("header '%s' has no opening bracket!\n", myHead.dosname);
419     }
420
421     do {
422         token = nextWord();
423         if (strcmp(token, "}") == 0) break;
424         switch (a = findToken(hdrFields, token)) {
425             case -1:
426                 AbEnd("unknown field '%s' in header '%s'\n", token, myHead.dosname);
427                 break;
428             case 0: /* author */
429                 myHead.author = nextPhrase();
430                 break;
431             case 1: /* info */
432                 myHead.info = nextPhrase();
433                 break;
434             case 2: /* date */
435                 myHead.year = atoi(nextWord());
436                 myHead.month = atoi(nextWord());
437                 myHead.day = atoi(nextWord());
438                 myHead.hour = atoi(nextWord());
439                 myHead.min = atoi(nextWord());
440                 break;
441             case 3: /* dostype */
442                 switch (b = findToken(hdrDOSTp, nextWord())) {
443                     case -1:
444                         AbEnd("unknown dostype in header '%s'\n", myHead.dosname);
445                         break;
446                     default:
447                         myHead.dostype = b / 2 + 128 + 1;
448                         break;
449                 }
450                 break;
451             case 4: /* mode */
452                 switch (b = findToken(hdrModes, nextWord())) {
453                     case -1:
454                         AbEnd("unknown mode in header '%s'\n", myHead.dosname);
455                     case 0:
456                         myHead.mode = 0x40;
457                         break;
458                     case 1:
459                         myHead.mode = 0x00;
460                         break;
461                     case 2:
462                         myHead.mode = 0xc0;
463                         break;
464                     case 3:
465                         myHead.mode = 0x80;
466                         break;
467                 }
468                 break;
469             case 5: /* structure */
470                 switch (b = findToken(hdrStructTp, nextWord())) {
471                     case -1:
472                         AbEnd("unknown structure type in header '%s'\n", myHead.dosname);
473                     case 0:
474                     case 1:
475                         myHead.structure = 0;
476                         break;
477                     case 2:
478                     case 3:
479                         myHead.structure = 1;
480                         break;
481                 }
482                 break;
483             case 6: /* icon */
484                 myHead.icon = nextPhrase();
485                 break;
486         }
487
488     } while (strcmp(token, "}") != 0);
489
490     /* OK, all information is gathered, do flushout */
491
492     fprintf(outputSFile,
493         "\n"
494         "\t\t.segment \"DIRENTRY\"\n\n"
495         "\t.byte %i\n"
496         "\t.word 0\n", myHead.dostype);
497
498     fillOut(myHead.dosname, 16, "$a0");
499
500     fprintf(outputSFile,
501         "\t.word 0\n"
502         "\t.byte %i\n"
503         "\t.byte %i\n"
504         "\t.byte %i, %i, %i, %i, %i\n\n"
505         "\t.word 0\n"
506         "\t.byte \"PRG formatted GEOS file V1.0\"\n\n",
507         myHead.structure, myHead.geostype,
508         myHead.year, myHead.month, myHead.day, myHead.hour, myHead.min);
509
510     fprintf(outputSFile,
511         "\n"
512         "\t\t.segment \"FILEINFO\"\n\n"
513         "\t.import __VLIR0_START__, __STARTUP_RUN__\n\n"
514         "\t.byte 3, 21, 63 | $80\n");
515
516     if (myHead.icon != NULL) {
517         fprintf(outputSFile,
518             "\t.incbin \"%s\", 0, 63\n",
519             myHead.icon);
520     } else {
521         for (a = 0; a != 63; a = a + 3) {
522             fprintf(outputSFile,
523                 "\t.byte %%%s, %%%s, %%%s\n",
524                 bintos(icon1[a], i1), bintos(icon1[a+1], i2), bintos(icon1[a+2], i3));
525         }
526     }
527
528     fprintf(outputSFile,
529         "\t.byte %i, %i, %i\n"
530         "\t.word __VLIR0_START__, __VLIR0_START__ - 1, __STARTUP_RUN__\n\n",
531         myHead.dostype, myHead.geostype, myHead.structure);
532
533     fillOut(myHead.classname, 12, "$20");
534
535     fillOut(myHead.version, 4, "0");
536
537     fprintf(outputSFile,
538         "\t.byte 0, 0, 0\n"
539         "\t.byte %i\n\n", myHead.mode);
540
541     setLen(myHead.author, 62);
542     fprintf(outputSFile,
543         "\t.byte \"%s\"\n"
544         "\t.byte 0\n"
545         "\t.res  (63 - %i)\n\n",
546         myHead.author, (int)(strlen(myHead.author) + 1));
547
548     setLen(myHead.info, 95);
549     fprintf(outputSFile,
550         "\t.byte \"%s\"\n"
551         "\t.byte 0\n\n", myHead.info);
552
553     if (fclose (outputSFile) != 0)
554         AbEnd("error closing %s: %s\n", outputSName, strerror(errno));
555 }
556
557
558 void DoVLIR(void) {
559
560     char *token;
561     int record, lastrecord;
562     int vlirsize, vlirtable[127];
563
564     openSFile();
565
566     vlirsize = strtol(nextWord(), NULL, 0);
567
568     if (strcmp(nextWord(), "{") != 0) {
569         AbEnd ("VLIR description has no opening bracket!\n");
570     }
571
572     lastrecord = -1;
573     memset(vlirtable, 0, sizeof(vlirtable));
574
575     do {
576         token = nextWord();
577         if (strcmp(token, "}") == 0) break;
578
579         record = atoi(token);
580         if (record < 0 || record > 126) {
581             AbEnd("VLIR record %i is out of range 0-126.\n", record);
582         }
583         if (vlirtable[record] == 1) {
584             AbEnd("VLIR record %i is defined twice.\n", record);
585         }
586
587         vlirtable[record] = 1;
588         if (record > lastrecord) lastrecord = record;
589     } while (strcmp(token, "}") != 0);
590
591     if (lastrecord == -1) {
592         AbEnd("There must be at least one VLIR record.\n");
593     }
594     
595     /* always include record 0 */
596     vlirtable[0] = 1;
597
598     /* OK, all information is gathered, do flushout */
599
600     fprintf(outputSFile,
601         "\n"
602         "\t\t.segment \"RECORDS\"\n\n"
603         "\t.export __OVERLAYSIZE__ : absolute = $%04x\n\n",
604         vlirsize);
605
606     for (record = 0; record <= lastrecord; record++) {
607         if (vlirtable[record] == 1) {
608             fprintf(outputSFile,
609                 "\t.import __VLIR%i_START__, __VLIR%i_LAST__\n",
610                 record, record);
611         }
612     }
613     fprintf(outputSFile,
614         "\n");
615
616     for (record = 0; record <= lastrecord; record++) {
617         if (vlirtable[record] == 1) {
618             fprintf(outputSFile,
619                 "\t.byte .lobyte ((__VLIR%i_LAST__ - __VLIR%i_START__ - 1) /    254) + 1\n"
620                 "\t.byte .lobyte ((__VLIR%i_LAST__ - __VLIR%i_START__ - 1) .MOD 254) + 2\n",
621                 record, record, record, record);
622         } else {
623             fprintf(outputSFile,
624                 "\t.byte $00\n"
625                 "\t.byte $FF\n");
626         }
627     }
628     fprintf(outputSFile,
629         "\n");
630
631     if (fclose(outputSFile) != 0)
632         AbEnd("error closing %s: %s\n", outputSName, strerror(errno));
633
634     openCFile();
635
636     fprintf(outputCFile,
637         "extern void _OVERLAYADDR__;\n"
638         "extern void _OVERLAYSIZE__;\n\n"
639         "#define OVERLAY_ADDR (char*)   &_OVERLAYADDR__\n"
640         "#define OVERLAY_SIZE (unsigned)&_OVERLAYSIZE__\n\n");
641
642     if (fclose(outputCFile) != 0)
643         AbEnd("error closing %s: %s\n", outputCName, strerror(errno));
644 }
645
646
647 char *filterInput(FILE *F, char *tbl) {
648
649     /* loads file into buffer filtering it out */
650     int a, prevchar = -1, i = 0, bracket = 0, quote = 1;
651
652     while (1) {
653         a = getc(F);
654         if ((a == '\n') || (a == '\015')) a = ' ';
655         if (a == ',' && quote) a = ' ';
656         if (a == '\042') quote =! quote;
657         if (quote) {
658             if ((a == '{') || (a == '(')) bracket++;
659             if ((a == '}') || (a == ')')) bracket--;
660         }
661         if (a == EOF) {
662             tbl[i] = '\0';
663             xrealloc(tbl, i + 1);
664             break;
665         }
666         if (IsSpace(a)) {
667             if ((prevchar != ' ') && (prevchar != -1)) {
668                 tbl[i++] = ' ';
669                 prevchar = ' ';
670             }
671         } else {
672             if (a == ';' && quote) {
673                 do {
674                     a = getc(F);
675                 } while (a != '\n');
676                 fseek(F, -1, SEEK_CUR);
677             } else {
678                 tbl[i++] = a;
679                 prevchar = a;
680             }
681         }
682     }
683
684     if (bracket != 0) AbEnd("there are unclosed brackets!\n");
685
686     return tbl;
687 }
688
689
690 void processFile(const char *filename) {
691
692     FILE *F;
693
694     char *str;
695     char *token;
696
697     int head = 0; /* number of processed HEADER sections */
698     int vlir = 0; /* number of processed VLIR sections */
699
700     if ((F = fopen(filename, "r")) == 0) {
701         AbEnd("can't open file %s for reading: %s\n", filename, strerror(errno));
702     }
703
704     str = filterInput(F, xmalloc(BLOODY_BIG_BUFFER));
705
706     token = strtok(str, " ");
707
708     do {
709         if (str != NULL) {
710             switch (findToken(mainToken, token)) {
711                 case 0:
712                     DoMenu();
713                     break;
714                 case 1:
715                     if (++head != 1) {
716                         AbEnd("more than one HEADER section, aborting.\n");
717                     } else {
718                         DoHeader();
719                     }
720                     break;
721                 case 2: break; /* icon not implemented yet */
722                 case 3: break; /* dialog not implemented yet */
723                 case 4:
724                     if (++vlir != 1) {
725                         AbEnd("more than one VLIR section, aborting.\n");
726                     } else {
727                         DoVLIR();
728                     }
729                     break;
730                 default:
731                     AbEnd("unknown section %s.\n",token);
732                     break;
733             }
734         }
735         token = nextWord();
736     } while (token != NULL);
737 }
738
739
740 int main(int argc, char *argv[]) {
741
742     int ffile = 0, i = 1;
743
744     ProgName = argv[0];
745
746     while (i < argc) {
747         const char *arg = argv[i];
748
749         if (arg[0] == '-') {
750             switch (arg[1]) {
751                 case 'f':
752                     forceFlag = 1;
753                     break;
754                 case 'o':
755                     outputCName = argv[++i];
756                     break;
757                 case 's':
758                     outputSName = argv[++i];
759                     break;
760                 case 'h':
761                 case '?':
762                     printUsage();
763                     exit(EXIT_SUCCESS);
764                     break;
765                 default:
766                     AbEnd("unknown option %s\n", arg);
767             }
768         } else {
769             ffile++;
770
771             if (outputCName == NULL) outputCName = MakeFilename(arg, ".h");
772             if (outputSName == NULL) outputSName = MakeFilename(arg, ".s");
773
774             processFile(arg);
775         }
776
777         i++;
778     }
779
780     if (ffile == 0) AbEnd("no input file\n");
781
782     return EXIT_SUCCESS;
783 }