Edit

kc3-lang/angle/src/tests/preprocessor_tests/location_test.cpp

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2015-09-09 15:07:24
    Hash : 247374cb
    Message : Allow limited form of expressions in #line directives Reuse ExpressionParser that's also used for parsing preprocessor conditionals to parse line and file numbers in #line directives. According to recent Khronos discussions, the intent of the spec is that expressions in #line directives should be interpreted similarly to expressions in conditional directives, so reusing ExpressionParser is a natural way to implement this. This enables simple math operators operating on integers. There are a few unclear corner cases, but this approach is enough to support practical use cases and pass the dEQP tests. Valid line directives have one of the following forms: #line line-expression #line line-expression file-expression ExpressionParser is first run to parse the line-expression. In ambiguous cases the ExpressionParser consumes as much of the line as possible to form line-expression. Then, if end-of-line hasn't been reached, file-expression is parsed by running ExpressionParser again. As an example of an ambiguous case: #line 1 + 2 This could alternatively be interpreted to mean line-expression "1" and file-expression "+ 2" where + is the unary + operator, but ANGLE now interprets it to mean line-expression "1 + 2". Because of these ambiguous cases, a bison grammar that would parse multiple expressions on the same line couldn't be easily constructed, so this solution where ExpressionParser is run twice was chosen instead. The problematic corner cases are: - ExpressionParser uses 64-bit integers internally for evaluating the expression's value. It's possible to interpret the ESSL3 spec so that 32-bit integer wraparound behavior would be required also for #line directive expressions. - It's unclear whether the defined operator can be used in #line expressions. In this patch it is disabled. Hoping for further clarification from Khronos. - It's unclear how short-circuiting should affect the parsing of undefined identifiers in #line expressions. Now it's consistent with #if expressions (undefined identifiers are OK if they're short-circuited). dEQP expectations are updated for preprocessor tests, including ones not affected specifically by this change. BUG=angleproject:989 TEST=angle_unittests, dEQP-GLES3.functional.shaders.preprocessor.* (4 start passing), dEQP-GLES2.functional.shaders.preprocessor.* (4 start passing) Change-Id: I55c5bf75857da5de855cc600d3603ee19399f328 Reviewed-on: https://chromium-review.googlesource.com/300964 Reviewed-by: Jamie Madill <jmadill@chromium.org> Tryjob-Request: Olli Etuaho <oetuaho@nvidia.com> Tested-by: Olli Etuaho <oetuaho@nvidia.com>

  • src/tests/preprocessor_tests/location_test.cpp
  • //
    // Copyright (c) 2012 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    #include "PreprocessorTest.h"
    #include "compiler/preprocessor/Token.h"
    
    class LocationTest : public PreprocessorTest
    {
    protected:
        void expectLocation(int count,
                            const char* const string[],
                            const int length[],
                            const pp::SourceLocation& location)
        {
            ASSERT_TRUE(mPreprocessor.init(count, string, length));
    
            pp::Token token;
            mPreprocessor.lex(&token);
            EXPECT_EQ(pp::Token::IDENTIFIER, token.type);
            EXPECT_EQ("foo", token.text);
    
            EXPECT_EQ(location.file, token.location.file);
            EXPECT_EQ(location.line, token.location.line);
        }
    };
    
    TEST_F(LocationTest, String0_Line1)
    {
        const char* str = "foo";
        pp::SourceLocation loc(0, 1);
    
        SCOPED_TRACE("String0_Line1");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, String0_Line2)
    {
        const char* str = "\nfoo";
        pp::SourceLocation loc(0, 2);
    
        SCOPED_TRACE("String0_Line2");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, String1_Line1)
    {
        const char* const str[] = {"\n\n", "foo"};
        pp::SourceLocation loc(1, 1);
    
        SCOPED_TRACE("String1_Line1");
        expectLocation(2, str, NULL, loc);
    }
    
    TEST_F(LocationTest, String1_Line2)
    {
        const char* const str[] = {"\n\n", "\nfoo"};
        pp::SourceLocation loc(1, 2);
    
        SCOPED_TRACE("String1_Line2");
        expectLocation(2, str, NULL, loc);
    }
    
    TEST_F(LocationTest, NewlineInsideCommentCounted)
    {
        const char* str = "/*\n\n*/foo";
        pp::SourceLocation loc(0, 3);
    
        SCOPED_TRACE("NewlineInsideCommentCounted");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, ErrorLocationAfterComment)
    {
        const char* str = "/*\n\n*/@";
    
        ASSERT_TRUE(mPreprocessor.init(1, &str, NULL));
        EXPECT_CALL(mDiagnostics, print(pp::Diagnostics::PP_INVALID_CHARACTER,
                                        pp::SourceLocation(0, 3),
                                        "@"));
    
        pp::Token token;
        mPreprocessor.lex(&token);
    }
    
    // The location of a token straddling two or more strings is that of the
    // first character of the token.
    
    TEST_F(LocationTest, TokenStraddlingTwoStrings)
    {
        const char* const str[] = {"f", "oo"};
        pp::SourceLocation loc(0, 1);
    
        SCOPED_TRACE("TokenStraddlingTwoStrings");
        expectLocation(2, str, NULL, loc);
    }
    
    TEST_F(LocationTest, TokenStraddlingThreeStrings)
    {
        const char* const str[] = {"f", "o", "o"};
        pp::SourceLocation loc(0, 1);
    
        SCOPED_TRACE("TokenStraddlingThreeStrings");
        expectLocation(3, str, NULL, loc);
    }
    
    TEST_F(LocationTest, EndOfFileWithoutNewline)
    {
        const char* const str[] = {"foo"};
        ASSERT_TRUE(mPreprocessor.init(1, str, NULL));
    
        pp::Token token;
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::IDENTIFIER, token.type);
        EXPECT_EQ("foo", token.text);
        EXPECT_EQ(0, token.location.file);
        EXPECT_EQ(1, token.location.line);
    
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::LAST, token.type);
        EXPECT_EQ(0, token.location.file);
        EXPECT_EQ(1, token.location.line);
    }
    
    TEST_F(LocationTest, EndOfFileAfterNewline)
    {
        const char* const str[] = {"foo\n"};
        ASSERT_TRUE(mPreprocessor.init(1, str, NULL));
    
        pp::Token token;
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::IDENTIFIER, token.type);
        EXPECT_EQ("foo", token.text);
        EXPECT_EQ(0, token.location.file);
        EXPECT_EQ(1, token.location.line);
    
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::LAST, token.type);
        EXPECT_EQ(0, token.location.file);
        EXPECT_EQ(2, token.location.line);
    }
    
    TEST_F(LocationTest, EndOfFileAfterEmptyString)
    {
        const char* const str[] = {"foo\n", "\n", ""};
        ASSERT_TRUE(mPreprocessor.init(3, str, NULL));
    
        pp::Token token;
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::IDENTIFIER, token.type);
        EXPECT_EQ("foo", token.text);
        EXPECT_EQ(0, token.location.file);
        EXPECT_EQ(1, token.location.line);
    
        mPreprocessor.lex(&token);
        EXPECT_EQ(pp::Token::LAST, token.type);
        EXPECT_EQ(2, token.location.file);
        EXPECT_EQ(1, token.location.line);
    }
    
    TEST_F(LocationTest, ValidLineDirective1)
    {
        const char* str = "#line 10\n"
                          "foo";
        pp::SourceLocation loc(0, 10);
    
        SCOPED_TRACE("ValidLineDirective1");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, ValidLineDirective2)
    {
        const char* str = "#line 10 20\n"
                          "foo";
        pp::SourceLocation loc(20, 10);
    
        SCOPED_TRACE("ValidLineDirective2");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveCommentsIgnored)
    {
        const char* str = "/* bar */"
                          "#"
                          "/* bar */"
                          "line"
                          "/* bar */"
                          "10"
                          "/* bar */"
                          "20"
                          "/* bar */"
                          "// bar   "
                          "\n"
                          "foo";
        pp::SourceLocation loc(20, 10);
    
        SCOPED_TRACE("LineDirectiveCommentsIgnored");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveWithMacro1)
    {
        const char* str = "#define L 10\n"
                          "#define F(x) x\n"
                          "#line L F(20)\n"
                          "foo";
        pp::SourceLocation loc(20, 10);
    
        SCOPED_TRACE("LineDirectiveWithMacro1");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveWithMacro2)
    {
        const char* str = "#define LOC 10 20\n"
                          "#line LOC\n"
                          "foo";
        pp::SourceLocation loc(20, 10);
    
        SCOPED_TRACE("LineDirectiveWithMacro2");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveWithPredefinedMacro)
    {
        const char* str = "#line __LINE__ __FILE__\n"
                          "foo";
        pp::SourceLocation loc(0, 1);
    
        SCOPED_TRACE("LineDirectiveWithMacro");
        expectLocation(1, &str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveNewlineBeforeStringBreak)
    {
        const char* const str[] = {"#line 10 20\n", "foo"};
        // String number is incremented after it is set by the line directive.
        // Also notice that line number is reset after the string break.
        pp::SourceLocation loc(21, 1);
    
        SCOPED_TRACE("LineDirectiveNewlineBeforeStringBreak");
        expectLocation(2, str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveNewlineAfterStringBreak)
    {
        const char* const str[] = {"#line 10 20", "\nfoo"};
        // String number is incremented before it is set by the line directive.
        pp::SourceLocation loc(20, 10);
    
        SCOPED_TRACE("LineDirectiveNewlineAfterStringBreak");
        expectLocation(2, str, NULL, loc);
    }
    
    TEST_F(LocationTest, LineDirectiveMissingNewline)
    {
        const char* str = "#line 10";
        ASSERT_TRUE(mPreprocessor.init(1, &str, NULL));
    
        using testing::_;
        // Error reported about EOF.
        EXPECT_CALL(mDiagnostics, print(pp::Diagnostics::PP_EOF_IN_DIRECTIVE, _, _));
    
        pp::Token token;
        mPreprocessor.lex(&token);
    }
    
    struct LineTestParam
    {
        const char* str;
        pp::Diagnostics::ID id;
    };
    
    class InvalidLineTest : public LocationTest,
                            public testing::WithParamInterface<LineTestParam>
    {
    };
    
    TEST_P(InvalidLineTest, Identified)
    {
        LineTestParam param = GetParam();
        ASSERT_TRUE(mPreprocessor.init(1, &param.str, NULL));
    
        using testing::_;
        // Invalid line directive call.
        EXPECT_CALL(mDiagnostics, print(param.id, pp::SourceLocation(0, 1), _));
    
        pp::Token token;
        mPreprocessor.lex(&token);
    }
    
    static const LineTestParam kParams[] = {
        {"#line\n", pp::Diagnostics::PP_INVALID_LINE_DIRECTIVE},
        {"#line foo\n", pp::Diagnostics::PP_INVALID_LINE_NUMBER},
        {"#line defined(foo)\n", pp::Diagnostics::PP_INVALID_LINE_NUMBER},
        {"#line 10 foo\n", pp::Diagnostics::PP_INVALID_FILE_NUMBER},
        {"#line 10 20 foo\n", pp::Diagnostics::PP_UNEXPECTED_TOKEN},
        {"#line 0xffffffff\n", pp::Diagnostics::PP_INTEGER_OVERFLOW},
        {"#line 10 0xffffffff\n", pp::Diagnostics::PP_INTEGER_OVERFLOW}};
    
    INSTANTIATE_TEST_CASE_P(All, InvalidLineTest, testing::ValuesIn(kParams));
    
    struct LineExpressionTestParam
    {
        const char *expression;
        int expectedLine;
    };
    
    class LineExpressionTest : public LocationTest,
                               public testing::WithParamInterface<LineExpressionTestParam>
    {
    };
    
    TEST_P(LineExpressionTest, ExpressionEvaluation)
    {
        LineExpressionTestParam param = GetParam();
        const char *strs[3] = {"#line ", param.expression, "\nfoo"};
    
        pp::SourceLocation loc(2, param.expectedLine);
    
        expectLocation(3, strs, NULL, loc);
    }
    
    static const LineExpressionTestParam kParamsLineExpressionTest[] = {
        {"1 + 2", 3},
        {"5 - 3", 2},
        {"7 * 11", 77},
        {"20 / 10", 2},
        {"10 % 5", 0},
        {"7 && 3", 1},
        {"7 || 0", 1},
        {"11 == 11", 1},
        {"11 != 11", 0},
        {"11 > 7", 1},
        {"11 < 7", 0},
        {"11 >= 7", 1},
        {"11 <= 7", 0},
        {"!11", 0},
        {"-1", -1},
        {"+9", 9},
        {"(1 + 2) * 4", 12},
        {"3 | 5", 7},
        {"3 ^ 5", 6},
        {"3 & 5", 1},
        {"~5", ~5},
        {"2 << 3", 16},
        {"16 >> 2", 4}};
    
    INSTANTIATE_TEST_CASE_P(All, LineExpressionTest, testing::ValuesIn(kParamsLineExpressionTest));