root/tools/gn/parser_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetTokens
  2. DoParserPrintTest
  3. DoExpressionPrintTest
  4. DoParserErrorTest
  5. DoExpressionErrorTest
  6. TEST
  7. TEST
  8. TEST
  9. TEST
  10. TEST
  11. TEST
  12. TEST
  13. TEST
  14. TEST
  15. TEST
  16. TEST
  17. TEST
  18. TEST
  19. TEST
  20. TEST
  21. TEST
  22. TEST
  23. TEST
  24. TEST
  25. TEST
  26. TEST
  27. TEST
  28. TEST
  29. TEST
  30. TEST
  31. TEST

// Copyright (c) 2013 The Chromium 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 <iostream>
#include <sstream>

#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/input_file.h"
#include "tools/gn/parser.h"
#include "tools/gn/tokenizer.h"

namespace {

bool GetTokens(const InputFile* input, std::vector<Token>* result) {
  result->clear();
  Err err;
  *result = Tokenizer::Tokenize(input, &err);
  return !err.has_error();
}

void DoParserPrintTest(const char* input, const char* expected) {
  std::vector<Token> tokens;
  InputFile input_file(SourceFile("/test"));
  input_file.SetContents(input);
  ASSERT_TRUE(GetTokens(&input_file, &tokens));

  Err err;
  scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
  if (!result)
    err.PrintToStdout();
  ASSERT_TRUE(result);

  std::ostringstream collector;
  result->Print(collector, 0);

  EXPECT_EQ(expected, collector.str());
}

void DoExpressionPrintTest(const char* input, const char* expected) {
  std::vector<Token> tokens;
  InputFile input_file(SourceFile("/test"));
  input_file.SetContents(input);
  ASSERT_TRUE(GetTokens(&input_file, &tokens));

  Err err;
  scoped_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
  ASSERT_TRUE(result);

  std::ostringstream collector;
  result->Print(collector, 0);

  EXPECT_EQ(expected, collector.str());
}

// Expects the tokenizer or parser to identify an error at the given line and
// character.
void DoParserErrorTest(const char* input, int err_line, int err_char) {
  InputFile input_file(SourceFile("/test"));
  input_file.SetContents(input);

  Err err;
  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
  if (!err.has_error()) {
    scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
    ASSERT_FALSE(result);
    ASSERT_TRUE(err.has_error());
  }

  EXPECT_EQ(err_line, err.location().line_number());
  EXPECT_EQ(err_char, err.location().char_offset());
}

// Expects the tokenizer or parser to identify an error at the given line and
// character.
void DoExpressionErrorTest(const char* input, int err_line, int err_char) {
  InputFile input_file(SourceFile("/test"));
  input_file.SetContents(input);

  Err err;
  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
  if (!err.has_error()) {
    scoped_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
    ASSERT_FALSE(result);
    ASSERT_TRUE(err.has_error());
  }

  EXPECT_EQ(err_line, err.location().line_number());
  EXPECT_EQ(err_char, err.location().char_offset());
}

}  // namespace

TEST(Parser, Literal) {
  DoExpressionPrintTest("5", "LITERAL(5)\n");
  DoExpressionPrintTest("\"stuff\"", "LITERAL(\"stuff\")\n");
}

TEST(Parser, BinaryOp) {
  // TODO(scottmg): The tokenizer is dumb, and treats "5-1" as two integers,
  // not a binary operator between two positive integers.
  DoExpressionPrintTest("5 - 1",
      "BINARY(-)\n"
      " LITERAL(5)\n"
      " LITERAL(1)\n");
  DoExpressionPrintTest("5+1",
      "BINARY(+)\n"
      " LITERAL(5)\n"
      " LITERAL(1)\n");
  DoExpressionPrintTest("5 - 1 - 2",
      "BINARY(-)\n"
      " BINARY(-)\n"
      "  LITERAL(5)\n"
      "  LITERAL(1)\n"
      " LITERAL(2)\n");
}

TEST(Parser, FunctionCall) {
  DoExpressionPrintTest("foo()",
      "FUNCTION(foo)\n"
      " LIST\n");
  DoExpressionPrintTest("blah(1, 2)",
      "FUNCTION(blah)\n"
      " LIST\n"
      "  LITERAL(1)\n"
      "  LITERAL(2)\n");
  DoExpressionErrorTest("foo(1, 2,)", 1, 10);
}

