]> git.xonotic.org Git - xonotic/gmqcc.git/blob - main.c
Cleanups of compiler option configuration. Added ini/cfg parser system as well.
[xonotic/gmqcc.git] / main.c
1 /*
2  * Copyright (C) 2012
3  *     Dale Weiler
4  *     Wolfgang Bumiller
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy of
7  * this software and associated documentation files (the "Software"), to deal in
8  * the Software without restriction, including without limitation the rights to
9  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10  * of the Software, and to permit persons to whom the Software is furnished to do
11  * so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "gmqcc.h"
25 #include "lexer.h"
26
27 /* TODO: cleanup this whole file .. it's a fuckign mess */
28
29 /* set by the standard */
30 const oper_info *operators      = NULL;
31 size_t           operator_count = 0;
32 static bool      opts_output_wasset = false;
33
34 typedef struct { char *filename; int   type;  } argitem;
35 typedef struct { char *name;     char *value; } ppitem;
36 static argitem *items = NULL;
37 static ppitem  *ppems = NULL;
38
39 #define TYPE_QC  0
40 #define TYPE_ASM 1
41 #define TYPE_SRC 2
42
43 static const char *app_name;
44
45 static void version() {
46     con_out("GMQCC %d.%d.%d Built %s %s\n",
47         GMQCC_VERSION_MINOR,
48         GMQCC_VERSION_MAJOR,
49         GMQCC_VERSION_PATCH,
50         __DATE__,
51         __TIME__
52     );
53 }
54
55 static int usage() {
56     con_out("usage: %s [options] [files...]", app_name);
57     con_out("options:\n"
58             "  -h, --help             show this help message\n"
59             "  -debug                 turns on compiler debug messages\n"
60             "  -memchk                turns on compiler memory leak check\n");
61     con_out("  -o, --output=file      output file, defaults to progs.dat\n"
62             "  -s filename            add a progs.src file to be used\n");
63     con_out("  -E                     stop after preprocessing\n");
64     con_out("  -std=standard          select one of the following standards\n"
65             "       -std=qcc          original QuakeC\n"
66             "       -std=fteqcc       fteqcc QuakeC\n"
67             "       -std=gmqcc        this compiler (default)\n");
68     con_out("  -f<flag>               enable a flag\n"
69             "  -fno-<flag>            disable a flag\n"
70             "  -fhelp                 list possible flags\n");
71     con_out("  -W<warning>            enable a warning\n"
72             "  -Wno-<warning>         disable a warning\n"
73             "  -Wall                  enable all warnings\n"
74             "  -Werror                treat warnings as errors\n");
75     con_out("  -Whelp                 list possible warnings\n");
76     con_out("  -O<number>             optimization level\n"
77             "  -O<name>               enable specific optimization\n"
78             "  -Ono-<name>            disable specific optimization\n"
79             "  -Ohelp                 list optimizations\n");
80     con_out("  -force-crc=num         force a specific checksum into the header\n");
81     return -1;
82 }
83
84 /* command line parsing */
85 static bool options_witharg(int *argc_, char ***argv_, char **out) {
86     int  argc   = *argc_;
87     char **argv = *argv_;
88
89     if (argv[0][2]) {
90         *out = argv[0]+2;
91         return true;
92     }
93     /* eat up the next */
94     if (argc < 2) /* no parameter was provided */
95         return false;
96
97     *out = argv[1];
98     --*argc_;
99     ++*argv_;
100     return true;
101 }
102
103 static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
104     int  argc   = *argc_;
105     char **argv = *argv_;
106
107     size_t len = strlen(optname);
108
109     if (strncmp(argv[0]+ds, optname, len))
110         return false;
111
112     /* it's --optname, check how the parameter is supplied */
113     if (argv[0][ds+len] == '=') {
114         /* using --opt=param */
115         *out = argv[0]+ds+len+1;
116         return true;
117     }
118
119     if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
120         return false;
121
122     /* using --opt param */
123     *out = argv[1];
124     --*argc_;
125     ++*argv_;
126     return true;
127 }
128 static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
129     return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
130 }
131 static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
132     return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
133 }
134
135 static bool options_parse(int argc, char **argv) {
136     bool argend = false;
137     size_t itr;
138     char  buffer[1024];
139     char *redirout = (char*)stdout;
140     char *redirerr = (char*)stderr;
141
142     while (!argend && argc > 1) {
143         char *argarg;
144         argitem item;
145         ppitem  macro;
146
147         ++argv;
148         --argc;
149
150         if (argv[0][0] == '-') {
151             /* All gcc-type long options */
152             if (options_long_gcc("std", &argc, &argv, &argarg)) {
153                 if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
154
155                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  true);
156                     opts.standard = COMPILER_GMQCC;
157
158                 } else if (!strcmp(argarg, "qcc")) {
159
160                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
161                     opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
162                     opts.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.warn, WARN_TERNARY_PRECEDENCE,   true);
171                     opts_set(opts.flags, CORRECT_TERNARY,          false);
172                     opts.standard = COMPILER_FTEQCC;
173
174                 } else if (!strcmp(argarg, "qccx")) {
175
176                     opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
177                     opts.standard = COMPILER_QCCX;
178
179                 } else {
180                     con_out("Unknown standard: %s\n", argarg);
181                     return false;
182                 }
183                 continue;
184             }
185             if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
186                 opts.forcecrc   = true;
187                 opts.forced_crc = strtol(argarg, NULL, 0);
188                 continue;
189             }
190             if (options_long_gcc("redirout", &argc, &argv, &redirout)) {
191                 con_change(redirout, redirerr);
192                 continue;
193             }
194             if (options_long_gcc("redirerr", &argc, &argv, &redirerr)) {
195                 con_change(redirout, redirerr);
196                 continue;
197             }
198
199             /* show defaults (like pathscale) */
200             if (!strcmp(argv[0]+1, "show-defaults")) {
201                 size_t itr;
202                 char   buffer[1024];
203                 for (itr = 0; itr < COUNT_FLAGS; ++itr) {
204                     if (!OPTS_FLAG(itr))
205                         continue;
206
207                     memset(buffer, 0, sizeof(buffer));
208                     util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
209
210                     con_out("-f%s ", buffer);
211                 }
212                 for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
213                     if (!OPTS_WARN(itr))
214                         continue;
215
216                     memset(buffer, 0, sizeof(buffer));
217                     util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
218                     con_out("-W%s ", buffer);
219                 }
220                 con_out("\n");
221                 exit(0);
222             }
223
224             if (!strcmp(argv[0]+1, "debug")) {
225                 opts.debug = true;
226                 continue;
227             }
228             if (!strcmp(argv[0]+1, "dump")) {
229                 opts.dump = true;
230                 continue;
231             }
232             if (!strcmp(argv[0]+1, "dumpfin")) {
233                 opts.dumpfin = true;
234                 continue;
235             }
236             if (!strcmp(argv[0]+1, "memchk")) {
237                 opts.memchk = true;
238                 continue;
239             }
240             if (!strcmp(argv[0]+1, "nocolor")) {
241                 con_color(0);
242                 continue;
243             }
244
245             switch (argv[0][1]) {
246                 /* -h, show usage but exit with 0 */
247                 case 'h':
248                     usage();
249                     exit(0);
250                     /* break; never reached because of exit(0) */
251
252                 case 'v':
253                     version();
254                     exit(0);
255
256                 case 'E':
257                     opts.pp_only = true;
258                     break;
259
260                 /* debug turns on -flno */
261                 case 'g':
262                     opts_setflag("LNO", true);
263                     break;
264
265                 case 'D':
266                     if (!strlen(argv[0]+2)) {
267                         con_err("expected name after -D\n");
268                         exit(0);
269                     }
270
271                     if (!(argarg = strchr(argv[0] + 2, '='))) {
272                         macro.name  = util_strdup(argv[0]+2);
273                         macro.value = NULL;
274                     } else {
275                         *argarg='\0'; /* terminate for name */
276                         macro.name  = util_strdup(argv[0]+2);
277                         macro.value = util_strdup(argarg+1);
278                     }
279                     vec_push(ppems, macro);
280                     break;
281
282                 /* handle all -fflags */
283                 case 'f':
284                     util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
285                     if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
286                         con_out("Possible flags:\n");
287                         for (itr = 0; itr < COUNT_FLAGS; ++itr) {
288                             util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
289                             con_out(" -f%s\n", buffer);
290                         }
291                         exit(0);
292                     }
293                     else if (!strncmp(argv[0]+2, "NO_", 3)) {
294                         if (!opts_setflag(argv[0]+5, false)) {
295                             con_out("unknown flag: %s\n", argv[0]+2);
296                             return false;
297                         }
298                     }
299                     else if (!opts_setflag(argv[0]+2, true)) {
300                         con_out("unknown flag: %s\n", argv[0]+2);
301                         return false;
302                     }
303                     break;
304                 case 'W':
305                     util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
306                     if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
307                         con_out("Possible warnings:\n");
308                         for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
309                             util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
310                             con_out(" -W%s\n", buffer);
311                         }
312                         exit(0);
313                     }
314                     else if (!strcmp(argv[0]+2, "NO_ERROR")) {
315                         opts.werror = false;
316                         break;
317                     }
318                     else if (!strcmp(argv[0]+2, "ERROR")) {
319                         opts.werror = true;
320                         break;
321                     }
322                     else if (!strcmp(argv[0]+2, "NONE")) {
323                         for (itr = 0; itr < sizeof(opts.warn)/sizeof(opts.warn[0]); ++itr)
324                             opts.warn[itr] = 0;
325                         break;
326                     }
327                     else if (!strcmp(argv[0]+2, "ALL")) {
328                         for (itr = 0; itr < sizeof(opts.warn)/sizeof(opts.warn[0]); ++itr)
329                             opts.warn[itr] = 0xFFFFFFFFL;
330                         break;
331                     }
332                     if (!strncmp(argv[0]+2, "NO_", 3)) {
333                         if (!opts_setwarn(argv[0]+5, false)) {
334                             con_out("unknown warning: %s\n", argv[0]+2);
335                             return false;
336                         }
337                     }
338                     else if (!opts_setwarn(argv[0]+2, true)) {
339                         con_out("unknown warning: %s\n", argv[0]+2);
340                         return false;
341                     }
342                     break;
343
344                 case 'O':
345                     if (!options_witharg(&argc, &argv, &argarg)) {
346                         con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
347                         return false;
348                     }
349                     if (isdigit(argarg[0])) {
350                         opts.O = atoi(argarg);
351                         opts_setoptimlevel(opts.O);
352                     } else {
353                         util_strtocmd(argarg, argarg, strlen(argarg)+1);
354                         if (!strcmp(argarg, "HELP")) {
355                             con_out("Possible optimizations:\n");
356                             for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
357                                 util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
358                                 con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
359                             }
360                             exit(0);
361                         }
362                         else if (!strcmp(argarg, "ALL"))
363                             opts_setoptimlevel(opts.O = 9999);
364                         else if (!strncmp(argarg, "NO_", 3)) {
365                             if (!opts_setoptim(argarg+3, false)) {
366                                 con_out("unknown optimization: %s\n", argarg+3);
367                                 return false;
368                             }
369                         }
370                         else {
371                             if (!opts_setoptim(argarg, true)) {
372                                 con_out("unknown optimization: %s\n", argarg);
373                                 return false;
374                             }
375                         }
376                     }
377                     break;
378
379                 case 'o':
380                     if (!options_witharg(&argc, &argv, &argarg)) {
381                         con_out("option -o requires an argument: the output file name\n");
382                         return false;
383                     }
384                     opts.output = argarg;
385                     opts_output_wasset = true;
386                     break;
387
388                 case 'a':
389                 case 's':
390                     item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
391                     if (!options_witharg(&argc, &argv, &argarg)) {
392                         con_out("option -a requires a filename %s\n",
393                                 (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
394                         return false;
395                     }
396                     item.filename = argarg;
397                     vec_push(items, item);
398                     break;
399
400                 case '-':
401                     if (!argv[0][2]) {
402                         /* anything following -- is considered a non-option argument */
403                         argend = true;
404                         break;
405                     }
406             /* All long options without arguments */
407                     else if (!strcmp(argv[0]+2, "help")) {
408                         usage();
409                         exit(0);
410                     }
411                     else if (!strcmp(argv[0]+2, "version")) {
412                         version();
413                         exit(0);
414                     }
415                     else {
416             /* All long options with arguments */
417                         if (options_long_witharg("output", &argc, &argv, &argarg)) {
418                             opts.output = argarg;
419                             opts_output_wasset = true;
420                         } else {
421                             con_out("Unknown parameter: %s\n", argv[0]);
422                             return false;
423                         }
424                     }
425                     break;
426
427                 default:
428                     con_out("Unknown parameter: %s\n", argv[0]);
429                     return false;
430             }
431         }
432         else
433         {
434             /* it's a QC filename */
435             item.filename = argv[0];
436             item.type     = TYPE_QC;
437             vec_push(items, item);
438         }
439     }
440     return true;
441 }
442
443 /* returns the line number, or -1 on error */
444 static bool progs_nextline(char **out, size_t *alen,FILE *src) {
445     int    len;
446     char  *line;
447     char  *start;
448     char  *end;
449
450     line = *out;
451     len = util_getline(&line, alen, src);
452     if (len == -1)
453         return false;
454
455     /* start at first non-blank */
456     for (start = line; isspace(*start); ++start) {}
457     /* end at the first non-blank */
458     for (end = start;  *end && !isspace(*end);  ++end)   {}
459
460     *out = line;
461     /* move the actual filename to the beginning */
462     while (start != end) {
463         *line++ = *start++;
464     }
465     *line = 0;
466     return true;
467 }
468
469 int main(int argc, char **argv) {
470     size_t itr;
471     int    retval           = 0;
472     bool   opts_output_free = false;
473     bool   operators_free   = false;
474     bool   progs_src        = false;
475     FILE  *outfile          = NULL;
476
477     app_name = argv[0];
478     con_init ();
479     opts_init("progs.dat", COMPILER_GMQCC, (1024 << 3));
480
481     if (!options_parse(argc, argv)) {
482         return usage();
483     }
484
485     /* the standard decides which set of operators to use */
486     if (opts.standard == COMPILER_GMQCC) {
487         operators      = c_operators;
488         operator_count = c_operator_count;
489     } else if (opts.standard == COMPILER_FTEQCC) {
490         operators      = fte_operators;
491         operator_count = fte_operator_count;
492     } else {
493         operators      = qcc_operators;
494         operator_count = qcc_operator_count;
495     }
496
497     if (operators == fte_operators) {
498         /* fix ternary? */
499         if (OPTS_FLAG(CORRECT_TERNARY)) {
500             oper_info *newops;
501             if (operators[operator_count-2].id != opid1(',') ||
502                 operators[operator_count-1].id != opid2(':','?'))
503             {
504                 con_err("internal error: operator precedence table wasn't updated correctly!\n");
505                 exit(1);
506             }
507             operators_free = true;
508             newops = mem_a(sizeof(operators[0]) * operator_count);
509             memcpy(newops, operators, sizeof(operators[0]) * operator_count);
510             memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
511             memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
512             newops[operator_count-2].prec = newops[operator_count-1].prec+1;
513             operators = newops;
514         }
515     }
516
517     if (opts.dump) {
518         for (itr = 0; itr < COUNT_FLAGS; ++itr)
519             con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
520         for (itr = 0; itr < COUNT_WARNINGS; ++itr)
521             con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
522         
523         con_out("output             = %s\n", opts.output);
524         con_out("optimization level = %d\n", opts.O);
525         con_out("standard           = %i\n", opts.standard);
526     }
527
528     if (opts.pp_only) {
529         if (opts_output_wasset) {
530             outfile = util_fopen(opts.output, "wb");
531             if (!outfile) {
532                 con_err("failed to open `%s` for writing\n", opts.output);
533                 retval = 1;
534                 goto cleanup;
535             }
536         }
537         else
538             outfile = stdout;
539     }
540
541     if (!opts.pp_only) {
542         if (!parser_init()) {
543             con_err("failed to initialize parser\n");
544             retval = 1;
545             goto cleanup;
546         }
547     }
548
549     if (opts.pp_only || OPTS_FLAG(FTEPP)) {
550         if (!ftepp_init()) {
551             con_err("failed to initialize parser\n");
552             retval = 1;
553             goto cleanup;
554         }
555     }
556
557     util_debug("COM", "starting ...\n");
558
559     /* add macros */
560     if (opts.pp_only || OPTS_FLAG(FTEPP)) {
561         for (itr = 0; itr < vec_size(ppems); itr++) {
562             ftepp_add_macro(ppems[itr].name, ppems[itr].value);
563             mem_d(ppems[itr].name);
564
565             /* can be null */
566             if (ppems[itr].value)
567                 mem_d(ppems[itr].value);
568         }
569     }
570
571     if (!vec_size(items)) {
572         FILE *src;
573         char *line;
574         size_t linelen = 0;
575
576         progs_src = true;
577
578         src = util_fopen("progs.src", "rb");
579         if (!src) {
580             con_err("failed to open `progs.src` for reading\n");
581             retval = 1;
582             goto cleanup;
583         }
584
585         line = NULL;
586         if (!progs_nextline(&line, &linelen, src) || !line[0]) {
587             con_err("illformatted progs.src file: expected output filename in first line\n");
588             retval = 1;
589             goto srcdone;
590         }
591
592         if (!opts_output_wasset) {
593             opts.output = util_strdup(line);
594             opts_output_free = true;
595         }
596
597         while (progs_nextline(&line, &linelen, src)) {
598             argitem item;
599             if (!line[0] || (line[0] == '/' && line[1] == '/'))
600                 continue;
601             item.filename = util_strdup(line);
602             item.type     = TYPE_QC;
603             vec_push(items, item);
604         }
605
606 srcdone:
607         fclose(src);
608         mem_d(line);
609     }
610
611     if (retval)
612         goto cleanup;
613
614     if (vec_size(items)) {
615         if (!opts.pp_only) {
616             con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
617             con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
618         }
619         for (itr = 0; itr < vec_size(items); ++itr) {
620             if (!opts.pp_only) {
621                 con_out("  item: %s (%s)\n",
622                        items[itr].filename,
623                        ( (items[itr].type == TYPE_QC ? "qc" :
624                          (items[itr].type == TYPE_ASM ? "asm" :
625                          (items[itr].type == TYPE_SRC ? "progs.src" :
626                          ("unknown"))))));
627             }
628
629             if (opts.pp_only) {
630                 const char *out;
631                 if (!ftepp_preprocess_file(items[itr].filename)) {
632                     retval = 1;
633                     goto cleanup;
634                 }
635                 out = ftepp_get();
636                 if (out)
637                     fprintf(outfile, "%s", out);
638                 ftepp_flush();
639             }
640             else {
641                 if (OPTS_FLAG(FTEPP)) {
642                     const char *data;
643                     if (!ftepp_preprocess_file(items[itr].filename)) {
644                         retval = 1;
645                         goto cleanup;
646                     }
647                     data = ftepp_get();
648                     if (vec_size(data)) {
649                         if (!parser_compile_string_len(items[itr].filename, data, vec_size(data))) {
650                             retval = 1;
651                             goto cleanup;
652                         }
653                     }
654                     ftepp_flush();
655                 }
656                 else {
657                     if (!parser_compile_file(items[itr].filename)) {
658                         retval = 1;
659                         goto cleanup;
660                     }
661                 }
662             }
663
664             if (progs_src) {
665                 mem_d(items[itr].filename);
666                 items[itr].filename = NULL;
667             }
668         }
669
670         ftepp_finish();
671         if (!opts.pp_only) {
672             if (!parser_finish(opts.output)) {
673                 retval = 1;
674                 goto cleanup;
675             }
676         }
677     }
678
679     /* stuff */
680     if (!opts.pp_only) {
681         for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
682             if (opts_optimizationcount[itr]) {
683                 con_out("%s: %u\n", opts_opt_list[itr].name, (unsigned int)opts_optimizationcount[itr]);
684             }
685         }
686     }
687
688 cleanup:
689     util_debug("COM", "cleaning ...\n");
690     ftepp_finish();
691     con_close();
692     vec_free(items);
693     vec_free(ppems);
694
695     if (!opts.pp_only)
696         parser_cleanup();
697     if (opts_output_free)
698         mem_d((char*)opts.output);
699     if (operators_free)
700         mem_d((void*)operators);
701
702     lex_cleanup();
703     util_meminfo();
704     return retval;
705 }