]> git.xonotic.org Git - xonotic/gmqcc.git/blobdiff - parser.c
Implemented multi-line comparitive tests, fixed output of stdout/stderr pair logs...
[xonotic/gmqcc.git] / parser.c
index a292c9083afd311f335849065206a20a859599f4..99b7eb7bace4f651eb2fbf37d221cd18a14444cb 100644 (file)
--- 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;
 
@@ -48,6 +51,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
 static ast_block* parse_block(parser_t *parser, bool warnreturn);
 static bool parse_block_into(parser_t *parser, ast_block *block, bool warnreturn);
 static ast_expression* parse_statement_or_block(parser_t *parser);
+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);
 static ast_expression* parse_expression(parser_t *parser, bool stopatcomma);
 
@@ -1328,7 +1332,10 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
         if (!parser_next(parser)) {
             goto onerr;
         }
-        if (parser->tok == ';' || (!parens && parser->tok == ']')) {
+        if (parser->tok == ';' ||
+            (!parens && parser->tok == ']') ||
+            parser->tok == ':')
+        {
             break;
         }
     }
@@ -1675,7 +1682,208 @@ onerr:
     return false;
 }
 
-static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out)
+static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_expression *exp = NULL;
+    ast_return     *ret = NULL;
+    ast_value      *expected = parser->function->vtype;
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected return expression");
+        return false;
+    }
+
+    if (parser->tok != ';') {
+        exp = parse_expression(parser, false);
+        if (!exp)
+            return false;
+
+        if (exp->expression.vtype != expected->expression.next->expression.vtype) {
+            parseerror(parser, "return with invalid expression");
+        }
+
+        ret = ast_return_new(exp->expression.node.context, exp);
+        if (!ret) {
+            ast_delete(exp);
+            return false;
+        }
+    } else {
+        if (!parser_next(parser))
+            parseerror(parser, "parse error");
+        if (expected->expression.next->expression.vtype != TYPE_VOID) {
+            if (opts_standard != COMPILER_GMQCC)
+                (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
+            else
+                parseerror(parser, "return without value");
+        }
+        ret = ast_return_new(parser_ctx(parser), NULL);
+    }
+    *out = (ast_expression*)ret;
+    return true;
+}
+
+static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
+{
+    lex_ctx ctx = parser_ctx(parser);
+
+    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;
+}
+
+static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_expression *operand;
+    ast_value      *opval;
+    ast_switch     *switchnode;
+    ast_switch_case swcase;
+
+    lex_ctx ctx = parser_ctx(parser);
+
+    /* 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;
+
+    if (!OPTS_FLAG(RELAXED_SWITCH)) {
+        opval = (ast_value*)operand;
+        if (!ast_istype(operand, ast_value) || !opval->isconst) {
+            parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
+            ast_unref(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;
+    }
+
+    /* case list! */
+    while (parser->tok != '}') {
+        ast_block *block;
+
+        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;
+            }
+        }
+        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;
+        }
+        block = ast_block_new(parser_ctx(parser));
+        if (!block) {
+            if (swcase.value) ast_unref(swcase.value);
+            ast_delete(switchnode);
+            return false;
+        }
+        swcase.code = (ast_expression*)block;
+        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, block, &expr, true)) {
+                ast_delete(switchnode);
+                return false;
+            }
+            if (!expr)
+                continue;
+            vec_push(block->exprs, expr);
+        }
+    }
+
+    /* 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_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
 {
     if (parser->tok == TOKEN_TYPENAME || parser->tok == '.')
     {
@@ -1712,42 +1920,7 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
         }
         else if (!strcmp(parser_tokval(parser), "return"))
         {
-            ast_expression *exp = NULL;
-            ast_return     *ret = NULL;
-            ast_value      *expected = parser->function->vtype;
-
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected return expression");
-                return false;
-            }
-
-            if (parser->tok != ';') {
-                exp = parse_expression(parser, false);
-                if (!exp)
-                    return false;
-
-                if (exp->expression.vtype != expected->expression.next->expression.vtype) {
-                    parseerror(parser, "return with invalid expression");
-                }
-
-                ret = ast_return_new(exp->expression.node.context, exp);
-                if (!ret) {
-                    ast_delete(exp);
-                    return false;
-                }
-            } else {
-                if (!parser_next(parser))
-                    parseerror(parser, "parse error");
-                if (expected->expression.next->expression.vtype != TYPE_VOID) {
-                    if (opts_standard != COMPILER_GMQCC)
-                        (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
-                    else
-                        parseerror(parser, "return without value");
-                }
-                ret = ast_return_new(parser_ctx(parser), NULL);
-            }
-            *out = (ast_expression*)ret;
-            return true;
+            return parse_return(parser, block, out);
         }
         else if (!strcmp(parser_tokval(parser), "if"))
         {
@@ -1769,6 +1942,27 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
             }
             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;
+        }
         parseerror(parser, "Unexpected keyword");
         return false;
     }
@@ -1804,9 +1998,11 @@ static bool GMQCC_WARN parser_pop_local(parser_t *parser)
     varentry_t *ve;
 
     ve = &vec_last(parser->locals);
-    if (ast_istype(ve->var, ast_value) && !(((ast_value*)(ve->var))->uses)) {
-        if (parsewarning(parser, WARN_UNUSED_VARIABLE, "unused variable: `%s`", ve->name))
-            rv = false;
+    if (!parser->errors) {
+        if (ast_istype(ve->var, ast_value) && !(((ast_value*)(ve->var))->uses)) {
+            if (parsewarning(parser, WARN_UNUSED_VARIABLE, "unused variable: `%s`", ve->name))
+                rv = false;
+        }
     }
     mem_d(ve->name);
     vec_pop(parser->locals);
@@ -1832,7 +2028,7 @@ static bool parse_block_into(parser_t *parser, ast_block *block, bool warnreturn
         if (parser->tok == '}')
             break;
 
-        if (!parse_statement(parser, block, &expr)) {
+        if (!parse_statement(parser, block, &expr, false)) {
             /* parseerror(parser, "parse error"); */
             block = NULL;
             goto cleanup;
@@ -1884,7 +2080,7 @@ static ast_expression* parse_statement_or_block(parser_t *parser)
     ast_expression *expr = NULL;
     if (parser->tok == '{')
         return (ast_expression*)parse_block(parser, false);
-    if (!parse_statement(parser, NULL, &expr))
+    if (!parse_statement(parser, NULL, &expr, false))
         return NULL;
     return expr;
 }
@@ -2396,6 +2592,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 +2607,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 +2641,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), "<void>", 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 +2658,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 +2673,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 +2689,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), "<void>", 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 +2707,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 +2723,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 +2740,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 +2754,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 +2830,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)) {
@@ -3437,6 +3618,16 @@ bool parser_compile_file(const char *filename)
     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();
+}
+
 bool parser_compile_string(const char *name, const char *str)
 {
     parser->lex = lex_open_string(str, strlen(str), name);
@@ -3450,6 +3641,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 +3666,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);