blob: d4638ecfe254d10d1a6921cfd28adbbbdea37897 [file] [log] [blame]
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package preprocessor
import (
"android.googlesource.com/platform/tools/gpu/gfxapi/gles/glsl/ast"
"regexp"
"testing"
)
var (
A = Identifier("A")
B = Identifier("B")
C = Identifier("C")
a = Identifier("a")
b = Identifier("b")
c = Identifier("c")
)
type tokenOnlyPrinter []TokenInfo
func (top tokenOnlyPrinter) String() string {
ret := "["
for i, t := range top {
if i > 0 {
ret += " "
}
ret += t.Token.String()
}
return ret + "]"
}
var preprocessorTests = []struct {
Input string
ExpectedTokens []Token
ExpectedErrors []string
}{
{"const", []Token{KwConst}, nil},
{"A", []Token{A}, nil},
{"47", []Token{ast.IntValue(47)}, nil},
{"47u", []Token{ast.UintValue(47)}, nil},
{"47.5", []Token{ast.FloatValue(47.5)}, nil},
{"47.5e1", []Token{ast.FloatValue(475)}, nil},
{"47.5e+1", []Token{ast.FloatValue(475)}, nil},
{".75", []Token{ast.FloatValue(0.75)}, nil},
{"4.75", []Token{ast.FloatValue(4.75)}, nil},
{"<<=", []Token{ast.BoShlAssign}, nil},
{"<=", []Token{ast.BoLessEq}, nil},
{"<<", []Token{ast.BoShl}, nil},
{"<", []Token{ast.BoLess}, nil},
{"=", []Token{ast.BoAssign}, nil},
{"'", nil, []string{"Unknown token"}},
{"true; 1u;",
[]Token{KwTrue, OpSemicolon, ast.UintValue(1), OpSemicolon}, nil},
{"\n\nvoid\r\rA (\t\t)\v\v;\n\r\v",
[]Token{ast.TVoid, A, OpLParen, OpRParen, OpSemicolon}, nil},
{` void // int
A /* [
*///]
(/**/)
;// `, []Token{ast.TVoid, A, OpLParen, OpRParen, OpSemicolon}, nil},
{"/*...", nil, []string{"Unterminated block comment"}},
{"/*...*", nil, []string{"Unterminated block comment"}},
{`#define A B
A`, []Token{B}, nil},
{`#define int void
int`, []Token{ast.TVoid}, nil},
{`#define
a`, []Token{a}, []string{"#define needs an argument"}},
{`#define A a b c
#define b c a
A`, []Token{a, c, a, c}, nil},
{`#define B A B C
B`, []Token{A, B, C}, nil},
{`#define A a B
#define B b A
A B`, []Token{a, b, A, b, a, B}, nil},
{`#define A
#ifdef A
a
#else
b
#endif`, []Token{a}, nil},
{`#define A
#ifndef A
a
#else
b
#endif`, []Token{b}, nil},
{`#define A
#ifdef B
a
#else
b
#endif`, []Token{b}, nil},
{"#ifdef AAA", nil, []string{"Unterminated #if"}},
{`#ifdef A
#else
#else
#endif`, nil, []string{"multiple #else"}},
{"#else", nil, []string{"Unmatched #else"}},
{"#endif", nil, []string{"Unmatched #endif"}},
{"#ifdef\n#endif", nil, []string{"#ifdef needs an argument"}},
{"#define A B\n#define A C", nil, []string{"'A' already defined"}},
{"#undef A", nil, []string{"'A' not defined"}},
{"#error Hello world", nil, []string{"Hello world"}},
{"#define A B\n#undef A\nA", []Token{A}, nil},
{`#define A(x) b
A(a)`, []Token{b}, nil},
{`#define A(x) x
A(a)`, []Token{a}, nil},
{`#define A(x) a x c
A(b)`, []Token{a, b, c}, nil},
{`#define A(x) a x
#define B(x, y) A(x y)
B(b, c)`, []Token{a, b, c}, nil},
{`#define A(x) a x
#define B(x) x(b)
B(A)`, []Token{a, b}, nil},
{`#define A(x) a B(x)
#define B(x) A(x) b
B(c)
A(c)`, []Token{a, B, OpLParen, c, OpRParen, b, a, A, OpLParen, c, OpRParen, b}, nil},
{`#define A(x) x b
#define B(x) a A(x
B(c))`, []Token{a, c, b}, nil},
{`#define C(x, y) x y
C(a)`, []Token{a}, []string{"Incorrect number of arguments to macro 'C': expected 2, got 1."}},
{`#define A(x) x x
A(a
#undef A
#define A b
A)`, []Token{a, b, a, b}, nil},
{`#define A(x) x
A(a`, []Token{A}, []string{"Unexpected end of file while processing a macro."}},
{`#define A(x, y) x y
A((a,(b)),c)`, []Token{OpLParen, a, ast.BoComma, OpLParen, b, OpRParen, OpRParen, c}, nil},
{`#define A a B
#define B b A
#define C(x) c x
C(A)`, []Token{c, a, b, A}, nil},
{"#define f(x, x) x", nil, []string{"Macro 'f' contains two arguments named 'x'."}},
{`#define A(x) x
#define B(x) A(C)(x)
#define C(x) A(x)
B(a)`, []Token{a}, nil},
{`#define A (a) a
A(b)`, []Token{OpLParen, a, OpRParen, a, OpLParen, b, OpRParen}, nil},
{"+\\\n+", []Token{ast.UoPreinc}, nil},
{`#define A(int) int
A(b)`, []Token{b}, nil},
{"#define A(", nil, []string{"Macro definition ended unexpectedly."}},
{"#define A(*,b)", nil, []string{"Expected an identifier, got '\\*'."}},
{"#define A(a*b)", nil, []string{"Expected ',', '\\)', got '\\*'."}},
{`#ifdef A
#undef Ignored
#endif`, nil, nil},
{`#ifdef A
#error Ignored
#endif`, nil, nil},
{`#define A B(a
#define B(x) b
#define C(x) c x
C(A)`, []Token{c, B}, []string{"Unexpected end of file while processing a macro."}},
{`#define A(x) a A(x
A(b))`, []Token{a, A, OpLParen, b, OpRParen}, nil},
{`#define A1(x) A2(x
#define A2(x) A3(x
#define A3(x) x
#define B(x) B(A1(x))
B(a))`, []Token{B, OpLParen, a}, nil},
{`__LINE__ a __LINE__
b __LINE__ b
__LINE__`, []Token{ast.IntValue(1), a, ast.IntValue(1), b, ast.IntValue(2), b, ast.IntValue(3)}, nil},
{`__FILE__`, []Token{ast.IntValue(0)}, nil},
{`__VERSION__`, []Token{ast.IntValue(300)}, nil},
{`GL_ES`, []Token{ast.IntValue(1)}, nil},
// #if tests... see note on fakeExpressionEvaluator.
{`#if 1
A
#else
B
#endif`, []Token{A}, nil},
{`#if 1 2
A
#else
B
#endif`, []Token{B}, nil},
{`#if 1
A
#elif 1
B
#endif`, []Token{A}, nil},
{`#if 1 2
A
#elif 1
B
#endif`, []Token{B}, nil},
{`#if 1 2
A
#elif 1 2
B
#else
C
#endif`, []Token{C}, nil},
{`#if 1 2
A
#else
C
#elif 1 2
B
#endif`, []Token{C}, []string{"#elif after #else"}},
{`#elif 1`, nil, []string{"Unmatched #elif"}},
{`#define A 1 2
#if A
a
#else
b
#endif`, []Token{b}, nil},
{`#define A
#if defined(A)
a
#else
b
#endif`, []Token{a}, nil},
{`#define A
#if defined A
a
#else
b
#endif`, []Token{a}, nil},
{`#if defined A
a
#else
b
#endif`, []Token{a}, nil},
{`#if defined
a
#endif`, []Token{}, []string{"Operator 'defined' used incorrectly."}},
}
// We cannot test #if expressions here, as the expression evaluator is located in the ast package
// (which depends on this package) and this would create a circular dependency. Instead, we
// provide a fake evaluator, which returns the token count (modulo 2) as its result and allows us
// to test the #if logic. Tests for the correct expression evaluation are in the ast package.
func fakeExpressionEvaluator(tp *Preprocessor) (ast.IntValue, []error) {
count := 0
for tp.Next().Token != nil {
count++
}
return ast.IntValue(count % 2), tp.GetErrors()
}
func TestPreprocessor(t *testing.T) {
for _, test := range preprocessorTests {
bad := false
gotTokens, gotErrors := Preprocess(test.Input, fakeExpressionEvaluator, 0)
for i := 0; i < len(test.ExpectedTokens) && i < len(gotTokens); i++ {
if test.ExpectedTokens[i].String() != gotTokens[i].Token.String() {
t.Errorf("Token mismatch at position %d. "+
"Expected '%v', got '%v'.",
i, test.ExpectedTokens[i], gotTokens[i].Token)
bad = true
break
}
}
if len(test.ExpectedTokens) > len(gotTokens) {
t.Errorf("Missing tokens from position %d. Expected '%[2]v' (%[2]T).",
len(gotTokens), test.ExpectedTokens[len(gotTokens)])
bad = true
}
if len(test.ExpectedTokens) < len(gotTokens) {
t.Errorf("Superfluous tokens from position %d. Got '%[2]v' (%[2]T).",
len(test.ExpectedTokens), gotTokens[len(test.ExpectedTokens)].Token)
bad = true
}
for i := 0; i < len(test.ExpectedErrors) && i < len(gotErrors); i++ {
matched, err := regexp.MatchString(test.ExpectedErrors[i], gotErrors[i].Error())
if err != nil {
t.Errorf("Error matching regexp failed: %s", err)
bad = true
}
if !matched {
t.Errorf("Error mismatch at position %d. Expected '%[2]v', got '%[3]v'.",
i, test.ExpectedErrors[i], gotErrors[i])
bad = true
break
}
}
if len(test.ExpectedErrors) > len(gotErrors) {
t.Errorf("Missing errors from position %d. Expected '%[2]v'.",
len(gotErrors), test.ExpectedErrors[len(gotErrors)])
bad = true
}
if len(test.ExpectedErrors) < len(gotErrors) {
t.Errorf("Superfluous errors from position %d. Got '%[2]v'.",
len(test.ExpectedErrors), gotErrors[len(test.ExpectedErrors)])
bad = true
}
if bad {
t.Logf("Program: %s", test.Input)
t.Logf("Expected Tokens: %v", test.ExpectedTokens)
t.Logf("Obtained Tokens: %v", tokenOnlyPrinter(gotTokens))
t.Logf("Expected Errors: %v", test.ExpectedErrors)
t.Logf("Obtained Errors: %v", gotErrors)
}
}
}
func TestPeek(t *testing.T) {
pp := PreprocessStream("int a;", nil, 0)
t.Logf("Lookahead: %v %v %v %v\n", pp.PeekN(0).Token, pp.PeekN(1).Token, pp.PeekN(2).Token,
pp.PeekN(3).Token)
if pp.PeekN(0).Token != ast.TInt {
t.Errorf("First lookahead should be: %v", ast.TInt)
}
if pp.PeekN(1).Token != a {
t.Errorf("Second lookahead should be: %v", a)
}
if pp.PeekN(2).Token != OpSemicolon {
t.Errorf("Third lookahead should be: %v", OpSemicolon)
}
if pp.PeekN(3).Token != nil {
t.Errorf("Fourth lookahead should be: %v", nil)
}
}