TEST(Parser, ParenExpression) {
  const char* input = "(foo(1)) + (a + (b - c) + d)";
  const char* expected =
      "BINARY(+)\n"
      " FUNCTION(foo)\n"
      "  LIST\n"
      "   LITERAL(1)\n"
      " BINARY(+)\n"
      "  BINARY(+)\n"
      "   IDENTIFIER(a)\n"
      "   BINARY(-)\n"
      "    IDENTIFIER(b)\n"
      "    IDENTIFIER(c)\n"
      "  IDENTIFIER(d)\n";
  DoExpressionPrintTest(input, expected);
  DoExpressionErrorTest("(a +", 1, 4);
}

TEST(Parser, OrderOfOperationsLeftAssociative) {
  const char* input = "5 - 1 - 2\n";
  const char* expected =
      "BINARY(-)\n"
      " BINARY(-)\n"
      "  LITERAL(5)\n"
      "  LITERAL(1)\n"
      " LITERAL(2)\n";
  DoExpressionPrintTest(input, expected);
}

TEST(Parser, OrderOfOperationsEqualityBoolean) {
  const char* input =
      "if (a == \"b\" && is_stuff) {\n"
      "  print(\"hai\")\n"
      "}\n";
  const char* expected =
      "BLOCK\n"
      " CONDITION\n"
      "  BINARY(&&)\n"
      "   BINARY(==)\n"
      "    IDENTIFIER(a)\n"
      "    LITERAL(\"b\")\n"
      "   IDENTIFIER(is_stuff)\n"
      "  BLOCK\n"
      "   FUNCTION(print)\n"
      "    LIST\n"
      "     LITERAL(\"hai\")\n";
  DoParserPrintTest(input, expected);
}

TEST(Parser, UnaryOp) {
  DoExpressionPrintTest("!foo",
      "UNARY(!)\n"
      " IDENTIFIER(foo)\n");
}

TEST(Parser, List) {
  DoExpressionPrintTest("[]", "LIST\n");
  DoExpressionPrintTest("[1,asd,]",
      "LIST\n"
      " LITERAL(1)\n"
      " IDENTIFIER(asd)\n");
  DoExpressionPrintTest("[1, 2+3 - foo]",
      "LIST\n"
      " LITERAL(1)\n"
      " BINARY(-)\n"
      "  BINARY(+)\n"
      "   LITERAL(2)\n"
      "   LITERAL(3)\n"
      "  IDENTIFIER(foo)\n");
  DoExpressionPrintTest("[1,\n2,\n 3,\n  4]",
      "LIST\n"
      " LITERAL(1)\n"
      " LITERAL(2)\n"
      " LITERAL(3)\n"
      " LITERAL(4)\n");

  DoExpressionErrorTest("[a, 2+,]", 1, 6);
  DoExpressionErrorTest("[,]", 1, 2);
  DoExpressionErrorTest("[a,,]", 1, 4);
}

TEST(Parser, Assignment) {
  DoParserPrintTest("a=2",
                    "BLOCK\n"
                    " BINARY(=)\n"
                    "  IDENTIFIER(a)\n"
                    "  LITERAL(2)\n");
}

TEST(Parser, Accessor) {
  // Accessor indexing.
  DoParserPrintTest("a=b[c+2]",
                    "BLOCK\n"
                    " BINARY(=)\n"
                    "  IDENTIFIER(a)\n"
                    "  ACCESSOR\n"
                    "   b\n"  // AccessorNode is a bit weird in that it holds
                              // a Token, not a ParseNode for the base.
                    "   BINARY(+)\n"
                    "    IDENTIFIER(c)\n"
                    "    LITERAL(2)\n");
  DoParserErrorTest("a = b[1][0]", 1, 5);

  // Member accessors.
  DoParserPrintTest("a=b.c+2",
                    "BLOCK\n"
                    " BINARY(=)\n"
                    "  IDENTIFIER(a)\n"
                    "  BINARY(+)\n"
                    "   ACCESSOR\n"
                    "    b\n"
                    "    IDENTIFIER(c)\n"
                    "   LITERAL(2)\n");
  DoParserErrorTest("a = b.c.d", 1, 6);  // Can't nest accessors (currently).
  DoParserErrorTest("a.b = 5", 1, 1);  // Can't assign to accessors (currently).
}

