+ (void)block; /* not touching */
+
+ if (!parser_next(parser) || parser->tok != ';') {
+ parseerror(parser, "expected semicolon");
+ return false;
+ }
+
+ if (!parser_next(parser))
+ parseerror(parser, "parse error");
+
+ *out = (ast_expression*)ast_breakcont_new(ctx, is_continue);
+ return true;
+}
+
+/* returns true when it was a variable qualifier, false otherwise!
+ * on error, cvq is set to CV_WRONG
+ */
+static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *noreturn, bool *is_static)
+{
+ bool had_const = false;
+ bool had_var = false;
+ bool had_noref = false;
+ bool had_noreturn = false;
+ bool had_attrib = false;
+ bool had_static = false;
+
+ *cvq = CV_NONE;
+ for (;;) {
+ if (parser->tok == TOKEN_ATTRIBUTE_OPEN) {
+ had_attrib = true;
+ /* parse an attribute */
+ if (!parser_next(parser)) {
+ parseerror(parser, "expected attribute after `[[`");
+ *cvq = CV_WRONG;
+ return false;
+ }
+ if (!strcmp(parser_tokval(parser), "noreturn")) {
+ had_noreturn = true;
+ if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+ parseerror(parser, "`noreturn` attribute has no parameters, expected `]]`");
+ *cvq = CV_WRONG;
+ return false;
+ }
+ }
+ else if (!strcmp(parser_tokval(parser), "noref")) {
+ had_noref = true;
+ if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+ parseerror(parser, "`noref` attribute has no parameters, expected `]]`");
+ *cvq = CV_WRONG;
+ return false;
+ }
+ }
+ else
+ {
+ /* Skip tokens until we hit a ]] */
+ (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser));
+ while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+ if (!parser_next(parser)) {
+ parseerror(parser, "error inside attribute");
+ *cvq = CV_WRONG;
+ return false;
+ }
+ }
+ }
+ }
+ else if (!strcmp(parser_tokval(parser), "static"))
+ had_static = true;
+ else if (!strcmp(parser_tokval(parser), "const"))
+ had_const = true;
+ else if (!strcmp(parser_tokval(parser), "var"))
+ had_var = true;
+ else if (with_local && !strcmp(parser_tokval(parser), "local"))
+ had_var = true;
+ else if (!strcmp(parser_tokval(parser), "noref"))
+ had_noref = true;
+ else if (!had_const && !had_var && !had_noref && !had_noreturn && !had_attrib && !had_static) {
+ return false;
+ }
+ else
+ break;
+ if (!parser_next(parser))
+ goto onerr;
+ }
+ if (had_const)
+ *cvq = CV_CONST;
+ else if (had_var)
+ *cvq = CV_VAR;
+ else
+ *cvq = CV_NONE;
+ *noref = had_noref;
+ *noreturn = had_noreturn;
+ *is_static = had_static;
+ return true;
+onerr:
+ parseerror(parser, "parse error after variable qualifier");
+ *cvq = CV_WRONG;
+ return true;
+}
+
+static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out)
+{
+ ast_expression *operand;
+ ast_value *opval;
+ ast_value *typevar;
+ ast_switch *switchnode;
+ ast_switch_case swcase;
+
+ int cvq;
+ bool noref, noreturn, is_static;
+
+ lex_ctx ctx = parser_ctx(parser);
+
+ (void)block; /* not touching */
+ (void)opval;
+
+ /* parse over the opening paren */
+ if (!parser_next(parser) || parser->tok != '(') {
+ parseerror(parser, "expected switch operand in parenthesis");
+ return false;
+ }
+
+ /* parse into the expression */
+ if (!parser_next(parser)) {
+ parseerror(parser, "expected switch operand");
+ return false;
+ }
+ /* parse the operand */
+ operand = parse_expression_leave(parser, false);
+ if (!operand)
+ return false;
+
+ switchnode = ast_switch_new(ctx, operand);
+
+ /* closing paren */
+ if (parser->tok != ')') {
+ ast_delete(switchnode);
+ parseerror(parser, "expected closing paren after 'switch' operand");
+ return false;
+ }
+
+ /* parse over the opening paren */
+ if (!parser_next(parser) || parser->tok != '{') {
+ ast_delete(switchnode);
+ parseerror(parser, "expected list of cases");
+ return false;
+ }
+
+ if (!parser_next(parser)) {
+ ast_delete(switchnode);
+ parseerror(parser, "expected 'case' or 'default'");
+ return false;
+ }
+
+ /* new block; allow some variables to be declared here */
+ parser_enterblock(parser);
+ while (true) {
+ typevar = NULL;
+ if (parser->tok == TOKEN_IDENT)
+ typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+ if (typevar || parser->tok == TOKEN_TYPENAME) {
+ if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, false)) {
+ ast_delete(switchnode);
+ return false;
+ }
+ continue;
+ }
+ if (parse_var_qualifiers(parser, true, &cvq, &noref, &noreturn, &is_static))
+ {
+ if (cvq == CV_WRONG) {
+ ast_delete(switchnode);
+ return false;
+ }
+ if (!parse_variable(parser, block, false, cvq, NULL, noref, noreturn, is_static)) {
+ ast_delete(switchnode);
+ return false;
+ }
+ continue;
+ }
+ break;
+ }
+
+ /* case list! */
+ while (parser->tok != '}') {
+ ast_block *caseblock;
+
+ if (parser->tok != TOKEN_KEYWORD) {
+ ast_delete(switchnode);
+ parseerror(parser, "expected 'case' or 'default'");
+ return false;
+ }
+ if (!strcmp(parser_tokval(parser), "case")) {
+ if (!parser_next(parser)) {
+ ast_delete(switchnode);
+ parseerror(parser, "expected expression for case");
+ return false;
+ }
+ swcase.value = parse_expression_leave(parser, false);
+ if (!swcase.value) {
+ ast_delete(switchnode);
+ parseerror(parser, "expected expression for case");
+ return false;
+ }
+ if (!OPTS_FLAG(RELAXED_SWITCH)) {
+ if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */
+ parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
+ ast_unref(operand);
+ return false;
+ }
+ }
+ }
+ else if (!strcmp(parser_tokval(parser), "default")) {
+ swcase.value = NULL;
+ if (!parser_next(parser)) {
+ ast_delete(switchnode);
+ parseerror(parser, "expected colon");
+ return false;
+ }
+ }
+
+ /* Now the colon and body */
+ if (parser->tok != ':') {
+ if (swcase.value) ast_unref(swcase.value);
+ ast_delete(switchnode);
+ parseerror(parser, "expected colon");
+ return false;
+ }
+
+ if (!parser_next(parser)) {
+ if (swcase.value) ast_unref(swcase.value);
+ ast_delete(switchnode);
+ parseerror(parser, "expected statements or case");
+ return false;
+ }
+ caseblock = ast_block_new(parser_ctx(parser));
+ if (!caseblock) {
+ if (swcase.value) ast_unref(swcase.value);
+ ast_delete(switchnode);
+ return false;
+ }
+ swcase.code = (ast_expression*)caseblock;
+ vec_push(switchnode->cases, swcase);
+ while (true) {
+ ast_expression *expr;
+ if (parser->tok == '}')
+ break;
+ if (parser->tok == TOKEN_KEYWORD) {
+ if (!strcmp(parser_tokval(parser), "case") ||
+ !strcmp(parser_tokval(parser), "default"))
+ {
+ break;
+ }
+ }
+ if (!parse_statement(parser, caseblock, &expr, true)) {
+ ast_delete(switchnode);
+ return false;
+ }
+ if (!expr)
+ continue;
+ if (!ast_block_add_expr(caseblock, expr)) {
+ ast_delete(switchnode);
+ return false;
+ }
+ }
+ }
+
+ parser_leaveblock(parser);
+
+ /* closing paren */
+ if (parser->tok != '}') {
+ ast_delete(switchnode);
+ parseerror(parser, "expected closing paren of case list");
+ return false;
+ }
+ if (!parser_next(parser)) {
+ ast_delete(switchnode);
+ parseerror(parser, "parse error after switch");
+ return false;
+ }
+ *out = (ast_expression*)switchnode;
+ return true;
+}
+
+static bool parse_goto(parser_t *parser, ast_expression **out)
+{
+ size_t i;
+ ast_goto *gt;
+
+ if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+ parseerror(parser, "expected label name after `goto`");
+ return false;
+ }
+
+ 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, noreturn, is_static;
+ 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, false, false))
+ return false;
+ return true;
+ }
+ else if (parse_var_qualifiers(parser, !!block, &cvq, &noref, &noreturn, &is_static))
+ {
+ if (cvq == CV_WRONG)
+ return false;
+ return parse_variable(parser, block, true, cvq, NULL, noref, noreturn, is_static);
+ }
+ 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);