void ast_ternary_delete(ast_ternary *self)
{
- ast_unref(self->cond);
- ast_unref(self->on_true);
- ast_unref(self->on_false);
+ /* the if()s are only there because computed-gotos can set them
+ * to NULL
+ */
+ if (self->cond) ast_unref(self->cond);
+ if (self->on_true) ast_unref(self->on_true);
+ if (self->on_false) ast_unref(self->on_false);
ast_expression_delete((ast_expression*)self);
mem_d(self);
}
mem_d(self);
}
-ast_label* ast_label_new(lex_ctx ctx, const char *name)
+ast_label* ast_label_new(lex_ctx ctx, const char *name, bool undefined)
{
ast_instantiate(ast_label, ctx, ast_label_delete);
ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen);
- self->name = util_strdup(name);
- self->irblock = NULL;
- self->gotos = NULL;
+ self->expression.vtype = TYPE_NOEXPR;
+
+ self->name = util_strdup(name);
+ self->irblock = NULL;
+ self->gotos = NULL;
+ self->undefined = undefined;
return self;
}
size_t i;
ir_value *dummy;
+ if (self->undefined) {
+ compile_error(ast_ctx(self), "internal error: ast_label never defined");
+ return false;
+ }
+
*out = NULL;
if (lvalue) {
compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue");
const char *name;
ir_block *irblock;
ast_goto **gotos;
+ /* means it has not yet been defined */
+ bool undefined;
};
-ast_label* ast_label_new(lex_ctx ctx, const char *name);
+ast_label* ast_label_new(lex_ctx ctx, const char *name, bool undefined);
void ast_label_delete(ast_label*);
void ast_label_register_goto(ast_label*, ast_goto*);
* Doing colored output on windows is fucking stupid. The linux way is
* the real way. So we emulate it on windows :)
*/
-#ifdef _MSC_VER
+#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
WWHITE
};
-static int win_fputs(const char *str, FILE *h) {
+static int win_fputs(FILE *h, const char *str) {
/* state for translate */
int acolor;
int wcolor;
state = -1;
}
} else {
- file_putc(*str, h);
+ file_putc(h, *str);
length ++;
}
str++;
*/
static int con_write(FILE *handle, const char *fmt, va_list va) {
int ln;
- #ifndef _MSC_VER
+ #ifndef _WIN32
ln = vfprintf(handle, fmt, va);
#else
{
char data[4096];
memset(data, 0, sizeof(data));
vsnprintf(data, sizeof(data), fmt, va);
- ln = (GMQCC_IS_DEFINE(handle)) ? win_fputs(data, handle) : file_puts(data, handle);
+ ln = (GMQCC_IS_DEFINE(handle)) ? win_fputs(handle, data) : file_puts(handle, data);
}
#endif
return ln;
return fseek(fp, off, whence);
}
+int file_putc(FILE *fp, int ch) {
+ /* Invokes file_exception on windows if fp is null */
+ return fputc(ch, fp);
+}
+
/*
* Implements libc getline for systems that don't have it, which is
* assmed all. This works the same as getline().
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
+#include <time.h>
#include "gmqcc.h"
#include "lexer.h"
char *includename;
} ftepp_t;
-typedef struct {
- const char *name;
- char *(*func)(lex_file *);
-} predef_t;
-
/*
* Implement the predef subsystem now. We can do this safely with the
* help of lexer contexts.
static uint32_t ftepp_predef_countval = 0;
static uint32_t ftepp_predef_randval = 0;
+/* __DATE__ */
+char *ftepp_predef_date(lex_file *context) {
+ struct tm *itime;
+ time_t rtime;
+ char *value = mem_a(82);
+ /* 82 is enough for strftime but we also have " " in our string */
+
+ (void)context;
+
+ /* get time */
+ time (&rtime);
+ itime = localtime(&rtime);
+
+ strftime(value, 82, "\"%b %d %Y\"", itime);
+
+ return value;
+}
+
+/* __TIME__ */
+char *ftepp_predef_time(lex_file *context) {
+ struct tm *itime;
+ time_t rtime;
+ char *value = mem_a(82);
+ /* 82 is enough for strftime but we also have " " in our string */
+
+ (void)context;
+
+ /* get time */
+ time (&rtime);
+ itime = localtime(&rtime);
+
+ strftime(value, 82, "\"%X\"", itime);
+
+ return value;
+}
+
/* __LINE__ */
char *ftepp_predef_line(lex_file *context) {
char *value;
return value;
}
-static const predef_t ftepp_predefs[] = {
+const ftepp_predef_t ftepp_predefs[FTEPP_PREDEF_COUNT] = {
{ "__LINE__", &ftepp_predef_line },
{ "__FILE__", &ftepp_predef_file },
{ "__COUNTER__", &ftepp_predef_counter },
{ "__COUNTER_LAST__", &ftepp_predef_counterlast },
{ "__RANDOM__", &ftepp_predef_random },
{ "__RANDOM_LAST__", &ftepp_predef_randomlast },
+ { "__DATE__", &ftepp_predef_date },
+ { "__TIME__", &ftepp_predef_time }
};
#define ftepp_tokval(f) ((f)->lex->tok.value)
GMQCC_INLINE int file_getc (FILE *);
GMQCC_INLINE int file_printf (FILE *, const char *, ...);
GMQCC_INLINE int file_puts (FILE *, const char *);
+GMQCC_INLINE int file_putc (FILE *, int);
GMQCC_INLINE int file_seek (FILE *, long int, int);
GMQCC_INLINE size_t file_read (void *, size_t, size_t, FILE *);
TYPE_ARRAY ,
TYPE_NIL , /* it's its own type / untyped */
+ TYPE_NOEXPR , /* simply invalid in expressions */
TYPE_COUNT
};
/*===================================================================*/
/*====================== ftepp.c commandline ========================*/
/*===================================================================*/
+struct lex_file_s;
+typedef struct {
+ const char *name;
+ char *(*func)(struct lex_file_s *);
+} ftepp_predef_t;
+
+/*
+ * line, file, counter, counter_last, random, random_last, date, time
+ * increment when items are added
+ */
+#define FTEPP_PREDEF_COUNT 8
+
bool ftepp_init ();
bool ftepp_preprocess_file (const char *filename);
bool ftepp_preprocess_string(const char *name, const char *str);
void ftepp_add_define (const char *source, const char *name);
void ftepp_add_macro (const char *name, const char *value);
+extern const ftepp_predef_t ftepp_predefs[FTEPP_PREDEF_COUNT];
+
/*===================================================================*/
/*======================= main.c commandline ========================*/
/*===================================================================*/
"union",
"array",
- "nil"
+ "nil",
+ "<no-expression>"
};
size_t type_sizeof_[TYPE_COUNT] = {
0, /* TYPE_UNION */
0, /* TYPE_ARRAY */
0, /* TYPE_NIL */
+ 0, /* TYPE_NOESPR */
};
uint16_t type_store_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
uint16_t field_store_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
uint16_t type_storep_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
uint16_t type_eq_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
uint16_t type_ne_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
uint16_t type_not_instr[TYPE_COUNT] = {
AINSTR_END, /* union */
AINSTR_END, /* array */
AINSTR_END, /* nil */
+ AINSTR_END, /* noexpr */
};
/* protos */
int value;
} frame_macro;
-typedef struct {
+typedef struct lex_file_s {
FILE *file;
const char *open_string;
size_t open_string_length;
static bool parse_block_into(parser_t *parser, ast_block *block);
static bool parse_statement_or_block(parser_t *parser, ast_expression **out);
static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases);
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue);
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma);
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels);
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels);
static void parseerror(parser_t *parser, const char *fmt, ...)
{
return ( ast_expression*)util_htget(parser->htfields, name);
}
+static ast_expression* parser_find_label(parser_t *parser, const char *name)
+{
+ size_t i;
+ for(i = 0; i < vec_size(parser->labels); i++)
+ if (!strcmp(parser->labels[i]->name, name))
+ return (ast_expression*)parser->labels[i];
+ return NULL;
+}
+
static ast_expression* parser_find_global(parser_t *parser, const char *name)
{
return (ast_expression*)util_htget(parser->htglobals, name);
exprs[i] = sy->out[vec_size(sy->out)+i].out;
blocks[i] = sy->out[vec_size(sy->out)+i].block;
asvalue[i] = (ast_value*)exprs[i];
+
+ if (exprs[i]->expression.vtype == TYPE_NOEXPR &&
+ !(i != 0 && op->id == opid2('?',':')))
+ {
+ if (ast_istype(exprs[i], ast_label))
+ compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier");
+ else
+ compile_error(ast_ctx(exprs[i]), "not an expression");
+ (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i);
+ }
}
if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) {
}
}
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue)
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels)
{
ast_expression *expr = NULL;
shunt sy;
if (!var)
var = parser_find_field(parser, parser_tokval(parser));
}
+ if (!var && with_labels) {
+ var = (ast_expression*)parser_find_label(parser, parser_tokval(parser));
+ if (!with_labels) {
+ ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true);
+ var = (ast_expression*)lbl;
+ vec_push(parser->labels, lbl);
+ }
+ }
if (!var) {
/* intrinsics */
if (!strcmp(parser_tokval(parser), "__builtin_debug_typestring")) {
var = (ast_expression*)intrinsic_debug_typestring;
-
}
else
{
+ /*
+ * sometimes people use preprocessing predefs without enabling them
+ * i've done this thousands of times already myself. Lets check for
+ * it in the predef table. And diagnose it better :)
+ */
+ if (!OPTS_FLAG(FTEPP_PREDEFS)) {
+ size_t i;
+ for (i = 0; i < sizeof(ftepp_predefs)/sizeof(*ftepp_predefs); i++) {
+ if (!strcmp(ftepp_predefs[i].name, parser_tokval(parser))) {
+ parseerror(parser, "unexpected ident: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
+ goto onerr;
+ }
+ }
+ }
+
parseerror(parser, "unexpected ident: %s", parser_tokval(parser));
goto onerr;
}
return NULL;
}
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma)
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels)
{
- ast_expression *e = parse_expression_leave(parser, stopatcomma, false);
+ ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels);
if (!e)
return NULL;
if (!parser_next(parser)) {
return false;
}
/* parse the condition */
- cond = parse_expression_leave(parser, false, true);
+ cond = parse_expression_leave(parser, false, true, false);
if (!cond)
return false;
/* closing paren */
return false;
}
/* parse the condition */
- cond = parse_expression_leave(parser, false, true);
+ cond = parse_expression_leave(parser, false, true, false);
if (!cond)
return false;
/* closing paren */
return false;
}
/* parse the condition */
- cond = parse_expression_leave(parser, false, true);
+ cond = parse_expression_leave(parser, false, true, false);
if (!cond)
return false;
/* closing paren */
}
else if (parser->tok != ';')
{
- initexpr = parse_expression_leave(parser, false, false);
+ initexpr = parse_expression_leave(parser, false, false, false);
if (!initexpr)
goto onerr;
}
/* parse the condition */
if (parser->tok != ';') {
- cond = parse_expression_leave(parser, false, true);
+ cond = parse_expression_leave(parser, false, true, false);
if (!cond)
goto onerr;
}
/* parse the incrementor */
if (parser->tok != ')') {
- increment = parse_expression_leave(parser, false, false);
+ increment = parse_expression_leave(parser, false, false, false);
if (!increment)
goto onerr;
if (!ast_side_effects(increment)) {
}
if (parser->tok != ';') {
- exp = parse_expression(parser, false);
+ exp = parse_expression(parser, false, false);
if (!exp)
return false;
return false;
}
/* parse the operand */
- operand = parse_expression_leave(parser, false, false);
+ operand = parse_expression_leave(parser, false, false, false);
if (!operand)
return false;
parseerror(parser, "expected expression for case");
return false;
}
- swcase.value = parse_expression_leave(parser, false, false);
+ swcase.value = parse_expression_leave(parser, false, false, false);
if (!swcase.value) {
ast_delete(switchnode);
parseerror(parser, "expected expression for case");
return true;
}
+/* parse computed goto sides */
+static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) {
+ ast_expression *on_true;
+ ast_expression *on_false;
+ ast_expression *cond;
+
+ if (!*side)
+ return NULL;
+
+ if (ast_istype(*side, ast_ternary)) {
+ ast_ternary *tern = (ast_ternary*)*side;
+ on_true = parse_goto_computed(parser, &tern->on_true);
+ on_false = parse_goto_computed(parser, &tern->on_false);
+
+ if (!on_true || !on_false) {
+ parseerror(parser, "expected label or expression in ternary");
+ if (on_true) ast_unref(on_true);
+ if (on_false) ast_unref(on_false);
+ return NULL;
+ }
+
+ cond = tern->cond;
+ tern->cond = NULL;
+ ast_delete(tern);
+ *side = NULL;
+ return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false);
+ } else if (ast_istype(*side, ast_label)) {
+ ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name);
+ ast_goto_set_label(gt, ((ast_label*)*side));
+ *side = NULL;
+ return (ast_expression*)gt;
+ }
+ return NULL;
+}
+
static bool parse_goto(parser_t *parser, ast_expression **out)
{
- size_t i;
- ast_goto *gt;
+ ast_goto *gt = NULL;
+ ast_expression *lbl;
- if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
- parseerror(parser, "expected label name after `goto`");
+ if (!parser_next(parser))
return false;
+
+ if (parser->tok != TOKEN_IDENT) {
+ ast_expression *expression;
+
+ /* could be an expression i.e computed goto :-) */
+ if (parser->tok != '(') {
+ parseerror(parser, "expected label name after `goto`");
+ return false;
+ }
+
+ /* failed to parse expression for goto */
+ if (!(expression = parse_expression(parser, false, true)) ||
+ !(*out = parse_goto_computed(parser, &expression))) {
+ parseerror(parser, "invalid goto expression");
+ ast_unref(expression);
+ return false;
+ }
+
+ return true;
}
+ /* not computed goto */
gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser));
-
- for (i = 0; i < vec_size(parser->labels); ++i) {
- if (!strcmp(parser->labels[i]->name, parser_tokval(parser))) {
- ast_goto_set_label(gt, parser->labels[i]);
- break;
+ lbl = parser_find_label(parser, gt->name);
+ if (lbl) {
+ if (!ast_istype(lbl, ast_label)) {
+ parseerror(parser, "internal error: label is not an ast_label");
+ ast_delete(gt);
+ return false;
}
+ ast_goto_set_label(gt, (ast_label*)lbl);
}
- if (i == vec_size(parser->labels))
+ else
vec_push(parser->gotos, gt);
if (!parser_next(parser) || parser->tok != ';') {
parseerror(parser, "label must be an identifier");
return false;
}
- label = ast_label_new(parser_ctx(parser), parser_tokval(parser));
- if (!label)
- return false;
- vec_push(parser->labels, label);
+ label = (ast_label*)parser_find_label(parser, parser_tokval(parser));
+ if (label) {
+ if (!label->undefined) {
+ parseerror(parser, "label `%s` already defined", label->name);
+ return false;
+ }
+ label->undefined = false;
+ }
+ else {
+ label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false);
+ vec_push(parser->labels, label);
+ }
*out = (ast_expression*)label;
if (!parser_next(parser)) {
parseerror(parser, "parse error after label");
}
else
{
- ast_expression *exp = parse_expression(parser, false);
+ ast_expression *exp = parse_expression(parser, false, false);
if (!exp)
return false;
*out = exp;
if (!parser_next(parser))
return false;
- framenum = parse_expression_leave(parser, true, false);
+ framenum = parse_expression_leave(parser, true, false, false);
if (!framenum) {
parseerror(parser, "expected a framenumber constant in[frame,think] notation");
return false;
nextthink = (ast_expression*)thinkfunc;
} else {
- nextthink = parse_expression_leave(parser, true, false);
+ nextthink = parse_expression_leave(parser, true, false, false);
if (!nextthink) {
ast_unref(framenum);
parseerror(parser, "expected a think-function in [frame,think] notation");
return NULL;
}
- cexp = parse_expression_leave(parser, true, false);
+ cexp = parse_expression_leave(parser, true, false, false);
if (!cexp || !ast_istype(cexp, ast_value)) {
if (cexp)
ast_expression *cexp;
ast_value *cval;
- cexp = parse_expression_leave(parser, true, false);
+ cexp = parse_expression_leave(parser, true, false, false);
if (!cexp)
break;
# define popen _popen
# define pclose _pclose
# include <windows.h>
-# include <io.h>
+# include <io.h>
# include <fcntl.h>
/*
* Bidirectional piping implementation for windows using CreatePipe and DuplicateHandle +
# ifdef __MINGW32__
/* mingw32 has dirent.h */
# include <dirent.h>
-# elif defined (_MSC_VER)
+# elif defined (_WIN32)
/*
* visual studio lacks dirent.h it's a posix thing
* so we emulate it with the WinAPI.
if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
task_template_t *template = task_template_compile(files->d_name, curdir);
char buf[4096]; /* one page should be enough */
+ char *qcflags = NULL;
task_t task;
util_debug("TEST", "compiling task template: %s/%s\n", curdir, files->d_name);
*/
template->tempfilename = tempnam(curdir, "TMPDAT");
+ /*
+ * Additional QCFLAGS enviroment variable may be used
+ * to test compile flags for all tests. This needs to be
+ * BEFORE other flags (so that the .tmpl can override them)
+ */
+ qcflags = getenv("QCFLAGS");
+
/*
* Generate the command required to open a pipe to a process
* which will be refered to with a handle in the task for
* reading the data from the pipe.
*/
- memset (buf,0,sizeof(buf));
- snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
- task_bins[TASK_COMPILE],
- curdir,
- template->sourcefile,
- template->compileflags,
- template->tempfilename
- );
+ memset (buf,0,sizeof(buf));
+ if (qcflags) {
+ snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
+ task_bins[TASK_COMPILE],
+ curdir,
+ template->sourcefile,
+ qcflags,
+ template->compileflags,
+ template->tempfilename
+ );
+ } else {
+ snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
+ task_bins[TASK_COMPILE],
+ curdir,
+ template->sourcefile,
+ template->compileflags,
+ template->tempfilename
+ );
+ }
/*
* The task template was compiled, now lets create a task from
return success;
}
-/*
- * Removes all temporary 'progs.dat' files created during compilation
- * of all tests'
- */
-void task_cleanup(const char *curdir) {
- DIR *dir;
- struct dirent *files;
- char buffer[4096];
-
- dir = opendir(curdir);
-
- while ((files = readdir(dir))) {
- memset(buffer, 0, sizeof(buffer));
- if (strstr(files->d_name, "TMP")) {
- snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
- if (remove(buffer))
- con_err("error removing temporary file: %s\n", buffer);
- else
- util_debug("TEST", "removed temporary file: %s\n", buffer);
- }
- }
-
- closedir(dir);
-}
-
/*
* Task precleanup removes any existing temporary files or log files
* left behind from a previous invoke of the test-suite.
closedir(dir);
}
-void task_destroy(const char *curdir) {
+void task_destroy(void) {
/*
* Free all the data in the task list and finally the list itself
* then proceed to cleanup anything else outside the program like
con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
else
util_debug("TEST", "removed stdout log file: %s\n", task_tasks[i].stdoutlogfile);
-
if (remove(task_tasks[i].stderrlogfile))
con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
else
util_debug("TEST", "removed stderr log file: %s\n", task_tasks[i].stderrlogfile);
+
+ remove(task_tasks[i].template->tempfilename);
}
/* free util_strdup data for log files */
task_template_destroy(&task_tasks[i].template);
}
vec_free(task_tasks);
-
- /*
- * Cleanup outside stuff like temporary files.
- */
- task_cleanup(curdir);
}
/*
task_precleanup(curdir);
if (!task_propagate(curdir)) {
con_err("error: failed to propagate tasks\n");
- task_destroy(curdir);
+ task_destroy();
return false;
}
/*
* issues.
*/
task_schedualize();
- task_destroy(curdir);
+ task_destroy();
return true;
}
--- /dev/null
+void(string, ...) print = #1;
+
+// correct execution order:
+// label_3
+// label_2
+// label_4
+// label_3
+// label_1
+// label_5
+void main() {
+ float x = 1;
+ float y = 2;
+
+ goto label_3;
+
+ :label_1; print("label_1", "\n"); goto label_5;
+ :label_2; print("label_2", "\n"); goto label_4;
+ :label_3; print("label_3", "\n");
+
+ // will goto label_2
+ goto (x == y) ? label_1 : label_2;
+
+ :label_4; print("label_4", "\n");
+ {
+ x = 1;
+ y = 1;
+
+ // will goto label_1
+ // then goes label_5
+ goto label_3;
+ }
+
+ :label_5; print("label_5", "\n");
+}
--- /dev/null
+I: goto.qc
+D: test goto (both normal and computed)
+T: -execute
+C: -std=gmqcc
+F: goto failed
+S: goto worked
+M: label_3
+M: label_2
+M: label_4
+M: label_3
+M: label_1
+M: label_5