+ 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;
+ }
+ }
+ if (i == vec_size(parser->labels))
+ vec_push(parser->gotos, gt);
+
+ if (!parser_next(parser) || parser->tok != ';') {
+ parseerror(parser, "semicolon expected after goto label");
+ return false;
+ }
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after goto");
+ return false;
+ }
+
+ *out = (ast_expression*)gt;
+ return true;
+}
+
+static bool parse_skipwhite(parser_t *parser)
+{
+ do {
+ if (!parser_next(parser))
+ return false;
+ } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR);
+ return parser->tok < TOKEN_ERROR;
+}
+
+static bool parse_eol(parser_t *parser)
+{
+ if (!parse_skipwhite(parser))
+ return false;
+ return parser->tok == TOKEN_EOL;
+}
+
+static bool parse_pragma_do(parser_t *parser)
+{
+ if (!parser_next(parser) ||
+ parser->tok != TOKEN_IDENT ||
+ strcmp(parser_tokval(parser), "pragma"))
+ {
+ parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser));
+ return false;
+ }
+ if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
+ parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
+ return false;
+ }
+
+ if (!strcmp(parser_tokval(parser), "noref")) {
+ if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) {
+ parseerror(parser, "`noref` pragma requires an argument: 0 or 1");
+ return false;
+ }
+ parser->noref = !!parser_token(parser)->constval.i;
+ if (!parse_eol(parser)) {
+ parseerror(parser, "parse error after `noref` pragma");
+ return false;
+ }
+ }
+ else
+ {
+ (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
+ return false;
+ }
+
+ return true;
+}
+
+static bool parse_pragma(parser_t *parser)
+{
+ bool rv;
+ parser->lex->flags.preprocessing = true;
+ parser->lex->flags.mergelines = true;
+ rv = parse_pragma_do(parser);
+ if (parser->tok != TOKEN_EOL) {
+ parseerror(parser, "junk after pragma");
+ rv = false;
+ }
+ parser->lex->flags.preprocessing = false;
+ parser->lex->flags.mergelines = false;
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after pragma");
+ rv = false;
+ }
+ return rv;
+}
+
+static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
+{
+ bool noref;
+ int cvq = CV_NONE;
+ ast_value *typevar = NULL;
+
+ *out = NULL;
+
+ if (parser->tok == TOKEN_IDENT)
+ typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+
+ if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.')
+ {
+ /* local variable */
+ if (!block) {
+ parseerror(parser, "cannot declare a variable from here");
+ return false;
+ }
+ if (opts.standard == COMPILER_QCC) {
+ if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable"))
+ return false;
+ }
+ if (!parse_variable(parser, block, false, CV_NONE, typevar, false))
+ return false;
+ return true;
+ }
+ else if (parse_var_qualifiers(parser, !!block, &cvq, &noref))
+ {
+ if (cvq == CV_WRONG)
+ return false;
+ return parse_variable(parser, block, true, cvq, NULL, noref);
+ }
+ else if (parser->tok == TOKEN_KEYWORD)
+ {
+ if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype"))
+ {
+ char ty[1024];
+ ast_value *tdef;
+
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after __builtin_debug_printtype");
+ return false;
+ }
+
+ if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0)))
+ {
+ ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty));
+ con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty);
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after __builtin_debug_printtype typename argument");
+ return false;
+ }
+ }
+ else
+ {
+ if (!parse_statement(parser, block, out, allow_cases))
+ return false;
+ if (!*out)
+ con_out("__builtin_debug_printtype: got no output node\n");
+ else
+ {
+ ast_type_to_string(*out, ty, sizeof(ty));
+ con_out("__builtin_debug_printtype: `%s`\n", ty);
+ }
+ }
+ return true;
+ }
+ else if (!strcmp(parser_tokval(parser), "return"))
+ {
+ return parse_return(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "if"))
+ {
+ return parse_if(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "while"))
+ {
+ return parse_while(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "do"))
+ {
+ return parse_dowhile(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "for"))
+ {
+ if (opts.standard == COMPILER_QCC) {
+ if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?"))
+ return false;
+ }
+ return parse_for(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "break"))
+ {
+ return parse_break_continue(parser, block, out, false);
+ }
+ else if (!strcmp(parser_tokval(parser), "continue"))
+ {
+ return parse_break_continue(parser, block, out, true);
+ }
+ else if (!strcmp(parser_tokval(parser), "switch"))
+ {
+ return parse_switch(parser, block, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "case") ||
+ !strcmp(parser_tokval(parser), "default"))
+ {
+ if (!allow_cases) {
+ parseerror(parser, "unexpected 'case' label");
+ return false;
+ }
+ return true;
+ }
+ else if (!strcmp(parser_tokval(parser), "goto"))
+ {
+ return parse_goto(parser, out);
+ }
+ else if (!strcmp(parser_tokval(parser), "typedef"))
+ {
+ if (!parser_next(parser)) {
+ parseerror(parser, "expected type definition after 'typedef'");
+ return false;
+ }
+ return parse_typedef(parser);
+ }
+ parseerror(parser, "Unexpected keyword");
+ return false;
+ }
+ else if (parser->tok == '{')
+ {
+ ast_block *inner;
+ inner = parse_block(parser);
+ if (!inner)
+ return false;
+ *out = (ast_expression*)inner;
+ return true;
+ }
+ else if (parser->tok == ':')
+ {
+ size_t i;
+ ast_label *label;
+ if (!parser_next(parser)) {
+ parseerror(parser, "expected label name");
+ return false;
+ }
+ if (parser->tok != TOKEN_IDENT) {
+ 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);
+ *out = (ast_expression*)label;
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after label");
+ return false;
+ }
+ for (i = 0; i < vec_size(parser->gotos); ++i) {
+ if (!strcmp(parser->gotos[i]->name, label->name)) {
+ ast_goto_set_label(parser->gotos[i], label);
+ vec_remove(parser->gotos, i, 1);
+ --i;
+ }
+ }
+ return true;
+ }
+ else if (parser->tok == ';')
+ {
+ if (!parser_next(parser)) {
+ parseerror(parser, "parse error after empty statement");
+ return false;
+ }
+ return true;
+ }
+ else
+ {
+ ast_expression *exp = parse_expression(parser, false);
+ if (!exp)
+ return false;
+ *out = exp;
+ if (!ast_side_effects(exp)) {
+ if (genwarning(ast_ctx(exp), WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+ return false;
+ }
+ return true;
+ }
+}
+
+static bool parse_block_into(parser_t *parser, ast_block *block)
+{
+ bool retval = true;
+
+ parser_enterblock(parser);
+
+ if (!parser_next(parser)) { /* skip the '{' */
+ parseerror(parser, "expected function body");
+ goto cleanup;
+ }
+
+ while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)