text: // 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 "tools/gn/input_file.h"
  #include "tools/gn/parser.h"
  #include "tools/gn/tokenizer.h"
  #include "util/test/test.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;
    std::unique_ptr<ParseNode> result = Parser::Parse(tokens, &err);
    if (!result)
      err.PrintToStdout();
    ASSERT_TRUE(result);
  
    std::ostringstream collector;
    RenderToText(result->GetJSONNode(), 0, collector);
  
    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;
    std::unique_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
    ASSERT_TRUE(result);
  
    std::ostringstream collector;
    RenderToText(result->GetJSONNode(), 0, collector);
  
    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()) {
      std::unique_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().column_number());
  }
  
  // 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()) {
      std::unique_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().column_number());
  }
  
  }  // 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);
    DoExpressionErrorTest("foo(1 2)", 1, 7);
  }
  
  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");
  
    // No contents for binary operator.
    DoExpressionErrorTest("a = !", 1, 5);
  }
  
  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, 7);
    DoExpressionErrorTest("[,]", 1, 2);
    DoExpressionErrorTest("[a,,]", 1, 4);
  }
  
  TEST(Parser, Assignment) {
    DoParserPrintTest("a=2",
                      "BLOCK\n"
                      " BINARY(=)\n"
                      "  IDENTIFIER(a)\n"
                      "  LITERAL(2)\n");
  
    DoExpressionErrorTest("a = ", 1, 3);
  }
  
  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");
    DoParserPrintTest("a.b = 5",
                      "BLOCK\n"
                      " BINARY(=)\n"
                      "  ACCESSOR\n"
                      "   a\n"
                      "   IDENTIFIER(b)\n"
                      "  LITERAL(5)\n");
    DoParserErrorTest("a = b.c.d", 1, 6);  // Can't nest accessors (currently).
  
    // Error at the bad dot in the RHS, not the + operator (crbug.com/472038).
    DoParserErrorTest("foo(a + b.c.d)", 1, 10);
  }
  
  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, 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, CommentsStandalone) {
    const char* input =
        "# Toplevel comment.\n"
        "\n"
        "executable(\"wee\") {}\n";
    const char* expected =
        "BLOCK\n"
        " BLOCK_COMMENT(# Toplevel comment.)\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "  BLOCK\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsStandaloneEof) {
    const char* input =
        "executable(\"wee\") {}\n"
        "# EOF comment.\n";
    const char* expected =
        "BLOCK\n"
        " +AFTER_COMMENT(\"# EOF comment.\")\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "  BLOCK\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsLineAttached) {
    const char* input =
        "executable(\"wee\") {\n"
        "  # Some sources.\n"
        "  sources = [\n"
        "    \"stuff.cc\",\n"
        "    \"things.cc\",\n"
        "    # This file is special or something.\n"
        "    \"another.cc\",\n"
        "  ]\n"
        "}\n";
    const char* expected =
        "BLOCK\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    +BEFORE_COMMENT(\"# Some sources.\")\n"
        "    IDENTIFIER(sources)\n"
        "    LIST\n"
        "     LITERAL(\"stuff.cc\")\n"
        "     LITERAL(\"things.cc\")\n"
        "     LITERAL(\"another.cc\")\n"
        "      +BEFORE_COMMENT(\"# This file is special or something.\")\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsSuffix) {
    const char* input =
        "executable(\"wee\") { # This is some stuff.\n"
        "sources = [ \"a.cc\" # And another comment here.\n"
        "] }";
    const char* expected =
        "BLOCK\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "   END())\n"
        "    +SUFFIX_COMMENT(\"# This is some stuff.\")\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    IDENTIFIER(sources)\n"
        "    LIST\n"
        "     LITERAL(\"a.cc\")\n"
        "      +SUFFIX_COMMENT(\"# And another comment here.\")\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsSuffixDifferentLine) {
    const char* input =
        "executable(\"wee\") {\n"
        "  sources = [ \"a\",\n"
        "      \"b\" ] # Comment\n"
        "}\n";
    const char* expected =
        "BLOCK\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    IDENTIFIER(sources)\n"
        "    LIST\n"
        "     LITERAL(\"a\")\n"
        "     LITERAL(\"b\")\n"
        "     END(])\n"
        "      +SUFFIX_COMMENT(\"# Comment\")\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsSuffixMultiple) {
    const char* input =
        "executable(\"wee\") {\n"
        "  sources = [\n"
        "    \"a\",  # This is a comment,\n"
        "          # and some more,\n"  // Note that this is aligned with above.
        "          # then the end.\n"
        "  ]\n"
        "}\n";
    const char* expected =
        "BLOCK\n"
        " FUNCTION(executable)\n"
        "  LIST\n"
        "   LITERAL(\"wee\")\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    IDENTIFIER(sources)\n"
        "    LIST\n"
        "     LITERAL(\"a\")\n"
        "      +SUFFIX_COMMENT(\"# This is a comment,\")\n"
        "      +SUFFIX_COMMENT(\"# and some more,\")\n"
        "      +SUFFIX_COMMENT(\"# then the end.\")\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsConnectedInList) {
    const char* input =
        "defines = [\n"
        "\n"
        "  # Connected comment.\n"
        "  \"WEE\",\n"
        "  \"BLORPY\",\n"
        "]\n";
    const char* expected =
        "BLOCK\n"
        " BINARY(=)\n"
        "  IDENTIFIER(defines)\n"
        "  LIST\n"
        "   LITERAL(\"WEE\")\n"
        "    +BEFORE_COMMENT(\"# Connected comment.\")\n"
        "   LITERAL(\"BLORPY\")\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, CommentsAtEndOfBlock) {
    const char* input =
        "if (is_win) {\n"
        "  sources = [\"a.cc\"]\n"
        "  # Some comment at end.\n"
        "}\n";
    const char* expected =
        "BLOCK\n"
        " CONDITION\n"
        "  IDENTIFIER(is_win)\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    IDENTIFIER(sources)\n"
        "    LIST\n"
        "     LITERAL(\"a.cc\")\n"
        "   END(})\n"
        "    +BEFORE_COMMENT(\"# Some comment at end.\")\n";
    DoParserPrintTest(input, expected);
  }
  
  // TODO(scottmg): I could be convinced this is incorrect. It's not clear to me
  // which thing this comment is intended to be attached to.
  TEST(Parser, CommentsEndOfBlockSingleLine) {
    const char* input =
        "defines = [ # EOL defines.\n"
        "]\n";
    const char* expected =
        "BLOCK\n"
        " BINARY(=)\n"
        "  IDENTIFIER(defines)\n"
        "   +SUFFIX_COMMENT(\"# EOL defines.\")\n"
        "  LIST\n";
    DoParserPrintTest(input, expected);
  }
  
  TEST(Parser, HangingIf) {
    DoParserErrorTest("if", 1, 1);
  }
  
  TEST(Parser, NegatingList) {
    DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30);
  }
  
  TEST(Parser, ConditionNoBracesIf) {
    DoParserErrorTest(
        "if (true)\n"
        "  foreach(foo, []) {}\n"
        "else {\n"
        "  foreach(bar, []) {}\n"
        "}\n",
        2, 3);
  }
  
  TEST(Parser, ConditionNoBracesElse) {
    DoParserErrorTest(
        "if (true) {\n"
        "  foreach(foo, []) {}\n"
        "} else\n"
        "  foreach(bar, []) {}\n",
        4, 3);
  }
  
  TEST(Parser, ConditionNoBracesElseIf) {
    DoParserErrorTest(
        "if (true) {\n"
        "  foreach(foo, []) {}\n"
        "} else if (true)\n"
        "  foreach(bar, []) {}\n",
        4, 3);
  }
  
  // Disallow standalone {} for introducing new scopes. These are ambiguous with
  // target declarations (e.g. is:
  //   foo("bar") {}
  // a function with an associated block, or a standalone function with a
  // freestanding block.
  TEST(Parser, StandaloneBlock) {
    // The error is reported at the end of the block when nothing is done
    // with it. If we had said "a = { ..." then it would have been OK.
    DoParserErrorTest(
        "if (true) {\n"
        "}\n"
        "{\n"
        "  assert(false)\n"
        "}\n",
        5, 1);
  }
  
  TEST(Parser, BlockValues) {
    const char* input =
        "print({a = 1 b = 2}, 3)\n"
        "a = { b = \"asd\" }";
    const char* expected =
        "BLOCK\n"
        " FUNCTION(print)\n"
        "  LIST\n"
        "   BLOCK\n"
        "    BINARY(=)\n"
        "     IDENTIFIER(a)\n"
        "     LITERAL(1)\n"
        "    BINARY(=)\n"
        "     IDENTIFIER(b)\n"
        "     LITERAL(2)\n"
        "   LITERAL(3)\n"
        " BINARY(=)\n"
        "  IDENTIFIER(a)\n"
        "  BLOCK\n"
        "   BINARY(=)\n"
        "    IDENTIFIER(b)\n"
        "    LITERAL(\"asd\")\n";
    DoParserPrintTest(input, expected);
  }
  