From: Dale Weiler Date: Mon, 19 Nov 2012 02:13:46 +0000 (+0000) Subject: Merge branch 'master' into test-suite X-Git-Tag: 0.1.9~404 X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=commitdiff_plain;h=61fa54318c3aa1ce0c2099f9793d830e5896c016;hp=091173341eace2d290f6c0ebc4159c3afca7fbaa Merge branch 'master' into test-suite Conflicts: Makefile ir.c --- diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..9283835 --- /dev/null +++ b/INSTALL @@ -0,0 +1,39 @@ + Installing gmqcc + +1. Prerequisites + - A C-Compiler such as gcc or clang + - GNU Make. This document will assume GNU-Make to be executed via + `make'. On BSD systems you probably have to use `gmake' instead. + +2. Compilation + Run the GNU make program `make' or `gmake'. + + make + + If no error appears, the following binary files will have been + created: + - gmqcc + - qcvm + +3. Installation + The `install' target will install the 2 binaries to /usr/local/bin + by default. + The Makefile honors the following variables: + + - DESTDIR: The installation directory root. + - PREFIX: The installation prefix, default: /usr/local + - BINDIR: Directory for binary executables, + deafult: $PREFIX/bin + + To install to /usr/local run: + + make install + + To install to /usr run: + + make PREFIX=/usr install + + To install to a package-staging directory such as $pkgdir when + writing an ArchLinux PKGBUILD file: + + make DESTDIR=$pkgdir install diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..029afc5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2012 Dale Weiler, Wolfgang Bumiller + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 89168d7..aa5ef11 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,9 @@ OBJ = \ code.o \ ast.o \ ir.o \ - con.o - + con.o \ + ftepp.o + OBJ_T = test.o util.o con.o OBJ_C = main.o lexer.o parser.o OBJ_X = exec-standalone.o util.o con.o diff --git a/README b/README new file mode 100644 index 0000000..2999a47 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ + The gmqcc Quake C Compiler + +For licensing, see the LICENSE file. + +For installation notes, see the INSTALL file. diff --git a/exec.c b/exec.c index ee61c1a..f50eac1 100644 --- a/exec.c +++ b/exec.c @@ -334,16 +334,15 @@ static void trace_print_global(qc_program *prog, unsigned int glob, int vtype) def = prog_getdef(prog, glob); value = (qcany*)(&prog->globals[glob]); + len = printf("[@%u] ", glob); if (def) { const char *name = prog_getstring(prog, def->name); if (name[0] == '#') - len = printf("$"); + len += printf("$"); else - len = printf("%s ", name); + len += printf("%s ", name); vtype = def->type & DEF_TYPEMASK; } - else - len = printf("[@%u] ", glob); switch (vtype) { case TYPE_VOID: diff --git a/ftepp.c b/ftepp.c new file mode 100644 index 0000000..1d73430 --- /dev/null +++ b/ftepp.c @@ -0,0 +1,1280 @@ +/* + * Copyright (C) 2012 + * Wolfgang Bumiller + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "gmqcc.h" +#include "lexer.h" + +typedef struct { + bool on; + bool was_on; + bool had_else; +} ppcondition; + +typedef struct { + int token; + char *value; + /* a copy from the lexer */ + union { + vector v; + int i; + double f; + int t; /* type */ + } constval; +} pptoken; + +typedef struct { + lex_ctx ctx; + + char *name; + char **params; + /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ + bool has_params; + + pptoken **output; +} ppmacro; + +typedef struct { + lex_file *lex; + int token; + bool newline; + unsigned int errors; + + bool output_on; + ppcondition *conditions; + ppmacro **macros; + + char *output_string; + + char *itemname; +} ftepp_t; + +#define ftepp_tokval(f) ((f)->lex->tok.value) +#define ftepp_ctx(f) ((f)->lex->tok.ctx) + +static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap); + va_end(ap); +} + +static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) +{ + va_list ap; + + ftepp->errors++; + + va_start(ap, fmt); + con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap); + va_end(ap); +} + +static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) +{ + va_list ap; + int lvl = LVL_WARNING; + + if (!OPTS_WARN(warntype)) + return false; + + if (opts_werror) { + lvl = LVL_ERROR; + ftepp->errors++; + } + + va_start(ap, fmt); + con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap); + va_end(ap); + return opts_werror; +} + +static pptoken *pptoken_make(ftepp_t *ftepp) +{ + pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); + token->token = ftepp->token; +#if 0 + if (token->token == TOKEN_WHITE) + token->value = util_strdup(" "); + else +#else + token->value = util_strdup(ftepp_tokval(ftepp)); +#endif + memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); + return token; +} + +static void pptoken_delete(pptoken *self) +{ + mem_d(self->value); + mem_d(self); +} + +static ppmacro *ppmacro_new(lex_ctx ctx, const char *name) +{ + ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); + memset(macro, 0, sizeof(*macro)); + macro->name = util_strdup(name); + return macro; +} + +static void ppmacro_delete(ppmacro *self) +{ + size_t i; + for (i = 0; i < vec_size(self->params); ++i) + mem_d(self->params[i]); + vec_free(self->params); + for (i = 0; i < vec_size(self->output); ++i) + pptoken_delete(self->output[i]); + vec_free(self->output); + mem_d(self->name); + mem_d(self); +} + +static ftepp_t* ftepp_new() +{ + ftepp_t *ftepp; + + ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); + memset(ftepp, 0, sizeof(*ftepp)); + + ftepp->output_on = true; + + return ftepp; +} + +static void ftepp_delete(ftepp_t *self) +{ + size_t i; + if (self->itemname) + mem_d(self->itemname); + for (i = 0; i < vec_size(self->macros); ++i) + ppmacro_delete(self->macros[i]); + vec_free(self->macros); + vec_free(self->conditions); + if (self->lex) + lex_close(self->lex); + mem_d(self); +} + +static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) +{ + if (ignore_cond || ftepp->output_on) + { + size_t len; + char *data; + len = strlen(str); + data = vec_add(ftepp->output_string, len); + memcpy(data, str, len); + } +} + +static void ftepp_update_output_condition(ftepp_t *ftepp) +{ + size_t i; + ftepp->output_on = true; + for (i = 0; i < vec_size(ftepp->conditions); ++i) + ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; +} + +static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) +{ + size_t i; + for (i = 0; i < vec_size(ftepp->macros); ++i) { + if (!strcmp(name, ftepp->macros[i]->name)) + return ftepp->macros[i]; + } + return NULL; +} + +static void ftepp_macro_delete(ftepp_t *ftepp, const char *name) +{ + size_t i; + for (i = 0; i < vec_size(ftepp->macros); ++i) { + if (!strcmp(name, ftepp->macros[i]->name)) { + vec_remove(ftepp->macros, i, 1); + return; + } + } +} + +static inline int ftepp_next(ftepp_t *ftepp) +{ + return (ftepp->token = lex_do(ftepp->lex)); +} + +/* Important: this does not skip newlines! */ +static bool ftepp_skipspace(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE) + return true; + while (ftepp_next(ftepp) == TOKEN_WHITE) {} + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/* this one skips EOLs as well */ +static bool ftepp_skipallwhite(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) + return true; + do { + ftepp_next(ftepp); + } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/** + * The huge macro parsing code... + */ +static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) +{ + do { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == ')') + break; + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + break; + default: + ftepp_error(ftepp, "unexpected token in parameter list"); + return false; + } + vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } while (ftepp->token == ','); + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren after macro parameter list"); + return false; + } + ftepp_next(ftepp); + /* skipspace happens in ftepp_define */ + return true; +} + +static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) +{ + pptoken *ptok; + while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } + /* recursive expansion can cause EOFs here */ + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); + return false; + } + return true; +} + +static bool ftepp_define(ftepp_t *ftepp) +{ + ppmacro *macro; + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (macro && ftepp->output_on) { + if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp))) + return false; + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + } + macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + + if (ftepp->token == '(') { + macro->has_params = true; + if (!ftepp_define_params(ftepp, macro)) + return false; + } + + if (!ftepp_skipspace(ftepp)) + return false; + + if (!ftepp_define_body(ftepp, macro)) + return false; + + if (ftepp->output_on) + vec_push(ftepp->macros, macro); + else { + ppmacro_delete(macro); + } + return true; +} + +/** + * When a macro is used we have to handle parameters as well + * as special-concatenation via ## or stringification via # + * + * Note: parenthesis can nest, so FOO((a),b) is valid, but only + * this kind of parens. Curly braces or [] don't count towards the + * paren-level. + */ +typedef struct { + pptoken **tokens; +} macroparam; + +static void macroparam_clean(macroparam *self) +{ + size_t i; + for (i = 0; i < vec_size(self->tokens); ++i) + pptoken_delete(self->tokens[i]); + vec_free(self->tokens); +} + +/* need to leave the last token up */ +static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) +{ + macroparam *params = NULL; + pptoken *ptok; + macroparam mp; + size_t parens = 0; + size_t i; + + if (!ftepp_skipallwhite(ftepp)) + return false; + while (ftepp->token != ')') { + mp.tokens = NULL; + if (!ftepp_skipallwhite(ftepp)) + return false; + while (parens || ftepp->token != ',') { + if (ftepp->token == '(') + ++parens; + else if (ftepp->token == ')') { + if (!parens) + break; + --parens; + } + ptok = pptoken_make(ftepp); + vec_push(mp.tokens, ptok); + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected EOF in macro call"); + goto on_error; + } + } + vec_push(params, mp); + mp.tokens = NULL; + if (ftepp->token == ')') + break; + if (ftepp->token != ',') { + ftepp_error(ftepp, "expected closing paren or comma in macro call"); + goto on_error; + } + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected EOF in macro call"); + goto on_error; + } + } + /* need to leave that up + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected EOF in macro call"); + goto on_error; + } + */ + *out_params = params; + return true; + +on_error: + if (mp.tokens) + macroparam_clean(&mp); + for (i = 0; i < vec_size(params); ++i) + macroparam_clean(¶ms[i]); + vec_free(params); + return false; +} + +static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) +{ + size_t i; + for (i = 0; i < vec_size(macro->params); ++i) { + if (!strcmp(macro->params[i], name)) { + *idx = i; + return true; + } + } + return false; +} + +static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) +{ + char chs[2]; + const char *ch; + chs[1] = 0; + switch (token->token) { + case TOKEN_STRINGCONST: + ch = token->value; + while (*ch) { + /* in preprocessor mode strings already are string, + * so we don't get actual newline bytes here. + * Still need to escape backslashes and quotes. + */ + switch (*ch) { + case '\\': ftepp_out(ftepp, "\\\\", false); break; + case '"': ftepp_out(ftepp, "\\\"", false); break; + default: + chs[0] = *ch; + ftepp_out(ftepp, chs, false); + break; + } + ++ch; + } + break; + case TOKEN_WHITE: + ftepp_out(ftepp, " ", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\\n", false); + break; + default: + ftepp_out(ftepp, token->value, false); + break; + } +} + +static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) +{ + size_t i; + ftepp_out(ftepp, "\"", false); + for (i = 0; i < vec_size(param->tokens); ++i) + ftepp_stringify_token(ftepp, param->tokens[i]); + ftepp_out(ftepp, "\"", false); +} + +static void ftepp_recursion_header(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma push(line)\n", false); +} + +static void ftepp_recursion_footer(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma pop(line)\n", false); +} + +static bool ftepp_preprocess(ftepp_t *ftepp); +static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params) +{ + char *old_string = ftepp->output_string; + lex_file *old_lexer = ftepp->lex; + bool retval = true; + + size_t o, pi, pv; + lex_file *inlex; + + int nextok; + + /* really ... */ + if (!vec_size(macro->output)) + return true; + + ftepp->output_string = NULL; + for (o = 0; o < vec_size(macro->output); ++o) { + pptoken *out = macro->output[o]; + switch (out->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!macro_params_find(macro, out->value, &pi)) { + ftepp_out(ftepp, out->value, false); + break; + } else { + for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) { + out = params[pi].tokens[pv]; + if (out->token == TOKEN_EOL) + ftepp_out(ftepp, "\n", false); + else + ftepp_out(ftepp, out->value, false); + } + } + break; + case '#': + if (o + 1 < vec_size(macro->output)) { + nextok = macro->output[o+1]->token; + if (nextok == '#') { + /* raw concatenation */ + ++o; + break; + } + if ( (nextok == TOKEN_IDENT || + nextok == TOKEN_KEYWORD || + nextok == TOKEN_TYPENAME) && + macro_params_find(macro, macro->output[o+1]->value, &pi)) + { + ++o; + ftepp_stringify(ftepp, ¶ms[pi]); + break; + } + } + ftepp_out(ftepp, "#", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\n", false); + break; + default: + ftepp_out(ftepp, out->value, false); + break; + } + } + vec_push(ftepp->output_string, 0); + /* Now run the preprocessor recursively on this string buffer */ + /* + printf("__________\n%s\n=========\n", ftepp->output_string); + */ + inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); + if (!inlex) { + ftepp_error(ftepp, "internal error: failed to instantiate lexer"); + retval = false; + goto cleanup; + } + ftepp->output_string = old_string; + ftepp->lex = inlex; + ftepp_recursion_header(ftepp); + if (!ftepp_preprocess(ftepp)) { + lex_close(ftepp->lex); + retval = false; + goto cleanup; + } + ftepp_recursion_footer(ftepp); + old_string = ftepp->output_string; + +cleanup: + ftepp->lex = old_lexer; + ftepp->output_string = old_string; + return retval; +} + +static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) +{ + size_t o; + macroparam *params = NULL; + bool retval = true; + + if (!macro->has_params) { + if (!ftepp_macro_expand(ftepp, macro, NULL)) + return false; + ftepp_next(ftepp); + return true; + } + ftepp_next(ftepp); + + if (!ftepp_skipallwhite(ftepp)) + return false; + + if (ftepp->token != '(') { + ftepp_error(ftepp, "expected macro parameters in parenthesis"); + return false; + } + + ftepp_next(ftepp); + if (!ftepp_macro_call_params(ftepp, ¶ms)) + return false; + + if (vec_size(params) != vec_size(macro->params)) { + ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name, + (unsigned int)vec_size(macro->params), + (unsigned int)vec_size(params)); + retval = false; + goto cleanup; + } + + if (!ftepp_macro_expand(ftepp, macro, params)) + retval = false; + ftepp_next(ftepp); + +cleanup: + for (o = 0; o < vec_size(params); ++o) + macroparam_clean(¶ms[o]); + vec_free(params); + return retval; +} + +/** + * #if - the FTEQCC way: + * defined(FOO) => true if FOO was #defined regardless of parameters or contents + * => True if the number is not 0 + * ! => True if the factor yields false + * !! => ERROR on 2 or more unary nots + * => becomes the macro's FIRST token regardless of parameters + * && => True if both expressions are true + * || => True if either expression is true + * => False + * => False (remember for macros the rule applies instead) + * Unary + and - are weird and wrong in fteqcc so we don't allow them + * parenthesis in expressions are allowed + * parameter lists on macros are errors + * No mathematical calculations are executed + */ +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out) +{ + ppmacro *macro; + bool wasnot = false; + + if (!ftepp_skipspace(ftepp)) + return false; + + while (ftepp->token == '!') { + wasnot = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!strcmp(ftepp_tokval(ftepp), "defined")) { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != '(') { + ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_IDENT && + ftepp->token != TOKEN_TYPENAME && + ftepp->token != TOKEN_KEYWORD) + { + ftepp_error(ftepp, "defined() used on an unexpected token type"); + return false; + } + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + *out = !!macro; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren"); + return false; + } + break; + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro || !vec_size(macro->output)) { + *out = false; + } else { + /* This does not expand recursively! */ + switch (macro->output[0]->token) { + case TOKEN_INTCONST: + *out = !!(macro->output[0]->constval.f); + break; + case TOKEN_FLOATCONST: + *out = !!(macro->output[0]->constval.f); + break; + default: + *out = false; + break; + } + } + break; + case TOKEN_STRINGCONST: + *out = false; + break; + case TOKEN_INTCONST: + *out = !!(ftepp->lex->tok.constval.i); + break; + case TOKEN_FLOATCONST: + *out = !!(ftepp->lex->tok.constval.f); + break; + + case '(': + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, out)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren in #if expression"); + return false; + } + break; + + default: + ftepp_error(ftepp, "junk in #if"); + return false; + } + if (wasnot) + *out = !*out; + + ftepp->lex->flags.noops = false; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + ftepp->lex->flags.noops = true; + + if (ftepp->token == ')') + return true; + + if (ftepp->token != TOKEN_OPERATOR) + return true; + + if (!strcmp(ftepp_tokval(ftepp), "&&") || + !strcmp(ftepp_tokval(ftepp), "||")) + { + bool next = false; + char opc = ftepp_tokval(ftepp)[0]; + + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, &next)) + return false; + + if (opc == '&') + *out = *out && next; + else + *out = *out || next; + return true; + } + else { + ftepp_error(ftepp, "junk after #if"); + return false; + } +} + +static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) +{ + bool result = false; + + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == TOKEN_EOL) { + ftepp_error(ftepp, "expected expression for #if-directive"); + return false; + } + + if (!ftepp_if_expr(ftepp, &result)) + return false; + + cond->on = result; + return true; +} + +/** + * ifdef is rather simple + */ +static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) +{ + ppmacro *macro; + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + cond->on = !!macro; + return true; +} + +/** + * undef is also simple + */ +static bool ftepp_undef(ftepp_t *ftepp) +{ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->output_on) { + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + return true; +} + +/* Special unescape-string function which skips a leading quote + * and stops at a quote, not just at \0 + */ +static void unescape(const char *str, char *out) { + ++str; + while (*str && *str != '"') { + if (*str == '\\') { + ++str; + switch (*str) { + case '\\': *out++ = *str; break; + case '"': *out++ = *str; break; + case 'a': *out++ = '\a'; break; + case 'b': *out++ = '\b'; break; + case 'r': *out++ = '\r'; break; + case 'n': *out++ = '\n'; break; + case 't': *out++ = '\t'; break; + case 'f': *out++ = '\f'; break; + case 'v': *out++ = '\v'; break; + default: + *out++ = '\\'; + *out++ = *str; + break; + } + ++str; + continue; + } + + *out++ = *str++; + } + *out = 0; +} + +static char *ftepp_include_find(ftepp_t *ftepp, const char *file) +{ + char *filename = NULL; + size_t len; + + if (ftepp->itemname) { + const char *last_slash; + last_slash = strrchr(ftepp->itemname, '/'); + if (last_slash) { + len = last_slash - ftepp->itemname; + memcpy(vec_add(filename, len), ftepp->itemname, len); + vec_push(filename, '/'); + } + else { + len = strlen(ftepp->itemname); + memcpy(vec_add(filename, len), ftepp->itemname, len); + if (vec_last(filename) != '/') + vec_push(filename, '/'); + } + } + len = strlen(file); + memcpy(vec_add(filename, len), file, len); + vec_push(filename, 0); + return filename; +} + +/** + * Include a file. + * FIXME: do we need/want a -I option? + * FIXME: what about when dealing with files in subdirectories coming from a progs.src? + */ +static bool ftepp_include(ftepp_t *ftepp) +{ + lex_file *old_lexer = ftepp->lex; + lex_file *inlex; + lex_ctx ctx; + char lineno[128]; + char *filename; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->token != TOKEN_STRINGCONST) { + ftepp_error(ftepp, "expected filename to include"); + return false; + } + + ctx = ftepp_ctx(ftepp); + + unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_out(ftepp, ")\n#pragma line(1)\n", false); + + filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp)); + inlex = lex_open(filename); + if (!inlex) { + ftepp_error(ftepp, "failed to open include file `%s`", filename); + vec_free(filename); + return false; + } + vec_free(filename); + ftepp->lex = inlex; + if (!ftepp_preprocess(ftepp)) { + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + return false; + } + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ctx.file, false); + snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); + ftepp_out(ftepp, lineno, false); + + /* skip the line */ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_EOL) { + ftepp_error(ftepp, "stray tokens after #include"); + return false; + } + (void)ftepp_next(ftepp); + + return true; +} + +/* Basic structure handlers */ +static bool ftepp_else_allowed(ftepp_t *ftepp) +{ + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#else without #if"); + return false; + } + if (vec_last(ftepp->conditions).had_else) { + ftepp_error(ftepp, "multiple #else for a single #if"); + return false; + } + return true; +} + +static bool ftepp_hash(ftepp_t *ftepp) +{ + ppcondition cond; + ppcondition *pc; + + lex_ctx ctx = ftepp_ctx(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + if (!strcmp(ftepp_tokval(ftepp), "define")) { + return ftepp_define(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "undef")) { + return ftepp_undef(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "elif")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_if(ftepp, &cond)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on && cond.on; + pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "if")) { + if (!ftepp_if(ftepp, &cond)) + return false; + cond.was_on = cond.on; + vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "else")) { + if (!ftepp_else_allowed(ftepp)) + return false; + pc = &vec_last(ftepp->conditions); + pc->on = !pc->was_on; + pc->had_else = true; + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "endif")) { + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#endif without #if"); + return false; + } + vec_pop(ftepp->conditions); + ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "include")) { + return ftepp_include(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { + ftepp_out(ftepp, "#", false); + break; + } + else { + ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); + return false; + } + break; + default: + ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); + return false; + case TOKEN_EOL: + ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); + return false; + case TOKEN_EOF: + ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); + return false; + + /* Builtins! Don't forget the builtins! */ + case TOKEN_INTCONST: + case TOKEN_FLOATCONST: + ftepp_out(ftepp, "#", false); + return true; + } + if (!ftepp_skipspace(ftepp)) + return false; + return true; +} + +static bool ftepp_preprocess(ftepp_t *ftepp) +{ + ppmacro *macro; + bool newline = true; + + ftepp->lex->flags.preprocessing = true; + ftepp->lex->flags.mergelines = false; + ftepp->lex->flags.noops = true; + + ftepp_next(ftepp); + do + { + if (ftepp->token >= TOKEN_EOF) + break; +#if 0 + ftepp->newline = newline; + newline = false; +#else + /* For the sake of FTE compatibility... FU, really */ + ftepp->newline = newline = true; +#endif + + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + if (!ftepp_macro_call(ftepp, macro)) + ftepp->token = TOKEN_ERROR; + break; + case '#': + if (!ftepp->newline) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + ftepp->lex->flags.mergelines = true; + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "error in preprocessor directive"); + ftepp->token = TOKEN_ERROR; + break; + } + if (!ftepp_hash(ftepp)) + ftepp->token = TOKEN_ERROR; + ftepp->lex->flags.mergelines = false; + break; + case TOKEN_EOL: + newline = true; + ftepp_out(ftepp, "\n", true); + ftepp_next(ftepp); + break; + default: + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + } while (!ftepp->errors && ftepp->token < TOKEN_EOF); + + newline = ftepp->token == TOKEN_EOF; + return newline; +} + +/* Like in parser.c - files keep the previous state so we have one global + * preprocessor. Except here we will want to warn about dangling #ifs. + */ +static ftepp_t *ftepp; + +static bool ftepp_preprocess_done() +{ + bool retval = true; + lex_close(ftepp->lex); + ftepp->lex = NULL; + if (vec_size(ftepp->conditions)) { + if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) + retval = false; + } + if (ftepp->itemname) { + mem_d(ftepp->itemname); + ftepp->itemname = NULL; + } + return retval; +} + +bool ftepp_preprocess_file(const char *filename) +{ + ftepp->lex = lex_open(filename); + ftepp->itemname = util_strdup(filename); + if (!ftepp->lex) { + con_out("failed to open file \"%s\"\n", filename); + return false; + } + if (!ftepp_preprocess(ftepp)) { + ftepp_delete(ftepp); + return false; + } + return ftepp_preprocess_done(); +} + +bool ftepp_preprocess_string(const char *name, const char *str) +{ + ftepp->lex = lex_open_string(str, strlen(str), name); + ftepp->itemname = util_strdup(name); + if (!ftepp->lex) { + con_out("failed to create lexer for string \"%s\"\n", name); + return false; + } + if (!ftepp_preprocess(ftepp)) { + ftepp_delete(ftepp); + return false; + } + return ftepp_preprocess_done(); +} + +bool ftepp_init() +{ + ftepp = ftepp_new(); + return !!ftepp; +} + +const char *ftepp_get() +{ + return ftepp->output_string; +} + +void ftepp_flush() +{ + vec_free(ftepp->output_string); +} + +void ftepp_finish() +{ + if (!ftepp) + return; + ftepp_delete(ftepp); + ftepp = NULL; +} diff --git a/gmqcc.h b/gmqcc.h index 5136903..265bee5 100644 --- a/gmqcc.h +++ b/gmqcc.h @@ -752,6 +752,18 @@ bool parser_compile_file (const char *filename); bool parser_compile_string(const char *name, const char *str); bool parser_finish (const char *output); void parser_cleanup (); +/* There's really no need to strlen() preprocessed files */ +bool parser_compile_string_len(const char *name, const char *str, size_t len); + +/*===================================================================*/ +/*====================== ftepp.c commandline ========================*/ +/*===================================================================*/ +bool ftepp_init (); +bool ftepp_preprocess_file (const char *filename); +bool ftepp_preprocess_string(const char *name, const char *str); +void ftepp_finish (); +const char *ftepp_get (); +void ftepp_flush (); /*===================================================================*/ /*======================= main.c commandline ========================*/ diff --git a/ir.c b/ir.c index 5f037ec..9907b35 100644 --- a/ir.c +++ b/ir.c @@ -259,6 +259,7 @@ ir_builder* ir_builder_new(const char *modulename) self->functions = NULL; self->globals = NULL; self->fields = NULL; + self->extparams = NULL; self->filenames = NULL; self->filestrings = NULL; @@ -280,6 +281,10 @@ void ir_builder_delete(ir_builder* self) ir_function_delete_quick(self->functions[i]); } vec_free(self->functions); + for (i = 0; i != vec_size(self->extparams); ++i) { + ir_value_delete(self->extparams[i]); + } + vec_free(self->extparams); for (i = 0; i != vec_size(self->globals); ++i) { ir_value_delete(self->globals[i]); } @@ -2500,10 +2505,13 @@ tailcall: * generation already. This would even include later * reuse.... probably... :) */ - size_t p; + size_t p, first; ir_value *retvalue; - for (p = 0; p < vec_size(instr->params); ++p) + first = vec_size(instr->params); + if (first > 8) + first = 8; + for (p = 0; p < first; ++p) { ir_value *param = instr->params[p]; @@ -2518,6 +2526,33 @@ tailcall: stmt.o2.u1 = OFS_PARM0 + 3 * p; vec_push(code_statements, stmt); } + /* No whandle extparams */ + first = vec_size(instr->params); + for (; p < first; ++p) + { + ir_builder *ir = func->owner; + ir_value *param = instr->params[p]; + ir_value *target; + + if (p-8 >= vec_size(ir->extparams)) { + irerror(instr->context, "Not enough extparam-globals have been created"); + return false; + } + + target = ir->extparams[p-8]; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.u1 = 0; + + if (param->vtype == TYPE_FIELD) + stmt.opcode = field_store_instr[param->fieldtype]; + else + stmt.opcode = type_store_instr[param->vtype]; + stmt.o1.u1 = ir_value_code_addr(param); + stmt.o2.u1 = ir_value_code_addr(target); + vec_push(code_statements, stmt); + } + stmt.opcode = INSTR_CALL0 + vec_size(instr->params); if (stmt.opcode > INSTR_CALL8) stmt.opcode = INSTR_CALL8; @@ -2652,6 +2687,8 @@ static bool gen_global_function(ir_builder *ir, ir_value *global) fun.file = ir_builder_filestring(ir, global->context.file); fun.profile = 0; /* always 0 */ fun.nargs = vec_size(irfun->params); + if (fun.nargs > 8) + fun.nargs = 8; for (i = 0;i < 8; ++i) { if (i >= fun.nargs) @@ -2698,6 +2735,63 @@ static bool gen_global_function(ir_builder *ir, ir_value *global) return true; } +static void ir_gen_extparam(ir_builder *ir) +{ + prog_section_def def; + ir_value *global; + char name[128]; + + snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparams)+8)); + global = ir_value_var(name, store_global, TYPE_VECTOR); + + def.name = code_genstring(name); + def.type = TYPE_VECTOR; + def.offset = vec_size(code_globals); + + vec_push(code_defs, def); + ir_value_code_setaddr(global, def.offset); + vec_push(code_globals, 0); + vec_push(code_globals, 0); + vec_push(code_globals, 0); + + vec_push(ir->extparams, global); +} + +static bool gen_function_extparam_copy(ir_function *self) +{ + size_t i, ext, numparams; + + ir_builder *ir = self->owner; + ir_value *ep; + prog_section_statement stmt; + + numparams = vec_size(self->params); + if (!numparams) + return true; + + stmt.opcode = INSTR_STORE_F; + stmt.o3.s1 = 0; + for (i = 8; i < numparams; ++i) { + ext = i - 8; + if (ext >= vec_size(ir->extparams)) + ir_gen_extparam(ir); + + ep = ir->extparams[ext]; + + stmt.opcode = type_store_instr[self->locals[i]->vtype]; + if (self->locals[i]->vtype == TYPE_FIELD && + self->locals[i]->fieldtype == TYPE_VECTOR) + { + stmt.opcode = INSTR_STORE_V; + } + stmt.o1.u1 = ir_value_code_addr(ep); + stmt.o2.u1 = ir_value_code_addr(self->locals[i]); + vec_push(code_statements, stmt); + } + + return true; +} + static bool gen_global_function_code(ir_builder *ir, ir_value *global) { prog_section_function *fundef; @@ -2721,6 +2815,10 @@ static bool gen_global_function_code(ir_builder *ir, ir_value *global) fundef = &code_functions[irfun->code_function_def]; fundef->entry = vec_size(code_statements); + if (!gen_function_extparam_copy(irfun)) { + irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name); + return false; + } if (!gen_function_code(irfun)) { irerror(irfun->context, "Failed to generate code for function %s", irfun->name); return false; @@ -2972,7 +3070,8 @@ bool ir_builder_generate(ir_builder *self, const char *filename) stmt.o3.u1 = 0; vec_push(code_statements, stmt); - con_out("writing '%s'...\n", filename); + if (!opts_pp_only) + con_out("writing '%s'...\n", filename); return code_write(filename); } diff --git a/ir.h b/ir.h index 9b5171d..06efa5b 100644 --- a/ir.h +++ b/ir.h @@ -290,6 +290,8 @@ typedef struct ir_builder_s ir_value **globals; ir_value **fields; + ir_value **extparams; + const char **filenames; qcint *filestrings; /* we cache the #IMMEDIATE string here */ diff --git a/lexer.c b/lexer.c index 513499a..a71e50f 100644 --- a/lexer.c +++ b/lexer.c @@ -10,32 +10,32 @@ char* *lex_filenames; void lexerror(lex_file *lex, const char *fmt, ...) { - va_list ap; + va_list ap; - va_start(ap, fmt); - if (lex) + va_start(ap, fmt); + if (lex) con_vprintmsg(LVL_ERROR, lex->name, lex->sline, "parse error", fmt, ap); else con_vprintmsg(LVL_ERROR, "", 0, "parse error", fmt, ap); - va_end(ap); + va_end(ap); } bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...) { - va_list ap; - int lvl = LVL_WARNING; + va_list ap; + int lvl = LVL_WARNING; if (!OPTS_WARN(warntype)) return false; if (opts_werror) - lvl = LVL_ERROR; + lvl = LVL_ERROR; - va_start(ap, fmt); + va_start(ap, fmt); con_vprintmsg(lvl, lex->name, lex->sline, "warning", fmt, ap); - va_end(ap); + va_end(ap); - return opts_werror; + return opts_werror; } @@ -289,13 +289,13 @@ static int lex_getch(lex_file *lex) if (lex->peekpos) { lex->peekpos--; - if (lex->peek[lex->peekpos] == '\n') + if (!lex->push_line && lex->peek[lex->peekpos] == '\n') lex->line++; return lex->peek[lex->peekpos]; } ch = lex_fgetc(lex); - if (ch == '\n') + if (!lex->push_line && ch == '\n') lex->line++; else if (ch == '?') return lex_try_trigraph(lex, ch); @@ -307,7 +307,7 @@ static int lex_getch(lex_file *lex) static void lex_ungetch(lex_file *lex, int ch) { lex->peek[lex->peekpos++] = ch; - if (ch == '\n') + if (!lex->push_line && ch == '\n') lex->line--; } @@ -347,6 +347,115 @@ static void lex_endtoken(lex_file *lex) vec_shrinkby(lex->tok.value, 1); } +static bool lex_try_pragma(lex_file *lex) +{ + int ch; + char *pragma = NULL; + char *command = NULL; + char *param = NULL; + size_t line; + + if (lex->flags.preprocessing) + return false; + + line = lex->line; + + ch = lex_getch(lex); + if (ch != '#') { + lex_ungetch(lex, ch); + return false; + } + + for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(pragma, ch); + vec_push(pragma, 0); + + if (ch != ' ' || strcmp(pragma, "pragma")) { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex)) + vec_push(command, ch); + vec_push(command, 0); + + if (ch != '(') { + lex_ungetch(lex, ch); + goto unroll; + } + + for (ch = lex_getch(lex); vec_size(param) < 32 && ch != ')' && ch != '\n'; ch = lex_getch(lex)) + vec_push(param, ch); + vec_push(param, 0); + + if (ch != ')') { + lex_ungetch(lex, ch); + goto unroll; + } + + if (!strcmp(command, "push")) { + if (!strcmp(param, "line")) { + lex->push_line++; + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "pop")) { + if (!strcmp(param, "line")) { + if (lex->push_line) + lex->push_line--; + --line; + } + else + goto unroll; + } + else if (!strcmp(command, "file")) { + lex->name = util_strdup(param); + vec_push(lex_filenames, lex->name); + } + else if (!strcmp(command, "line")) { + line = strtol(param, NULL, 0)-1; + } + else + goto unroll; + + lex->line = line; + while (ch != '\n' && ch != EOF) + ch = lex_getch(lex); + return true; + +unroll: + if (command) { + vec_pop(command); + while (vec_size(command)) { + lex_ungetch(lex, vec_last(command)); + vec_pop(command); + } + vec_free(command); + } + if (command) { + vec_pop(command); + while (vec_size(command)) { + lex_ungetch(lex, vec_last(command)); + vec_pop(command); + } + vec_free(command); + } + if (pragma) { + vec_pop(pragma); + while (vec_size(pragma)) { + lex_ungetch(lex, vec_last(pragma)); + vec_pop(pragma); + } + vec_free(pragma); + } + lex_ungetch(lex, '#'); + + lex->line = line; + return false; +} + /* Skip whitespace and comments and return the first * non-white character. * As this makes use of the above getch() ungetch() functions, @@ -388,6 +497,10 @@ static int lex_skipwhite(lex_file *lex) { ch = lex_getch(lex); while (ch != EOF && isspace(ch)) { + if (ch == '\n') { + if (lex_try_pragma(lex)) + continue; + } if (lex->flags.preprocessing) { if (ch == '\n') { /* end-of-line */ @@ -415,13 +528,17 @@ static int lex_skipwhite(lex_file *lex) if (lex->flags.preprocessing) { haswhite = true; + /* lex_tokench(lex, '/'); lex_tokench(lex, '/'); + */ + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); } while (ch != EOF && ch != '\n') { if (lex->flags.preprocessing) - lex_tokench(lex, ch); + lex_tokench(lex, ' '); /* ch); */ ch = lex_getch(lex); } if (lex->flags.preprocessing) { @@ -436,8 +553,12 @@ static int lex_skipwhite(lex_file *lex) /* multiline comment */ if (lex->flags.preprocessing) { haswhite = true; + /* lex_tokench(lex, '/'); lex_tokench(lex, '*'); + */ + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); } while (ch != EOF) @@ -447,14 +568,18 @@ static int lex_skipwhite(lex_file *lex) ch = lex_getch(lex); if (ch == '/') { if (lex->flags.preprocessing) { + /* lex_tokench(lex, '*'); lex_tokench(lex, '/'); + */ + lex_tokench(lex, ' '); + lex_tokench(lex, ' '); } break; } } if (lex->flags.preprocessing) { - lex_tokench(lex, ch); + lex_tokench(lex, ' '); /* ch); */ } } ch = ' '; /* cause TRUE in the isspace check */ @@ -561,7 +686,17 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) if (ch == quote) return TOKEN_STRINGCONST; - if (!lex->flags.preprocessing && ch == '\\') { + if (lex->flags.preprocessing && ch == '\\') { + lex_tokench(lex, ch); + ch = lex_getch(lex); + if (ch == EOF) { + lexerror(lex, "unexpected end of file"); + lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */ + return (lex->tok.ttype = TOKEN_ERROR); + } + lex_tokench(lex, ch); + } + else if (ch == '\\') { ch = lex_getch(lex); if (ch == EOF) { lexerror(lex, "unexpected end of file"); @@ -571,6 +706,8 @@ static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote) switch (ch) { case '\\': break; + case '\'': break; + case '"': break; case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'r': ch = '\r'; break; @@ -678,7 +815,21 @@ int lex_do(lex_file *lex) return TOKEN_FATAL; #endif - ch = lex_skipwhite(lex); + while (true) { + ch = lex_skipwhite(lex); + if (!lex->flags.mergelines || ch != '\\') + break; + ch = lex_getch(lex); + if (ch != '\n') { + lex_ungetch(lex, ch); + ch = '\\'; + break; + } + /* we reached a linemerge */ + lex_tokench(lex, '\n'); + continue; + } + lex->sline = lex->line; lex->tok.ctx.line = lex->sline; lex->tok.ctx.file = lex->name; @@ -851,6 +1002,7 @@ int lex_do(lex_file *lex) return (lex->tok.ttype = TOKEN_OPERATOR); case ')': case ';': + case ':': case '{': case '}': case ']': diff --git a/lexer.h b/lexer.h index 76d2b7a..561148c 100644 --- a/lexer.h +++ b/lexer.h @@ -119,11 +119,14 @@ typedef struct { bool noops; bool nodigraphs; /* used when lexing string constants */ bool preprocessing; /* whitespace and EOLs become actual tokens */ + bool mergelines; /* backslash at the end of a line escapes the newline */ } flags; int framevalue; frame_macro *frames; char *modelname; + + size_t push_line; } lex_file; lex_file* lex_open (const char *file); diff --git a/main.c b/main.c index b483db9..4c8062d 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 * Dale Weiler + * Wolfgang Bumiller * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -57,28 +58,28 @@ static const char *app_name; static int usage() { con_out("usage: %s [options] [files...]", app_name); con_out("options:\n" - " -h, --help show this help message\n" - " -debug turns on compiler debug messages\n" - " -memchk turns on compiler memory leak check\n"); + " -h, --help show this help message\n" + " -debug turns on compiler debug messages\n" + " -memchk turns on compiler memory leak check\n"); con_out(" -o, --output=file output file, defaults to progs.dat\n" - " -a filename add an asm file to be assembled\n" - " -s filename add a progs.src file to be used\n"); + " -a filename add an asm file to be assembled\n" + " -s filename add a progs.src file to be used\n"); con_out(" -E stop after preprocessing\n"); con_out(" -f enable a flag\n" - " -fno- disable a flag\n" - " -std standard select one of the following standards\n" - " -std=qcc original QuakeC\n" - " -std=fteqcc fteqcc QuakeC\n" - " -std=gmqcc this compiler (default)\n"); + " -fno- disable a flag\n" + " -std standard select one of the following standards\n" + " -std=qcc original QuakeC\n" + " -std=fteqcc fteqcc QuakeC\n" + " -std=gmqcc this compiler (default)\n"); con_out(" -W enable a warning\n" - " -Wno- disable a warning\n" - " -Wall enable all warnings\n" - " -Werror treat warnings as errors\n"); + " -Wno- disable a warning\n" + " -Wall enable all warnings\n" + " -Werror treat warnings as errors\n"); con_out(" -force-crc=num force a specific checksum into the header\n"); con_out("\n"); con_out("flags:\n" - " -fadjust-vector-fields\n" - " when assigning a vector field, its _y and _z fields also get assigned\n" + " -fadjust-vector-fields\n" + " when assigning a vector field, its _y and _z fields also get assigned\n" ); return -1; } @@ -202,6 +203,7 @@ static bool options_parse(int argc, char **argv) { options_set(opts_flags, ADJUST_VECTOR_FIELDS, false); opts_standard = COMPILER_QCC; } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) { + options_set(opts_flags, FTEPP, true); options_set(opts_flags, ADJUST_VECTOR_FIELDS, false); opts_standard = COMPILER_FTEQCC; } else if (!strcmp(argarg, "qccx")) { @@ -416,6 +418,8 @@ int main(int argc, char **argv) { size_t itr; int retval = 0; bool opts_output_free = false; + bool progs_src = false; + FILE *outfile = NULL; app_name = argv[0]; con_init(); @@ -436,8 +440,11 @@ int main(int argc, char **argv) { options_set(opts_warn, WARN_EFFECTLESS_STATEMENT, true); options_set(opts_warn, WARN_END_SYS_FIELDS, true); options_set(opts_warn, WARN_ASSIGN_FUNCTION_TYPES, true); + options_set(opts_warn, WARN_PREPROCESSOR, true); + options_set(opts_warn, WARN_MULTIFILE_IF, true); options_set(opts_flags, ADJUST_VECTOR_FIELDS, true); + options_set(opts_flags, FTEPP, false); if (!options_parse(argc, argv)) { return usage(); @@ -464,53 +471,53 @@ int main(int argc, char **argv) { con_out("standard = %i\n", opts_standard); } - if (!parser_init()) { - con_out("failed to initialize parser\n"); - retval = 1; - goto cleanup; - } - - util_debug("COM", "starting ...\n"); - - if (vec_size(items)) { - con_out("Mode: manual\n"); - con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); - for (itr = 0; itr < vec_size(items); ++itr) { - con_out(" item: %s (%s)\n", - items[itr].filename, - ( (items[itr].type == TYPE_QC ? "qc" : - (items[itr].type == TYPE_ASM ? "asm" : - (items[itr].type == TYPE_SRC ? "progs.src" : - ("unknown")))))); - - if (!parser_compile_file(items[itr].filename)) - { - retval = 1; - goto cleanup; + if (opts_pp_only) { + if (opts_output_wasset) { + outfile = util_fopen(opts_output, "wb"); + if (!outfile) { + con_err("failed to open `%s` for writing\n", opts_output); + retval = 1; + goto cleanup; } } + else + outfile = stdout; + } - if (!parser_finish(opts_output)) { + if (!opts_pp_only) { + if (!parser_init()) { + con_err("failed to initialize parser\n"); retval = 1; goto cleanup; } + } + if (opts_pp_only || OPTS_FLAG(FTEPP)) { + if (!ftepp_init()) { + con_err("failed to initialize parser\n"); + retval = 1; + goto cleanup; + } + } - } else { + util_debug("COM", "starting ...\n"); + + if (!vec_size(items)) { FILE *src; char *line; size_t linelen = 0; - con_out("Mode: progs.src\n"); + progs_src = true; + src = util_fopen("progs.src", "rb"); if (!src) { - con_out("failed to open `progs.src` for reading\n"); + con_err("failed to open `progs.src` for reading\n"); retval = 1; goto cleanup; } line = NULL; if (!progs_nextline(&line, &linelen, src) || !line[0]) { - con_out("illformatted progs.src file: expected output filename in first line\n"); + con_err("illformatted progs.src file: expected output filename in first line\n"); retval = 1; goto srcdone; } @@ -521,30 +528,92 @@ int main(int argc, char **argv) { } while (progs_nextline(&line, &linelen, src)) { + argitem item; if (!line[0] || (line[0] == '/' && line[1] == '/')) continue; - con_out(" src: %s\n", line); - if (!parser_compile_file(line)) { - retval = 1; - goto srcdone; - } + item.filename = util_strdup(line); + item.type = TYPE_QC; + vec_push(items, item); } - parser_finish(opts_output); - srcdone: fclose(src); mem_d(line); } + if (retval) + goto cleanup; + + if (vec_size(items)) { + if (!opts_pp_only) { + con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual")); + con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items)); + } + for (itr = 0; itr < vec_size(items); ++itr) { + if (!opts_pp_only) { + con_out(" item: %s (%s)\n", + items[itr].filename, + ( (items[itr].type == TYPE_QC ? "qc" : + (items[itr].type == TYPE_ASM ? "asm" : + (items[itr].type == TYPE_SRC ? "progs.src" : + ("unknown")))))); + } + + if (opts_pp_only) { + if (!ftepp_preprocess_file(items[itr].filename)) { + retval = 1; + goto cleanup; + } + fprintf(outfile, "%s", ftepp_get()); + ftepp_flush(); + } + else { + if (OPTS_FLAG(FTEPP)) { + const char *data; + if (!ftepp_preprocess_file(items[itr].filename)) { + retval = 1; + goto cleanup; + } + data = ftepp_get(); + if (!parser_compile_string_len(items[itr].filename, data, vec_size(data)-1)) { + retval = 1; + goto cleanup; + } + ftepp_flush(); + } + else { + if (!parser_compile_file(items[itr].filename)) { + retval = 1; + goto cleanup; + } + } + } + + if (progs_src) { + mem_d(items[itr].filename); + items[itr].filename = NULL; + } + } + + ftepp_finish(); + if (!opts_pp_only) { + if (!parser_finish(opts_output)) { + retval = 1; + goto cleanup; + } + } + } + /* stuff */ cleanup: util_debug("COM", "cleaning ...\n"); + ftepp_finish(); con_close(); vec_free(items); - parser_cleanup(); + if (!opts_pp_only) + parser_cleanup(); if (opts_output_free) mem_d((char*)opts_output); diff --git a/opts.def b/opts.def index e628f43..9b64833 100644 --- a/opts.def +++ b/opts.def @@ -31,6 +31,7 @@ GMQCC_DEFINE_FLAG(DARKPLACES_STRING_TABLE_BUG) GMQCC_DEFINE_FLAG(OMIT_NULL_BYTES) GMQCC_DEFINE_FLAG(ADJUST_VECTOR_FIELDS) + GMQCC_DEFINE_FLAG(FTEPP) #endif /* warning flags */ @@ -52,6 +53,8 @@ GMQCC_DEFINE_FLAG(EFFECTLESS_STATEMENT) GMQCC_DEFINE_FLAG(END_SYS_FIELDS) GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES) + GMQCC_DEFINE_FLAG(PREPROCESSOR) + GMQCC_DEFINE_FLAG(MULTIFILE_IF) #endif /* some cleanup so we don't have to */ diff --git a/parser.c b/parser.c index 805c28c..25c9c74 100644 --- a/parser.c +++ b/parser.c @@ -20,6 +20,9 @@ typedef struct { ast_value **imm_string; ast_value **imm_vector; + /* must be deleted first, they reference immediates and values */ + ast_value **accessors; + ast_value *imm_float_zero; ast_value *imm_vector_zero; @@ -2396,6 +2399,7 @@ static bool parser_create_array_accessor(parser_t *parser, ast_value *array, con { ast_function *func = NULL; ast_value *fval = NULL; + ast_block *body = NULL; fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION); if (!fval) { @@ -2410,15 +2414,25 @@ static bool parser_create_array_accessor(parser_t *parser, ast_value *array, con return false; } + body = ast_block_new(ast_ctx(array)); + if (!body) { + parseerror(parser, "failed to create block for array accessor"); + ast_delete(fval); + ast_delete(func); + return false; + } + + vec_push(func->blocks, body); *out = fval; + vec_push(parser->accessors, fval); + return true; } static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname) { ast_expression *root = NULL; - ast_block *body = NULL; ast_value *index = NULL; ast_value *value = NULL; ast_function *func; @@ -2434,12 +2448,6 @@ static bool parser_create_array_setter(parser_t *parser, ast_value *array, const func = fval->constval.vfunc; fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - body = ast_block_new(ast_ctx(array)); - if (!body) { - parseerror(parser, "failed to create block for array accessor"); - goto cleanup; - } - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); value = ast_value_copy((ast_value*)array->expression.next); @@ -2457,12 +2465,10 @@ static bool parser_create_array_setter(parser_t *parser, ast_value *array, const goto cleanup; } - vec_push(body->exprs, root); - vec_push(func->blocks, body); + vec_push(func->blocks[0]->exprs, root); array->setter = fval; return true; cleanup: - if (body) ast_delete(body); if (index) ast_delete(index); if (value) ast_delete(value); if (root) ast_delete(root); @@ -2474,7 +2480,6 @@ cleanup: static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname) { ast_expression *root = NULL; - ast_block *body = NULL; ast_value *entity = NULL; ast_value *index = NULL; ast_value *value = NULL; @@ -2491,12 +2496,6 @@ static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, func = fval->constval.vfunc; fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "", TYPE_VOID); - body = ast_block_new(ast_ctx(array)); - if (!body) { - parseerror(parser, "failed to create block for array accessor"); - goto cleanup; - } - entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY); index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); value = ast_value_copy((ast_value*)array->expression.next); @@ -2515,12 +2514,10 @@ static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, goto cleanup; } - vec_push(body->exprs, root); - vec_push(func->blocks, body); + vec_push(func->blocks[0]->exprs, root); array->setter = fval; return true; cleanup: - if (body) ast_delete(body); if (entity) ast_delete(entity); if (index) ast_delete(index); if (value) ast_delete(value); @@ -2533,7 +2530,6 @@ cleanup: static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname) { ast_expression *root = NULL; - ast_block *body = NULL; ast_value *index = NULL; ast_value *fval; ast_function *func; @@ -2551,12 +2547,6 @@ static bool parser_create_array_getter(parser_t *parser, ast_value *array, const func = fval->constval.vfunc; fval->expression.next = ast_type_copy(ast_ctx(array), elemtype); - body = ast_block_new(ast_ctx(array)); - if (!body) { - parseerror(parser, "failed to create block for array accessor"); - goto cleanup; - } - index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT); if (!index) { @@ -2571,12 +2561,10 @@ static bool parser_create_array_getter(parser_t *parser, ast_value *array, const goto cleanup; } - vec_push(body->exprs, root); - vec_push(func->blocks, body); + vec_push(func->blocks[0]->exprs, root); array->getter = fval; return true; cleanup: - if (body) ast_delete(body); if (index) ast_delete(index); if (root) ast_delete(root); ast_delete(func); @@ -2649,8 +2637,8 @@ static ast_value *parse_parameter_list(parser_t *parser, ast_value *var) } /* sanity check */ - if (vec_size(params) > 8) - parseerror(parser, "more than 8 parameters are currently not supported"); + if (vec_size(params) > 8 && opts_standard == COMPILER_QCC) + (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard"); /* parse-out */ if (!parser_next(parser)) { @@ -3431,7 +3419,17 @@ bool parser_compile_file(const char *filename) { parser->lex = lex_open(filename); if (!parser->lex) { - con_out("failed to open file \"%s\"\n", filename); + con_err("failed to open file \"%s\"\n", filename); + return false; + } + return parser_compile(); +} + +bool parser_compile_string_len(const char *name, const char *str, size_t len) +{ + parser->lex = lex_open_string(str, len, name); + if (!parser->lex) { + con_err("failed to create lexer for string \"%s\"\n", name); return false; } return parser_compile(); @@ -3441,7 +3439,7 @@ bool parser_compile_string(const char *name, const char *str) { parser->lex = lex_open_string(str, strlen(str), name); if (!parser->lex) { - con_out("failed to create lexer for string \"%s\"\n", name); + con_err("failed to create lexer for string \"%s\"\n", name); return false; } return parser_compile(); @@ -3450,6 +3448,11 @@ bool parser_compile_string(const char *name, const char *str) void parser_cleanup() { size_t i; + for (i = 0; i < vec_size(parser->accessors); ++i) { + ast_delete(parser->accessors[i]->constval.vfunc); + parser->accessors[i]->constval.vfunc = NULL; + ast_delete(parser->accessors[i]); + } for (i = 0; i < vec_size(parser->functions); ++i) { ast_delete(parser->functions[i]); } @@ -3470,6 +3473,7 @@ void parser_cleanup() ast_delete(parser->globals[i].var); mem_d(parser->globals[i].name); } + vec_free(parser->accessors); vec_free(parser->functions); vec_free(parser->imm_vector); vec_free(parser->imm_string);