]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - main.cpp
Merge branch 'cleanup' of git://github.com/graphitemaster/gmqcc into cleanup
[xonotic/gmqcc.git] / main.cpp
1 #include <stdlib.h>
2 #include <string.h>
3
4 #include "gmqcc.h"
5 #include "lexer.h"
6
7 /* TODO: cleanup this whole file .. it's a fuckign mess */
8
9 /* set by the standard */
10 const oper_info *operators = nullptr;
11 size_t operator_count = 0;
12 static bool opts_output_wasset = false;
13 struct argitem { char *filename; int type; };
14 struct ppitem { char *name; char *value; };
15 static argitem *items = nullptr;
16 static ppitem  *ppems = nullptr;
17
18 #define TYPE_QC  0
19 #define TYPE_ASM 1
20 #define TYPE_SRC 2
21
22 static const char *app_name;
23
24 static void version(void) {
25     con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING,
26         GMQCC_VERSION_MAJOR,
27         GMQCC_VERSION_MINOR,
28         GMQCC_VERSION_PATCH,
29         __DATE__,
30         __TIME__
31     );
32 }
33
34 static int usage(void) {
35     con_out("usage: %s [options] [files...]", app_name);
36     con_out("options:\n"
37             "  -h, --help             show this help message\n"
38             "  -debug                 turns on compiler debug messages\n");
39     con_out("  -o, --output=file      output file, defaults to progs.dat\n"
40             "  -s filename            add a progs.src file to be used\n");
41     con_out("  -E                     stop after preprocessing\n");
42     con_out("  -q, --quiet            be less verbose\n");
43     con_out("  -config file           use the specified ini file\n");
44     con_out("  -std=standard          select one of the following standards\n"
45             "       -std=qcc          original QuakeC\n"
46             "       -std=fteqcc       fteqcc QuakeC\n"
47             "       -std=gmqcc        this compiler (default)\n");
48     con_out("  -f<flag>               enable a flag\n"
49             "  -fno-<flag>            disable a flag\n"
50             "  -fhelp                 list possible flags\n");
51     con_out("  -W<warning>            enable a warning\n"
52             "  -Wno-<warning>         disable a warning\n"
53             "  -Wall                  enable all warnings\n");
54     con_out("  -Werror                treat warnings as errors\n"
55             "  -Werror-<warning>      treat a warning as error\n"
56             "  -Wno-error-<warning>   opposite of the above\n");
57     con_out("  -Whelp                 list possible warnings\n");
58     con_out("  -O<number>             optimization level\n"
59             "  -O<name>               enable specific optimization\n"
60             "  -Ono-<name>            disable specific optimization\n"
61             "  -Ohelp                 list optimizations\n");
62     con_out("  -force-crc=num         force a specific checksum into the header\n");
63     con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
64     con_out("  -coverage              add coverage support\n");
65     return -1;
66 }
67
68 /* command line parsing */
69 static bool options_witharg(int *argc_, char ***argv_, char **out) {
70     int  argc   = *argc_;
71     char **argv = *argv_;
72
73     if (argv[0][2]) {
74         *out = argv[0]+2;
75         return true;
76     }
77     /* eat up the next */
78     if (argc < 2) /* no parameter was provided */
79         return false;
80
81     *out = argv[1];
82     --*argc_;
83     ++*argv_;
84     return true;
85 }
86
87 static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
88     int  argc   = *argc_;
89     char **argv = *argv_;
90
91     size_t len = strlen(optname);
92
93     if (strncmp(argv[0]+ds, optname, len))
94         return false;
95
96     /* it's --optname, check how the parameter is supplied */
97     if (argv[0][ds+len] == '=') {
98         /* using --opt=param */
99         *out = argv[0]+ds+len+1;
100         return true;
101     }
102
103     if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
104         return false;
105
106     /* using --opt param */
107     *out = argv[1];
108     --*argc_;
109     ++*argv_;
110     return true;
111 }
112 static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
113     return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
114 }
115 static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
116     return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
117 }
118
119 static bool options_parse(int argc, char **argv) {
120     bool argend = false;
121     size_t itr;
122     char buffer[1024];
123     char *config = nullptr;
124
125     while (!argend && argc > 1) {
126         char *argarg;
127         argitem item;
128         ppitem macro;
129
130         ++argv;
131         --argc;
132
133         if (argv[0][0] == '-') {
134             /* All gcc-type long options */
135             if (options_long_gcc("std", &argc, &argv, &argarg)) {
136                 if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
137
138                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,          true);
139                     opts_set(opts.flags, CORRECT_LOGIC,                 true);
140                     opts_set(opts.flags, SHORT_LOGIC,                   true);
141                     opts_set(opts.flags, UNTYPED_NIL,                   true);
142                     opts_set(opts.flags, VARIADIC_ARGS,                 true);
143                     opts_set(opts.flags, FALSE_EMPTY_STRINGS,           false);
144                     opts_set(opts.flags, TRUE_EMPTY_STRINGS,            true);
145                     opts_set(opts.flags, LOOP_LABELS,                   true);
146                     opts_set(opts.flags, TRANSLATABLE_STRINGS,          true);
147                     opts_set(opts.flags, INITIALIZED_NONCONSTANTS,      true);
148                     opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true);
149                     opts_set(opts.werror, WARN_MISSING_RETURN_VALUES,   true);
150                     opts_set(opts.flags,  EXPRESSIONS_FOR_BUILTINS,     true);
151                     opts_set(opts.warn,   WARN_BREAKDEF,                true);
152
153
154
155                     OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC;
156
157                 } else if (!strcmp(argarg, "qcc")) {
158
159                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
160                     opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
161
162                     OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC;
163
164                 } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) {
165
166                     opts_set(opts.flags, FTEPP,                    true);
167                     opts_set(opts.flags, TRANSLATABLE_STRINGS,     true);
168                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,     false);
169                     opts_set(opts.flags, ASSIGN_FUNCTION_TYPES,    true);
170                     opts_set(opts.flags, CORRECT_TERNARY,          false);
171                     opts_set(opts.warn, WARN_TERNARY_PRECEDENCE,   true);
172                     opts_set(opts.warn, WARN_BREAKDEF,             true);
173
174                     OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC;
175
176                 } else if (!strcmp(argarg, "qccx")) {
177
178                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
179                     OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX;
180
181                 } else {
182                     con_out("Unknown standard: %s\n", argarg);
183                     return false;
184                 }
185                 continue;
186             }
187             if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
188
189                 OPTS_OPTION_BOOL(OPTION_FORCECRC)   = true;
190                 OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, nullptr, 0);
191                 continue;
192             }
193             if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
194                 OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, nullptr, 0);
195                 opts_set(opts.flags, EMULATE_STATE, true);
196                 continue;
197             }
198             if (options_long_gcc("config", &argc, &argv, &argarg)) {
199                 config = argarg;
200                 continue;
201             }
202             if (options_long_gcc("progsrc", &argc, &argv, &argarg)) {
203                 OPTS_OPTION_STR(OPTION_PROGSRC) = argarg;
204                 continue;
205             }
206
207             /* show defaults (like pathscale) */
208             if (!strcmp(argv[0]+1, "show-defaults")) {
209                 for (itr = 0; itr < COUNT_FLAGS; ++itr) {
210                     if (!OPTS_FLAG(itr))
211                         continue;
212
213                     memset(buffer, 0, sizeof(buffer));
214                     util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
215
216                     con_out("-f%s ", buffer);
217                 }
218                 for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
219                     if (!OPTS_WARN(itr))
220                         continue;
221
222                     memset(buffer, 0, sizeof(buffer));
223                     util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
224                     con_out("-W%s ", buffer);
225                 }
226                 con_out("\n");
227                 exit(0);
228             }
229
230             if (!strcmp(argv[0]+1, "debug")) {
231                 OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
232                 continue;
233             }
234             if (!strcmp(argv[0]+1, "dump")) {
235                 OPTS_OPTION_BOOL(OPTION_DUMP)  = true;
236                 continue;
237             }
238             if (!strcmp(argv[0]+1, "dumpfin")) {
239                 OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true;
240                 continue;
241             }
242             if (!strcmp(argv[0]+1, "nocolor")) {
243                 con_color(0);
244                 continue;
245             }
246             if (!strcmp(argv[0]+1, "coverage")) {
247                 OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
248                 continue;
249             }
250
251             switch (argv[0][1]) {
252                 /* -h, show usage but exit with 0 */
253                 case 'h':
254                     usage();
255                     exit(0);
256                     /* break; never reached because of exit(0) */
257
258                 case 'v':
259                     version();
260                     exit(0);
261
262                 case 'E':
263                     OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true;
264                     opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */
265                     break;
266
267                 /* debug turns on -flno */
268                 case 'g':
269                     opts_setflag("LNO", true);
270                     OPTS_OPTION_BOOL(OPTION_G) = true;
271                     break;
272
273                 case 'q':
274                     OPTS_OPTION_BOOL(OPTION_QUIET) = true;
275                     break;
276
277                 case 'D':
278                     if (!strlen(argv[0]+2)) {
279                         con_err("expected name after -D\n");
280                         exit(0);
281                     }
282
283                     if (!(argarg = strchr(argv[0] + 2, '='))) {
284                         macro.name  = util_strdup(argv[0]+2);
285                         macro.value = nullptr;
286                     } else {
287                         *argarg='\0'; /* terminate for name */
288                         macro.name  = util_strdup(argv[0]+2);
289                         macro.value = util_strdup(argarg+1);
290                     }
291                     vec_push(ppems, macro);
292                     break;
293
294                 /* handle all -fflags */
295                 case 'f':
296                     util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
297                     if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
298                         con_out("Possible flags:\n\n");
299                         for (itr = 0; itr < COUNT_FLAGS; ++itr) {
300                             util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
301                             con_out(" -f%s\n", buffer);
302                         }
303                         exit(0);
304                     }
305                     else if (!strncmp(argv[0]+2, "NO_", 3)) {
306                         if (!opts_setflag(argv[0]+5, false)) {
307                             con_out("unknown flag: %s\n", argv[0]+2);
308                             return false;
309                         }
310                     }
311                     else if (!opts_setflag(argv[0]+2, true)) {
312                         con_out("unknown flag: %s\n", argv[0]+2);
313                         return false;
314                     }
315                     break;
316                 case 'W':
317                     util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
318                     if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
319                         con_out("Possible warnings:\n");
320                         for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
321                             util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
322                             con_out(" -W%s\n", buffer);
323                             if (itr == WARN_DEBUG)
324                                 con_out("   Warnings included by -Wall:\n");
325                         }
326                         exit(0);
327                     }
328                     else if (!strcmp(argv[0]+2, "NO_ERROR") ||
329                              !strcmp(argv[0]+2, "NO_ERROR_ALL"))
330                     {
331                         for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
332                             opts.werror[itr] = 0;
333                         break;
334                     }
335                     else if (!strcmp(argv[0]+2, "ERROR") ||
336                              !strcmp(argv[0]+2, "ERROR_ALL"))
337                     {
338                         opts_backup_non_Werror_all();
339                         for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
340                             opts.werror[itr] = 0xFFFFFFFFL;
341                         opts_restore_non_Werror_all();
342                         break;
343                     }
344                     else if (!strcmp(argv[0]+2, "NONE")) {
345                         for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
346                             opts.warn[itr] = 0;
347                         break;
348                     }
349                     else if (!strcmp(argv[0]+2, "ALL")) {
350                         opts_backup_non_Wall();
351                         for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
352                             opts.warn[itr] = 0xFFFFFFFFL;
353                         opts_restore_non_Wall();
354                         break;
355                     }
356                     else if (!strncmp(argv[0]+2, "ERROR_", 6)) {
357                         if (!opts_setwerror(argv[0]+8, true)) {
358                             con_out("unknown warning: %s\n", argv[0]+2);
359                             return false;
360                         }
361                     }
362                     else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) {
363                         if (!opts_setwerror(argv[0]+11, false)) {
364                             con_out("unknown warning: %s\n", argv[0]+2);
365                             return false;
366                         }
367                     }
368                     else if (!strncmp(argv[0]+2, "NO_", 3)) {
369                         if (!opts_setwarn(argv[0]+5, false)) {
370                             con_out("unknown warning: %s\n", argv[0]+2);
371                             return false;
372                         }
373                     }
374                     else if (!opts_setwarn(argv[0]+2, true)) {
375                         con_out("unknown warning: %s\n", argv[0]+2);
376                         return false;
377                     }
378                     break;
379
380                 case 'O':
381                     if (!options_witharg(&argc, &argv, &argarg)) {
382                         con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
383                         return false;
384                     }
385                     if (util_isdigit(argarg[0])) {
386                         uint32_t val = (uint32_t)strtol(argarg, nullptr, 10);
387                         OPTS_OPTION_U32(OPTION_O) = val;
388                         opts_setoptimlevel(val);
389                     } else {
390                         util_strtocmd(argarg, argarg, strlen(argarg)+1);
391                         if (!strcmp(argarg, "HELP")) {
392                             con_out("Possible optimizations:\n");
393                             for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
394                                 util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
395                                 con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
396                             }
397                             exit(0);
398                         }
399                         else if (!strcmp(argarg, "ALL"))
400                             opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999);
401                         else if (!strncmp(argarg, "NO_", 3)) {
402                             /* constant folding cannot be turned off for obvious reasons */
403                             if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) {
404                                 con_out("unknown optimization: %s\n", argarg+3);
405                                 return false;
406                             }
407                         }
408                         else {
409                             if (!opts_setoptim(argarg, true)) {
410                                 con_out("unknown optimization: %s\n", argarg);
411                                 return false;
412                             }
413                         }
414                     }
415                     break;
416
417                 case 'o':
418                     if (!options_witharg(&argc, &argv, &argarg)) {
419                         con_out("option -o requires an argument: the output file name\n");
420                         return false;
421                     }
422                     OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
423                     opts_output_wasset = true;
424                     break;
425
426                 case 'a':
427                 case 's':
428                     item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
429                     if (!options_witharg(&argc, &argv, &argarg)) {
430                         con_out("option -a requires a filename %s\n",
431                                 (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
432                         return false;
433                     }
434                     item.filename = argarg;
435                     vec_push(items, item);
436                     break;
437
438                 case '-':
439                     if (!argv[0][2]) {
440                         /* anything following -- is considered a non-option argument */
441                         argend = true;
442                         break;
443                     }
444             /* All long options without arguments */
445                     else if (!strcmp(argv[0]+2, "help")) {
446                         usage();
447                         exit(0);
448                     }
449                     else if (!strcmp(argv[0]+2, "version")) {
450                         version();
451                         exit(0);
452                     }
453                     else if (!strcmp(argv[0]+2, "quiet")) {
454                         OPTS_OPTION_BOOL(OPTION_QUIET) = true;
455                         break;
456                     }
457                     else if (!strcmp(argv[0]+2, "add-info")) {
458                         OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true;
459                         break;
460                     }
461                     else {
462             /* All long options with arguments */
463                         if (options_long_witharg("output", &argc, &argv, &argarg)) {
464                             OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
465                             opts_output_wasset = true;
466                         } else {
467                             con_out("Unknown parameter: %s\n", argv[0]);
468                             return false;
469                         }
470                     }
471                     break;
472
473                 default:
474                     con_out("Unknown parameter: %s\n", argv[0]);
475                     return false;
476             }
477         }
478         else
479         {
480             /* it's a QC filename */
481             item.filename = argv[0];
482             item.type     = TYPE_QC;
483             vec_push(items, item);
484         }
485     }
486     opts_ini_init(config);
487     return true;
488 }
489
490 /* returns the line number, or -1 on error */
491 static bool progs_nextline(char **out, size_t *alen, FILE *src) {
492     int    len;
493     char  *line;
494     char  *start;
495     char  *end;
496
497     line = *out;
498     len  = util_getline(&line, alen, src);
499     if (len == -1)
500         return false;
501
502     /* start at first non-blank */
503     for (start = line; util_isspace(*start); ++start) {}
504     /* end at the first non-blank */
505     for (end = start; *end && !util_isspace(*end);  ++end)   {}
506
507     *out = line;
508     /* move the actual filename to the beginning */
509     while (start != end) {
510         *line++ = *start++;
511     }
512     *line = 0;
513     return true;
514 }
515
516 int main(int argc, char **argv) {
517     size_t          itr;
518     int             retval           = 0;
519     bool            operators_free   = false;
520     bool            progs_src        = false;
521     FILE       *outfile         = nullptr;
522     parser_t *parser          = nullptr;
523     ftepp_t  *ftepp           = nullptr;
524
525     app_name = argv[0];
526     con_init ();
527     opts_init("progs.dat", COMPILER_QCC, (1024 << 3));
528
529     util_seed(time(0));
530
531     if (!options_parse(argc, argv)) {
532         return usage();
533     }
534
535     if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) {
536         con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive");
537         exit(EXIT_FAILURE);
538     }
539
540     /* the standard decides which set of operators to use */
541     if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
542         operators      = c_operators;
543         operator_count = GMQCC_ARRAY_COUNT(c_operators);
544     } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
545         operators      = fte_operators;
546         operator_count = GMQCC_ARRAY_COUNT(fte_operators);
547     } else {
548         operators      = qcc_operators;
549         operator_count = GMQCC_ARRAY_COUNT(qcc_operators);
550     }
551
552     if (operators == fte_operators) {
553         /* fix ternary? */
554         if (OPTS_FLAG(CORRECT_TERNARY)) {
555             oper_info *newops;
556             if (operators[operator_count-2].id != opid1(',') ||
557                 operators[operator_count-1].id != opid2(':','?'))
558             {
559                 con_err("internal error: operator precedence table wasn't updated correctly!\n");
560                 exit(EXIT_FAILURE);
561             }
562             operators_free = true;
563             newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count);
564             memcpy(newops, operators, sizeof(operators[0]) * operator_count);
565             memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
566             memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
567             newops[operator_count-2].prec = newops[operator_count-1].prec+1;
568             operators = newops;
569         }
570     }
571
572     if (OPTS_OPTION_BOOL(OPTION_DUMP)) {
573         for (itr = 0; itr < COUNT_FLAGS; ++itr)
574             con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
575         for (itr = 0; itr < COUNT_WARNINGS; ++itr)
576             con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
577
578         con_out("output             = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT));
579         con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O));
580         con_out("standard           = %u\n", OPTS_OPTION_U32(OPTION_STANDARD));
581     }
582
583     if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
584         if (opts_output_wasset) {
585             outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb");
586             if (!outfile) {
587                 con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT));
588                 retval = 1;
589                 goto cleanup;
590             }
591         }
592         else {
593             outfile = con_default_out();
594         }
595     }
596
597     if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
598         if (!(parser = parser_create())) {
599             con_err("failed to initialize parser\n");
600             retval = 1;
601             goto cleanup;
602         }
603     }
604
605     if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
606         if (!(ftepp = ftepp_create())) {
607             con_err("failed to initialize parser\n");
608             retval = 1;
609             goto cleanup;
610         }
611     }
612
613     /* add macros */
614     if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
615         for (itr = 0; itr < vec_size(ppems); itr++) {
616             ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value);
617             mem_d(ppems[itr].name);
618
619             /* can be null */
620             if (ppems[itr].value)
621                 mem_d(ppems[itr].value);
622         }
623     }
624
625     if (!vec_size(items)) {
626         FILE *src;
627         char      *line    = nullptr;
628         size_t     linelen = 0;
629         bool       hasline = false;
630
631         progs_src = true;
632
633         src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
634         if (!src) {
635             con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC));
636             retval = 1;
637             goto cleanup;
638         }
639
640         while (progs_nextline(&line, &linelen, src)) {
641             argitem item;
642
643             if (!line[0] || (line[0] == '/' && line[1] == '/'))
644                 continue;
645
646             if (hasline) {
647                 item.filename = util_strdup(line);
648                 item.type     = TYPE_QC;
649                 vec_push(items, item);
650             } else if (!opts_output_wasset) {
651                 OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line);
652                 hasline                        = true;
653             }
654         }
655
656         fclose(src);
657         mem_d(line);
658     }
659
660     if (vec_size(items)) {
661         if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
662             !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
663         {
664             con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
665             con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
666         }
667
668         for (itr = 0; itr < vec_size(items); ++itr) {
669             if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
670                 !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
671             {
672                 con_out("  item: %s (%s)\n",
673                        items[itr].filename,
674                        ( (items[itr].type == TYPE_QC ? "qc" :
675                          (items[itr].type == TYPE_ASM ? "asm" :
676                          (items[itr].type == TYPE_SRC ? "progs.src" :
677                          ("unknown"))))));
678             }
679
680             if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
681                 const char *out;
682                 if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
683                     retval = 1;
684                     goto cleanup;
685                 }
686                 out = ftepp_get(ftepp);
687                 if (out)
688                     fprintf(outfile, "%s", out);
689                 ftepp_flush(ftepp);
690             }
691             else {
692                 if (OPTS_FLAG(FTEPP)) {
693                     const char *data;
694                     if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
695                         retval = 1;
696                         goto cleanup;
697                     }
698                     data = ftepp_get(ftepp);
699                     if (vec_size(data)) {
700                         if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) {
701                             retval = 1;
702                             goto cleanup;
703                         }
704                     }
705                     ftepp_flush(ftepp);
706                 }
707                 else {
708                     if (!parser_compile_file(parser, items[itr].filename)) {
709                         retval = 1;
710                         goto cleanup;
711                     }
712                 }
713             }
714
715             if (progs_src) {
716                 mem_d(items[itr].filename);
717                 items[itr].filename = nullptr;
718             }
719         }
720
721         ftepp_finish(ftepp);
722         ftepp = nullptr;
723         if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
724             if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) {
725                 retval = 1;
726                 goto cleanup;
727             }
728         }
729     }
730
731 cleanup:
732     if (ftepp)
733         ftepp_finish(ftepp);
734     con_close();
735     vec_free(items);
736     vec_free(ppems);
737
738     if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY))
739         if(parser) parser_cleanup(parser);
740
741     /* free allocated option strings */
742     for (itr = 0; itr < OPTION_COUNT; itr++)
743         if (OPTS_OPTION_DUPED(itr))
744             mem_d(OPTS_OPTION_STR(itr));
745
746     if (operators_free)
747         mem_d((void*)operators);
748
749     lex_cleanup();
750
751     if (!retval && compile_errors)
752         retval = 1;
753     return retval;
754 }