TEST(Parser, Condition) {
  DoParserPrintTest("if(1) { a = 2 }",
                    "BLOCK\n"
                    " CONDITION\n"
                    "  LITERAL(1)\n"
                    "  BLOCK\n"
                    "   BINARY(=)\n"
                    "    IDENTIFIER(a)\n"
                    "    LITERAL(2)\n");

  DoParserPrintTest("if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }",
                    "BLOCK\n"
                    " CONDITION\n"
                    "  LITERAL(1)\n"
                    "  BLOCK\n"
                    "   BINARY(=)\n"
                    "    IDENTIFIER(a)\n"
                    "    LITERAL(2)\n"
                    "  CONDITION\n"
                    "   LITERAL(0)\n"
                    "   BLOCK\n"
                    "    BINARY(=)\n"
                    "     IDENTIFIER(a)\n"
                    "     LITERAL(3)\n"
                    "   BLOCK\n"
                    "    BINARY(=)\n"
                    "     IDENTIFIER(a)\n"
                    "     LITERAL(4)\n");
}

TEST(Parser, OnlyCallAndAssignInBody) {
  DoParserErrorTest("[]", 1, 2);
  DoParserErrorTest("3 + 4", 1, 5);
  DoParserErrorTest("6 - 7", 1, 5);
  DoParserErrorTest("if (1) { 5 } else { print(4) }", 1, 12);
}

TEST(Parser, NoAssignmentInCondition) {
  DoParserErrorTest("if (a=2) {}", 1, 5);
}

TEST(Parser, CompleteFunction) {
  const char* input =
      "cc_test(\"foo\") {\n"
      "  sources = [\n"
      "    \"foo.cc\",\n"
      "    \"foo.h\"\n"
      "  ]\n"
      "  dependencies = [\n"
      "    \"base\"\n"
      "  ]\n"
      "}\n";
  const char* expected =
      "BLOCK\n"
      " FUNCTION(cc_test)\n"
      "  LIST\n"
      "   LITERAL(\"foo\")\n"
      "  BLOCK\n"
      "   BINARY(=)\n"
      "    IDENTIFIER(sources)\n"
      "    LIST\n"
      "     LITERAL(\"foo.cc\")\n"
      "     LITERAL(\"foo.h\")\n"
      "   BINARY(=)\n"
      "    IDENTIFIER(dependencies)\n"
      "    LIST\n"
      "     LITERAL(\"base\")\n";
  DoParserPrintTest(input, expected);
}

TEST(Parser, FunctionWithConditional) {
  const char* input =
      "cc_test(\"foo\") {\n"
      "  sources = [\"foo.cc\"]\n"
      "  if (OS == \"mac\") {\n"
      "    sources += \"bar.cc\"\n"
      "  } else if (OS == \"win\") {\n"
      "    sources -= [\"asd.cc\", \"foo.cc\"]\n"
      "  } else {\n"
      "    dependencies += [\"bar.cc\"]\n"
      "  }\n"
      "}\n";
  const char* expected =
      "BLOCK\n"
      " FUNCTION(cc_test)\n"
      "  LIST\n"
      "   LITERAL(\"foo\")\n"
      "  BLOCK\n"
      "   BINARY(=)\n"
      "    IDENTIFIER(sources)\n"
      "    LIST\n"
      "     LITERAL(\"foo.cc\")\n"
      "   CONDITION\n"
      "    BINARY(==)\n"
      "     IDENTIFIER(OS)\n"
      "     LITERAL(\"mac\")\n"
      "    BLOCK\n"
      "     BINARY(+=)\n"
      "      IDENTIFIER(sources)\n"
      "      LITERAL(\"bar.cc\")\n"
      "    CONDITION\n"
      "     BINARY(==)\n"
      "      IDENTIFIER(OS)\n"
      "      LITERAL(\"win\")\n"
      "     BLOCK\n"
      "      BINARY(-=)\n"
      "       IDENTIFIER(sources)\n"
      "       LIST\n"
      "        LITERAL(\"asd.cc\")\n"
      "        LITERAL(\"foo.cc\")\n"
      "     BLOCK\n"
      "      BINARY(+=)\n"
      "       IDENTIFIER(dependencies)\n"
      "       LIST\n"
      "        LITERAL(\"bar.cc\")\n";
  DoParserPrintTest(input, expected);
}

