# Copyright 2014 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.

import imp
import os.path
import sys
import unittest

from mojom.parse import ast
from mojom.parse import lexer
from mojom.parse import parser


class ParserTest(unittest.TestCase):
  """Tests |parser.Parse()|."""

  def testTrivialValidSource(self):
    """Tests a trivial, but valid, .mojom source."""

    source = """\
        // This is a comment.

        module my_module;
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testSourceWithCrLfs(self):
    """Tests a .mojom source with CR-LFs instead of LFs."""

    source = "// This is a comment.\r\n\r\nmodule my_module;\r\n"
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testUnexpectedEOF(self):
    """Tests a "truncated" .mojom source."""

    source = """\
        // This is a comment.

        module my_module
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom: Error: Unexpected end of file$"):
      parser.Parse(source, "my_file.mojom")

  def testCommentLineNumbers(self):
    """Tests that line numbers are correctly tracked when comments are
    present."""

    source1 = """\
        // Isolated C++-style comments.

        // Foo.
        asdf1
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\n *asdf1$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        // Consecutive C++-style comments.
        // Foo.
        // Bar.

        struct Yada {  // Baz.
                       // Quux.
          int32 x;
        };

        asdf2
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\n *asdf2$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        /* Single-line C-style comments. */
        /* Foobar. */

        /* Baz. */
        asdf3
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\n *asdf3$"):
      parser.Parse(source3, "my_file.mojom")

    source4 = """\
        /* Multi-line C-style comments.
        */
        /*
        Foo.
        Bar.
        */

        /* Baz
           Quux. */
        asdf4
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\n *asdf4$"):
      parser.Parse(source4, "my_file.mojom")

  def testSimpleStruct(self):
    """Tests a simple .mojom source that just defines a struct."""

    source = """\
        module my_module;

        struct MyStruct {
          int32 a;
          double b;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Struct(
                'MyStruct', None,
                ast.StructBody([
                    ast.StructField('a', None, None, 'int32', None),
                    ast.StructField('b', None, None, 'double', None)
                ]))
        ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testSimpleStructWithoutModule(self):
    """Tests a simple struct without an explict module statement."""

    source = """\
        struct MyStruct {
          int32 a;
          double b;
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('a', None, None, 'int32', None),
                ast.StructField('b', None, None, 'double', None)
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testValidStructDefinitions(self):
    """Tests all types of definitions that can occur in a struct."""

    source = """\
        struct MyStruct {
          enum MyEnum { VALUE };
          const double kMyConst = 1.23;
          int32 a;
          SomeOtherStruct b;  // Invalidity detected at another stage.
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.Enum('MyEnum', None,
                         ast.EnumValueList(ast.EnumValue('VALUE', None, None))),
                ast.Const('kMyConst', None, 'double', '1.23'),
                ast.StructField('a', None, None, 'int32', None),
                ast.StructField('b', None, None, 'SomeOtherStruct', None)
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidStructDefinitions(self):
    """Tests that definitions that aren't allowed in a struct are correctly
    detected."""

    source1 = """\
        struct MyStruct {
          MyMethod(int32 a);
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\(':\n"
        r" *MyMethod\(int32 a\);$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        struct MyStruct {
          struct MyInnerStruct {
            int32 a;
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
        r" *struct MyInnerStruct {$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        struct MyStruct {
          interface MyInterface {
            MyMethod(int32 a);
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
        r" *interface MyInterface {$"):
      parser.Parse(source3, "my_file.mojom")

  def testMissingModuleName(self):
    """Tests an (invalid) .mojom with a missing module name."""

    source1 = """\
        // Missing module name.
        module ;
        struct MyStruct {
          int32 a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Unexpected ';':\n *module ;$"):
      parser.Parse(source1, "my_file.mojom")

    # Another similar case, but make sure that line-number tracking/reporting
    # is correct.
    source2 = """\
        module
        // This line intentionally left unblank.

        struct MyStruct {
          int32 a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'struct':\n"
        r" *struct MyStruct {$"):
      parser.Parse(source2, "my_file.mojom")

  def testMultipleModuleStatements(self):
    """Tests an (invalid) .mojom with multiple module statements."""

    source = """\
        module foo;
        module bar;
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Multiple \"module\" statements not "
        r"allowed:\n *module bar;$"):
      parser.Parse(source, "my_file.mojom")

  def testModuleStatementAfterImport(self):
    """Tests an (invalid) .mojom with a module statement after an import."""

    source = """\
        import "foo.mojom";
        module foo;
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: \"module\" statements must precede imports "
        r"and definitions:\n *module foo;$"):
      parser.Parse(source, "my_file.mojom")

  def testModuleStatementAfterDefinition(self):
    """Tests an (invalid) .mojom with a module statement after a definition."""

    source = """\
        struct MyStruct {
          int32 a;
        };
        module foo;
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:4: Error: \"module\" statements must precede imports "
        r"and definitions:\n *module foo;$"):
      parser.Parse(source, "my_file.mojom")

  def testImportStatementAfterDefinition(self):
    """Tests an (invalid) .mojom with an import statement after a definition."""

    source = """\
        struct MyStruct {
          int32 a;
        };
        import "foo.mojom";
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:4: Error: \"import\" statements must precede "
        r"definitions:\n *import \"foo.mojom\";$"):
      parser.Parse(source, "my_file.mojom")

  def testEnums(self):
    """Tests that enum statements are correctly parsed."""

    source = """\
        module my_module;
        enum MyEnum1 { VALUE1, VALUE2 };  // No trailing comma.
        enum MyEnum2 {
          VALUE1 = -1,
          VALUE2 = 0,
          VALUE3 = + 987,  // Check that space is allowed.
          VALUE4 = 0xAF12,
          VALUE5 = -0x09bcd,
          VALUE6 = VALUE5,
          VALUE7,  // Leave trailing comma.
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Enum(
                'MyEnum1', None,
                ast.EnumValueList([
                    ast.EnumValue('VALUE1', None, None),
                    ast.EnumValue('VALUE2', None, None)
                ])),
            ast.Enum(
                'MyEnum2', None,
                ast.EnumValueList([
                    ast.EnumValue('VALUE1', None, '-1'),
                    ast.EnumValue('VALUE2', None, '0'),
                    ast.EnumValue('VALUE3', None, '+987'),
                    ast.EnumValue('VALUE4', None, '0xAF12'),
                    ast.EnumValue('VALUE5', None, '-0x09bcd'),
                    ast.EnumValue('VALUE6', None, ('IDENTIFIER', 'VALUE5')),
                    ast.EnumValue('VALUE7', None, None)
                ]))
        ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidEnumInitializers(self):
    """Tests that invalid enum initializers are correctly detected."""

    # Floating point value.
    source2 = "enum MyEnum { VALUE = 0.123 };"
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n"
        r"enum MyEnum { VALUE = 0\.123 };$"):
      parser.Parse(source2, "my_file.mojom")

    # Boolean value.
    source2 = "enum MyEnum { VALUE = true };"
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected 'true':\n"
        r"enum MyEnum { VALUE = true };$"):
      parser.Parse(source2, "my_file.mojom")

  def testConsts(self):
    """Tests some constants and struct members initialized with them."""

    source = """\
        module my_module;

        struct MyStruct {
          const int8 kNumber = -1;
          int8 number@0 = kNumber;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Struct(
                'MyStruct', None,
                ast.StructBody([
                    ast.Const('kNumber', None, 'int8', '-1'),
                    ast.StructField('number', None, ast.Ordinal(0), 'int8',
                                    ('IDENTIFIER', 'kNumber'))
                ]))
        ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testNoConditionals(self):
    """Tests that ?: is not allowed."""

    source = """\
        module my_module;

        enum MyEnum {
          MY_ENUM_1 = 1 ? 2 : 3
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected '\?':\n"
        r" *MY_ENUM_1 = 1 \? 2 : 3$"):
      parser.Parse(source, "my_file.mojom")

  def testSimpleOrdinals(self):
    """Tests that (valid) ordinal values are scanned correctly."""

    source = """\
        module my_module;

        // This isn't actually valid .mojom, but the problem (missing ordinals)
        // should be handled at a different level.
        struct MyStruct {
          int32 a0@0;
          int32 a1@1;
          int32 a2@2;
          int32 a9@9;
          int32 a10 @10;
          int32 a11 @11;
          int32 a29 @29;
          int32 a1234567890 @1234567890;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Struct(
                'MyStruct', None,
                ast.StructBody([
                    ast.StructField('a0', None, ast.Ordinal(0), 'int32', None),
                    ast.StructField('a1', None, ast.Ordinal(1), 'int32', None),
                    ast.StructField('a2', None, ast.Ordinal(2), 'int32', None),
                    ast.StructField('a9', None, ast.Ordinal(9), 'int32', None),
                    ast.StructField('a10', None, ast.Ordinal(10), 'int32',
                                    None),
                    ast.StructField('a11', None, ast.Ordinal(11), 'int32',
                                    None),
                    ast.StructField('a29', None, ast.Ordinal(29), 'int32',
                                    None),
                    ast.StructField('a1234567890', None,
                                    ast.Ordinal(1234567890), 'int32', None)
                ]))
        ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidOrdinals(self):
    """Tests that (lexically) invalid ordinals are correctly detected."""

    source1 = """\
        module my_module;

        struct MyStruct {
          int32 a_missing@;
        };
        """
    with self.assertRaisesRegexp(
        lexer.LexError, r"^my_file\.mojom:4: Error: Missing ordinal value$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        module my_module;

        struct MyStruct {
          int32 a_octal@01;
        };
        """
    with self.assertRaisesRegexp(
        lexer.LexError, r"^my_file\.mojom:4: Error: "
        r"Octal and hexadecimal ordinal values not allowed$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        module my_module; struct MyStruct { int32 a_invalid_octal@08; };
        """
    with self.assertRaisesRegexp(
        lexer.LexError, r"^my_file\.mojom:1: Error: "
        r"Octal and hexadecimal ordinal values not allowed$"):
      parser.Parse(source3, "my_file.mojom")

    source4 = "module my_module; struct MyStruct { int32 a_hex@0x1aB9; };"
    with self.assertRaisesRegexp(
        lexer.LexError, r"^my_file\.mojom:1: Error: "
        r"Octal and hexadecimal ordinal values not allowed$"):
      parser.Parse(source4, "my_file.mojom")

    source5 = "module my_module; struct MyStruct { int32 a_hex@0X0; };"
    with self.assertRaisesRegexp(
        lexer.LexError, r"^my_file\.mojom:1: Error: "
        r"Octal and hexadecimal ordinal values not allowed$"):
      parser.Parse(source5, "my_file.mojom")

    source6 = """\
        struct MyStruct {
          int32 a_too_big@999999999999;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: "
        r"Ordinal value 999999999999 too large:\n"
        r" *int32 a_too_big@999999999999;$"):
      parser.Parse(source6, "my_file.mojom")

  def testNestedNamespace(self):
    """Tests that "nested" namespaces work."""

    source = """\
        module my.mod;

        struct MyStruct {
          int32 a;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my.mod'), None), ast.ImportList(), [
            ast.Struct(
                'MyStruct', None,
                ast.StructBody(ast.StructField('a', None, None, 'int32', None)))
        ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testValidHandleTypes(self):
    """Tests (valid) handle types."""

    source = """\
        struct MyStruct {
          handle a;
          handle<data_pipe_consumer> b;
          handle <data_pipe_producer> c;
          handle < message_pipe > d;
          handle
            < shared_buffer
            > e;
          handle
            <platform

            > f;
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('a', None, None, 'handle', None),
                ast.StructField('b', None, None, 'handle<data_pipe_consumer>',
                                None),
                ast.StructField('c', None, None, 'handle<data_pipe_producer>',
                                None),
                ast.StructField('d', None, None, 'handle<message_pipe>', None),
                ast.StructField('e', None, None, 'handle<shared_buffer>', None),
                ast.StructField('f', None, None, 'handle<platform>', None)
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidHandleType(self):
    """Tests an invalid (unknown) handle type."""

    source = """\
        struct MyStruct {
          handle<wtf_is_this> foo;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: "
        r"Invalid handle type 'wtf_is_this':\n"
        r" *handle<wtf_is_this> foo;$"):
      parser.Parse(source, "my_file.mojom")

  def testValidDefaultValues(self):
    """Tests default values that are valid (to the parser)."""

    source = """\
        struct MyStruct {
          int16 a0 = 0;
          uint16 a1 = 0x0;
          uint16 a2 = 0x00;
          uint16 a3 = 0x01;
          uint16 a4 = 0xcd;
          int32 a5 = 12345;
          int64 a6 = -12345;
          int64 a7 = +12345;
          uint32 a8 = 0x12cd3;
          uint32 a9 = -0x12cD3;
          uint32 a10 = +0x12CD3;
          bool a11 = true;
          bool a12 = false;
          float a13 = 1.2345;
          float a14 = -1.2345;
          float a15 = +1.2345;
          float a16 = 123.;
          float a17 = .123;
          double a18 = 1.23E10;
          double a19 = 1.E-10;
          double a20 = .5E+10;
          double a21 = -1.23E10;
          double a22 = +.123E10;
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('a0', None, None, 'int16', '0'),
                ast.StructField('a1', None, None, 'uint16', '0x0'),
                ast.StructField('a2', None, None, 'uint16', '0x00'),
                ast.StructField('a3', None, None, 'uint16', '0x01'),
                ast.StructField('a4', None, None, 'uint16', '0xcd'),
                ast.StructField('a5', None, None, 'int32', '12345'),
                ast.StructField('a6', None, None, 'int64', '-12345'),
                ast.StructField('a7', None, None, 'int64', '+12345'),
                ast.StructField('a8', None, None, 'uint32', '0x12cd3'),
                ast.StructField('a9', None, None, 'uint32', '-0x12cD3'),
                ast.StructField('a10', None, None, 'uint32', '+0x12CD3'),
                ast.StructField('a11', None, None, 'bool', 'true'),
                ast.StructField('a12', None, None, 'bool', 'false'),
                ast.StructField('a13', None, None, 'float', '1.2345'),
                ast.StructField('a14', None, None, 'float', '-1.2345'),
                ast.StructField('a15', None, None, 'float', '+1.2345'),
                ast.StructField('a16', None, None, 'float', '123.'),
                ast.StructField('a17', None, None, 'float', '.123'),
                ast.StructField('a18', None, None, 'double', '1.23E10'),
                ast.StructField('a19', None, None, 'double', '1.E-10'),
                ast.StructField('a20', None, None, 'double', '.5E+10'),
                ast.StructField('a21', None, None, 'double', '-1.23E10'),
                ast.StructField('a22', None, None, 'double', '+.123E10')
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testValidFixedSizeArray(self):
    """Tests parsing a fixed size array."""

    source = """\
        struct MyStruct {
          array<int32> normal_array;
          array<int32, 1> fixed_size_array_one_entry;
          array<int32, 10> fixed_size_array_ten_entries;
          array<array<array<int32, 1>>, 2> nested_arrays;
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('normal_array', None, None, 'int32[]', None),
                ast.StructField('fixed_size_array_one_entry', None, None,
                                'int32[1]', None),
                ast.StructField('fixed_size_array_ten_entries', None, None,
                                'int32[10]', None),
                ast.StructField('nested_arrays', None, None, 'int32[1][][2]',
                                None)
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testValidNestedArray(self):
    """Tests parsing a nested array."""

    source = "struct MyStruct { array<array<int32>> nested_array; };"
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody(
                ast.StructField('nested_array', None, None, 'int32[][]', None)))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidFixedArraySize(self):
    """Tests that invalid fixed array bounds are correctly detected."""

    source1 = """\
        struct MyStruct {
          array<int32, 0> zero_size_array;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n"
        r" *array<int32, 0> zero_size_array;$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        struct MyStruct {
          array<int32, 999999999999> too_big_array;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n"
        r" *array<int32, 999999999999> too_big_array;$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        struct MyStruct {
          array<int32, abcdefg> not_a_number;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n"
        r" *array<int32, abcdefg> not_a_number;"):
      parser.Parse(source3, "my_file.mojom")

  def testValidAssociativeArrays(self):
    """Tests that we can parse valid associative array structures."""

    source1 = "struct MyStruct { map<string, uint8> data; };"
    expected1 = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody(
                [ast.StructField('data', None, None, 'uint8{string}', None)]))
    ])
    self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)

    source2 = "interface MyInterface { MyMethod(map<string, uint8> a); };"
    expected2 = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody(
                ast.Method(
                    'MyMethod', None, None,
                    ast.ParameterList(
                        ast.Parameter('a', None, None, 'uint8{string}')),
                    None)))
    ])
    self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)

    source3 = "struct MyStruct { map<string, array<uint8>> data; };"
    expected3 = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody(
                [ast.StructField('data', None, None, 'uint8[]{string}', None)]))
    ])
    self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)

  def testValidMethod(self):
    """Tests parsing method declarations."""

    source1 = "interface MyInterface { MyMethod(int32 a); };"
    expected1 = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody(
                ast.Method(
                    'MyMethod', None, None,
                    ast.ParameterList(ast.Parameter('a', None, None, 'int32')),
                    None)))
    ])
    self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)

    source2 = """\
        interface MyInterface {
          MyMethod1@0(int32 a@0, int64 b@1);
          MyMethod2@1() => ();
        };
        """
    expected2 = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody([
                ast.Method(
                    'MyMethod1', None, ast.Ordinal(0),
                    ast.ParameterList([
                        ast.Parameter('a', None, ast.Ordinal(0), 'int32'),
                        ast.Parameter('b', None, ast.Ordinal(1), 'int64')
                    ]), None),
                ast.Method('MyMethod2', None, ast.Ordinal(1),
                           ast.ParameterList(), ast.ParameterList())
            ]))
    ])
    self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)

    source3 = """\
        interface MyInterface {
          MyMethod(string a) => (int32 a, bool b);
        };
        """
    expected3 = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody(
                ast.Method(
                    'MyMethod', None, None,
                    ast.ParameterList(ast.Parameter('a', None, None, 'string')),
                    ast.ParameterList([
                        ast.Parameter('a', None, None, 'int32'),
                        ast.Parameter('b', None, None, 'bool')
                    ]))))
    ])
    self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)

  def testInvalidMethods(self):
    """Tests that invalid method declarations are correctly detected."""

    # No trailing commas.
    source1 = """\
        interface MyInterface {
          MyMethod(string a,);
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\)':\n"
        r" *MyMethod\(string a,\);$"):
      parser.Parse(source1, "my_file.mojom")

    # No leading commas.
    source2 = """\
        interface MyInterface {
          MyMethod(, string a);
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected ',':\n"
        r" *MyMethod\(, string a\);$"):
      parser.Parse(source2, "my_file.mojom")

  def testValidInterfaceDefinitions(self):
    """Tests all types of definitions that can occur in an interface."""

    source = """\
        interface MyInterface {
          enum MyEnum { VALUE };
          const int32 kMyConst = 123;
          MyMethod(int32 x) => (MyEnum y);
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody([
                ast.Enum('MyEnum', None,
                         ast.EnumValueList(ast.EnumValue('VALUE', None, None))),
                ast.Const('kMyConst', None, 'int32', '123'),
                ast.Method(
                    'MyMethod', None, None,
                    ast.ParameterList(ast.Parameter('x', None, None, 'int32')),
                    ast.ParameterList(ast.Parameter('y', None, None, 'MyEnum')))
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidInterfaceDefinitions(self):
    """Tests that definitions that aren't allowed in an interface are correctly
    detected."""

    source1 = """\
        interface MyInterface {
          struct MyStruct {
            int32 a;
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
        r" *struct MyStruct {$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        interface MyInterface {
          interface MyInnerInterface {
            MyMethod(int32 x);
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
        r" *interface MyInnerInterface {$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        interface MyInterface {
          int32 my_field;
        };
        """
    # The parser thinks that "int32" is a plausible name for a method, so it's
    # "my_field" that gives it away.
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n"
        r" *int32 my_field;$"):
      parser.Parse(source3, "my_file.mojom")

  def testValidAttributes(self):
    """Tests parsing attributes (and attribute lists)."""

    # Note: We use structs because they have (optional) attribute lists.

    # Empty attribute list.
    source1 = "[] struct MyStruct {};"
    expected1 = ast.Mojom(
        None, ast.ImportList(),
        [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())])
    self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)

    # One-element attribute list, with name value.
    source2 = "[MyAttribute=MyName] struct MyStruct {};"
    expected2 = ast.Mojom(None, ast.ImportList(), [
        ast.Struct('MyStruct',
                   ast.AttributeList(ast.Attribute("MyAttribute", "MyName")),
                   ast.StructBody())
    ])
    self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)

    # Two-element attribute list, with one string value and one integer value.
    source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};"
    expected3 = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct',
            ast.AttributeList([
                ast.Attribute("MyAttribute1", "hello"),
                ast.Attribute("MyAttribute2", 5)
            ]), ast.StructBody())
    ])
    self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)

    # Various places that attribute list is allowed.
    source4 = """\
        [Attr0=0] module my_module;

        [Attr1=1] import "my_import";

        [Attr2=2] struct MyStruct {
          [Attr3=3] int32 a;
        };
        [Attr4=4] union MyUnion {
          [Attr5=5] int32 a;
        };
        [Attr6=6] enum MyEnum {
          [Attr7=7] a
        };
        [Attr8=8] interface MyInterface {
          [Attr9=9] MyMethod([Attr10=10] int32 a) => ([Attr11=11] bool b);
        };
        [Attr12=12] const double kMyConst = 1.23;
        """
    expected4 = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'),
                   ast.AttributeList([ast.Attribute("Attr0", 0)])),
        ast.ImportList(
            ast.Import(
                ast.AttributeList([ast.Attribute("Attr1", 1)]), "my_import")),
        [
            ast.Struct(
                'MyStruct', ast.AttributeList(ast.Attribute("Attr2", 2)),
                ast.StructBody(
                    ast.StructField(
                        'a', ast.AttributeList([ast.Attribute("Attr3", 3)]),
                        None, 'int32', None))),
            ast.Union(
                'MyUnion', ast.AttributeList(ast.Attribute("Attr4", 4)),
                ast.UnionBody(
                    ast.UnionField(
                        'a', ast.AttributeList([ast.Attribute("Attr5", 5)]),
                        None, 'int32'))),
            ast.Enum(
                'MyEnum', ast.AttributeList(ast.Attribute("Attr6", 6)),
                ast.EnumValueList(
                    ast.EnumValue(
                        'VALUE', ast.AttributeList([ast.Attribute("Attr7", 7)]),
                        None))),
            ast.Interface(
                'MyInterface', ast.AttributeList(ast.Attribute("Attr8", 8)),
                ast.InterfaceBody(
                    ast.Method(
                        'MyMethod', ast.AttributeList(
                            ast.Attribute("Attr9", 9)), None,
                        ast.ParameterList(
                            ast.Parameter(
                                'a',
                                ast.AttributeList([ast.Attribute("Attr10", 10)
                                                   ]), None, 'int32')),
                        ast.ParameterList(
                            ast.Parameter(
                                'b',
                                ast.AttributeList([ast.Attribute("Attr11", 11)
                                                   ]), None, 'bool'))))),
            ast.Const('kMyConst', ast.AttributeList(
                ast.Attribute("Attr12", 12)), 'double', '1.23')
        ])
    self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4)

    # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|
    # literal (non-name) values, which is extremely dubious.)

  def testInvalidAttributes(self):
    """Tests that invalid attributes and attribute lists are correctly
    detected."""

    # Trailing commas not allowed.
    source1 = "[MyAttribute=MyName,] struct MyStruct {};"
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
        r"\[MyAttribute=MyName,\] struct MyStruct {};$"):
      parser.Parse(source1, "my_file.mojom")

    # Missing value.
    source2 = "[MyAttribute=] struct MyStruct {};"
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
        r"\[MyAttribute=\] struct MyStruct {};$"):
      parser.Parse(source2, "my_file.mojom")

    # Missing key.
    source3 = "[=MyName] struct MyStruct {};"
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:1: Error: Unexpected '=':\n"
        r"\[=MyName\] struct MyStruct {};$"):
      parser.Parse(source3, "my_file.mojom")

  def testValidImports(self):
    """Tests parsing import statements."""

    # One import (no module statement).
    source1 = "import \"somedir/my.mojom\";"
    expected1 = ast.Mojom(None,
                          ast.ImportList(ast.Import(None, "somedir/my.mojom")),
                          [])
    self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)

    # Two imports (no module statement).
    source2 = """\
        import "somedir/my1.mojom";
        import "somedir/my2.mojom";
        """
    expected2 = ast.Mojom(
        None,
        ast.ImportList([
            ast.Import(None, "somedir/my1.mojom"),
            ast.Import(None, "somedir/my2.mojom")
        ]), [])
    self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)

    # Imports with module statement.
    source3 = """\
        module my_module;
        import "somedir/my1.mojom";
        import "somedir/my2.mojom";
        """
    expected3 = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None),
        ast.ImportList([
            ast.Import(None, "somedir/my1.mojom"),
            ast.Import(None, "somedir/my2.mojom")
        ]), [])
    self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)

  def testInvalidImports(self):
    """Tests that invalid import statements are correctly detected."""

    source1 = """\
        // Make the error occur on line 2.
        import invalid
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n"
        r" *import invalid$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        import  // Missing string.
        struct MyStruct {
          int32 a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
        r" *struct MyStruct {$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        import "foo.mojom"  // Missing semicolon.
        struct MyStruct {
          int32 a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
        r" *struct MyStruct {$"):
      parser.Parse(source3, "my_file.mojom")

  def testValidNullableTypes(self):
    """Tests parsing nullable types."""

    source = """\
        struct MyStruct {
          int32? a;  // This is actually invalid, but handled at a different
                     // level.
          string? b;
          array<int32> ? c;
          array<string ? > ? d;
          array<array<int32>?>? e;
          array<int32, 1>? f;
          array<string?, 1>? g;
          some_struct? h;
          handle? i;
          handle<data_pipe_consumer>? j;
          handle<data_pipe_producer>? k;
          handle<message_pipe>? l;
          handle<shared_buffer>? m;
          pending_receiver<some_interface>? n;
          handle<platform>? o;
        };
        """
    expected = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('a', None, None, 'int32?', None),
                ast.StructField('b', None, None, 'string?', None),
                ast.StructField('c', None, None, 'int32[]?', None),
                ast.StructField('d', None, None, 'string?[]?', None),
                ast.StructField('e', None, None, 'int32[]?[]?', None),
                ast.StructField('f', None, None, 'int32[1]?', None),
                ast.StructField('g', None, None, 'string?[1]?', None),
                ast.StructField('h', None, None, 'some_struct?', None),
                ast.StructField('i', None, None, 'handle?', None),
                ast.StructField('j', None, None, 'handle<data_pipe_consumer>?',
                                None),
                ast.StructField('k', None, None, 'handle<data_pipe_producer>?',
                                None),
                ast.StructField('l', None, None, 'handle<message_pipe>?', None),
                ast.StructField('m', None, None, 'handle<shared_buffer>?',
                                None),
                ast.StructField('n', None, None, 'rcv<some_interface>?', None),
                ast.StructField('o', None, None, 'handle<platform>?', None)
            ]))
    ])
    self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)

  def testInvalidNullableTypes(self):
    """Tests that invalid nullable types are correctly detected."""
    source1 = """\
        struct MyStruct {
          string?? a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\?':\n"
        r" *string\?\? a;$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        struct MyStruct {
          handle?<data_pipe_consumer> a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '<':\n"
        r" *handle\?<data_pipe_consumer> a;$"):
      parser.Parse(source2, "my_file.mojom")

  def testSimpleUnion(self):
    """Tests a simple .mojom source that just defines a union."""
    source = """\
        module my_module;

        union MyUnion {
          int32 a;
          double b;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Union(
                'MyUnion', None,
                ast.UnionBody([
                    ast.UnionField('a', None, None, 'int32'),
                    ast.UnionField('b', None, None, 'double')
                ]))
        ])
    actual = parser.Parse(source, "my_file.mojom")
    self.assertEquals(actual, expected)

  def testUnionWithOrdinals(self):
    """Test that ordinals are assigned to fields."""
    source = """\
        module my_module;

        union MyUnion {
          int32 a @10;
          double b @30;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Union(
                'MyUnion', None,
                ast.UnionBody([
                    ast.UnionField('a', None, ast.Ordinal(10), 'int32'),
                    ast.UnionField('b', None, ast.Ordinal(30), 'double')
                ]))
        ])
    actual = parser.Parse(source, "my_file.mojom")
    self.assertEquals(actual, expected)

  def testUnionWithStructMembers(self):
    """Test that struct members are accepted."""
    source = """\
        module my_module;

        union MyUnion {
          SomeStruct s;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Union(
                'MyUnion', None,
                ast.UnionBody([ast.UnionField('s', None, None, 'SomeStruct')]))
        ])
    actual = parser.Parse(source, "my_file.mojom")
    self.assertEquals(actual, expected)

  def testUnionWithArrayMember(self):
    """Test that array members are accepted."""
    source = """\
        module my_module;

        union MyUnion {
          array<int32> a;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Union(
                'MyUnion', None,
                ast.UnionBody([ast.UnionField('a', None, None, 'int32[]')]))
        ])
    actual = parser.Parse(source, "my_file.mojom")
    self.assertEquals(actual, expected)

  def testUnionWithMapMember(self):
    """Test that map members are accepted."""
    source = """\
        module my_module;

        union MyUnion {
          map<int32, string> m;
        };
        """
    expected = ast.Mojom(
        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [
            ast.Union(
                'MyUnion', None,
                ast.UnionBody(
                    [ast.UnionField('m', None, None, 'string{int32}')]))
        ])
    actual = parser.Parse(source, "my_file.mojom")
    self.assertEquals(actual, expected)

  def testUnionDisallowNestedStruct(self):
    """Tests that structs cannot be nested in unions."""
    source = """\
        module my_module;

        union MyUnion {
          struct MyStruct {
            int32 a;
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'struct':\n"
        r" *struct MyStruct {$"):
      parser.Parse(source, "my_file.mojom")

  def testUnionDisallowNestedInterfaces(self):
    """Tests that interfaces cannot be nested in unions."""
    source = """\
        module my_module;

        union MyUnion {
          interface MyInterface {
            MyMethod(int32 a);
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:4: Error: Unexpected 'interface':\n"
        r" *interface MyInterface {$"):
      parser.Parse(source, "my_file.mojom")

  def testUnionDisallowNestedUnion(self):
    """Tests that unions cannot be nested in unions."""
    source = """\
        module my_module;

        union MyUnion {
          union MyOtherUnion {
            int32 a;
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'union':\n"
        r" *union MyOtherUnion {$"):
      parser.Parse(source, "my_file.mojom")

  def testUnionDisallowNestedEnum(self):
    """Tests that enums cannot be nested in unions."""
    source = """\
        module my_module;

        union MyUnion {
          enum MyEnum {
            A,
          };
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:4: Error: Unexpected 'enum':\n"
        r" *enum MyEnum {$"):
      parser.Parse(source, "my_file.mojom")

  def testValidAssociatedKinds(self):
    """Tests parsing associated interfaces and requests."""
    source1 = """\
        struct MyStruct {
          associated MyInterface a;
          pending_associated_receiver<MyInterface> b;
          associated MyInterface? c;
          pending_associated_receiver<MyInterface>? d;
        };
        """
    expected1 = ast.Mojom(None, ast.ImportList(), [
        ast.Struct(
            'MyStruct', None,
            ast.StructBody([
                ast.StructField('a', None, None, 'asso<MyInterface>', None),
                ast.StructField('b', None, None, 'rca<MyInterface>', None),
                ast.StructField('c', None, None, 'asso<MyInterface>?', None),
                ast.StructField('d', None, None, 'rca<MyInterface>?', None)
            ]))
    ])
    self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)

    source2 = """\
        interface MyInterface {
          MyMethod(associated A a) =>(pending_associated_receiver<B> b);
        };"""
    expected2 = ast.Mojom(None, ast.ImportList(), [
        ast.Interface(
            'MyInterface', None,
            ast.InterfaceBody(
                ast.Method(
                    'MyMethod', None, None,
                    ast.ParameterList(ast.Parameter('a', None, None,
                                                    'asso<A>')),
                    ast.ParameterList(ast.Parameter('b', None, None,
                                                    'rca<B>')))))
    ])
    self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)

  def testInvalidAssociatedKinds(self):
    """Tests that invalid associated interfaces and requests are correctly
    detected."""
    source1 = """\
        struct MyStruct {
          associated associated SomeInterface a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError,
        r"^my_file\.mojom:2: Error: Unexpected 'associated':\n"
        r" *associated associated SomeInterface a;$"):
      parser.Parse(source1, "my_file.mojom")

    source2 = """\
        struct MyStruct {
          associated handle a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected 'handle':\n"
        r" *associated handle a;$"):
      parser.Parse(source2, "my_file.mojom")

    source3 = """\
        struct MyStruct {
          associated? MyInterface& a;
        };
        """
    with self.assertRaisesRegexp(
        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '\?':\n"
        r" *associated\? MyInterface& a;$"):
      parser.Parse(source3, "my_file.mojom")


if __name__ == "__main__":
  unittest.main()