TEST(Parser, NestedBlocks) {
  const char* input = "{cc_test(\"foo\") {{foo=1}\n{}}}";
  const char* expected =
      "BLOCK\n"
      " BLOCK\n"
      "  FUNCTION(cc_test)\n"
      "   LIST\n"
      "    LITERAL(\"foo\")\n"
      "   BLOCK\n"
      "    BLOCK\n"
      "     BINARY(=)\n"
      "      IDENTIFIER(foo)\n"
      "      LITERAL(1)\n"
      "    BLOCK\n";
  DoParserPrintTest(input, expected);
  const char* input_with_newline = "{cc_test(\"foo\") {{foo=1}\n{}}}";
  DoParserPrintTest(input_with_newline, expected);
}

TEST(Parser, UnterminatedBlock) {
  DoParserErrorTest("stuff() {", 1, 9);
}

TEST(Parser, BadlyTerminatedNumber) {
  DoParserErrorTest("1234z", 1, 5);
}

TEST(Parser, NewlinesInUnusualPlaces) {
  DoParserPrintTest(
      "if\n"
      "(\n"
      "a\n"
      ")\n"
      "{\n"
      "}\n",
      "BLOCK\n"
      " CONDITION\n"
      "  IDENTIFIER(a)\n"
      "  BLOCK\n");
}

TEST(Parser, NewlinesInUnusualPlaces2) {
  DoParserPrintTest(
      "a\n=\n2\n",
      "BLOCK\n"
      " BINARY(=)\n"
      "  IDENTIFIER(a)\n"
      "  LITERAL(2)\n");
  DoParserPrintTest(
      "x =\ny if\n(1\n) {}",
      "BLOCK\n"
      " BINARY(=)\n"
      "  IDENTIFIER(x)\n"
      "  IDENTIFIER(y)\n"
      " CONDITION\n"
      "  LITERAL(1)\n"
      "  BLOCK\n");
  DoParserPrintTest(
      "x = 3\n+2",
      "BLOCK\n"
      " BINARY(=)\n"
      "  IDENTIFIER(x)\n"
      "  BINARY(+)\n"
      "   LITERAL(3)\n"
      "   LITERAL(2)\n"
      );
}

TEST(Parser, NewlineBeforeSubscript) {
  const char* input = "a = b[1]";
  const char* input_with_newline = "a = b\n[1]";
  const char* expected =
    "BLOCK\n"
    " BINARY(=)\n"
    "  IDENTIFIER(a)\n"
    "  ACCESSOR\n"
    "   b\n"
    "   LITERAL(1)\n";
  DoParserPrintTest(
      input,
      expected);
  DoParserPrintTest(
      input_with_newline,
      expected);
}

TEST(Parser, SequenceOfExpressions) {
  DoParserPrintTest(
      "a = 1 b = 2",
      "BLOCK\n"
      " BINARY(=)\n"
      "  IDENTIFIER(a)\n"
      "  LITERAL(1)\n"
      " BINARY(=)\n"
      "  IDENTIFIER(b)\n"
      "  LITERAL(2)\n");
}

TEST(Parser, BlockAfterFunction) {
  const char* input = "func(\"stuff\") {\n}";
  // TODO(scottmg): Do we really want these to mean different things?
  const char* input_with_newline = "func(\"stuff\")\n{\n}";
  const char* expected =
    "BLOCK\n"
    " FUNCTION(func)\n"
    "  LIST\n"
    "   LITERAL(\"stuff\")\n"
    "  BLOCK\n";
  DoParserPrintTest(input, expected);
  DoParserPrintTest(input_with_newline, expected);
}

TEST(Parser, LongExpression) {
  const char* input = "a = b + c && d || e";
  const char* expected =
    "BLOCK\n"
    " BINARY(=)\n"
    "  IDENTIFIER(a)\n"
    "  BINARY(||)\n"
    "   BINARY(&&)\n"
    "    BINARY(+)\n"
    "     IDENTIFIER(b)\n"
    "     IDENTIFIER(c)\n"
    "    IDENTIFIER(d)\n"
    "   IDENTIFIER(e)\n";
  DoParserPrintTest(input, expected);
}

TEST(Parser, HangingIf) {
  DoParserErrorTest("if", 1, 1);
}

TEST(Parser, NegatingList) {
  DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30);
}

/* [<][>][^][v][top][bottom][index][help] */