[{"id":13656,"web_url":"https://patchwork.libcamera.org/comment/13656/","msgid":"<20201110002253.GB477907@oden.dyn.berto.se>","date":"2020-11-10T00:22:53","subject":"Re: [libcamera-devel] [PATCH v4 01/37] utils: ipc: import mojo","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Paul,\n\nThanks for your work.\n\nOn 2020-11-06 19:36:31 +0900, Paul Elder wrote:\n> Import mojo from the Chromium repository, so that we can use it for\n> generating code for the IPC mechanism. The commit from which this was\n> taken is:\n> a079161ec8c6907b883f9cb84fc8c4e7896cb1d0 \"Add PPAPI constructs for\n> sending focus object to PdfAccessibilityTree\"\n> \n> This tree has been pruned to remove directories that didn't have any\n> necessary code:\n> - mojo/* except for mojo/public\n>   - mojo core, docs, and misc files\n> - mojo/public/* except for mojo/public/{tools,LICENSE}\n>   - language bindings for IPC, tests, and some mojo internals\n> - mojo/public/tools/{fuzzers,chrome_ipc}\n> - mojo/public/tools/bindings/generators\n>   - code generation for other languages\n> \n> No files were modified.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nThis patch is _huge_ and there is no way I will review all files and \ndiff them against the mentioned upstream commit, so I trust your when \nyou say none of the files are modified,\n\nAcked-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> \n> ---\n> No change in v4\n> \n> No change in v3\n> \n> Changes in v2:\n> - add chromium commit id and list of pruned directories to the changelog\n> ---\n>  utils/ipc/mojo/public/LICENSE                 |   27 +\n>  utils/ipc/mojo/public/tools/.style.yapf       |    6 +\n>  utils/ipc/mojo/public/tools/BUILD.gn          |   18 +\n>  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  108 +\n>  .../ipc/mojo/public/tools/bindings/README.md  |  816 +++++++\n>  .../chromium_bindings_configuration.gni       |   51 +\n>  .../tools/bindings/compile_typescript.py      |   27 +\n>  .../tools/bindings/concatenate-files.py       |   54 +\n>  ...concatenate_and_replace_closure_exports.py |   73 +\n>  .../bindings/format_typemap_generator_args.py |   36 +\n>  .../tools/bindings/gen_data_files_list.py     |   52 +\n>  .../tools/bindings/generate_type_mappings.py  |  187 ++\n>  .../ipc/mojo/public/tools/bindings/mojom.gni  | 1941 +++++++++++++++++\n>  .../bindings/mojom_bindings_generator.py      |  390 ++++\n>  .../mojom_bindings_generator_unittest.py      |   62 +\n>  .../tools/bindings/mojom_types_downgrader.py  |  119 +\n>  .../tools/bindings/validate_typemap_config.py |   57 +\n>  utils/ipc/mojo/public/tools/mojom/README.md   |   14 +\n>  .../mojom/check_stable_mojom_compatibility.py |  170 ++\n>  ...eck_stable_mojom_compatibility_unittest.py |  260 +++\n>  .../mojo/public/tools/mojom/const_unittest.py |   90 +\n>  .../mojo/public/tools/mojom/enum_unittest.py  |   92 +\n>  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   43 +\n>  .../mojo/public/tools/mojom/mojom/__init__.py |    0\n>  .../mojo/public/tools/mojom/mojom/error.py    |   28 +\n>  .../mojo/public/tools/mojom/mojom/fileutil.py |   45 +\n>  .../tools/mojom/mojom/fileutil_unittest.py    |   40 +\n>  .../tools/mojom/mojom/generate/__init__.py    |    0\n>  .../mojom/mojom/generate/constant_resolver.py |   93 +\n>  .../tools/mojom/mojom/generate/generator.py   |  325 +++\n>  .../mojom/generate/generator_unittest.py      |   74 +\n>  .../tools/mojom/mojom/generate/module.py      | 1635 ++++++++++++++\n>  .../mojom/mojom/generate/module_unittest.py   |   31 +\n>  .../public/tools/mojom/mojom/generate/pack.py |  258 +++\n>  .../mojom/mojom/generate/pack_unittest.py     |  225 ++\n>  .../mojom/mojom/generate/template_expander.py |   83 +\n>  .../tools/mojom/mojom/generate/translate.py   |  854 ++++++++\n>  .../mojom/generate/translate_unittest.py      |   73 +\n>  .../tools/mojom/mojom/parse/__init__.py       |    0\n>  .../public/tools/mojom/mojom/parse/ast.py     |  427 ++++\n>  .../tools/mojom/mojom/parse/ast_unittest.py   |  121 +\n>  .../mojom/mojom/parse/conditional_features.py |   82 +\n>  .../parse/conditional_features_unittest.py    |  233 ++\n>  .../public/tools/mojom/mojom/parse/lexer.py   |  251 +++\n>  .../tools/mojom/mojom/parse/lexer_unittest.py |  198 ++\n>  .../public/tools/mojom/mojom/parse/parser.py  |  488 +++++\n>  .../mojom/mojom/parse/parser_unittest.py      | 1390 ++++++++++++\n>  .../mojo/public/tools/mojom/mojom_parser.py   |  361 +++\n>  .../tools/mojom/mojom_parser_test_case.py     |   73 +\n>  .../tools/mojom/mojom_parser_unittest.py      |  171 ++\n>  .../tools/mojom/stable_attribute_unittest.py  |  127 ++\n>  .../mojom/version_compatibility_unittest.py   |  397 ++++\n>  .../public/tools/run_all_python_unittests.py  |   28 +\n>  utils/ipc/tools/diagnosis/crbug_1001171.py    |   51 +\n>  54 files changed, 12855 insertions(+)\n>  create mode 100644 utils/ipc/mojo/public/LICENSE\n>  create mode 100644 utils/ipc/mojo/public/tools/.style.yapf\n>  create mode 100644 utils/ipc/mojo/public/tools/BUILD.gn\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/BUILD.gn\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/README.md\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/mojom.gni\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/README.md\n>  create mode 100755 utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n>  create mode 100755 utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/const_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/__init__.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/error.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n>  create mode 100755 utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n>  create mode 100755 utils/ipc/mojo/public/tools/run_all_python_unittests.py\n>  create mode 100644 utils/ipc/tools/diagnosis/crbug_1001171.py\n> \n> diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE\n> new file mode 100644\n> index 00000000..972bb2ed\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/LICENSE\n> @@ -0,0 +1,27 @@\n> +// Copyright 2014 The Chromium Authors. All rights reserved.\n> +//\n> +// Redistribution and use in source and binary forms, with or without\n> +// modification, are permitted provided that the following conditions are\n> +// met:\n> +//\n> +//    * Redistributions of source code must retain the above copyright\n> +// notice, this list of conditions and the following disclaimer.\n> +//    * Redistributions in binary form must reproduce the above\n> +// copyright notice, this list of conditions and the following disclaimer\n> +// in the documentation and/or other materials provided with the\n> +// distribution.\n> +//    * Neither the name of Google Inc. nor the names of its\n> +// contributors may be used to endorse or promote products derived from\n> +// this software without specific prior written permission.\n> +//\n> +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n> +// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n> +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n> +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n> +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n> +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n> +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n> +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n> +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n> +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n> +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n> diff --git a/utils/ipc/mojo/public/tools/.style.yapf b/utils/ipc/mojo/public/tools/.style.yapf\n> new file mode 100644\n> index 00000000..b4ebbe24\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/.style.yapf\n> @@ -0,0 +1,6 @@\n> +[style]\n> +based_on_style = pep8\n> +\n> +# New directories should use a .style.yapf that does not include the following:\n> +column_limit = 80\n> +indent_width = 2\n> diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn\n> new file mode 100644\n> index 00000000..4c68350b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/BUILD.gn\n> @@ -0,0 +1,18 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +# The main target used to aggregate all unit tests for Python-based Mojo tools.\n> +# This is used to generate a complete isolate which can be pushed to bots to run\n> +# the tests.\n> +group(\"mojo_python_unittests\") {\n> +  data = [\n> +    \"run_all_python_unittests.py\",\n> +    \"//testing/scripts/common.py\",\n> +    \"//testing/scripts/run_isolated_script_test.py\",\n> +    \"//testing/test_env.py\",\n> +    \"//testing/xvfb.py\",\n> +  ]\n> +  deps = [ \"//mojo/public/tools/mojom/mojom:tests\" ]\n> +  data_deps = [ \"//third_party/catapult/third_party/typ/\" ]\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> new file mode 100644\n> index 00000000..8ba6e922\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> @@ -0,0 +1,108 @@\n> +# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import(\"//mojo/public/tools/bindings/mojom.gni\")\n> +import(\"//third_party/jinja2/jinja2.gni\")\n> +\n> +action(\"precompile_templates\") {\n> +  sources = mojom_generator_sources\n> +  sources += [\n> +    \"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module.h.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/constant_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/constants.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/data_types_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/enum.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/enum_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/header.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/interface.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/struct.java.tmpl\",\n> +    \"$mojom_generator_root/generators/java_templates/union.java.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/enum_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/fuzzing.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/module_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/mojom-lite.js.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/lite/union_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/module.amd.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/module_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/union_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/js_templates/validation_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.cc.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.h.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.proto.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/module_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/mojom.tmpl\",\n> +  ]\n> +  script = mojom_generator_script\n> +\n> +  inputs = jinja2_sources\n> +  outputs = [\n> +    \"$target_gen_dir/cpp_templates.zip\",\n> +    \"$target_gen_dir/java_templates.zip\",\n> +    \"$target_gen_dir/mojolpm_templates.zip\",\n> +    \"$target_gen_dir/js_templates.zip\",\n> +    \"$target_gen_dir/ts_templates.zip\",\n> +  ]\n> +  args = [\n> +    \"-o\",\n> +    rebase_path(target_gen_dir, root_build_dir),\n> +    \"--use_bundled_pylibs\",\n> +    \"precompile\",\n> +  ]\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md\n> new file mode 100644\n> index 00000000..1a3d5c58\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/README.md\n> @@ -0,0 +1,816 @@\n> +# Mojom Interface Definition Language (IDL)\n> +This document is a subset of the [Mojo documentation](/mojo/README.md).\n> +\n> +[TOC]\n> +\n> +## Overview\n> +\n> +Mojom is the IDL for Mojo interfaces. Given a `.mojom` file, the\n> +[bindings\n> +generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/) can\n> +output bindings for any supported language: **C++**, **JavaScript**, or\n> +**Java**.\n> +\n> +For a trivial example consider the following hypothetical Mojom file we write to\n> +`//services/widget/public/mojom/frobinator.mojom`:\n> +\n> +```\n> +module widget.mojom;\n> +\n> +interface Frobinator {\n> +  Frobinate();\n> +};\n> +```\n> +\n> +This defines a single [interface](#Interfaces) named `Frobinator` in a\n> +[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as\n> +`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of\n> +definitions (structs, enums, *etc.*) may be included in a single Mojom file.\n> +\n> +If we add a corresponding GN target to\n> +`//services/widget/public/mojom/BUILD.gn`:\n> +\n> +```\n> +import(\"mojo/public/tools/bindings/mojom.gni\")\n> +\n> +mojom(\"mojom\") {\n> +  sources = [\n> +    \"frobinator.mojom\",\n> +  ]\n> +}\n> +```\n> +\n> +and then build this target:\n> +\n> +```\n> +ninja -C out/r services/widget/public/mojom\n> +```\n> +\n> +we'll find several generated sources in our output directory:\n> +\n> +```\n> +out/r/gen/services/widget/public/mojom/frobinator.mojom.cc\n> +out/r/gen/services/widget/public/mojom/frobinator.mojom.h\n> +out/r/gen/services/widget/public/mojom/frobinator.mojom-shared.h\n> +etc...\n> +```\n> +\n> +Each of these generated source modules includes a set of definitions\n> +representing the Mojom contents in C++. You can also build or depend on suffixed\n> +target names to get bindings for other languages. For example,\n> +\n> +```\n> +ninja -C out/r services/widget/public/mojom:mojom_js\n> +ninja -C out/r services/widget/public/mojom:mojom_java\n> +```\n> +\n> +would generate JavaScript and Java bindings respectively, in the same generated\n> +output directory.\n> +\n> +For more details regarding the generated\n> +outputs please see\n> +[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n> +\n> +## Mojom Syntax\n> +\n> +Mojom IDL allows developers to define **structs**, **unions**, **interfaces**,\n> +**constants**, and **enums**, all within the context of a **module**. These\n> +definitions are used to generate code in the supported target languages at build\n> +time.\n> +\n> +Mojom files may **import** other Mojom files in order to reference their\n> +definitions.\n> +\n> +### Primitive Types\n> +Mojom supports a few basic data types which may be composed into structs or used\n> +for message parameters.\n> +\n> +| Type                          | Description\n> +|-------------------------------|-------------------------------------------------------|\n> +| `bool`                        | Boolean type (`true` or `false`.)\n> +| `int8`, `uint8`               | Signed or unsigned 8-bit integer.\n> +| `int16`, `uint16`             | Signed or unsigned 16-bit integer.\n> +| `int32`, `uint32`             | Signed or unsigned 32-bit integer.\n> +| `int64`, `uint64`             | Signed or unsigned 64-bit integer.\n> +| `float`, `double`             | 32- or 64-bit floating point number.\n> +| `string`                      | UTF-8 encoded string.\n> +| `array<T>`                    | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.\n> +| `array<T, N>`                 | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.\n> +| `map<S, T>`                   | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n> +| `handle`                      | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.\n> +| `handle<message_pipe>`        | Generic message pipe handle.\n> +| `handle<shared_buffer>`       | Shared buffer handle.\n> +| `handle<data_pipe_producer>`  | Data pipe producer handle.\n> +| `handle<data_pipe_consumer>`  | Data pipe consumer handle.\n> +| `handle<platform>`            | A native platform/OS handle.\n> +| *`pending_remote<InterfaceType>`*             | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface.\n> +| *`pending_receiver<InterfaceType>`*            | A pending receiver for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface.\n> +| *`pending_associated_remote<InterfaceType>`*  | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces)\n> +| *`pending_associated_receiver<InterfaceType>`* | A pending associated receiver. See [Associated Interfaces](#Associated-Interfaces)\n> +| *T*?                          | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable.\n> +\n> +### Modules\n> +\n> +Every Mojom file may optionally specify a single **module** to which it belongs.\n> +\n> +This is used strictly for aggregaging all defined symbols therein within a\n> +common Mojom namespace. The specific impact this has on generated binidngs code\n> +varies for each target language. For example, if the following Mojom is used to\n> +generate bindings:\n> +\n> +```\n> +module business.stuff;\n> +\n> +interface MoneyGenerator {\n> +  GenerateMoney();\n> +};\n> +```\n> +\n> +Generated C++ bindings will define a class interface `MoneyGenerator` in the\n> +`business::stuff` namespace, while Java bindings will define an interface\n> +`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript\n> +bindings at this time are unaffected by module declarations.\n> +\n> +**NOTE:** By convention in the Chromium codebase, **all** Mojom files should\n> +declare a module name with at least (and preferrably exactly) one top-level name\n> +as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`,\n> +`business.mojom`, *etc.*\n> +\n> +This convention makes it easy to tell which symbols are generated by Mojom when\n> +reading non-Mojom code, and it also avoids namespace collisions in the fairly\n> +common scenario where you have a real C++ or Java `Foo` along with a\n> +corresponding Mojom `Foo` for its serialized representation.\n> +\n> +### Imports\n> +\n> +If your Mojom references definitions from other Mojom files, you must **import**\n> +those files. Import syntax is as follows:\n> +\n> +```\n> +import \"services/widget/public/mojom/frobinator.mojom\";\n> +```\n> +\n> +Import paths are always relative to the top-level directory.\n> +\n> +Note that circular imports are **not** supported.\n> +\n> +### Structs\n> +\n> +Structs are defined using the **struct** keyword, and they provide a way to\n> +group related fields together:\n> +\n> +``` cpp\n> +struct StringPair {\n> +  string first;\n> +  string second;\n> +};\n> +```\n> +\n> +Struct fields may be comprised of any of the types listed above in the\n> +[Primitive Types](#Primitive-Types) section.\n> +\n> +Default values may be specified as long as they are constant:\n> +\n> +``` cpp\n> +struct Request {\n> +  int32 id = -1;\n> +  string details;\n> +};\n> +```\n> +\n> +What follows is a fairly\n> +comprehensive example using the supported field types:\n> +\n> +``` cpp\n> +struct StringPair {\n> +  string first;\n> +  string second;\n> +};\n> +\n> +enum AnEnum {\n> +  YES,\n> +  NO\n> +};\n> +\n> +interface SampleInterface {\n> +  DoStuff();\n> +};\n> +\n> +struct AllTheThings {\n> +  // Note that these types can never be marked nullable!\n> +  bool boolean_value;\n> +  int8 signed_8bit_value = 42;\n> +  uint8 unsigned_8bit_value;\n> +  int16 signed_16bit_value;\n> +  uint16 unsigned_16bit_value;\n> +  int32 signed_32bit_value;\n> +  uint32 unsigned_32bit_value;\n> +  int64 signed_64bit_value;\n> +  uint64 unsigned_64bit_value;\n> +  float float_value_32bit;\n> +  double float_value_64bit;\n> +  AnEnum enum_value = AnEnum.YES;\n> +\n> +  // Strings may be nullable.\n> +  string? maybe_a_string_maybe_not;\n> +\n> +  // Structs may contain other structs. These may also be nullable.\n> +  StringPair some_strings;\n> +  StringPair? maybe_some_more_strings;\n> +\n> +  // In fact structs can also be nested, though in practice you must always make\n> +  // such fields nullable -- otherwise messages would need to be infinitely long\n> +  // in order to pass validation!\n> +  AllTheThings? more_things;\n> +\n> +  // Arrays may be templated over any Mojom type, and are always nullable:\n> +  array<int32> numbers;\n> +  array<int32>? maybe_more_numbers;\n> +\n> +  // Arrays of arrays of arrays... are fine.\n> +  array<array<array<AnEnum>>> this_works_but_really_plz_stop;\n> +\n> +  // The element type may be nullable if it's a type which is allowed to be\n> +  // nullable.\n> +  array<AllTheThings?> more_maybe_things;\n> +\n> +  // Fixed-size arrays get some extra validation on the receiving end to ensure\n> +  // that the correct number of elements is always received.\n> +  array<uint64, 2> uuid;\n> +\n> +  // Maps follow many of the same rules as arrays. Key types may be any\n> +  // non-handle, non-collection type, and value types may be any supported\n> +  // struct field type. Maps may also be nullable.\n> +  map<string, int32> one_map;\n> +  map<AnEnum, string>? maybe_another_map;\n> +  map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map;\n> +  map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous;\n> +\n> +  // And finally, all handle types are valid as struct fields and may be\n> +  // nullable. Note that interfaces and interface requests (the \"Foo\" and\n> +  // \"Foo&\" type syntax respectively) are just strongly-typed message pipe\n> +  // handles.\n> +  handle generic_handle;\n> +  handle<data_pipe_consumer> reader;\n> +  handle<data_pipe_producer>? maybe_writer;\n> +  handle<shared_buffer> dumping_ground;\n> +  handle<message_pipe> raw_message_pipe;\n> +  pending_remote<SampleInterface>? maybe_a_sample_interface_client_pipe;\n> +  pending_receiver<SampleInterface> non_nullable_sample_pending_receiver;\n> +  pending_receiver<SampleInterface>? nullable_sample_pending_receiver;\n> +  pending_associated_remote<SampleInterface> associated_interface_client;\n> +  pending_associated_receiver<SampleInterface> associated_pending_receiver;\n> +  pending_associated_receiver<SampleInterface>? maybe_another_pending_receiver;\n> +};\n> +```\n> +\n> +For details on how all of these different types translate to usable generated\n> +code, see\n> +[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n> +\n> +### Unions\n> +\n> +Mojom supports tagged unions using the **union** keyword. A union is a\n> +collection of fields which may taken the value of any single one of those fields\n> +at a time. Thus they provide a way to represent a variant value type while\n> +minimizing storage requirements.\n> +\n> +Union fields may be of any type supported by [struct](#Structs) fields. For\n> +example:\n> +\n> +```cpp\n> +union ExampleUnion {\n> +  string str;\n> +  StringPair pair;\n> +  int64 id;\n> +  array<uint64, 2> guid;\n> +  SampleInterface iface;\n> +};\n> +```\n> +\n> +For details on how unions like this translate to generated bindings code, see\n> +[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n> +\n> +### Enumeration Types\n> +\n> +Enumeration types may be defined using the **enum** keyword either directly\n> +within a module or nested within the namespace of some struct or interface:\n> +\n> +```\n> +module business.mojom;\n> +\n> +enum Department {\n> +  SALES = 0,\n> +  DEV,\n> +};\n> +\n> +struct Employee {\n> +  enum Type {\n> +    FULL_TIME,\n> +    PART_TIME,\n> +  };\n> +\n> +  Type type;\n> +  // ...\n> +};\n> +```\n> +\n> +Similar to C-style enums, individual values may be explicitly assigned within an\n> +enum definition. By default, values are based at zero and increment by\n> +1 sequentially.\n> +\n> +The effect of nested definitions on generated bindings varies depending on the\n> +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)\n> +\n> +### Constants\n> +\n> +Constants may be defined using the **const** keyword either directly within a\n> +module or nested within the namespace of some struct or interface:\n> +\n> +```\n> +module business.mojom;\n> +\n> +const string kServiceName = \"business\";\n> +\n> +struct Employee {\n> +  const uint64 kInvalidId = 0;\n> +\n> +  enum Type {\n> +    FULL_TIME,\n> +    PART_TIME,\n> +  };\n> +\n> +  uint64 id = kInvalidId;\n> +  Type type;\n> +};\n> +```\n> +\n> +The effect of nested definitions on generated bindings varies depending on the\n> +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)\n> +\n> +### Interfaces\n> +\n> +An **interface** is a logical bundle of parameterized request messages. Each\n> +request message may optionally define a parameterized response message. Here's\n> +an example to define an interface `Foo` with various kinds of requests:\n> +\n> +```\n> +interface Foo {\n> +  // A request which takes no arguments and expects no response.\n> +  MyMessage();\n> +\n> +  // A request which has some arguments and expects no response.\n> +  MyOtherMessage(string name, array<uint8> bytes);\n> +\n> +  // A request which expects a single-argument response.\n> +  MyMessageWithResponse(string command) => (bool success);\n> +\n> +  // A request which expects a response with multiple arguments.\n> +  MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d);\n> +};\n> +```\n> +\n> +Anything which is a valid struct field type (see [Structs](#Structs)) is also a\n> +valid request or response argument type. The type notation is the same for both.\n> +\n> +### Attributes\n> +\n> +Mojom definitions may have their meaning altered by **attributes**, specified\n> +with a syntax similar to Java or C# attributes. There are a handle of\n> +interesting attributes supported today.\n> +\n> +**`[Sync]`**\n> +:   The `Sync` attribute may be specified for any interface method which expects\n> +    a response. This makes it so that callers of the method can wait\n> +    synchronously for a response. See\n> +    [Synchronous Calls](/mojo/public/cpp/bindings/README.md#Synchronous-Calls)\n> +    in the C++ bindings documentation. Note that sync methods are only actually\n> +    synchronous when called from C++.\n> +\n> +**`[Extensible]`**\n> +:   The `Extensible` attribute may be specified for any enum definition. This\n> +    essentially disables builtin range validation when receiving values of the\n> +    enum type in a message, allowing older bindings to tolerate unrecognized\n> +    values from newer versions of the enum.\n> +\n> +**`[Native]`**\n> +:   The `Native` attribute may be specified for an empty struct declaration to\n> +    provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or\n> +    `IPC_STRUCT_TRAITS*` macros.\n> +    See\n> +    [Repurposing Legacy IPC Traits](/docs/mojo_ipc_conversion.md#repurposing-and-invocations)\n> +    for more details. Note support for this attribute is strictly limited to C++\n> +    bindings generation.\n> +\n> +**`[MinVersion=N]`**\n> +:   The `MinVersion` attribute is used to specify the version at which a given\n> +    field, enum value, interface method, or method parameter was introduced.\n> +    See [Versioning](#Versioning) for more details.\n> +\n> +**`[Stable]`**\n> +:   The `Stable` attribute specifies that a given mojom type or interface\n> +    definition can be considered stable over time, meaning it is safe to use for\n> +    things like persistent storage or communication between independent\n> +    version-skewed binaries. Stable definitions may only depend on builtin mojom\n> +    types or other stable definitions, and changes to such definitions MUST\n> +    preserve backward-compatibility through appropriate use of versioning.\n> +    Backward-compatibility of changes is enforced in the Chromium tree using a\n> +    strict presubmit check. See [Versioning](#Versioning) for more details on\n> +    backward-compatibility constraints.\n> +\n> +**`[EnableIf=value]`**\n> +:   The `EnableIf` attribute is used to conditionally enable definitions when\n> +    the mojom is parsed. If the `mojom` target in the GN file does not include\n> +    the matching `value` in the list of `enabled_features`, the definition\n> +    will be disabled. This is useful for mojom definitions that only make\n> +    sense on one platform. Note that the `EnableIf` attribute can only be set\n> +    once per definition.\n> +\n> +## Generated Code For Target Languages\n> +\n> +When the bindings generator successfully processes an input Mojom file, it emits\n> +corresponding code for each supported target language. For more details on how\n> +Mojom concepts translate to a given target langauge, please refer to the\n> +bindings API documentation for that language:\n> +\n> +* [C++ Bindings](/mojo/public/cpp/bindings/README.md)\n> +* [JavaScript Bindings](/mojo/public/js/README.md)\n> +* [Java Bindings](/mojo/public/java/bindings/README.md)\n> +\n> +## Message Validation\n> +\n> +Regardless of target language, all interface messages are validated during\n> +deserialization before they are dispatched to a receiving implementation of the\n> +interface. This helps to ensure consitent validation across interfaces without\n> +leaving the burden to developers and security reviewers every time a new message\n> +is added.\n> +\n> +If a message fails validation, it is never dispatched. Instead a **connection\n> +error** is raised on the binding object (see\n> +[C++ Connection Errors](/mojo/public/cpp/bindings/README.md#Connection-Errors),\n> +[Java Connection Errors](/mojo/public/java/bindings/README.md#Connection-Errors),\n> +or\n> +[JavaScript Connection Errors](/mojo/public/js/README.md#Connection-Errors) for\n> +details.)\n> +\n> +Some baseline level of validation is done automatically for primitive Mojom\n> +types.\n> +\n> +### Non-Nullable Objects\n> +\n> +Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*)\n> +may be marked nullable in Mojom definitions (see\n> +[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked\n> +nullable but a message is received with a null value in its place, that message\n> +will fail validation.\n> +\n> +### Enums\n> +\n> +Enums declared in Mojom are automatically validated against the range of legal\n> +values. For example if a Mojom declares the enum:\n> +\n> +``` cpp\n> +enum AdvancedBoolean {\n> +  TRUE = 0,\n> +  FALSE = 1,\n> +  FILE_NOT_FOUND = 2,\n> +};\n> +```\n> +\n> +and a message is received with the integral value 3 (or anything other than 0,\n> +1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will\n> +fail validation.\n> +\n> +*** note\n> +NOTE: It's possible to avoid this type of validation error by explicitly marking\n> +an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged\n> +between two different versions of the binding interface. See\n> +[Versioning](#Versioning).\n> +***\n> +\n> +### Other failures\n> +\n> +There are a  host of internal validation errors that may occur when a malformed\n> +message is received, but developers should not be concerned with these\n> +specifically; in general they can only result from internal bindings bugs,\n> +compromised processes, or some remote endpoint making a dubious effort to\n> +manually encode their own bindings messages.\n> +\n> +### Custom Validation\n> +\n> +It's also possible for developers to define custom validation logic for specific\n> +Mojom struct types by exploiting the\n> +[type mapping](/mojo/public/cpp/bindings/README.md#Type-Mapping) system for C++\n> +bindings. Messages rejected by custom validation logic trigger the same\n> +validation failure behavior as the built-in type validation routines.\n> +\n> +## Associated Interfaces\n> +\n> +As mentioned in the [Primitive Types](#Primitive-Types) section above, pending_remote\n> +and pending_receiver fields and parameters may be marked as `associated`. This\n> +essentially means that they are piggy-backed on some other interface's message\n> +pipe.\n> +\n> +Because individual interface message pipes operate independently there can be no\n> +relative ordering guarantees among them. Associated interfaces are useful when\n> +one interface needs to guarantee strict FIFO ordering with respect to one or\n> +more other interfaces, as they allow interfaces to share a single pipe.\n> +\n> +Currently associated interfaces are only supported in generated C++ bindings.\n> +See the documentation for\n> +[C++ Associated Interfaces](/mojo/public/cpp/bindings/README.md#Associated-Interfaces).\n> +\n> +## Versioning\n> +\n> +### Overview\n> +\n> +*** note\n> +**NOTE:** You don't need to worry about versioning if you don't care about\n> +backwards compatibility. Specifically, all parts of Chrome are updated\n> +atomically today and there is not yet any possibility of any two Chrome\n> +processes communicating with two different versions of any given Mojom\n> +interface.\n> +***\n> +\n> +Services extend their interfaces to support new features over time, and clients\n> +want to use those new features when they are available. If services and clients\n> +are not updated at the same time, it's important for them to be able to\n> +communicate with each other using different snapshots (versions) of their\n> +interfaces.\n> +\n> +This document shows how to extend Mojom interfaces in a backwards-compatible\n> +way. Changing interfaces in a non-backwards-compatible way is not discussed,\n> +because in that case communication between different interface versions is\n> +impossible anyway.\n> +\n> +### Versioned Structs\n> +\n> +You can use the `MinVersion` [attribute](#Attributes) to indicate from which\n> +version a struct field is introduced. Assume you have the following struct:\n> +\n> +``` cpp\n> +struct Employee {\n> +  uint64 employee_id;\n> +  string name;\n> +};\n> +```\n> +\n> +and you would like to add a birthday field. You can do:\n> +\n> +``` cpp\n> +struct Employee {\n> +  uint64 employee_id;\n> +  string name;\n> +  [MinVersion=1] Date? birthday;\n> +};\n> +```\n> +\n> +By default, fields belong to version 0. New fields must be appended to the\n> +struct definition (*i.e*., existing fields must not change **ordinal value**)\n> +with the `MinVersion` attribute set to a number greater than any previous\n> +existing versions.\n> +\n> +*** note\n> +**NOTE:** do not change existing fields in versioned structs, as this is\n> +not backwards-compatible. Instead, rename the old field to make its\n> +deprecation clear and add a new field with the new version number.\n> +***\n> +\n> +**Ordinal value** refers to the relative positional layout of a struct's fields\n> +(and an interface's methods) when encoded in a message. Implicitly, ordinal\n> +numbers are assigned to fields according to lexical position. In the example\n> +above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value\n> +of 1.\n> +\n> +Ordinal values can be specified explicitly using `**@**` notation, subject to\n> +the following hard constraints:\n> +\n> +* For any given struct or interface, if any field or method explicitly specifies\n> +    an ordinal value, all fields or methods must explicitly specify an ordinal\n> +    value.\n> +* For an *N*-field struct or *N*-method interface, the set of explicitly\n> +    assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces\n> +    should include placeholder methods to fill the ordinal positions of removed\n> +    methods (for example \"Unused_Message_7@7()\" or \"RemovedMessage@42()\", etc).\n> +\n> +You may reorder fields, but you must ensure that the ordinal values of existing\n> +fields remain unchanged. For example, the following struct remains\n> +backwards-compatible:\n> +\n> +``` cpp\n> +struct Employee {\n> +  uint64 employee_id@0;\n> +  [MinVersion=1] Date? birthday@2;\n> +  string name@1;\n> +};\n> +```\n> +\n> +*** note\n> +**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable.\n> +See [Primitive Types](#Primitive-Types).\n> +***\n> +\n> +### Versioned Interfaces\n> +\n> +There are two dimensions on which an interface can be extended\n> +\n> +**Appending New Parameters To Existing Methods**\n> +:   Parameter lists are treated as structs internally, so all the rules of\n> +    versioned structs apply to method parameter lists. The only difference is\n> +    that the version number is scoped to the whole interface rather than to any\n> +    individual parameter list.\n> +\n> +    Please note that adding a response to a message which did not previously\n> +    expect a response is a not a backwards-compatible change.\n> +\n> +**Appending New Methods**\n> +:   Similarly, you can reorder methods with explicit ordinal values as long as\n> +    the ordinal values of existing methods are unchanged.\n> +\n> +For example:\n> +\n> +``` cpp\n> +// Old version:\n> +interface HumanResourceDatabase {\n> +  AddEmployee(Employee employee) => (bool success);\n> +  QueryEmployee(uint64 id) => (Employee? employee);\n> +};\n> +\n> +// New version:\n> +interface HumanResourceDatabase {\n> +  AddEmployee(Employee employee) => (bool success);\n> +\n> +  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n> +      => (Employee? employee,\n> +          [MinVersion=1] array<uint8>? finger_print);\n> +\n> +  [MinVersion=1]\n> +  AttachFingerPrint(uint64 id, array<uint8> finger_print)\n> +      => (bool success);\n> +};\n> +```\n> +\n> +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n> +list of a request or response method to a destination using an older version of\n> +an interface, unrecognized fields are silently discarded. However, if the method\n> +call itself is not recognized, it is considered a validation error and the\n> +receiver will close its end of the interface pipe. For example, if a client on\n> +version 1 of the above interface sends an `AttachFingerPrint` request to an\n> +implementation of version 0, the client will be disconnected.\n> +\n> +Bindings target languages that support versioning expose means to query or\n> +assert the remote version from a client handle (*e.g.*, an\n> +`mojo::Remote<T>` in C++ bindings.)\n> +\n> +See\n> +[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)\n> +and\n> +[Java Versioning Considerations](/mojo/public/java/bindings/README.md#Versioning-Considerations)\n> +\n> +### Versioned Enums\n> +\n> +**By default, enums are non-extensible**, which means that generated message\n> +validation code does not expect to see new values in the future. When an unknown\n> +value is seen for a non-extensible enum field or parameter, a validation error\n> +is raised.\n> +\n> +If you want an enum to be extensible in the future, you can apply the\n> +`[Extensible]` [attribute](#Attributes):\n> +\n> +``` cpp\n> +[Extensible]\n> +enum Department {\n> +  SALES,\n> +  DEV,\n> +};\n> +```\n> +\n> +And later you can extend this enum without breaking backwards compatibility:\n> +\n> +``` cpp\n> +[Extensible]\n> +enum Department {\n> +  SALES,\n> +  DEV,\n> +  [MinVersion=1] RESEARCH,\n> +};\n> +```\n> +\n> +*** note\n> +**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute\n> +is strictly for documentation purposes. It has no impact on the generated code.\n> +***\n> +\n> +With extensible enums, bound interface implementations may receive unknown enum\n> +values and will need to deal with them gracefully. See\n> +[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)\n> +for details.\n> +\n> +## Grammar Reference\n> +\n> +Below is the (BNF-ish) context-free grammar of the Mojom language:\n> +\n> +```\n> +MojomFile = StatementList\n> +StatementList = Statement StatementList | Statement\n> +Statement = ModuleStatement | ImportStatement | Definition\n> +\n> +ModuleStatement = AttributeSection \"module\" Identifier \";\"\n> +ImportStatement = \"import\" StringLiteral \";\"\n> +Definition = Struct Union Interface Enum Const\n> +\n> +AttributeSection = <empty> | \"[\" AttributeList \"]\"\n> +AttributeList = <empty> | NonEmptyAttributeList\n> +NonEmptyAttributeList = Attribute\n> +                      | Attribute \",\" NonEmptyAttributeList\n> +Attribute = Name\n> +          | Name \"=\" Name\n> +          | Name \"=\" Literal\n> +\n> +Struct = AttributeSection \"struct\" Name \"{\" StructBody \"}\" \";\"\n> +       | AttributeSection \"struct\" Name \";\"\n> +StructBody = <empty>\n> +           | StructBody Const\n> +           | StructBody Enum\n> +           | StructBody StructField\n> +StructField = AttributeSection TypeSpec Name Ordinal Default \";\"\n> +\n> +Union = AttributeSection \"union\" Name \"{\" UnionBody \"}\" \";\"\n> +UnionBody = <empty> | UnionBody UnionField\n> +UnionField = AttributeSection TypeSpec Name Ordinal \";\"\n> +\n> +Interface = AttributeSection \"interface\" Name \"{\" InterfaceBody \"}\" \";\"\n> +InterfaceBody = <empty>\n> +              | InterfaceBody Const\n> +              | InterfaceBody Enum\n> +              | InterfaceBody Method\n> +Method = AttributeSection Name Ordinal \"(\" ParamterList \")\" Response \";\"\n> +ParameterList = <empty> | NonEmptyParameterList\n> +NonEmptyParameterList = Parameter\n> +                      | Parameter \",\" NonEmptyParameterList\n> +Parameter = AttributeSection TypeSpec Name Ordinal\n> +Response = <empty> | \"=>\" \"(\" ParameterList \")\"\n> +\n> +TypeSpec = TypeName \"?\" | TypeName\n> +TypeName = BasicTypeName\n> +         | Array\n> +         | FixedArray\n> +         | Map\n> +         | InterfaceRequest\n> +BasicTypeName = Identifier | \"associated\" Identifier | HandleType | NumericType\n> +NumericType = \"bool\" | \"int8\" | \"uint8\" | \"int16\" | \"uint16\" | \"int32\"\n> +            | \"uint32\" | \"int64\" | \"uint64\" | \"float\" | \"double\"\n> +HandleType = \"handle\" | \"handle\" \"<\" SpecificHandleType \">\"\n> +SpecificHandleType = \"message_pipe\"\n> +                   | \"shared_buffer\"\n> +                   | \"data_pipe_consumer\"\n> +                   | \"data_pipe_producer\"\n> +                   | \"platform\"\n> +Array = \"array\" \"<\" TypeSpec \">\"\n> +FixedArray = \"array\" \"<\" TypeSpec \",\" IntConstDec \">\"\n> +Map = \"map\" \"<\" Identifier \",\" TypeSpec \">\"\n> +InterfaceRequest = Identifier \"&\" | \"associated\" Identifier \"&\"\n> +\n> +Ordinal = <empty> | OrdinalValue\n> +\n> +Default = <empty> | \"=\" Constant\n> +\n> +Enum = AttributeSection \"enum\" Name \"{\" NonEmptyEnumValueList \"}\" \";\"\n> +     | AttributeSection \"enum\" Name \"{\" NonEmptyEnumValueList \",\" \"}\" \";\"\n> +NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList \",\" EnumValue\n> +EnumValue = AttributeSection Name\n> +          | AttributeSection Name \"=\" Integer\n> +          | AttributeSection Name \"=\" Identifier\n> +\n> +Const = \"const\" TypeSpec Name \"=\" Constant \";\"\n> +\n> +Constant = Literal | Identifier \";\"\n> +\n> +Identifier = Name | Name \".\" Identifier\n> +\n> +Literal = Integer | Float | \"true\" | \"false\" | \"default\" | StringLiteral\n> +\n> +Integer = IntConst | \"+\" IntConst | \"-\" IntConst\n> +IntConst = IntConstDec | IntConstHex\n> +\n> +Float = FloatConst | \"+\" FloatConst | \"-\" FloatConst\n> +\n> +; The rules below are for tokens matched strictly according to the given regexes\n> +\n> +Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/\n> +IntConstDec = /0|(1-9[0-9]*)/\n> +IntConstHex = /0[xX][0-9a-fA-F]+/\n> +OrdinalValue = /@(0|(1-9[0-9]*))/\n> +FloatConst = ... # Imagine it's close enough to C-style float syntax.\n> +StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes.\n> +```\n> +\n> +## Additional Documentation\n> +\n> +[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit)\n> +:    Describes the wire format used by Mojo bindings interfaces over message\n> +     pipes.\n> +\n> +[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit)\n> +:    Describes a text format used to facilitate bindings message validation\n> +     tests.\n> diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> new file mode 100644\n> index 00000000..d8a13874\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> @@ -0,0 +1,51 @@\n> +# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +_typemap_imports = [\n> +  \"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni\",\n> +  \"//chrome/common/importer/typemaps.gni\",\n> +  \"//chrome/common/media_router/mojom/typemaps.gni\",\n> +  \"//chrome/typemaps.gni\",\n> +  \"//chromecast/typemaps.gni\",\n> +  \"//chromeos/typemaps.gni\",\n> +  \"//chromeos/components/multidevice/mojom/typemaps.gni\",\n> +  \"//chromeos/services/cros_healthd/public/mojom/typemaps.gni\",\n> +  \"//chromeos/services/device_sync/public/mojom/typemaps.gni\",\n> +  \"//chromeos/services/network_config/public/mojom/typemaps.gni\",\n> +  \"//chromeos/services/secure_channel/public/mojom/typemaps.gni\",\n> +  \"//components/arc/mojom/typemaps.gni\",\n> +  \"//components/chromeos_camera/common/typemaps.gni\",\n> +  \"//components/services/storage/public/cpp/filesystem/typemaps.gni\",\n> +  \"//components/sync/mojom/typemaps.gni\",\n> +  \"//components/typemaps.gni\",\n> +  \"//content/browser/typemaps.gni\",\n> +  \"//content/public/common/typemaps.gni\",\n> +  \"//sandbox/mac/mojom/typemaps.gni\",\n> +  \"//services/media_session/public/cpp/typemaps.gni\",\n> +  \"//services/proxy_resolver/public/cpp/typemaps.gni\",\n> +  \"//services/resource_coordinator/public/cpp/typemaps.gni\",\n> +  \"//services/service_manager/public/cpp/typemaps.gni\",\n> +  \"//services/tracing/public/mojom/typemaps.gni\",\n> +]\n> +\n> +_typemaps = []\n> +foreach(typemap_import, _typemap_imports) {\n> +  # Avoid reassignment error by assigning to empty scope first.\n> +  _imported = {\n> +  }\n> +  _imported = read_file(typemap_import, \"scope\")\n> +  _typemaps += _imported.typemaps\n> +}\n> +\n> +typemaps = []\n> +foreach(typemap, _typemaps) {\n> +  typemaps += [\n> +    {\n> +      filename = typemap\n> +      config = read_file(typemap, \"scope\")\n> +    },\n> +  ]\n> +}\n> +\n> +component_macro_suffix = \"\"\n> diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> new file mode 100644\n> index 00000000..a978901b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> @@ -0,0 +1,27 @@\n> +# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import os\n> +import sys\n> +import argparse\n> +\n> +_HERE_PATH = os.path.dirname(__file__)\n> +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n> +\n> +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n> +import node\n> +import node_modules\n> +\n> +def main(argv):\n> +  parser = argparse.ArgumentParser()\n> +  parser.add_argument('--tsconfig_path', required=True)\n> +  args = parser.parse_args(argv)\n> +\n> +  result = node.RunNode([node_modules.PathToTypescript()] +\n> +                        ['--project', args.tsconfig_path])\n> +  if len(result) != 0:\n> +    raise RuntimeError('Failed to compile Typescript: \\n%s' % result)\n> +\n> +if __name__ == '__main__':\n> +  main(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> new file mode 100755\n> index 00000000..48bc66fd\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> @@ -0,0 +1,54 @@\n> +#!/usr/bin/env python\n> +# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +#\n> +# This utility concatenates several files into one. On Unix-like systems\n> +# it is equivalent to:\n> +#   cat file1 file2 file3 ...files... > target\n> +#\n> +# The reason for writing a separate utility is that 'cat' is not available\n> +# on all supported build platforms, but Python is, and hence this provides\n> +# us with an easy and uniform way of doing this on all platforms.\n> +\n> +# for py2/py3 compatibility\n> +from __future__ import print_function\n> +\n> +import optparse\n> +\n> +\n> +def Concatenate(filenames):\n> +  \"\"\"Concatenate files.\n> +\n> +  Args:\n> +    files: Array of file names.\n> +           The last name is the target; all earlier ones are sources.\n> +\n> +  Returns:\n> +    True, if the operation was successful.\n> +  \"\"\"\n> +  if len(filenames) < 2:\n> +    print(\"An error occurred generating %s:\\nNothing to do.\" % filenames[-1])\n> +    return False\n> +\n> +  try:\n> +    with open(filenames[-1], \"wb\") as target:\n> +      for filename in filenames[:-1]:\n> +        with open(filename, \"rb\") as current:\n> +          target.write(current.read())\n> +    return True\n> +  except IOError as e:\n> +    print(\"An error occurred when writing %s:\\n%s\" % (filenames[-1], e))\n> +    return False\n> +\n> +\n> +def main():\n> +  parser = optparse.OptionParser()\n> +  parser.set_usage(\"\"\"Concatenate several files into one.\n> +      Equivalent to: cat file1 ... > target.\"\"\")\n> +  (_options, args) = parser.parse_args()\n> +  exit(0 if Concatenate(args) else 1)\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> new file mode 100755\n> index 00000000..be8985ce\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> @@ -0,0 +1,73 @@\n> +#!/usr/bin/env python\n> +# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +\"\"\"Simple utility which concatenates a set of files into a single output file\n> +while also stripping any goog.provide or goog.require lines. This allows us to\n> +provide a very primitive sort of \"compilation\" without any extra toolchain\n> +support and without having to modify otherwise compilable sources in the tree\n> +which use these directives.\n> +\n> +goog.provide lines are replaced with an equivalent invocation of\n> +mojo.internal.exportModule, which accomplishes essentially the same thing in an\n> +uncompiled context. A singular exception is made for the 'mojo.internal' export,\n> +which is instead replaced with an inlined assignment to initialize the\n> +namespace.\n> +\"\"\"\n> +\n> +from __future__ import print_function\n> +\n> +import optparse\n> +import re\n> +\n> +\n> +_MOJO_INTERNAL_MODULE_NAME = \"mojo.internal\"\n> +_MOJO_EXPORT_MODULE_SYMBOL = \"mojo.internal.exportModule\"\n> +\n> +\n> +def FilterLine(filename, line, output):\n> +  if line.startswith(\"goog.require\"):\n> +    return\n> +\n> +  if line.startswith(\"goog.provide\"):\n> +    match = re.match(\"goog.provide\\('([^']+)'\\);\", line)\n> +    if not match:\n> +      print(\"Invalid goog.provide line in %s:\\n%s\" % (filename, line))\n> +      exit(1)\n> +\n> +    module_name = match.group(1)\n> +    if module_name == _MOJO_INTERNAL_MODULE_NAME:\n> +      output.write(\"self.mojo = { internal: {} };\")\n> +    else:\n> +      output.write(\"%s('%s');\\n\" % (_MOJO_EXPORT_MODULE_SYMBOL, module_name))\n> +    return\n> +\n> +  output.write(line)\n> +\n> +def ConcatenateAndReplaceExports(filenames):\n> +  if (len(filenames) < 2):\n> +    print(\"At least two filenames (one input and the output) are required.\")\n> +    return False\n> +\n> +  try:\n> +    with open(filenames[-1], \"w\") as target:\n> +      for filename in filenames[:-1]:\n> +        with open(filename, \"r\") as current:\n> +          for line in current.readlines():\n> +            FilterLine(filename, line, target)\n> +    return True\n> +  except IOError as e:\n> +    print(\"Error generating %s\\n: %s\" % (filenames[-1], e))\n> +    return False\n> +\n> +def main():\n> +  parser = optparse.OptionParser()\n> +  parser.set_usage(\"\"\"file1 [file2...] outfile\n> +    Concatenate several files into one, stripping Closure provide and\n> +    require directives along the way.\"\"\")\n> +  (_, args) = parser.parse_args()\n> +  exit(0 if ConcatenateAndReplaceExports(args) else 1)\n> +\n> +if __name__ == \"__main__\":\n> +  main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> new file mode 100755\n> index 00000000..7ac4af5f\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> @@ -0,0 +1,36 @@\n> +#!/usr/bin/env python\n> +# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from __future__ import print_function\n> +\n> +import sys\n> +\n> +# This utility converts mojom dependencies into their corresponding typemap\n> +# paths and formats them to be consumed by generate_type_mappings.py.\n> +\n> +\n> +def FormatTypemap(typemap_filename):\n> +  # A simple typemap is valid Python with a minor alteration.\n> +  with open(typemap_filename) as f:\n> +    typemap_content = f.read().replace('=\\n', '=')\n> +  typemap = {}\n> +  exec typemap_content in typemap\n> +\n> +  for header in typemap.get('public_headers', []):\n> +    yield 'public_headers=%s' % header\n> +  for header in typemap.get('traits_headers', []):\n> +    yield 'traits_headers=%s' % header\n> +  for header in typemap.get('type_mappings', []):\n> +    yield 'type_mappings=%s' % header\n> +\n> +\n> +def main():\n> +  typemaps = sys.argv[1:]\n> +  print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))\n> +                 for typemap in typemaps))\n> +\n> +\n> +if __name__ == '__main__':\n> +  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> new file mode 100644\n> index 00000000..79c9e50e\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> @@ -0,0 +1,52 @@\n> +# Copyright 2017 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Generates a list of all files in a directory.\n> +\n> +This script takes in a directory and an output file name as input.\n> +It then reads the directory and creates a list of all file names\n> +in that directory.  The list is written to the output file.\n> +There is also an option to pass in '-p' or '--pattern'\n> +which will check each file name against a regular expression\n> +pattern that is passed in.  Only files which match the regex\n> +will be written to the list.\n> +\"\"\"\n> +\n> +from __future__ import print_function\n> +\n> +import os\n> +import re\n> +import sys\n> +\n> +from cStringIO import StringIO\n> +from optparse import OptionParser\n> +\n> +sys.path.insert(\n> +    0,\n> +    os.path.join(\n> +        os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n> +\n> +from mojom.generate.generator import WriteFile\n> +\n> +\n> +def main():\n> +  parser = OptionParser()\n> +  parser.add_option('-d', '--directory', help='Read files from DIRECTORY')\n> +  parser.add_option('-o', '--output', help='Write list to FILE')\n> +  parser.add_option('-p',\n> +                    '--pattern',\n> +                    help='Only reads files that name matches PATTERN',\n> +                    default=\".\")\n> +  (options, _) = parser.parse_args()\n> +  pattern = re.compile(options.pattern)\n> +  files = [f for f in os.listdir(options.directory) if pattern.match(f)]\n> +\n> +  stream = StringIO()\n> +  for f in files:\n> +    print(f, file=stream)\n> +\n> +  WriteFile(stream.getvalue(), options.output)\n> +  stream.close()\n> +\n> +if __name__ == '__main__':\n> +  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> new file mode 100755\n> index 00000000..64ca048f\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> @@ -0,0 +1,187 @@\n> +#!/usr/bin/env python\n> +# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Generates a JSON typemap from its command-line arguments and dependencies.\n> +\n> +Each typemap should be specified in an command-line argument of the form\n> +key=value, with an argument of \"--start-typemap\" preceding each typemap.\n> +\n> +For example,\n> +generate_type_mappings.py --output=foo.typemap --start-typemap \\\\\n> +    public_headers=foo.h traits_headers=foo_traits.h \\\\\n> +    type_mappings=mojom.Foo=FooImpl\n> +\n> +generates a foo.typemap containing\n> +{\n> +  \"c++\": {\n> +    \"mojom.Foo\": {\n> +      \"typename\": \"FooImpl\",\n> +      \"traits_headers\": [\n> +        \"foo_traits.h\"\n> +      ],\n> +      \"public_headers\": [\n> +        \"foo.h\"\n> +      ]\n> +    }\n> +  }\n> +}\n> +\n> +Then,\n> +generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\\\\n> +    --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\\\\n> +    type_mappings=mojom.Bar=BarImpl\n> +\n> +generates a bar.typemap containing\n> +{\n> +  \"c++\": {\n> +    \"mojom.Bar\": {\n> +      \"typename\": \"BarImpl\",\n> +      \"traits_headers\": [\n> +        \"bar_traits.h\"\n> +      ],\n> +      \"public_headers\": [\n> +        \"bar.h\"\n> +      ]\n> +    },\n> +    \"mojom.Foo\": {\n> +      \"typename\": \"FooImpl\",\n> +      \"traits_headers\": [\n> +        \"foo_traits.h\"\n> +      ],\n> +      \"public_headers\": [\n> +        \"foo.h\"\n> +      ]\n> +    }\n> +  }\n> +}\n> +\"\"\"\n> +\n> +import argparse\n> +import json\n> +import os\n> +import re\n> +import sys\n> +\n> +sys.path.insert(\n> +    0,\n> +    os.path.join(\n> +        os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n> +\n> +from mojom.generate.generator import WriteFile\n> +\n> +def ReadTypemap(path):\n> +  with open(path) as f:\n> +    return json.load(f)['c++']\n> +\n> +\n> +def ParseTypemapArgs(args):\n> +  typemaps = [s for s in '\\n'.join(args).split('--start-typemap\\n') if s]\n> +  result = {}\n> +  for typemap in typemaps:\n> +    result.update(ParseTypemap(typemap))\n> +  return result\n> +\n> +\n> +def LoadCppTypemapConfig(path):\n> +  configs = {}\n> +  with open(path) as f:\n> +    for config in json.load(f):\n> +      for entry in config['types']:\n> +        configs[entry['mojom']] = {\n> +            'typename': entry['cpp'],\n> +            'public_headers': config.get('traits_headers', []),\n> +            'traits_headers': config.get('traits_private_headers', []),\n> +            'copyable_pass_by_value': entry.get('copyable_pass_by_value',\n> +                                                False),\n> +            'force_serialize': entry.get('force_serialize', False),\n> +            'hashable': entry.get('hashable', False),\n> +            'move_only': entry.get('move_only', False),\n> +            'nullable_is_same_type': entry.get('nullable_is_same_type', False),\n> +            'non_copyable_non_movable': False,\n> +        }\n> +  return configs\n> +\n> +\n> +def ParseTypemap(typemap):\n> +  values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []}\n> +  for line in typemap.split('\\n'):\n> +    if not line:\n> +      continue\n> +    key, _, value = line.partition('=')\n> +    values[key].append(value.lstrip('/'))\n> +  result = {}\n> +  mapping_pattern = \\\n> +      re.compile(r\"\"\"^([^=]+)           # mojom type\n> +                     =\n> +                     ([^[]+)            # native type\n> +                     (?:\\[([^]]+)\\])?$  # optional attribute in square brackets\n> +                 \"\"\", re.X)\n> +  for typename in values['type_mappings']:\n> +    match_result = mapping_pattern.match(typename)\n> +    assert match_result, (\n> +        \"Cannot parse entry in the \\\"type_mappings\\\" section: %s\" % typename)\n> +\n> +    mojom_type = match_result.group(1)\n> +    native_type = match_result.group(2)\n> +    attributes = []\n> +    if match_result.group(3):\n> +      attributes = match_result.group(3).split(',')\n> +\n> +    assert mojom_type not in result, (\n> +        \"Cannot map multiple native types (%s, %s) to the same mojom type: %s\" %\n> +        (result[mojom_type]['typename'], native_type, mojom_type))\n> +\n> +    result[mojom_type] = {\n> +        'public_headers': values['public_headers'],\n> +        'traits_headers': values['traits_headers'],\n> +        'typename': native_type,\n> +\n> +        # Attributes supported for individual mappings.\n> +        'copyable_pass_by_value': 'copyable_pass_by_value' in attributes,\n> +        'force_serialize': 'force_serialize' in attributes,\n> +        'hashable': 'hashable' in attributes,\n> +        'move_only': 'move_only' in attributes,\n> +        'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,\n> +        'nullable_is_same_type': 'nullable_is_same_type' in attributes,\n> +    }\n> +  return result\n> +\n> +\n> +def main():\n> +  parser = argparse.ArgumentParser(\n> +      description=__doc__,\n> +      formatter_class=argparse.RawDescriptionHelpFormatter)\n> +  parser.add_argument(\n> +      '--dependency',\n> +      type=str,\n> +      action='append',\n> +      default=[],\n> +      help=('A path to another JSON typemap to merge into the output. '\n> +            'This may be repeated to merge multiple typemaps.'))\n> +  parser.add_argument(\n> +      '--cpp-typemap-config',\n> +      type=str,\n> +      action='store',\n> +      dest='cpp_config_path',\n> +      help=('A path to a single JSON-formatted typemap config as emitted by'\n> +            'GN when processing a mojom_cpp_typemap build rule.'))\n> +  parser.add_argument('--output',\n> +                      type=str,\n> +                      required=True,\n> +                      help='The path to which to write the generated JSON.')\n> +  params, typemap_params = parser.parse_known_args()\n> +  typemaps = ParseTypemapArgs(typemap_params)\n> +  if params.cpp_config_path:\n> +    typemaps.update(LoadCppTypemapConfig(params.cpp_config_path))\n> +  missing = [path for path in params.dependency if not os.path.exists(path)]\n> +  if missing:\n> +    raise IOError('Missing dependencies: %s' % ', '.join(missing))\n> +  for path in params.dependency:\n> +    typemaps.update(ReadTypemap(path))\n> +\n> +  WriteFile(json.dumps({'c++': typemaps}, indent=2), params.output)\n> +\n> +\n> +if __name__ == '__main__':\n> +  main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> new file mode 100644\n> index 00000000..a739fa6e\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> @@ -0,0 +1,1941 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import(\"//build/config/jumbo.gni\")\n> +import(\"//third_party/closure_compiler/closure_args.gni\")\n> +import(\"//third_party/closure_compiler/compile_js.gni\")\n> +import(\"//third_party/protobuf/proto_library.gni\")\n> +import(\"//ui/webui/webui_features.gni\")\n> +\n> +# TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're\n> +# used to conditionally enable message ID scrambling in a way which is\n> +# consistent across toolchains and which is affected by branded vs non-branded\n> +# Chrome builds. Ideally we could create some generic knobs here that could be\n> +# flipped elsewhere though.\n> +import(\"//build/config/chrome_build.gni\")\n> +import(\"//build/config/chromecast_build.gni\")\n> +import(\"//build/config/chromeos/ui_mode.gni\")\n> +import(\"//build/config/nacl/config.gni\")\n> +import(\"//build/toolchain/kythe.gni\")\n> +import(\"//components/nacl/features.gni\")\n> +import(\"//third_party/jinja2/jinja2.gni\")\n> +import(\"//tools/ipc_fuzzer/ipc_fuzzer.gni\")\n> +declare_args() {\n> +  # Indicates whether typemapping should be supported in this build\n> +  # configuration. This may be disabled when building external projects which\n> +  # depend on //mojo but which do not need/want all of the Chromium tree\n> +  # dependencies that come with typemapping.\n> +  #\n> +  # Note that (perhaps obviously) a huge amount of Chromium code will not build\n> +  # with typemapping disabled, so it is never valid to set this to |false| in\n> +  # any Chromium build configuration.\n> +  enable_mojom_typemapping = true\n> +\n> +  # Controls message ID scrambling behavior. If |true|, message IDs are\n> +  # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on\n> +  # non-Chrome OS desktop platforms. Set to |false| to disable message ID\n> +  # scrambling on all platforms.\n> +  enable_mojom_message_id_scrambling = true\n> +\n> +  # Enables Closure compilation of generated JS lite bindings. In environments\n> +  # where compilation is supported, any mojom target \"foo\" will also have a\n> +  # corresponding \"foo_js_library_for_compile\" target generated.\n> +  enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n> +\n> +  # Enables generating Typescript bindings and compiling them to JS bindings.\n> +  enable_typescript_bindings = false\n> +\n> +  # Enables generating javascript fuzzing-related code and the bindings for the\n> +  # MojoLPM fuzzer targets. Off by default.\n> +  enable_mojom_fuzzer = false\n> +}\n> +\n> +# NOTE: We would like to avoid scrambling message IDs where it doesn't add\n> +# value, so we limit the behavior to desktop builds for now. There is some\n> +# redundancy in the conditions here, but it is tolerated for clarity:\n> +# We're explicit about Mac, Windows, and Linux desktop support, but it's\n> +# also necessary to ensure that bindings in alternate toolchains (e.g.\n> +# NaCl IRT) are always consistent with the default toolchain; for that\n> +# reason we always enable scrambling within NaCl toolchains when possible,\n> +# as well as within the default toolchain when NaCl is enabled.\n> +#\n> +# Finally, because we *cannot* enable scrambling on Chrome OS (it would break\n> +# ARC) we have to explicitly opt out there even when NaCl is enabled (and\n> +# consequently also when building for NaCl toolchains.) For this reason we\n> +# check |target_os| explicitly, as it's consistent across all toolchains.\n> +enable_scrambled_message_ids =\n> +    enable_mojom_message_id_scrambling &&\n> +    (is_mac || is_win || (is_linux && !is_chromeos && !is_chromecast &&\n> +                          !chromeos_is_browser_only) ||\n> +     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&\n> +      (target_os != \"chromeos\" && !chromeos_is_browser_only)))\n> +\n> +_mojom_tools_root = \"//mojo/public/tools\"\n> +_mojom_library_root = \"$_mojom_tools_root/mojom/mojom\"\n> +mojom_parser_script = \"$_mojom_tools_root/mojom/mojom_parser.py\"\n> +mojom_parser_sources = [\n> +  \"$_mojom_library_root/__init__.py\",\n> +  \"$_mojom_library_root/error.py\",\n> +  \"$_mojom_library_root/generate/__init__.py\",\n> +  \"$_mojom_library_root/generate/constant_resolver.py\",\n> +  \"$_mojom_library_root/generate/generator.py\",\n> +  \"$_mojom_library_root/generate/module.py\",\n> +  \"$_mojom_library_root/generate/pack.py\",\n> +  \"$_mojom_library_root/generate/template_expander.py\",\n> +  \"$_mojom_library_root/generate/translate.py\",\n> +  \"$_mojom_library_root/parse/__init__.py\",\n> +  \"$_mojom_library_root/parse/ast.py\",\n> +  \"$_mojom_library_root/parse/lexer.py\",\n> +  \"$_mojom_library_root/parse/parser.py\",\n> +]\n> +\n> +mojom_generator_root = \"$_mojom_tools_root/bindings\"\n> +mojom_generator_script = \"$mojom_generator_root/mojom_bindings_generator.py\"\n> +mojom_generator_sources =\n> +    mojom_parser_sources + [\n> +      \"$mojom_generator_root/generators/mojom_cpp_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_java_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_js_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_ts_generator.py\",\n> +      \"$mojom_generator_script\",\n> +    ]\n> +\n> +if (enable_scrambled_message_ids) {\n> +  declare_args() {\n> +    # The path to a file whose contents can be used as the basis for a message\n> +    # ID scrambling salt.\n> +    mojom_message_id_salt_path = \"//chrome/VERSION\"\n> +\n> +    # The path to a file whose contents will be concatenated to the contents of\n> +    # the file at |mojom_message_id_salt_path| to form a complete salt for\n> +    # message ID scrambling. May be the empty string, in which case the contents\n> +    # of the above file alone are used as the complete salt.\n> +    if (is_chrome_branded) {\n> +      mojom_message_id_salt_suffix_path =\n> +          \"//mojo/internal/chrome-message-id-salt-suffix\"\n> +    } else {\n> +      mojom_message_id_salt_suffix_path = \"\"\n> +    }\n> +  }\n> +\n> +  assert(mojom_message_id_salt_path != \"\")\n> +  message_scrambling_args = [\n> +    \"--scrambled_message_id_salt_path\",\n> +    rebase_path(mojom_message_id_salt_path, root_build_dir),\n> +  ]\n> +  message_scrambling_inputs = [ mojom_message_id_salt_path ]\n> +\n> +  if (mojom_message_id_salt_suffix_path != \"\") {\n> +    message_scrambling_args += [\n> +      \"--scrambled_message_id_salt_path\",\n> +      rebase_path(mojom_message_id_salt_suffix_path, root_build_dir),\n> +    ]\n> +    message_scrambling_inputs += [ mojom_message_id_salt_suffix_path ]\n> +  }\n> +} else {\n> +  message_scrambling_args = []\n> +  message_scrambling_inputs = []\n> +}\n> +\n> +if (enable_mojom_typemapping) {\n> +  _bindings_configuration_files =\n> +      [ \"//mojo/public/tools/bindings/chromium_bindings_configuration.gni\" ]\n> +  _bindings_configurations = []\n> +  foreach(config_file, _bindings_configuration_files) {\n> +    _bindings_configurations += [ read_file(config_file, \"scope\") ]\n> +  }\n> +  foreach(configuration, _bindings_configurations) {\n> +    # Check that the mojom field of each typemap refers to a mojom that exists.\n> +    foreach(typemap, configuration.typemaps) {\n> +      _typemap_config = {\n> +      }\n> +      _typemap_config = typemap.config\n> +      read_file(_typemap_config.mojom, \"\")\n> +    }\n> +  }\n> +} else {\n> +  _bindings_configuration_files = []\n> +  _bindings_configurations = [\n> +    {\n> +      typemaps = []\n> +      component_macro_suffix = \"\"\n> +    },\n> +  ]\n> +}\n> +\n> +if (!is_ios) {\n> +  _bindings_configurations += [\n> +    {\n> +      variant = \"blink\"\n> +      component_macro_suffix = \"_BLINK\"\n> +      for_blink = true\n> +      typemaps = []\n> +    },\n> +  ]\n> +}\n> +\n> +# Generates targets for building C++, JavaScript and Java bindings from mojom\n> +# files. The output files will go under the generated file directory tree with\n> +# the same path as each input file.\n> +#\n> +# Other targets should depend on one of these generated targets (where \"foo\"\n> +# is the target name):\n> +#\n> +#   foo\n> +#       C++ bindings.\n> +#\n> +#   foo_blink\n> +#       C++ bindings using Blink standard types.\n> +#\n> +#   foo_java\n> +#       Java bindings.\n> +#\n> +#   foo_js\n> +#       JavaScript bindings; used as compile-time dependency.\n> +#\n> +#   foo_js_data_deps\n> +#       JavaScript bindings; used as run-time dependency.\n> +#\n> +# Parameters:\n> +#\n> +#   sources (optional if one of the deps sets listed below is present)\n> +#       List of source .mojom files to compile.\n> +#\n> +#   deps (optional)\n> +#       Note: this can contain only other mojom targets.\n> +#\n> +#       DEPRECATED: This is synonymous with public_deps because all mojom\n> +#       dependencies must be public by design. Please use public_deps.\n> +#\n> +#   public_deps (optional)\n> +#       Note: this can contain only other mojom targets.\n> +#\n> +#   parser_deps (optional)\n> +#       List of non-mojom targets required for the mojom sources to be parsed.\n> +#\n> +#   import_dirs (optional)\n> +#       List of import directories that will get added when processing sources.\n> +#\n> +#   testonly (optional)\n> +#\n> +#   visibility (optional)\n> +#\n> +#   visibility_blink (optional)\n> +#       The value to use for visibility for the blink variant. If unset,\n> +#       |visibility| is used.\n> +#\n> +#   cpp_only (optional)\n> +#       If set to true, only the C++ bindings targets will be generated.\n> +#\n> +#       NOTE: If the global |enable_mojom_fuzzer| build arg is true, JS bindings\n> +#       will still be generated even when |cpp_only| is set to |true|, unless\n> +#       you also set |enable_fuzzing| to |false| in your mojom target.\n> +#\n> +#   cpp_typemaps (optional)\n> +#       A list of typemaps to be applied to the generated C++ bindings for this\n> +#       mojom target. Note that this only applies to the non-Blink variant of\n> +#       generated C++ bindings.\n> +#\n> +#       Every typemap is a GN scope describing how one or more mojom types maps\n> +#       to a non-mojom C++ type, including necessary deps and headers required\n> +#       for the mapping to work. See the Typemaps section below.\n> +#\n> +#   blink_cpp_typemaps (optional)\n> +#       Same as above, but for the Blink variant of generated C++ bindings.\n> +#\n> +#   generate_java (optional)\n> +#       If set to true, Java bindings are generated for Android builds. If\n> +#       |cpp_only| is set to true, it overrides this to prevent generation of\n> +#       Java bindings.\n> +#\n> +#   enable_fuzzing (optional)\n> +#       Enables generation of fuzzing sources for the target if the global build\n> +#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If\n> +#       fuzzing generation is enabled for a target, the target will always\n> +#       generate JS bindings even if |cpp_only| is set to |true|. See note\n> +#       above.\n> +#\n> +#   support_lazy_serialization (optional)\n> +#       If set to |true|, generated C++ bindings will effectively prefer to\n> +#       transmit messages in an unserialized form when going between endpoints\n> +#       in the same process. This avoids the runtime cost of serialization,\n> +#       deserialization, and validation logic at the expensive of increased\n> +#       code size. Defaults to |false|.\n> +#\n> +#   disable_variants (optional)\n> +#       If |true|, no variant sources will be generated for the target. Defaults\n> +#       to |false|.\n> +#\n> +#   disallow_native_types (optional)\n> +#       If set to |true|, mojoms in this target may not apply the [Native]\n> +#       attribute to struct or enum declarations. This avoids emitting code\n> +#       which depends on legacy IPC serialization. Default is |false|, meaning\n> +#       [Native] types are allowed.\n> +#\n> +#   disallow_interfaces (optional)\n> +#       If set to |true|, mojoms in this target may not define interfaces.\n> +#       Generates bindings with a smaller set of dependencies. Defaults to\n> +#       |false|.\n> +#\n> +#   scramble_message_ids (optional)\n> +#       If set to |true| (the default), generated mojom interfaces will use\n> +#       scrambled ordinal identifiers in encoded messages.\n> +#\n> +#   component_output_prefix (optional)\n> +#       The prefix to use for the output_name of any component library emitted\n> +#       for generated C++ bindings. If this is omitted, C++ bindings targets are\n> +#       emitted as source_sets instead. Because this controls the name of the\n> +#       output shared library binary in the root output directory, it must be\n> +#       unique across the entire build configuration.\n> +#\n> +#       This is required if |component_macro_prefix| is specified.\n> +#\n> +#   component_macro_prefix (optional)\n> +#       This specifies a macro prefix to use for component export macros and\n> +#       should therefore be globally unique in the project. For example if this\n> +#       is \"FOO_BAR\", then the generated C++ sources will be built with\n> +#       IS_FOO_BAR_{suffix}_IMPL defined, and the generated public headers will\n> +#       annotate public symbol definitions with\n> +#       COMPONENT_EXPORT(FOO_BAR_{suffix}). \"suffix\" in this case depends on\n> +#       which internal subtarget is generating the code (e.g. \"SHARED\", or a\n> +#       variant name like \"BLINK\").\n> +#\n> +#   enabled_features (optional)\n> +#       Definitions in a mojom file can be guarded by an EnableIf attribute. If\n> +#       the value specified by the attribute does not match any items in the\n> +#       list of enabled_features, the definition will be disabled, with no code\n> +#       emitted for it.\n> +#\n> +#   generate_closure_exports (optional)\n> +#       Generates JS lite bindings will use goog.provide and goog.require\n> +#       annotations to export its symbols and import core Mojo bindings support\n> +#       and other mojom dependency modules. Use this if you plan to compile your\n> +#       bindings into a larger JS binary. Defaults to |false|, instead\n> +#       generating JS lite bindings which assume they will be manually loaded in\n> +#       correct dependency order. Note that this only has an effect if\n> +#       the |enable_mojom_closure_compile| global arg is set to |true| as well.\n> +#\n> +#   use_typescript_sources (optional)\n> +#       Uses the Typescript generator to generate JavaScript bindings.\n> +#\n> +#   js_generate_struct_deserializers (optional)\n> +#       Generates JS deerialize methods for structs.\n> +#\n> +#   extra_cpp_template_paths (optional)\n> +#       List of extra C++ templates that are used to generate additional source\n> +#       and/or header files. The templates should end with extension \".tmpl\".\n> +#\n> +# The following parameters are used to support the component build. They are\n> +# needed so that bindings which are linked with a component can use the same\n> +# export settings for classes. The first three are for the chromium variant, and\n> +# the last three are for the blink variant. These parameters can also override\n> +# |component_macro_prefix| for a specific variant, allowing e.g. one variant\n> +# to be linked into a larger non-mojom component target, while all other\n> +# variants get their own unique component target.\n> +#   export_class_attribute (optional)\n> +#       The attribute to add to the class declaration. e.g. \"CONTENT_EXPORT\"\n> +#   export_define (optional)\n> +#       A define to be added to the source_set which is needed by the export\n> +#       header. e.g. \"CONTENT_IMPLEMENTATION=1\"\n> +#   export_header (optional)\n> +#       A header to be added to the generated bindings to support the component\n> +#       build. e.g. \"content/common/content_export.h\"\n> +#   export_class_attribute_blink (optional)\n> +#   export_define_blink (optional)\n> +#   export_header_blink (optional)\n> +#       These three parameters are the blink variants of the previous 3.\n> +#\n> +# The following parameters are used to correct component build dependencies.\n> +# They are needed so mojom-mojom dependencies follow the rule that dependencies\n> +# on a source set in another component are replaced by a dependency on the\n> +# containing component. The first two are for the chromium variant; the other\n> +# two are for the blink variant.\n> +#   overridden_deps (optional)\n> +#       The list of mojom deps to be overridden.\n> +#   component_deps (optional)\n> +#       The list of component deps to add to replace overridden_deps.\n> +#   overridden_deps_blink (optional)\n> +#   component_deps_blink (optional)\n> +#       These two parameters are the blink variants of the previous two.\n> +#\n> +#   check_includes_blink (optional)\n> +#       Overrides the check_includes variable for the blink variant.\n> +#       If check_includes_blink is not defined, the check_includes variable\n> +#       retains its original value.\n> +#\n> +# Typemaps\n> +# ========\n> +# The cpp_typemaps and blink_cpp_typemaps each specify an optional list of\n> +# typemapping configurations. Each configuration is a GN scope with metadata\n> +# describing what and how to map.\n> +#\n> +# Typemap scope parameters:\n> +#   types\n> +#       A list of type specifications for this typemap. Each type specification\n> +#       is a nested GN scope which can be expressed with the following syntax:\n> +#\n> +#           {\n> +#             mojom = \"foo.mojom.Bar\"\n> +#             cpp = \"::foo::LegitBar\"\n> +#             move_only = true\n> +#             # etc...\n> +#           }\n> +#\n> +#       Each type specification supports the following values:\n> +#\n> +#         mojom (required)\n> +#             The fully qualified name of a mojom type to be mapped. This is a\n> +#             string like \"foo.mojom.Bar\".\n> +#\n> +#         cpp (required)\n> +#             The fully qualified name of the C++ type to which the mojom type\n> +#             should be mapped in generated bindings. This is a string like\n> +#             \"::base::Value\" or \"std::vector<::base::Value>\".\n> +#\n> +#         move_only (optional)\n> +#             A boolean value (default false) which indicates whether the C++\n> +#             type is move-only. If true, generated bindings will pass the type\n> +#             by value and use std::move() at call sites.\n> +#\n> +#         copyable_pass_by_value (optional)\n> +#             A boolean value (default false) which effectively indicates\n> +#             whether the C++ type is very cheap to copy. If so, generated\n> +#             bindings will pass by value but not use std::move() at call sites.\n> +#\n> +#         nullable_is_same_type (optional)\n> +#             A boolean value (default false) which indicates that the C++ type\n> +#             has some baked-in semantic notion of a \"null\" state. If true, the\n> +#             traits for the type must define IsNull and SetToNull methods.\n> +#\n> +#             When false, nullable fields are represented by wrapping the C++\n> +#             type with base::Optional, and null values are simply\n> +#             base::nullopt.\n> +#\n> +#         hashable (optional)\n> +#             A boolean value (default false) indicating whether the C++ type is\n> +#             hashable. Set to true if true AND needed (i.e. you need to use the\n> +#             type as the key of a mojom map).\n> +#\n> +#         force_serialize (optional)\n> +#             A boolean value (default false) which disables lazy serialization\n> +#             of the typemapped type if lazy serialization is enabled for the\n> +#             mojom target applying this typemap.\n> +#\n> +# Additional typemap scope parameters:\n> +#\n> +#   traits_headers (optional)\n> +#       Headers which must be included in the generated mojom in order for\n> +#       serialization to be possible. This generally means including at least\n> +#       the header for the corresponding mojom traits definitions.\n> +#\n> +#   traits_private_headers (optional)\n> +#       Headers which must be included in generated C++ serialization code for\n> +#       a mojom using the typemap. This should be used only when including a\n> +#       header in |traits_headers| is problematic for compilation, as is\n> +#       sometimes the case with legacy IPC message headers.\n> +#\n> +#   traits_sources (optional)\n> +#       The references to the source files (typically a single .cc and .h file)\n> +#       defining an appropriate set of EnumTraits or StructTraits, etc for the\n> +#       the type-mapping. Using this will cause the listed sources to be\n> +#       integrated directly into the dependent mojom's generated type-mapping\n> +#       targets.\n> +#\n> +#       Prefer using |traits_public_deps| over inlined |traits_sources|, as this\n> +#       will generally lead to easier build maintenance over time.\n> +#\n> +#       NOTE: If a typemap is shared by Blink and non-Blink bindings, you cannot\n> +#       use this and MUST use |traits_public_deps| to reference traits built\n> +#       within a separate target.\n> +#\n> +#   traits_deps / traits_public_deps (optional)\n> +#       Any dependencies of sources in |traits_headers| or |traits_sources| must\n> +#       be listed here.\n> +#\n> +template(\"mojom\") {\n> +  assert(\n> +      defined(invoker.sources) || defined(invoker.deps) ||\n> +          defined(invoker.public_deps),\n> +      \"\\\"sources\\\" or \\\"deps\\\" must be defined for the $target_name template.\")\n> +\n> +  if (defined(invoker.export_class_attribute) ||\n> +      defined(invoker.export_define) || defined(invoker.export_header)) {\n> +    assert(defined(invoker.export_class_attribute))\n> +    assert(defined(invoker.export_define))\n> +    assert(defined(invoker.export_header))\n> +  }\n> +  if (defined(invoker.export_class_attribute_blink) ||\n> +      defined(invoker.export_define_blink) ||\n> +      defined(invoker.export_header_blink)) {\n> +    assert(defined(invoker.export_class_attribute_blink))\n> +    assert(defined(invoker.export_define_blink))\n> +    assert(defined(invoker.export_header_blink))\n> +\n> +    # Not all platforms use the Blink variant, so make sure GN doesn't complain\n> +    # about these values being inconsequential.\n> +    not_needed(invoker,\n> +               [\n> +                 \"export_class_attribute_blink\",\n> +                 \"export_define_blink\",\n> +                 \"export_header_blink\",\n> +               ])\n> +  }\n> +  if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {\n> +    assert(defined(invoker.overridden_deps))\n> +    assert(defined(invoker.component_deps))\n> +  }\n> +\n> +  if (defined(invoker.overridden_deps_blink) ||\n> +      defined(invoker.component_deps_blink)) {\n> +    assert(defined(invoker.overridden_deps_blink))\n> +    assert(defined(invoker.component_deps_blink))\n> +  }\n> +\n> +  # Type-mapping may be disabled or we may not generate C++ bindings.\n> +  not_needed(invoker,\n> +             [\n> +               \"cpp_typemaps\",\n> +               \"blink_cpp_typemaps\",\n> +             ])\n> +\n> +  require_full_cpp_deps =\n> +      !defined(invoker.disallow_native_types) ||\n> +      !invoker.disallow_native_types || !defined(invoker.disallow_interfaces) ||\n> +      !invoker.disallow_interfaces\n> +\n> +  all_deps = []\n> +  if (defined(invoker.deps)) {\n> +    all_deps += invoker.deps\n> +  }\n> +  if (defined(invoker.public_deps)) {\n> +    all_deps += invoker.public_deps\n> +  }\n> +\n> +  if (defined(invoker.component_macro_prefix)) {\n> +    assert(defined(invoker.component_output_prefix))\n> +  }\n> +\n> +  group(\"${target_name}__is_mojom\") {\n> +  }\n> +\n> +  # Explicitly ensure that all dependencies (invoker.deps and\n> +  # invoker.public_deps) are mojom targets.\n> +  group(\"${target_name}__check_deps_are_all_mojom\") {\n> +    deps = []\n> +    foreach(d, all_deps) {\n> +      name = get_label_info(d, \"label_no_toolchain\")\n> +      toolchain = get_label_info(d, \"toolchain\")\n> +      deps += [ \"${name}__is_mojom(${toolchain})\" ]\n> +    }\n> +  }\n> +\n> +  sources_list = []\n> +  if (defined(invoker.sources)) {\n> +    sources_list = invoker.sources\n> +  }\n> +\n> +  # Reset sources_assignment_filter for the BUILD.gn file to prevent\n> +  # regression during the migration of Chromium away from the feature.\n> +  # See docs/no_sources_assignment_filter.md for more information.\n> +  # TODO(crbug.com/1018739): remove this when migration is done.\n> +  set_sources_assignment_filter([])\n> +\n> +  # Listed sources may be relative to the current target dir, or they may be\n> +  # absolute paths, including paths to generated mojom files. While those are\n> +  # fine as-is for input references, deriving output paths can be more subtle.\n> +  #\n> +  # Here we rewrite all source paths to be relative to the root build dir and\n> +  # strip any root_gen_dir prefixes.\n> +  #\n> +  # So for a target in //foo/bar with:\n> +  #\n> +  #     sources = [\n> +  #       \"a.mojom\",\n> +  #       \"b/c.mojom\",\n> +  #       \"//baz/d.mojom\",\n> +  #       \"$target_gen_dir/e.mojom\",\n> +  #     ]\n> +  #\n> +  # output_file_base_paths will be:\n> +  #\n> +  #     [\n> +  #       \"foo/bar/a.mojom\",\n> +  #       \"foo/bar/b/c.mojom\",\n> +  #       \"baz/d.mojom\",\n> +  #       \"foo/bar/e.mojom\",\n> +  #     ]\n> +  #\n> +  # This result is essentially a list of base filename paths which are suitable\n> +  # for the naming of any generated output files derived from their\n> +  # corresponding input mojoms. These paths are always considered to be relative\n> +  # to root_gen_dir.\n> +  source_abspaths = rebase_path(sources_list, \"//\")\n> +  output_file_base_paths = []\n> +  foreach(path, source_abspaths) {\n> +    output_file_base_paths +=\n> +        [ string_replace(path, rebase_path(root_gen_dir, \"//\") + \"/\", \"\") ]\n> +  }\n> +\n> +  # Sanity check that either all input files have a .mojom extension, or\n> +  # all input files have a .test-mojom extension AND |testonly| is |true|.\n> +  sources_list_filenames =\n> +      process_file_template(sources_list, \"{{source_file_part}}\")\n> +  sources_list_filenames_with_mojom_extension =\n> +      process_file_template(sources_list, \"{{source_name_part}}.mojom\")\n> +  if (sources_list_filenames != sources_list_filenames_with_mojom_extension) {\n> +    sources_list_filenames_with_test_mojom_extension =\n> +        process_file_template(sources_list, \"{{source_name_part}}.test-mojom\")\n> +    if (sources_list_filenames ==\n> +        sources_list_filenames_with_test_mojom_extension) {\n> +      assert(\n> +          defined(invoker.testonly) && invoker.testonly,\n> +          \"mojom targets for .test-mojom files must set |testonly| to |true|\")\n> +    } else {\n> +      assert(\n> +          false,\n> +          \"One or more mojom files has an invalid extension. The only \" +\n> +              \"allowed extensions are .mojom and .test-mojom, and any given \" +\n> +              \"mojom target must use one or the other exclusively.\")\n> +    }\n> +  }\n> +\n> +  build_metadata_filename = \"$target_gen_dir/$target_name.build_metadata\"\n> +  build_metadata = {\n> +  }\n> +  build_metadata.sources = rebase_path(sources_list)\n> +  build_metadata.deps = []\n> +  foreach(dep, all_deps) {\n> +    dep_target_gen_dir = get_label_info(dep, \"target_gen_dir\")\n> +    dep_name = get_label_info(dep, \"name\")\n> +    build_metadata.deps +=\n> +        [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\") ]\n> +  }\n> +  write_file(build_metadata_filename, build_metadata, \"json\")\n> +\n> +  generate_fuzzing =\n> +      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&\n> +      enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)\n> +\n> +  parser_target_name = \"${target_name}__parser\"\n> +  parser_deps = []\n> +  foreach(dep, all_deps) {\n> +    _label = get_label_info(dep, \"label_no_toolchain\")\n> +    parser_deps += [ \"${_label}__parser\" ]\n> +  }\n> +  if (defined(invoker.parser_deps)) {\n> +    parser_deps += invoker.parser_deps\n> +  }\n> +  if (sources_list == []) {\n> +    # Even without sources we generate a parser target to at least forward\n> +    # other parser dependencies.\n> +    group(parser_target_name) {\n> +      public_deps = parser_deps\n> +    }\n> +  } else {\n> +    enabled_features = []\n> +    if (defined(invoker.enabled_features)) {\n> +      enabled_features += invoker.enabled_features\n> +    }\n> +    if (is_posix) {\n> +      enabled_features += [ \"is_posix\" ]\n> +    }\n> +    if (is_android) {\n> +      enabled_features += [ \"is_android\" ]\n> +    } else if (is_chromeos) {\n> +      enabled_features += [ \"is_chromeos\" ]\n> +    } else if (is_fuchsia) {\n> +      enabled_features += [ \"is_fuchsia\" ]\n> +    } else if (is_ios) {\n> +      enabled_features += [ \"is_ios\" ]\n> +    } else if (is_linux) {\n> +      enabled_features += [ \"is_linux\" ]\n> +    } else if (is_mac) {\n> +      enabled_features += [ \"is_mac\" ]\n> +    } else if (is_win) {\n> +      enabled_features += [ \"is_win\" ]\n> +    }\n> +\n> +    action(parser_target_name) {\n> +      script = mojom_parser_script\n> +      inputs = mojom_parser_sources + [ build_metadata_filename ]\n> +      sources = sources_list\n> +      deps = parser_deps\n> +      outputs = []\n> +      foreach(base_path, output_file_base_paths) {\n> +        filename = get_path_info(base_path, \"file\")\n> +        dirname = get_path_info(base_path, \"dir\")\n> +        outputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n> +      }\n> +\n> +      filelist = []\n> +      foreach(source, sources_list) {\n> +        filelist += [ rebase_path(source) ]\n> +      }\n> +      response_file_contents = filelist\n> +\n> +      args = [\n> +        # Resolve relative input mojom paths against both the root src dir and\n> +        # the root gen dir.\n> +        \"--input-root\",\n> +        rebase_path(\"//\"),\n> +        \"--input-root\",\n> +        rebase_path(root_gen_dir),\n> +\n> +        \"--output-root\",\n> +        rebase_path(root_gen_dir),\n> +\n> +        \"--mojom-file-list={{response_file_name}}\",\n> +\n> +        \"--check-imports\",\n> +        rebase_path(build_metadata_filename),\n> +      ]\n> +\n> +      foreach(enabled_feature, enabled_features) {\n> +        args += [\n> +          \"--enable-feature\",\n> +          enabled_feature,\n> +        ]\n> +      }\n> +    }\n> +  }\n> +\n> +  generator_cpp_message_ids_target_name = \"${target_name}__generate_message_ids\"\n> +\n> +  # Generate code that is shared by different variants.\n> +  if (sources_list != []) {\n> +    common_generator_args = [\n> +      \"--use_bundled_pylibs\",\n> +      \"-o\",\n> +      rebase_path(root_gen_dir, root_build_dir),\n> +      \"generate\",\n> +      \"-d\",\n> +      rebase_path(\"//\", root_build_dir),\n> +      \"-I\",\n> +      rebase_path(\"//\", root_build_dir),\n> +      \"--bytecode_path\",\n> +      rebase_path(\"$root_gen_dir/mojo/public/tools/bindings\", root_build_dir),\n> +    ]\n> +\n> +    if (defined(invoker.disallow_native_types) &&\n> +        invoker.disallow_native_types) {\n> +      common_generator_args += [ \"--disallow_native_types\" ]\n> +    }\n> +\n> +    if (defined(invoker.disallow_interfaces) && invoker.disallow_interfaces) {\n> +      common_generator_args += [ \"--disallow_interfaces\" ]\n> +    }\n> +\n> +    if (defined(invoker.import_dirs)) {\n> +      foreach(import_dir, invoker.import_dirs) {\n> +        common_generator_args += [\n> +          \"-I\",\n> +          rebase_path(import_dir, root_build_dir),\n> +        ]\n> +      }\n> +    }\n> +\n> +    if (defined(invoker.component_macro_prefix)) {\n> +      shared_component_export_macro =\n> +          \"COMPONENT_EXPORT(${invoker.component_macro_prefix}_SHARED)\"\n> +      shared_component_impl_macro =\n> +          \"IS_${invoker.component_macro_prefix}_SHARED_IMPL\"\n> +      shared_component_output_name = \"${invoker.component_output_prefix}_shared\"\n> +    } else if (defined(invoker.export_class_attribute_shared) ||\n> +               defined(invoker.export_class_attribute)) {\n> +      if (defined(invoker.export_class_attribute_shared)) {\n> +        assert(defined(invoker.export_header_shared))\n> +        shared_component_export_macro = invoker.export_class_attribute_shared\n> +        shared_component_impl_macro = invoker.export_define_shared\n> +      } else {\n> +        assert(!defined(invoker.export_header_shared))\n> +\n> +        # If no explicit shared attribute/define was provided by the invoker,\n> +        # we derive some reasonable settings frorm the default variant.\n> +        shared_component_export_macro = \"COMPONENT_EXPORT(MOJOM_SHARED_\" +\n> +                                        invoker.export_class_attribute + \")\"\n> +        shared_component_impl_macro =\n> +            \"IS_MOJOM_SHARED_\" + invoker.export_class_attribute + \"_IMPL\"\n> +      }\n> +\n> +      if (defined(invoker.component_output_prefix)) {\n> +        shared_component_output_name =\n> +            \"${invoker.component_output_prefix}_shared\"\n> +      } else {\n> +        shared_component_output_name = \"${target_name}_shared\"\n> +      }\n> +    }\n> +\n> +    action(generator_cpp_message_ids_target_name) {\n> +      script = mojom_generator_script\n> +      inputs = mojom_generator_sources + jinja2_sources\n> +      sources = sources_list\n> +      deps = [\n> +        \":$parser_target_name\",\n> +        \"//mojo/public/tools/bindings:precompile_templates\",\n> +      ]\n> +      if (defined(invoker.parser_deps)) {\n> +        deps += invoker.parser_deps\n> +      }\n> +      outputs = []\n> +      args = common_generator_args\n> +      filelist = []\n> +      foreach(source, sources_list) {\n> +        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +      }\n> +      foreach(base_path, output_file_base_paths) {\n> +        outputs += [ \"$root_gen_dir/$base_path-shared-message-ids.h\" ]\n> +      }\n> +\n> +      response_file_contents = filelist\n> +\n> +      args += [\n> +        \"--filelist={{response_file_name}}\",\n> +        \"--generate_non_variant_code\",\n> +        \"--generate_message_ids\",\n> +        \"-g\",\n> +        \"c++\",\n> +      ]\n> +\n> +      if (!defined(invoker.scramble_message_ids) ||\n> +          invoker.scramble_message_ids) {\n> +        inputs += message_scrambling_inputs\n> +        args += message_scrambling_args\n> +      }\n> +    }\n> +\n> +    generator_shared_target_name = \"${target_name}_shared__generator\"\n> +    action(generator_shared_target_name) {\n> +      visibility = [ \":*\" ]\n> +      script = mojom_generator_script\n> +      inputs = mojom_generator_sources + jinja2_sources\n> +      sources = sources_list\n> +      deps = [\n> +        \":$parser_target_name\",\n> +        \"//mojo/public/tools/bindings:precompile_templates\",\n> +      ]\n> +      if (defined(invoker.parser_deps)) {\n> +        deps += invoker.parser_deps\n> +      }\n> +\n> +      outputs = []\n> +      args = common_generator_args\n> +      filelist = []\n> +      foreach(source, sources_list) {\n> +        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +      }\n> +      foreach(base_path, output_file_base_paths) {\n> +        outputs += [\n> +          \"$root_gen_dir/$base_path-params-data.h\",\n> +          \"$root_gen_dir/$base_path-shared-internal.h\",\n> +          \"$root_gen_dir/$base_path-shared.cc\",\n> +          \"$root_gen_dir/$base_path-shared.h\",\n> +        ]\n> +      }\n> +\n> +      response_file_contents = filelist\n> +\n> +      args += [\n> +        \"--filelist={{response_file_name}}\",\n> +        \"--generate_non_variant_code\",\n> +        \"-g\",\n> +        \"c++\",\n> +      ]\n> +\n> +      if (defined(shared_component_export_macro)) {\n> +        args += [\n> +          \"--export_attribute\",\n> +          shared_component_export_macro,\n> +          \"--export_header\",\n> +          \"base/component_export.h\",\n> +        ]\n> +      }\n> +\n> +      # Enable adding annotations to generated C++ headers that are used for\n> +      # cross-references in CodeSearch.\n> +      if (enable_kythe_annotations) {\n> +        args += [ \"--enable_kythe_annotations\" ]\n> +      }\n> +    }\n> +  } else {\n> +    group(generator_cpp_message_ids_target_name) {\n> +    }\n> +  }\n> +\n> +  shared_cpp_sources_target_name = \"${target_name}_shared_cpp_sources\"\n> +  jumbo_source_set(shared_cpp_sources_target_name) {\n> +    if (defined(invoker.testonly)) {\n> +      testonly = invoker.testonly\n> +    }\n> +    deps = []\n> +    public_deps = []\n> +    if (output_file_base_paths != []) {\n> +      sources = []\n> +      foreach(base_path, output_file_base_paths) {\n> +        sources += [\n> +          \"$root_gen_dir/$base_path-params-data.h\",\n> +          \"$root_gen_dir/$base_path-shared-internal.h\",\n> +          \"$root_gen_dir/$base_path-shared.cc\",\n> +          \"$root_gen_dir/$base_path-shared.h\",\n> +        ]\n> +      }\n> +      public_deps += [ \":$generator_shared_target_name\" ]\n> +    }\n> +    if (require_full_cpp_deps) {\n> +      public_deps += [ \"//mojo/public/cpp/bindings\" ]\n> +    } else {\n> +      public_deps += [ \"//mojo/public/cpp/bindings:bindings_base\" ]\n> +    }\n> +    foreach(d, all_deps) {\n> +      # Resolve the name, so that a target //mojo/something becomes\n> +      # //mojo/something:something and we can append shared_cpp_sources_suffix\n> +      # to get the cpp dependency name.\n> +      full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +      public_deps += [ \"${full_name}_shared\" ]\n> +    }\n> +    if (defined(shared_component_impl_macro)) {\n> +      defines = [ shared_component_impl_macro ]\n> +    }\n> +  }\n> +\n> +  shared_cpp_library_target_name = \"${target_name}_shared\"\n> +  if (defined(shared_component_output_name)) {\n> +    component(shared_cpp_library_target_name) {\n> +      if (defined(invoker.testonly)) {\n> +        testonly = invoker.testonly\n> +      }\n> +      output_name = \"$shared_component_output_name\"\n> +      public_deps = [ \":$shared_cpp_sources_target_name\" ]\n> +    }\n> +  } else {\n> +    group(shared_cpp_library_target_name) {\n> +      if (defined(invoker.testonly)) {\n> +        testonly = invoker.testonly\n> +      }\n> +      public_deps = [ \":$shared_cpp_sources_target_name\" ]\n> +    }\n> +  }\n> +\n> +  if (generate_fuzzing) {\n> +    # This block generates the proto files used for the MojoLPM fuzzer,\n> +    # and the corresponding proto targets that will be linked in the fuzzer\n> +    # targets. These are independent of the typemappings, and can be done\n> +    # separately here.\n> +\n> +    generator_mojolpm_proto_target_name =\n> +        \"${target_name}_mojolpm_proto_generator\"\n> +    action(generator_mojolpm_proto_target_name) {\n> +      script = mojom_generator_script\n> +      inputs = mojom_generator_sources + jinja2_sources\n> +      sources = invoker.sources\n> +      deps = [\n> +        \":$parser_target_name\",\n> +        \"//mojo/public/tools/bindings:precompile_templates\",\n> +      ]\n> +\n> +      outputs = []\n> +      args = common_generator_args\n> +      filelist = []\n> +      foreach(source, invoker.sources) {\n> +        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        outputs += [ \"$target_gen_dir/$source.mojolpm.proto\" ]\n> +      }\n> +\n> +      response_file_contents = filelist\n> +\n> +      args += [\n> +        \"--filelist={{response_file_name}}\",\n> +        \"--generate_non_variant_code\",\n> +        \"-g\",\n> +        \"mojolpm\",\n> +      ]\n> +    }\n> +\n> +    mojolpm_proto_target_name = \"${target_name}_mojolpm_proto\"\n> +    if (defined(invoker.sources)) {\n> +      proto_library(mojolpm_proto_target_name) {\n> +        testonly = true\n> +        generate_python = false\n> +        sources = process_file_template(\n> +                invoker.sources,\n> +                [ \"{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n> +        import_dirs = [ \"${root_gen_dir}\" ]\n> +        proto_in_dir = \"${root_gen_dir}\"\n> +        proto_out_dir = \".\"\n> +        proto_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto_copy\" ]\n> +        proto_deps += [ \":$generator_mojolpm_proto_target_name\" ]\n> +        link_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto\" ]\n> +\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append mojolpm_proto_suffix\n> +          # to get the proto dependency name.\n> +          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +          proto_deps += [ \"${full_name}_mojolpm_proto\" ]\n> +          link_deps += [ \"${full_name}_mojolpm_proto\" ]\n> +        }\n> +      }\n> +    } else {\n> +      group(mojolpm_proto_target_name) {\n> +        testonly = true\n> +        public_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto\" ]\n> +        if (defined(generator_shared_target_name)) {\n> +          public_deps += [ \":$generator_shared_target_name\" ]\n> +        }\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append #mojolpm_proto_suffix\n> +          # to get the proto dependency name.\n> +          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +          public_deps += [ \"${full_name}_mojolpm_proto\" ]\n> +        }\n> +      }\n> +    }\n> +  }\n> +\n> +  # Generate code for variants.\n> +  if (!defined(invoker.disable_variants) || !invoker.disable_variants) {\n> +    enabled_configurations = _bindings_configurations\n> +  } else {\n> +    first_config = _bindings_configurations[0]\n> +    assert(!defined(first_config.variant))\n> +    enabled_configurations = [ first_config ]\n> +  }\n> +  foreach(bindings_configuration, enabled_configurations) {\n> +    cpp_only = false\n> +    if (defined(invoker.cpp_only)) {\n> +      cpp_only = invoker.cpp_only\n> +    }\n> +    variant_suffix = \"\"\n> +    if (defined(bindings_configuration.variant)) {\n> +      variant = bindings_configuration.variant\n> +      variant_suffix = \"_${variant}\"\n> +      cpp_only = true\n> +    }\n> +\n> +    cpp_typemap_configs = []\n> +    export_defines = []\n> +    export_defines_overridden = false\n> +    force_source_set = false\n> +    if (defined(bindings_configuration.for_blink) &&\n> +        bindings_configuration.for_blink) {\n> +      if (defined(invoker.blink_cpp_typemaps)) {\n> +        cpp_typemap_configs = invoker.blink_cpp_typemaps\n> +      }\n> +      if (defined(invoker.export_define_blink)) {\n> +        export_defines_overridden = true\n> +        export_defines = [ invoker.export_define_blink ]\n> +        force_source_set = true\n> +      }\n> +    } else {\n> +      if (defined(invoker.cpp_typemaps)) {\n> +        cpp_typemap_configs = invoker.cpp_typemaps\n> +      }\n> +\n> +      if (defined(invoker.export_define)) {\n> +        export_defines_overridden = true\n> +        export_defines = [ invoker.export_define ]\n> +        force_source_set = true\n> +      }\n> +    }\n> +    not_needed([ \"cpp_typemap_configs\" ])\n> +\n> +    if (!export_defines_overridden && defined(invoker.component_macro_prefix)) {\n> +      output_name_override =\n> +          \"${invoker.component_output_prefix}${variant_suffix}\"\n> +      export_defines =\n> +          [ \"IS_${invoker.component_macro_prefix}\" +\n> +            \"${bindings_configuration.component_macro_suffix}_IMPL\" ]\n> +    }\n> +\n> +    export_args = []\n> +    export_args_overridden = false\n> +    if (defined(bindings_configuration.for_blink) &&\n> +        bindings_configuration.for_blink) {\n> +      if (defined(invoker.export_class_attribute_blink)) {\n> +        export_args_overridden = true\n> +        export_args += [\n> +          \"--export_attribute\",\n> +          invoker.export_class_attribute_blink,\n> +          \"--export_header\",\n> +          invoker.export_header_blink,\n> +        ]\n> +      }\n> +    } else if (defined(invoker.export_class_attribute)) {\n> +      export_args_overridden = true\n> +      export_args += [\n> +        \"--export_attribute\",\n> +        invoker.export_class_attribute,\n> +        \"--export_header\",\n> +        invoker.export_header,\n> +      ]\n> +    }\n> +\n> +    if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n> +      export_args += [\n> +        \"--export_attribute\",\n> +        \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n> +            \"${bindings_configuration.component_macro_suffix})\",\n> +        \"--export_header\",\n> +        \"base/component_export.h\",\n> +      ]\n> +    }\n> +\n> +    generate_java = false\n> +    if (!cpp_only && defined(invoker.generate_java)) {\n> +      generate_java = invoker.generate_java\n> +    }\n> +    type_mappings_target_name = \"${target_name}${variant_suffix}__type_mappings\"\n> +    type_mappings_path =\n> +        \"$target_gen_dir/${target_name}${variant_suffix}__type_mappings\"\n> +    active_typemaps = []\n> +    if (sources_list != []) {\n> +      generator_cpp_output_suffixes = []\n> +      variant_dash_suffix = \"\"\n> +      if (defined(variant)) {\n> +        variant_dash_suffix = \"-${variant}\"\n> +      }\n> +      generator_cpp_output_suffixes += [\n> +        \"${variant_dash_suffix}-forward.h\",\n> +        \"${variant_dash_suffix}-import-headers.h\",\n> +        \"${variant_dash_suffix}-test-utils.cc\",\n> +        \"${variant_dash_suffix}-test-utils.h\",\n> +        \"${variant_dash_suffix}.cc\",\n> +        \"${variant_dash_suffix}.h\",\n> +      ]\n> +      foreach(source, sources_list) {\n> +        # TODO(sammc): Use a map instead of a linear scan when GN supports maps.\n> +        foreach(typemap, bindings_configuration.typemaps) {\n> +          _typemap_config = {\n> +          }\n> +          _typemap_config = typemap.config\n> +          if (get_path_info(source, \"abspath\") == _typemap_config.mojom) {\n> +            active_typemaps += [ typemap ]\n> +          }\n> +        }\n> +      }\n> +\n> +      generator_target_name = \"${target_name}${variant_suffix}__generator\"\n> +      action(generator_target_name) {\n> +        visibility = [ \":*\" ]\n> +        script = mojom_generator_script\n> +        inputs = mojom_generator_sources + jinja2_sources\n> +        sources = sources_list\n> +        deps = [\n> +          \":$parser_target_name\",\n> +          \":$type_mappings_target_name\",\n> +          \"//mojo/public/tools/bindings:precompile_templates\",\n> +        ]\n> +        if (defined(invoker.parser_deps)) {\n> +          deps += invoker.parser_deps\n> +        }\n> +        outputs = []\n> +        args = common_generator_args + export_args\n> +        filelist = []\n> +        foreach(source, sources_list) {\n> +          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        }\n> +        foreach(base_path, output_file_base_paths) {\n> +          outputs += [\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h\",\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h\",\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc\",\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h\",\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}.cc\",\n> +            \"$root_gen_dir/${base_path}${variant_dash_suffix}.h\",\n> +          ]\n> +          if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +            outputs += [\n> +              \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc\",\n> +              \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h\",\n> +            ]\n> +          }\n> +        }\n> +\n> +        response_file_contents = filelist\n> +\n> +        args += [\n> +          \"--filelist={{response_file_name}}\",\n> +          \"-g\",\n> +        ]\n> +\n> +        if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +          args += [ \"c++,mojolpm\" ]\n> +        } else {\n> +          args += [ \"c++\" ]\n> +        }\n> +\n> +        if (defined(bindings_configuration.variant)) {\n> +          args += [\n> +            \"--variant\",\n> +            bindings_configuration.variant,\n> +          ]\n> +        }\n> +\n> +        args += [\n> +          \"--typemap\",\n> +          rebase_path(type_mappings_path, root_build_dir),\n> +        ]\n> +\n> +        if (defined(bindings_configuration.for_blink) &&\n> +            bindings_configuration.for_blink) {\n> +          args += [ \"--for_blink\" ]\n> +        }\n> +\n> +        if (defined(invoker.support_lazy_serialization) &&\n> +            invoker.support_lazy_serialization) {\n> +          args += [ \"--support_lazy_serialization\" ]\n> +        }\n> +\n> +        if (enable_kythe_annotations) {\n> +          args += [ \"--enable_kythe_annotations\" ]\n> +        }\n> +\n> +        if (!defined(invoker.scramble_message_ids) ||\n> +            invoker.scramble_message_ids) {\n> +          inputs += message_scrambling_inputs\n> +          args += message_scrambling_args\n> +        }\n> +\n> +        if (defined(invoker.extra_cpp_template_paths)) {\n> +          foreach(extra_cpp_template, invoker.extra_cpp_template_paths) {\n> +            args += [\n> +              \"--extra_cpp_template_paths\",\n> +              rebase_path(extra_cpp_template, root_build_dir),\n> +            ]\n> +            assert(\n> +                get_path_info(extra_cpp_template, \"extension\") == \"tmpl\",\n> +                \"--extra_cpp_template_paths only accepts template files ending in extension .tmpl\")\n> +            foreach(base_path, output_file_base_paths) {\n> +              template_file_name = get_path_info(\"$extra_cpp_template\", \"name\")\n> +              outputs += [ \"$root_gen_dir/${base_path}${variant_dash_suffix}-${template_file_name}\" ]\n> +            }\n> +          }\n> +        }\n> +      }\n> +    }\n> +\n> +    if (generate_fuzzing && !defined(variant)) {\n> +      # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n> +      # do this here so that we can use the typemap configuration for the\n> +      # empty-variant Mojo target.\n> +\n> +      mojolpm_target_name = \"${target_name}_mojolpm\"\n> +      mojolpm_generator_target_name = \"${target_name}__generator\"\n> +      source_set(mojolpm_target_name) {\n> +        # There are still a few missing header dependencies between mojo targets\n> +        # with typemaps and the dependencies of their typemap headers. It would\n> +        # be good to enable include checking for these in the future though.\n> +        check_includes = false\n> +        testonly = true\n> +        if (defined(invoker.sources)) {\n> +          sources = process_file_template(\n> +                  invoker.sources,\n> +                  [\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n> +                  ])\n> +          deps = []\n> +        } else {\n> +          sources = []\n> +          deps = []\n> +        }\n> +\n> +        public_deps = [\n> +          \":$generator_shared_target_name\",\n> +\n> +          # NB: hardcoded dependency on the no-variant variant generator, since\n> +          # mojolpm only uses the no-variant type.\n> +          \":$mojolpm_generator_target_name\",\n> +          \":$mojolpm_proto_target_name\",\n> +          \"//mojo/public/tools/fuzzers:mojolpm\",\n> +        ]\n> +\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append variant_suffix to\n> +          # get the cpp dependency name.\n> +          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +          public_deps += [ \"${full_name}_mojolpm\" ]\n> +        }\n> +\n> +        foreach(typemap, active_typemaps) {\n> +          _typemap_config = {\n> +          }\n> +          _typemap_config = typemap.config\n> +\n> +          if (defined(_typemap_config.deps)) {\n> +            deps += _typemap_config.deps\n> +          }\n> +          if (defined(_typemap_config.public_deps)) {\n> +            public_deps += _typemap_config.public_deps\n> +          }\n> +        }\n> +        foreach(config, cpp_typemap_configs) {\n> +          if (defined(config.traits_deps)) {\n> +            deps += config.traits_deps\n> +          }\n> +          if (defined(config.traits_public_deps)) {\n> +            public_deps += config.traits_public_deps\n> +          }\n> +        }\n> +      }\n> +    }\n> +\n> +    # Write the typemapping configuration for this target out to a file to be\n> +    # validated by a Python script. This helps catch mistakes that can't\n> +    # be caught by logic in GN.\n> +    _typemap_config_filename =\n> +        \"$target_gen_dir/${target_name}${variant_suffix}.typemap_config\"\n> +    _typemap_stamp_filename = \"${_typemap_config_filename}.validated\"\n> +    _typemap_validator_target_name = \"${type_mappings_target_name}__validator\"\n> +    _rebased_typemap_configs = []\n> +    foreach(config, cpp_typemap_configs) {\n> +      _rebased_config = {\n> +      }\n> +      _rebased_config = config\n> +      if (defined(config.traits_headers)) {\n> +        _rebased_config.traits_headers = []\n> +        _rebased_config.traits_headers =\n> +            rebase_path(config.traits_headers, \"//\")\n> +      }\n> +      if (defined(config.traits_private_headers)) {\n> +        _rebased_config.traits_private_headers = []\n> +        _rebased_config.traits_private_headers =\n> +            rebase_path(config.traits_private_headers, \"//\")\n> +      }\n> +      _rebased_typemap_configs += [ _rebased_config ]\n> +    }\n> +    write_file(_typemap_config_filename, _rebased_typemap_configs, \"json\")\n> +    _mojom_target_name = target_name\n> +    action(_typemap_validator_target_name) {\n> +      script = \"$mojom_generator_root/validate_typemap_config.py\"\n> +      inputs = [ _typemap_config_filename ]\n> +      outputs = [ _typemap_stamp_filename ]\n> +      args = [\n> +        get_label_info(_mojom_target_name, \"label_no_toolchain\"),\n> +        rebase_path(_typemap_config_filename),\n> +        rebase_path(_typemap_stamp_filename),\n> +      ]\n> +    }\n> +\n> +    action(type_mappings_target_name) {\n> +      inputs = _bindings_configuration_files + mojom_generator_sources +\n> +               jinja2_sources + [ _typemap_stamp_filename ]\n> +      outputs = [ type_mappings_path ]\n> +      script = \"$mojom_generator_root/generate_type_mappings.py\"\n> +      deps = [ \":$_typemap_validator_target_name\" ]\n> +      args = [\n> +        \"--output\",\n> +        rebase_path(type_mappings_path, root_build_dir),\n> +      ]\n> +\n> +      foreach(d, all_deps) {\n> +        name = get_label_info(d, \"label_no_toolchain\")\n> +        toolchain = get_label_info(d, \"toolchain\")\n> +        dependency_output = \"${name}${variant_suffix}__type_mappings\"\n> +        dependency_target = \"${dependency_output}(${toolchain})\"\n> +        deps += [ dependency_target ]\n> +        dependency_output_dir =\n> +            get_label_info(dependency_output, \"target_gen_dir\")\n> +        dependency_name = get_label_info(dependency_output, \"name\")\n> +        dependency_path =\n> +            rebase_path(\"$dependency_output_dir/${dependency_name}\",\n> +                        root_build_dir)\n> +        args += [\n> +          \"--dependency\",\n> +          dependency_path,\n> +        ]\n> +      }\n> +\n> +      if (sources_list != []) {\n> +        # TODO(sammc): Pass the typemap description in a file to avoid command\n> +        # line length limitations.\n> +        typemap_description = []\n> +        foreach(typemap, active_typemaps) {\n> +          _typemap_config = {\n> +          }\n> +          _typemap_config = typemap.config\n> +\n> +          typemap_description += [ \"--start-typemap\" ]\n> +          if (defined(_typemap_config.public_headers)) {\n> +            foreach(value, _typemap_config.public_headers) {\n> +              typemap_description += [ \"public_headers=$value\" ]\n> +            }\n> +          }\n> +          if (defined(_typemap_config.traits_headers)) {\n> +            foreach(value, _typemap_config.traits_headers) {\n> +              typemap_description += [ \"traits_headers=$value\" ]\n> +            }\n> +          }\n> +          foreach(value, _typemap_config.type_mappings) {\n> +            typemap_description += [ \"type_mappings=$value\" ]\n> +          }\n> +\n> +          # The typemap configuration files are not actually used as inputs here\n> +          # but this establishes a necessary build dependency to ensure that\n> +          # typemap changes force a rebuild of affected targets.\n> +          if (defined(typemap.filename)) {\n> +            inputs += [ typemap.filename ]\n> +          }\n> +        }\n> +        args += typemap_description\n> +\n> +        # Newer GN-based typemaps are aggregated into a single config.\n> +        inputs += [ _typemap_config_filename ]\n> +        args += [\n> +          \"--cpp-typemap-config\",\n> +          rebase_path(_typemap_config_filename, root_build_dir),\n> +        ]\n> +      }\n> +    }\n> +\n> +    group(\"${target_name}${variant_suffix}_headers\") {\n> +      public_deps = []\n> +      if (sources_list != []) {\n> +        public_deps += [\n> +          \":$generator_cpp_message_ids_target_name\",\n> +          \":$generator_shared_target_name\",\n> +          \":$generator_target_name\",\n> +        ]\n> +      }\n> +      foreach(d, all_deps) {\n> +        full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +        public_deps += [ \"${full_name}${variant_suffix}_headers\" ]\n> +      }\n> +    }\n> +\n> +    if (!force_source_set && defined(invoker.component_macro_prefix)) {\n> +      output_target_type = \"component\"\n> +    } else {\n> +      output_target_type = \"source_set\"\n> +    }\n> +\n> +    js_data_deps_target_name = target_name + \"_js_data_deps\"\n> +    not_needed([ \"js_data_deps_target_name\" ])\n> +\n> +    target(\"jumbo_\" + output_target_type, \"${target_name}${variant_suffix}\") {\n> +      if (defined(output_name_override)) {\n> +        output_name = output_name_override\n> +      }\n> +      if (defined(bindings_configuration.for_blink) &&\n> +          bindings_configuration.for_blink &&\n> +          defined(invoker.visibility_blink)) {\n> +        visibility = invoker.visibility_blink\n> +      } else if (defined(invoker.visibility)) {\n> +        visibility = invoker.visibility\n> +      }\n> +      if (defined(invoker.testonly)) {\n> +        testonly = invoker.testonly\n> +      }\n> +      defines = export_defines\n> +      if (output_file_base_paths != []) {\n> +        sources = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          foreach(suffix, generator_cpp_output_suffixes) {\n> +            sources += [ \"$root_gen_dir/${base_path}$suffix\" ]\n> +          }\n> +        }\n> +      }\n> +      deps = [\n> +        \":$generator_cpp_message_ids_target_name\",\n> +        \"//mojo/public/cpp/bindings:struct_traits\",\n> +        \"//mojo/public/interfaces/bindings:bindings_headers\",\n> +      ]\n> +      public_deps = [\n> +        \":$shared_cpp_library_target_name\",\n> +        \"//base\",\n> +      ]\n> +      if (require_full_cpp_deps) {\n> +        public_deps += [ \"//mojo/public/cpp/bindings\" ]\n> +      } else {\n> +        public_deps += [ \"//mojo/public/cpp/bindings:bindings_base\" ]\n> +      }\n> +\n> +      if (sources_list != []) {\n> +        public_deps += [ \":$generator_target_name\" ]\n> +      }\n> +      foreach(d, all_deps) {\n> +        # Resolve the name, so that a target //mojo/something becomes\n> +        # //mojo/something:something and we can append variant_suffix to\n> +        # get the cpp dependency name.\n> +        full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +        public_deps += [ \"${full_name}${variant_suffix}\" ]\n> +      }\n> +      if (defined(bindings_configuration.for_blink) &&\n> +          bindings_configuration.for_blink) {\n> +        if (defined(invoker.overridden_deps_blink)) {\n> +          foreach(d, invoker.overridden_deps_blink) {\n> +            # Resolve the name, so that a target //mojo/something becomes\n> +            # //mojo/something:something and we can append variant_suffix\n> +            # to get the cpp dependency name.\n> +            full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +            public_deps -= [ \"${full_name}${variant_suffix}\" ]\n> +          }\n> +          public_deps += invoker.component_deps_blink\n> +        }\n> +        if (defined(invoker.check_includes_blink)) {\n> +          check_includes = invoker.check_includes_blink\n> +        }\n> +      } else {\n> +        if (defined(invoker.check_includes_blink)) {\n> +          not_needed(invoker, [ \"check_includes_blink\" ])\n> +        }\n> +        if (defined(invoker.overridden_deps)) {\n> +          foreach(d, invoker.overridden_deps) {\n> +            # Resolve the name, so that a target //mojo/something becomes\n> +            # //mojo/something:something and we can append variant_suffix\n> +            # to get the cpp dependency name.\n> +            full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +            public_deps -= [ \"${full_name}${variant_suffix}\" ]\n> +          }\n> +          public_deps += invoker.component_deps\n> +        }\n> +      }\n> +      foreach(typemap, active_typemaps) {\n> +        _typemap_config = {\n> +        }\n> +        _typemap_config = typemap.config\n> +        if (defined(_typemap_config.sources)) {\n> +          sources += _typemap_config.sources\n> +        }\n> +        if (defined(_typemap_config.public_deps)) {\n> +          public_deps += _typemap_config.public_deps\n> +        }\n> +        if (defined(_typemap_config.deps)) {\n> +          deps += _typemap_config.deps\n> +        }\n> +      }\n> +      foreach(config, cpp_typemap_configs) {\n> +        if (defined(config.traits_sources)) {\n> +          sources += config.traits_sources\n> +        }\n> +        if (defined(config.traits_deps)) {\n> +          deps += config.traits_deps\n> +        }\n> +        if (defined(config.traits_public_deps)) {\n> +          public_deps += config.traits_public_deps\n> +        }\n> +      }\n> +      if (defined(invoker.export_header)) {\n> +        sources += [ \"//\" + invoker.export_header ]\n> +      }\n> +      if (defined(bindings_configuration.for_blink) &&\n> +          bindings_configuration.for_blink) {\n> +        public_deps += [ \"//mojo/public/cpp/bindings:wtf_support\" ]\n> +      }\n> +\n> +      if (generate_fuzzing) {\n> +        # Generate JS bindings by default if IPC fuzzer is enabled.\n> +        public_deps += [ \":$js_data_deps_target_name\" ]\n> +      }\n> +    }\n> +\n> +    if (generate_java && is_android) {\n> +      import(\"//build/config/android/rules.gni\")\n> +\n> +      java_generator_target_name = target_name + \"_java__generator\"\n> +      if (sources_list != []) {\n> +        action(java_generator_target_name) {\n> +          script = mojom_generator_script\n> +          inputs = mojom_generator_sources + jinja2_sources\n> +          sources = sources_list\n> +          deps = [\n> +            \":$parser_target_name\",\n> +            \":$type_mappings_target_name\",\n> +            \"//mojo/public/tools/bindings:precompile_templates\",\n> +          ]\n> +          outputs = []\n> +          args = common_generator_args\n> +          filelist = []\n> +          foreach(source, sources_list) {\n> +            filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +          }\n> +          foreach(base_path, output_file_base_paths) {\n> +            outputs += [ \"$root_gen_dir/$base_path.srcjar\" ]\n> +          }\n> +\n> +          response_file_contents = filelist\n> +\n> +          args += [\n> +            \"--filelist={{response_file_name}}\",\n> +            \"-g\",\n> +            \"java\",\n> +          ]\n> +\n> +          if (!defined(invoker.scramble_message_ids) ||\n> +              invoker.scramble_message_ids) {\n> +            inputs += message_scrambling_inputs\n> +            args += message_scrambling_args\n> +          }\n> +        }\n> +      } else {\n> +        group(java_generator_target_name) {\n> +        }\n> +      }\n> +\n> +      java_srcjar_target_name = target_name + \"_java_sources\"\n> +      action(java_srcjar_target_name) {\n> +        script = \"//build/android/gyp/zip.py\"\n> +        inputs = []\n> +        if (output_file_base_paths != []) {\n> +          foreach(base_path, output_file_base_paths) {\n> +            inputs += [ \"$root_gen_dir/${base_path}.srcjar\" ]\n> +          }\n> +        }\n> +        output = \"$target_gen_dir/$target_name.srcjar\"\n> +        outputs = [ output ]\n> +        rebase_inputs = rebase_path(inputs, root_build_dir)\n> +        rebase_output = rebase_path(output, root_build_dir)\n> +        args = [\n> +          \"--input-zips=$rebase_inputs\",\n> +          \"--output=$rebase_output\",\n> +        ]\n> +        deps = []\n> +        if (sources_list != []) {\n> +          deps = [ \":$java_generator_target_name\" ]\n> +        }\n> +      }\n> +\n> +      java_target_name = target_name + \"_java\"\n> +      android_library(java_target_name) {\n> +        forward_variables_from(invoker, [ \"enable_bytecode_checks\" ])\n> +        deps = [\n> +          \"//base:base_java\",\n> +          \"//mojo/public/java:bindings_java\",\n> +          \"//mojo/public/java:system_java\",\n> +        ]\n> +\n> +        # Disable warnings/checks on these generated files.\n> +        chromium_code = false\n> +\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append \"_java\" to get the java\n> +          # dependency name.\n> +          full_name = get_label_info(d, \"label_no_toolchain\")\n> +          deps += [ \"${full_name}_java\" ]\n> +        }\n> +\n> +        srcjar_deps = [ \":$java_srcjar_target_name\" ]\n> +      }\n> +    }\n> +  }\n> +\n> +  use_typescript_for_target =\n> +      enable_typescript_bindings && defined(invoker.use_typescript_sources) &&\n> +      invoker.use_typescript_sources\n> +\n> +  if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {\n> +    not_needed(invoker, [ \"use_typescript_sources\" ])\n> +  }\n> +\n> +  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> +      !use_typescript_for_target) {\n> +    if (sources_list != []) {\n> +      generator_js_target_name = \"${target_name}_js__generator\"\n> +      action(generator_js_target_name) {\n> +        script = mojom_generator_script\n> +        inputs = mojom_generator_sources + jinja2_sources\n> +        sources = sources_list\n> +        deps = [\n> +          \":$parser_target_name\",\n> +          \"//mojo/public/tools/bindings:precompile_templates\",\n> +        ]\n> +        if (defined(invoker.parser_deps)) {\n> +          deps += invoker.parser_deps\n> +        }\n> +        outputs = []\n> +        args = common_generator_args\n> +        filelist = []\n> +        foreach(source, sources_list) {\n> +          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        }\n> +        foreach(base_path, output_file_base_paths) {\n> +          outputs += [\n> +            \"$root_gen_dir/$base_path.js\",\n> +            \"$root_gen_dir/$base_path.externs.js\",\n> +            \"$root_gen_dir/$base_path-lite.js\",\n> +            \"$root_gen_dir/$base_path.html\",\n> +            \"$root_gen_dir/$base_path-lite-for-compile.js\",\n> +          ]\n> +        }\n> +\n> +        response_file_contents = filelist\n> +\n> +        args += [\n> +          \"--filelist={{response_file_name}}\",\n> +          \"-g\",\n> +          \"javascript\",\n> +          \"--js_bindings_mode=new\",\n> +        ]\n> +\n> +        if (defined(invoker.js_generate_struct_deserializers) &&\n> +            invoker.js_generate_struct_deserializers) {\n> +          args += [ \"--js_generate_struct_deserializers\" ]\n> +        }\n> +\n> +        if (!defined(invoker.scramble_message_ids) ||\n> +            invoker.scramble_message_ids) {\n> +          inputs += message_scrambling_inputs\n> +          args += message_scrambling_args\n> +        }\n> +\n> +        if (generate_fuzzing) {\n> +          args += [ \"--generate_fuzzing\" ]\n> +        }\n> +      }\n> +    }\n> +\n> +    js_target_name = target_name + \"_js\"\n> +    group(js_target_name) {\n> +      public_deps = []\n> +      if (sources_list != []) {\n> +        public_deps += [ \":$generator_js_target_name\" ]\n> +      }\n> +\n> +      foreach(d, all_deps) {\n> +        full_name = get_label_info(d, \"label_no_toolchain\")\n> +        public_deps += [ \"${full_name}_js\" ]\n> +      }\n> +    }\n> +\n> +    group(js_data_deps_target_name) {\n> +      deps = []\n> +      if (sources_list != []) {\n> +        data = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          data += [\n> +            \"$root_gen_dir/${base_path}.js\",\n> +            \"$root_gen_dir/${base_path}-lite.js\",\n> +          ]\n> +        }\n> +        deps += [ \":$generator_js_target_name\" ]\n> +      }\n> +\n> +      data_deps = []\n> +      foreach(d, all_deps) {\n> +        full_name = get_label_info(d, \"label_no_toolchain\")\n> +        data_deps += [ \"${full_name}_js_data_deps\" ]\n> +      }\n> +    }\n> +\n> +    js_library_target_name = \"${target_name}_js_library\"\n> +    if (sources_list != []) {\n> +      js_library(js_library_target_name) {\n> +        extra_public_deps = [ \":$generator_js_target_name\" ]\n> +        sources = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          sources += [ \"$root_gen_dir/${base_path}-lite.js\" ]\n> +        }\n> +        externs_list = [\n> +          \"${externs_path}/mojo_core.js\",\n> +          \"${externs_path}/pending.js\",\n> +        ]\n> +\n> +        deps = []\n> +        foreach(d, all_deps) {\n> +          full_name = get_label_info(d, \"label_no_toolchain\")\n> +          deps += [ \"${full_name}_js_library\" ]\n> +        }\n> +      }\n> +    } else {\n> +      group(js_library_target_name) {\n> +      }\n> +    }\n> +\n> +    js_library_for_compile_target_name = \"${target_name}_js_library_for_compile\"\n> +    if (sources_list != []) {\n> +      js_library(js_library_for_compile_target_name) {\n> +        extra_public_deps = [ \":$generator_js_target_name\" ]\n> +        sources = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          sources += [ \"$root_gen_dir/${base_path}-lite-for-compile.js\" ]\n> +        }\n> +        externs_list = [\n> +          \"${externs_path}/mojo_core.js\",\n> +          \"${externs_path}/pending.js\",\n> +        ]\n> +        deps = []\n> +        if (!defined(invoker.disallow_native_types)) {\n> +          deps += [ \"//mojo/public/js:bindings_lite_sources\" ]\n> +        }\n> +        foreach(d, all_deps) {\n> +          full_name = get_label_info(d, \"label_no_toolchain\")\n> +          deps += [ \"${full_name}_js_library_for_compile\" ]\n> +        }\n> +      }\n> +    } else {\n> +      group(js_library_for_compile_target_name) {\n> +      }\n> +    }\n> +  }\n> +  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> +      use_typescript_for_target) {\n> +    generator_js_target_names = []\n> +    source_filelist = []\n> +    foreach(source, sources_list) {\n> +      source_filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +    }\n> +\n> +    dependency_types = [\n> +      {\n> +        name = \"regular\"\n> +        ts_extension = \".ts\"\n> +        js_extension = \".js\"\n> +      },\n> +      {\n> +        name = \"es_modules\"\n> +        ts_extension = \".m.ts\"\n> +        js_extension = \".m.js\"\n> +      },\n> +    ]\n> +\n> +    foreach(dependency_type, dependency_types) {\n> +      ts_outputs = []\n> +      js_outputs = []\n> +\n> +      foreach(base_path, output_file_base_paths) {\n> +        ts_outputs +=\n> +            [ \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\" ]\n> +        js_outputs +=\n> +            [ \"$root_gen_dir/$base_path-lite${dependency_type.js_extension}\" ]\n> +      }\n> +\n> +      # Generate Typescript bindings.\n> +      generator_ts_target_name =\n> +          \"${target_name}_${dependency_type.name}__ts__generator\"\n> +      action(generator_ts_target_name) {\n> +        script = mojom_generator_script\n> +        inputs = mojom_generator_sources + jinja2_sources\n> +        sources = sources_list\n> +        deps = [\n> +          \":$parser_target_name\",\n> +          \"//mojo/public/tools/bindings:precompile_templates\",\n> +        ]\n> +\n> +        outputs = ts_outputs\n> +        args = common_generator_args\n> +        response_file_contents = source_filelist\n> +\n> +        args += [\n> +          \"--filelist={{response_file_name}}\",\n> +          \"-g\",\n> +          \"typescript\",\n> +        ]\n> +\n> +        if (dependency_type.name == \"es_modules\") {\n> +          args += [ \"--ts_use_es_modules\" ]\n> +        }\n> +\n> +        # TODO(crbug.com/1007587): Support scramble_message_ids.\n> +        # TODO(crbug.com/1007591): Support generate_fuzzing.\n> +      }\n> +\n> +      # Create tsconfig.json for the generated Typescript.\n> +      tsconfig_filename =\n> +          \"$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json\"\n> +      tsconfig = {\n> +      }\n> +      tsconfig.compilerOptions = {\n> +        composite = true\n> +        target = \"es6\"\n> +        module = \"es6\"\n> +        lib = [\n> +          \"es6\",\n> +          \"esnext.bigint\",\n> +        ]\n> +        strict = true\n> +      }\n> +      tsconfig.files = []\n> +      foreach(base_path, output_file_base_paths) {\n> +        tsconfig.files += [ rebase_path(\n> +                \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\",\n> +                target_gen_dir,\n> +                root_gen_dir) ]\n> +      }\n> +      tsconfig.references = []\n> +\n> +      # Get tsconfigs for deps.\n> +      foreach(d, all_deps) {\n> +        dep_target_gen_dir = rebase_path(get_label_info(d, \"target_gen_dir\"))\n> +        dep_name = get_label_info(d, \"name\")\n> +        reference = {\n> +        }\n> +        reference.path = \"$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json\"\n> +        tsconfig.references += [ reference ]\n> +      }\n> +      write_file(tsconfig_filename, tsconfig, \"json\")\n> +\n> +      # Compile previously generated Typescript to Javascript.\n> +      generator_js_target_name =\n> +          \"${target_name}_${dependency_type.name}__js__generator\"\n> +      generator_js_target_names += [ generator_js_target_name ]\n> +\n> +      action(generator_js_target_name) {\n> +        script = \"$mojom_generator_root/compile_typescript.py\"\n> +        sources = ts_outputs\n> +        outputs = js_outputs\n> +        public_deps = [ \":$generator_ts_target_name\" ]\n> +        foreach(d, all_deps) {\n> +          full_name = get_label_info(d, \"label_no_toolchain\")\n> +          public_deps +=\n> +              [ \"${full_name}_${dependency_type.name}__js__generator\" ]\n> +        }\n> +\n> +        absolute_tsconfig_path =\n> +            rebase_path(tsconfig_filename, \"\", target_gen_dir)\n> +        args = [ \"--tsconfig_path=$absolute_tsconfig_path\" ]\n> +      }\n> +    }\n> +\n> +    js_target_name = target_name + \"_js\"\n> +    group(js_target_name) {\n> +      public_deps = []\n> +      if (sources_list != []) {\n> +        foreach(generator_js_target_name, generator_js_target_names) {\n> +          public_deps += [ \":$generator_js_target_name\" ]\n> +        }\n> +      }\n> +\n> +      foreach(d, all_deps) {\n> +        full_name = get_label_info(d, \"label_no_toolchain\")\n> +        public_deps += [ \"${full_name}_js\" ]\n> +      }\n> +    }\n> +\n> +    group(js_data_deps_target_name) {\n> +      data = js_outputs\n> +      deps = []\n> +      foreach(generator_js_target_name, generator_js_target_names) {\n> +        deps += [ \":$generator_js_target_name\" ]\n> +      }\n> +      data_deps = []\n> +      foreach(d, all_deps) {\n> +        full_name = get_label_info(d, \"label_no_toolchain\")\n> +        data_deps += [ \"${full_name}_js_data_deps\" ]\n> +      }\n> +    }\n> +  }\n> +}\n> +\n> +# A helper for the mojom() template above when component libraries are desired\n> +# for generated C++ bindings units. Supports all the same arguments as mojom()\n> +# except for the optional |component_output_prefix| and |component_macro_prefix|\n> +# arguments. These are instead shortened to |output_prefix| and |macro_prefix|\n> +# and are *required*.\n> +template(\"mojom_component\") {\n> +  assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix))\n> +\n> +  mojom(target_name) {\n> +    forward_variables_from(invoker,\n> +                           \"*\",\n> +                           [\n> +                             \"output_prefix\",\n> +                             \"macro_prefix\",\n> +                           ])\n> +    component_output_prefix = invoker.output_prefix\n> +    component_macro_prefix = invoker.macro_prefix\n> +  }\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> new file mode 100755\n> index 00000000..da9efc71\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> @@ -0,0 +1,390 @@\n> +#!/usr/bin/env python\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +\"\"\"The frontend for the Mojo bindings system.\"\"\"\n> +\n> +from __future__ import print_function\n> +\n> +import argparse\n> +\n> +import hashlib\n> +import importlib\n> +import json\n> +import os\n> +import pprint\n> +import re\n> +import struct\n> +import sys\n> +\n> +# Disable lint check for finding modules:\n> +# pylint: disable=F0401\n> +\n> +def _GetDirAbove(dirname):\n> +  \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n> +  also be \"above\" this file).\"\"\"\n> +  path = os.path.abspath(__file__)\n> +  while True:\n> +    path, tail = os.path.split(path)\n> +    assert tail\n> +    if tail == dirname:\n> +      return path\n> +\n> +\n> +sys.path.insert(\n> +    0,\n> +    os.path.join(\n> +        os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n> +\n> +from mojom.error import Error\n> +import mojom.fileutil as fileutil\n> +from mojom.generate.module import Module\n> +from mojom.generate import template_expander\n> +from mojom.generate import translate\n> +from mojom.generate.generator import WriteFile\n> +\n> +sys.path.append(\n> +    os.path.join(_GetDirAbove(\"mojo\"), \"tools\", \"diagnosis\"))\n> +import crbug_1001171\n> +\n> +\n> +_BUILTIN_GENERATORS = {\n> +    \"c++\": \"mojom_cpp_generator\",\n> +    \"javascript\": \"mojom_js_generator\",\n> +    \"java\": \"mojom_java_generator\",\n> +    \"mojolpm\": \"mojom_mojolpm_generator\",\n> +    \"typescript\": \"mojom_ts_generator\",\n> +}\n> +\n> +\n> +def LoadGenerators(generators_string):\n> +  if not generators_string:\n> +    return []  # No generators.\n> +\n> +  generators = {}\n> +  for generator_name in [s.strip() for s in generators_string.split(\",\")]:\n> +    language = generator_name.lower()\n> +    if language not in _BUILTIN_GENERATORS:\n> +      print(\"Unknown generator name %s\" % generator_name)\n> +      sys.exit(1)\n> +    generator_module = importlib.import_module(\n> +        \"generators.%s\" % _BUILTIN_GENERATORS[language])\n> +    generators[language] = generator_module\n> +  return generators\n> +\n> +\n> +def MakeImportStackMessage(imported_filename_stack):\n> +  \"\"\"Make a (human-readable) message listing a chain of imports. (Returned\n> +  string begins with a newline (if nonempty) and does not end with one.)\"\"\"\n> +  return ''.join(\n> +      reversed([\"\\n  %s was imported by %s\" % (a, b) for (a, b) in \\\n> +                    zip(imported_filename_stack[1:], imported_filename_stack)]))\n> +\n> +\n> +class RelativePath(object):\n> +  \"\"\"Represents a path relative to the source tree or generated output dir.\"\"\"\n> +\n> +  def __init__(self, path, source_root, output_dir):\n> +    self.path = path\n> +    if path.startswith(source_root):\n> +      self.root = source_root\n> +    elif path.startswith(output_dir):\n> +      self.root = output_dir\n> +    else:\n> +      raise Exception(\"Invalid input path %s\" % path)\n> +\n> +  def relative_path(self):\n> +    return os.path.relpath(\n> +        os.path.abspath(self.path), os.path.abspath(self.root))\n> +\n> +\n> +def _GetModulePath(path, output_dir):\n> +  return os.path.join(output_dir, path.relative_path() + '-module')\n> +\n> +\n> +def ScrambleMethodOrdinals(interfaces, salt):\n> +  already_generated = set()\n> +  for interface in interfaces:\n> +    i = 0\n> +    already_generated.clear()\n> +    for method in interface.methods:\n> +      if method.explicit_ordinal is not None:\n> +        continue\n> +      while True:\n> +        i = i + 1\n> +        if i == 1000000:\n> +          raise Exception(\"Could not generate %d method ordinals for %s\" %\n> +              (len(interface.methods), interface.mojom_name))\n> +        # Generate a scrambled method.ordinal value. The algorithm doesn't have\n> +        # to be very strong, cryptographically. It just needs to be non-trivial\n> +        # to guess the results without the secret salt, in order to make it\n> +        # harder for a compromised process to send fake Mojo messages.\n> +        sha256 = hashlib.sha256(salt)\n> +        sha256.update(interface.mojom_name.encode('utf-8'))\n> +        sha256.update(str(i).encode('utf-8'))\n> +        # Take the first 4 bytes as a little-endian uint32.\n> +        ordinal = struct.unpack('<L', sha256.digest()[:4])[0]\n> +        # Trim to 31 bits, so it always fits into a Java (signed) int.\n> +        ordinal = ordinal & 0x7fffffff\n> +        if ordinal in already_generated:\n> +          continue\n> +        already_generated.add(ordinal)\n> +        method.ordinal = ordinal\n> +        method.ordinal_comment = (\n> +            'The %s value is based on sha256(salt + \"%s%d\").' %\n> +            (ordinal, interface.mojom_name, i))\n> +        break\n> +\n> +\n> +def ReadFileContents(filename):\n> +  with open(filename, 'rb') as f:\n> +    return f.read()\n> +\n> +\n> +class MojomProcessor(object):\n> +  \"\"\"Takes parsed mojom modules and generates language bindings from them.\n> +\n> +  Attributes:\n> +    _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from\n> +        relative mojom filename paths to the module AST for that mojom file.\n> +  \"\"\"\n> +  def __init__(self, should_generate):\n> +    self._should_generate = should_generate\n> +    self._processed_files = {}\n> +    self._typemap = {}\n> +\n> +  def LoadTypemaps(self, typemaps):\n> +    # Support some very simple single-line comments in typemap JSON.\n> +    comment_expr = r\"^\\s*//.*$\"\n> +    def no_comments(line):\n> +      return not re.match(comment_expr, line)\n> +    for filename in typemaps:\n> +      with open(filename) as f:\n> +        typemaps = json.loads(\"\".join(filter(no_comments, f.readlines())))\n> +        for language, typemap in typemaps.items():\n> +          language_map = self._typemap.get(language, {})\n> +          language_map.update(typemap)\n> +          self._typemap[language] = language_map\n> +    if 'c++' in self._typemap:\n> +      self._typemap['mojolpm'] = self._typemap['c++']\n> +\n> +  def _GenerateModule(self, args, remaining_args, generator_modules,\n> +                      rel_filename, imported_filename_stack):\n> +    # Return the already-generated module.\n> +    if rel_filename.path in self._processed_files:\n> +      return self._processed_files[rel_filename.path]\n> +\n> +    if rel_filename.path in imported_filename_stack:\n> +      print(\"%s: Error: Circular dependency\" % rel_filename.path + \\\n> +          MakeImportStackMessage(imported_filename_stack + [rel_filename.path]))\n> +      sys.exit(1)\n> +\n> +    module_path = _GetModulePath(rel_filename, args.output_dir)\n> +    with open(module_path, 'rb') as f:\n> +      module = Module.Load(f)\n> +\n> +    if args.scrambled_message_id_salt_paths:\n> +      salt = b''.join(\n> +          map(ReadFileContents, args.scrambled_message_id_salt_paths))\n> +      ScrambleMethodOrdinals(module.interfaces, salt)\n> +\n> +    if self._should_generate(rel_filename.path):\n> +      for language, generator_module in generator_modules.items():\n> +        generator = generator_module.Generator(\n> +            module, args.output_dir, typemap=self._typemap.get(language, {}),\n> +            variant=args.variant, bytecode_path=args.bytecode_path,\n> +            for_blink=args.for_blink,\n> +            js_bindings_mode=args.js_bindings_mode,\n> +            js_generate_struct_deserializers=\\\n> +                    args.js_generate_struct_deserializers,\n> +            export_attribute=args.export_attribute,\n> +            export_header=args.export_header,\n> +            generate_non_variant_code=args.generate_non_variant_code,\n> +            support_lazy_serialization=args.support_lazy_serialization,\n> +            disallow_native_types=args.disallow_native_types,\n> +            disallow_interfaces=args.disallow_interfaces,\n> +            generate_message_ids=args.generate_message_ids,\n> +            generate_fuzzing=args.generate_fuzzing,\n> +            enable_kythe_annotations=args.enable_kythe_annotations,\n> +            extra_cpp_template_paths=args.extra_cpp_template_paths,\n> +            generate_extra_cpp_only=args.generate_extra_cpp_only)\n> +        filtered_args = []\n> +        if hasattr(generator_module, 'GENERATOR_PREFIX'):\n> +          prefix = '--' + generator_module.GENERATOR_PREFIX + '_'\n> +          filtered_args = [arg for arg in remaining_args\n> +                           if arg.startswith(prefix)]\n> +        generator.GenerateFiles(filtered_args)\n> +\n> +    # Save result.\n> +    self._processed_files[rel_filename.path] = module\n> +    return module\n> +\n> +\n> +def _Generate(args, remaining_args):\n> +  if args.variant == \"none\":\n> +    args.variant = None\n> +\n> +  for idx, import_dir in enumerate(args.import_directories):\n> +    tokens = import_dir.split(\":\")\n> +    if len(tokens) >= 2:\n> +      args.import_directories[idx] = RelativePath(tokens[0], tokens[1],\n> +                                                  args.output_dir)\n> +    else:\n> +      args.import_directories[idx] = RelativePath(tokens[0], args.depth,\n> +                                                  args.output_dir)\n> +  generator_modules = LoadGenerators(args.generators_string)\n> +\n> +  fileutil.EnsureDirectoryExists(args.output_dir)\n> +\n> +  processor = MojomProcessor(lambda filename: filename in args.filename)\n> +  processor.LoadTypemaps(set(args.typemaps))\n> +\n> +  if args.filelist:\n> +    with open(args.filelist) as f:\n> +      args.filename.extend(f.read().split())\n> +\n> +  for filename in args.filename:\n> +    processor._GenerateModule(\n> +        args, remaining_args, generator_modules,\n> +        RelativePath(filename, args.depth, args.output_dir), [])\n> +\n> +  return 0\n> +\n> +\n> +def _Precompile(args, _):\n> +  generator_modules = LoadGenerators(\",\".join(_BUILTIN_GENERATORS.keys()))\n> +\n> +  template_expander.PrecompileTemplates(generator_modules, args.output_dir)\n> +  return 0\n> +\n> +\n> +def main():\n> +  parser = argparse.ArgumentParser(\n> +      description=\"Generate bindings from mojom files.\")\n> +  parser.add_argument(\"--use_bundled_pylibs\", action=\"store_true\",\n> +                      help=\"use Python modules bundled in the SDK\")\n> +  parser.add_argument(\n> +      \"-o\",\n> +      \"--output_dir\",\n> +      dest=\"output_dir\",\n> +      default=\".\",\n> +      help=\"output directory for generated files\")\n> +\n> +  subparsers = parser.add_subparsers()\n> +\n> +  generate_parser = subparsers.add_parser(\n> +      \"generate\", description=\"Generate bindings from mojom files.\")\n> +  generate_parser.add_argument(\"filename\", nargs=\"*\",\n> +                               help=\"mojom input file\")\n> +  generate_parser.add_argument(\"--filelist\", help=\"mojom input file list\")\n> +  generate_parser.add_argument(\"-d\", \"--depth\", dest=\"depth\", default=\".\",\n> +                               help=\"depth from source root\")\n> +  generate_parser.add_argument(\"-g\",\n> +                               \"--generators\",\n> +                               dest=\"generators_string\",\n> +                               metavar=\"GENERATORS\",\n> +                               default=\"c++,javascript,java,mojolpm\",\n> +                               help=\"comma-separated list of generators\")\n> +  generate_parser.add_argument(\n> +      \"--gen_dir\", dest=\"gen_directories\", action=\"append\", metavar=\"directory\",\n> +      default=[], help=\"add a directory to be searched for the syntax trees.\")\n> +  generate_parser.add_argument(\n> +      \"-I\", dest=\"import_directories\", action=\"append\", metavar=\"directory\",\n> +      default=[],\n> +      help=\"add a directory to be searched for import files. The depth from \"\n> +           \"source root can be specified for each import by appending it after \"\n> +           \"a colon\")\n> +  generate_parser.add_argument(\"--typemap\", action=\"append\", metavar=\"TYPEMAP\",\n> +                               default=[], dest=\"typemaps\",\n> +                               help=\"apply TYPEMAP to generated output\")\n> +  generate_parser.add_argument(\"--variant\", dest=\"variant\", default=None,\n> +                               help=\"output a named variant of the bindings\")\n> +  generate_parser.add_argument(\n> +      \"--bytecode_path\", required=True, help=(\n> +          \"the path from which to load template bytecode; to generate template \"\n> +          \"bytecode, run %s precompile BYTECODE_PATH\" % os.path.basename(\n> +              sys.argv[0])))\n> +  generate_parser.add_argument(\"--for_blink\", action=\"store_true\",\n> +                               help=\"Use WTF types as generated types for mojo \"\n> +                               \"string/array/map.\")\n> +  generate_parser.add_argument(\n> +      \"--js_bindings_mode\", choices=[\"new\", \"old\"], default=\"old\",\n> +      help=\"This option only affects the JavaScript bindings. The value could \"\n> +      \"be \\\"new\\\" to generate new-style lite JS bindings in addition to the \"\n> +      \"old, or \\\"old\\\" to only generate old bindings.\")\n> +  generate_parser.add_argument(\n> +      \"--js_generate_struct_deserializers\", action=\"store_true\",\n> +      help=\"Generate javascript deserialize methods for structs in \"\n> +      \"mojom-lite.js file\")\n> +  generate_parser.add_argument(\n> +      \"--export_attribute\", default=\"\",\n> +      help=\"Optional attribute to specify on class declaration to export it \"\n> +      \"for the component build.\")\n> +  generate_parser.add_argument(\n> +      \"--export_header\", default=\"\",\n> +      help=\"Optional header to include in the generated headers to support the \"\n> +      \"component build.\")\n> +  generate_parser.add_argument(\n> +      \"--generate_non_variant_code\", action=\"store_true\",\n> +      help=\"Generate code that is shared by different variants.\")\n> +  generate_parser.add_argument(\n> +      \"--scrambled_message_id_salt_path\",\n> +      dest=\"scrambled_message_id_salt_paths\",\n> +      help=\"If non-empty, the path to a file whose contents should be used as\"\n> +      \"a salt for generating scrambled message IDs. If this switch is specified\"\n> +      \"more than once, the contents of all salt files are concatenated to form\"\n> +      \"the salt value.\", default=[], action=\"append\")\n> +  generate_parser.add_argument(\n> +      \"--support_lazy_serialization\",\n> +      help=\"If set, generated bindings will serialize lazily when possible.\",\n> +      action=\"store_true\")\n> +  generate_parser.add_argument(\n> +      \"--extra_cpp_template_paths\",\n> +      dest=\"extra_cpp_template_paths\",\n> +      action=\"append\",\n> +      metavar=\"path_to_template\",\n> +      default=[],\n> +      help=\"Provide a path to a new template (.tmpl) that is used to generate \"\n> +      \"additional C++ source/header files \")\n> +  generate_parser.add_argument(\n> +      \"--generate_extra_cpp_only\",\n> +      help=\"If set and extra_cpp_template_paths provided, will only generate\"\n> +      \"extra_cpp_template related C++ bindings\",\n> +      action=\"store_true\")\n> +  generate_parser.add_argument(\n> +      \"--disallow_native_types\",\n> +      help=\"Disallows the [Native] attribute to be specified on structs or \"\n> +      \"enums within the mojom file.\", action=\"store_true\")\n> +  generate_parser.add_argument(\n> +      \"--disallow_interfaces\",\n> +      help=\"Disallows interface definitions within the mojom file. It is an \"\n> +      \"error to specify this flag when processing a mojom file which defines \"\n> +      \"any interface.\", action=\"store_true\")\n> +  generate_parser.add_argument(\n> +      \"--generate_message_ids\",\n> +      help=\"Generates only the message IDs header for C++ bindings. Note that \"\n> +      \"this flag only matters if --generate_non_variant_code is also \"\n> +      \"specified.\", action=\"store_true\")\n> +  generate_parser.add_argument(\n> +      \"--generate_fuzzing\",\n> +      action=\"store_true\",\n> +      help=\"Generates additional bindings for fuzzing in JS.\")\n> +  generate_parser.add_argument(\n> +      \"--enable_kythe_annotations\",\n> +      action=\"store_true\",\n> +      help=\"Adds annotations for kythe metadata generation.\")\n> +\n> +  generate_parser.set_defaults(func=_Generate)\n> +\n> +  precompile_parser = subparsers.add_parser(\"precompile\",\n> +      description=\"Precompile templates for the mojom bindings generator.\")\n> +  precompile_parser.set_defaults(func=_Precompile)\n> +\n> +  args, remaining_args = parser.parse_known_args()\n> +  return args.func(args, remaining_args)\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  with crbug_1001171.DumpStateOnLookupError():\n> +    sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> new file mode 100644\n> index 00000000..bddbe3f4\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> @@ -0,0 +1,62 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +from mojom_bindings_generator import MakeImportStackMessage\n> +from mojom_bindings_generator import ScrambleMethodOrdinals\n> +\n> +\n> +class FakeIface(object):\n> +  def __init__(self):\n> +    self.mojom_name = None\n> +    self.methods = None\n> +\n> +\n> +class FakeMethod(object):\n> +  def __init__(self, explicit_ordinal=None):\n> +    self.explicit_ordinal = explicit_ordinal\n> +    self.ordinal = explicit_ordinal\n> +    self.ordinal_comment = None\n> +\n> +\n> +class MojoBindingsGeneratorTest(unittest.TestCase):\n> +  \"\"\"Tests mojo_bindings_generator.\"\"\"\n> +\n> +  def testMakeImportStackMessage(self):\n> +    \"\"\"Tests MakeImportStackMessage().\"\"\"\n> +    self.assertEqual(MakeImportStackMessage([\"x\"]), \"\")\n> +    self.assertEqual(MakeImportStackMessage([\"x\", \"y\"]),\n> +                     \"\\n  y was imported by x\")\n> +    self.assertEqual(MakeImportStackMessage([\"x\", \"y\", \"z\"]),\n> +                     \"\\n  z was imported by y\\n  y was imported by x\")\n> +\n> +  def testScrambleMethodOrdinals(self):\n> +    \"\"\"Tests ScrambleMethodOrdinals().\"\"\"\n> +    interface = FakeIface()\n> +    interface.mojom_name = 'RendererConfiguration'\n> +    interface.methods = [\n> +        FakeMethod(),\n> +        FakeMethod(),\n> +        FakeMethod(),\n> +        FakeMethod(explicit_ordinal=42)\n> +    ]\n> +    ScrambleMethodOrdinals([interface], \"foo\".encode('utf-8'))\n> +    # These next three values are hard-coded. If the generation algorithm\n> +    # changes from being based on sha256(seed + interface.name + str(i)) then\n> +    # these numbers will obviously need to change too.\n> +    #\n> +    # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is\n> +    # '\\xa5\\xbc\\xf9\\xca' and that hex(1257880741) = '0x4af9bca5'. The\n> +    # difference in 0x4a vs 0xca is because we only take 31 bits.\n> +    self.assertEqual(interface.methods[0].ordinal, 1257880741)\n> +    self.assertEqual(interface.methods[1].ordinal, 631133653)\n> +    self.assertEqual(interface.methods[2].ordinal, 549336076)\n> +\n> +    # Explicit method ordinals should not be scrambled.\n> +    self.assertEqual(interface.methods[3].ordinal, 42)\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> new file mode 100755\n> index 00000000..15f0e3ba\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> @@ -0,0 +1,119 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Downgrades *.mojom files to the old mojo types for remotes and receivers.\"\"\"\n> +\n> +import argparse\n> +import fnmatch\n> +import os\n> +import re\n> +import shutil\n> +import sys\n> +import tempfile\n> +\n> +# List of patterns and replacements to match and use against the contents of a\n> +# mojo file. Each replacement string will be used with Python string's format()\n> +# function, so the '{}' substring is used to mark where the mojo type should go.\n> +_MOJO_REPLACEMENTS = {\n> +    r'pending_remote': r'{}',\n> +    r'pending_receiver': r'{}&',\n> +    r'pending_associated_remote': r'associated {}',\n> +    r'pending_associated_receiver': r'associated {}&',\n> +}\n> +\n> +# Pre-compiled regular expression that matches against any of the replacements.\n> +_REGEXP_PATTERN = re.compile(\n> +    r'|'.join(\n> +        ['{}\\s*<\\s*(.*?)\\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),\n> +    flags=re.DOTALL)\n> +\n> +\n> +def ReplaceFunction(match_object):\n> +  \"\"\"Returns the right replacement for the string matched against the regexp.\"\"\"\n> +  for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):\n> +    if match_object.group(0).startswith(match):\n> +      return repl.format(match_object.group(index))\n> +\n> +\n> +def DowngradeFile(path, output_dir=None):\n> +  \"\"\"Downgrades the mojom file specified by |path| to the old mojo types.\n> +\n> +  Optionally pass |output_dir| to place the result under a separate output\n> +  directory, preserving the relative path to the file included in |path|.\n> +  \"\"\"\n> +  # Use a temporary file to dump the new contents after replacing the patterns.\n> +  with open(path) as src_mojo_file:\n> +    with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:\n> +      tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())\n> +      tmp_mojo_file.write(tmp_contents)\n> +\n> +  # Files should be placed in the desired output directory\n> +  if output_dir:\n> +    output_filepath = os.path.join(output_dir, os.path.basename(path))\n> +    if not os.path.exists(output_dir):\n> +      os.makedirs(output_dir)\n> +  else:\n> +    output_filepath = path\n> +\n> +  # Write the new contents preserving the original file's attributes.\n> +  shutil.copystat(path, tmp_mojo_file.name)\n> +  shutil.move(tmp_mojo_file.name, output_filepath)\n> +\n> +  # Make sure to \"touch\" the new file so that access, modify and change times\n> +  # are always newer than the source file's, otherwise Modify time will be kept\n> +  # as per the call to shutil.copystat(), causing unnecessary generations of the\n> +  # output file in subsequent builds due to ninja considering it dirty.\n> +  os.utime(output_filepath, None)\n> +\n> +\n> +def DowngradeDirectory(path, output_dir=None):\n> +  \"\"\"Downgrades mojom files inside directory |path| to the old mojo types.\n> +\n> +  Optionally pass |output_dir| to place the result under a separate output\n> +  directory, preserving the relative path to the file included in |path|.\n> +  \"\"\"\n> +  # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7\n> +  mojom_filepaths = []\n> +  for dir_path, _, filenames in os.walk(path):\n> +    for filename in fnmatch.filter(filenames, \"*mojom\"):\n> +      mojom_filepaths.append(os.path.join(dir_path, filename))\n> +\n> +  for path in mojom_filepaths:\n> +    absolute_dirpath = os.path.dirname(os.path.abspath(path))\n> +    if output_dir:\n> +      dest_dirpath = output_dir + absolute_dirpath\n> +    else:\n> +      dest_dirpath = absolute_dirpath\n> +    DowngradeFile(path, dest_dirpath)\n> +\n> +\n> +def DowngradePath(src_path, output_dir=None):\n> +  \"\"\"Downgrades the mojom files pointed by |src_path| to the old mojo types.\n> +\n> +  Optionally pass |output_dir| to place the result under a separate output\n> +  directory, preserving the relative path to the file included in |path|.\n> +  \"\"\"\n> +  if os.path.isdir(src_path):\n> +    DowngradeDirectory(src_path, output_dir)\n> +  elif os.path.isfile(src_path):\n> +    DowngradeFile(src_path, output_dir)\n> +  else:\n> +    print(\">>> {} not pointing to a valid file or directory\".format(src_path))\n> +    sys.exit(1)\n> +\n> +\n> +def main():\n> +  parser = argparse.ArgumentParser(\n> +      description=\"Downgrade *.mojom files to use the old mojo types.\")\n> +  parser.add_argument(\n> +      \"srcpath\", help=\"path to the file or directory to apply the conversion\")\n> +  parser.add_argument(\n> +      \"--outdir\", help=\"the directory to place the converted file(s) under\")\n> +  args = parser.parse_args()\n> +\n> +  DowngradePath(args.srcpath, args.outdir)\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> new file mode 100755\n> index 00000000..f1783d59\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> @@ -0,0 +1,57 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import argparse\n> +import json\n> +import os\n> +import re\n> +import sys\n> +\n> +\n> +def CheckCppTypemapConfigs(target_name, config_filename, out_filename):\n> +  _SUPPORTED_CONFIG_KEYS = set([\n> +      'types', 'traits_headers', 'traits_private_headers', 'traits_sources',\n> +      'traits_deps', 'traits_public_deps'\n> +  ])\n> +  _SUPPORTED_TYPE_KEYS = set([\n> +      'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',\n> +      'move_only', 'nullable_is_same_type'\n> +  ])\n> +  with open(config_filename, 'r') as f:\n> +    for config in json.load(f):\n> +      for key in config.keys():\n> +        if key not in _SUPPORTED_CONFIG_KEYS:\n> +          raise ValueError('Invalid typemap property \"%s\" when processing %s' %\n> +                           (key, target_name))\n> +\n> +      types = config.get('types')\n> +      if not types:\n> +        raise ValueError('Typemap for %s must specify at least one type to map'\n> +                         % target_name)\n> +\n> +      for entry in types:\n> +        for key in entry.keys():\n> +          if key not in _SUPPORTED_TYPE_KEYS:\n> +            raise IOError(\n> +                'Invalid type property \"%s\" in typemap for \"%s\" on target %s' %\n> +                (key, entry.get('mojom', '(unknown)'), target_name))\n> +\n> +  with open(out_filename, 'w') as f:\n> +    f.truncate(0)\n> +\n> +\n> +def main():\n> +  parser = argparse.ArgumentParser()\n> +  _, args = parser.parse_known_args()\n> +  if len(args) != 3:\n> +    print('Usage: validate_typemap_config.py target_name config_filename '\n> +          'stamp_filename')\n> +    sys.exit(1)\n> +\n> +  CheckCppTypemapConfigs(args[0], args[1], args[2])\n> +\n> +\n> +if __name__ == '__main__':\n> +  main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/README.md b/utils/ipc/mojo/public/tools/mojom/README.md\n> new file mode 100644\n> index 00000000..6a4ff78a\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/README.md\n> @@ -0,0 +1,14 @@\n> +# The Mojom Parser\n> +\n> +The Mojom format is an interface definition language (IDL) for describing\n> +interprocess communication (IPC) messages and data types for use with the\n> +low-level cross-platform\n> +[Mojo IPC library](https://chromium.googlesource.com/chromium/src/+/master/mojo/public/c/system/README.md).\n> +\n> +This directory consists of a `mojom` Python module, its tests, and supporting\n> +command-line tools. The Python module implements the parser used by the\n> +command-line tools and exposes an API to help external bindings generators emit\n> +useful code from the parser's outputs.\n> +\n> +TODO(https://crbug.com/1060464): Fill out this documentation once the library\n> +and tools have stabilized.\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> new file mode 100755\n> index 00000000..7e746112\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> @@ -0,0 +1,170 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Verifies backward-compatibility of mojom type changes.\n> +\n> +Given a set of pre- and post-diff mojom file contents, and a root directory\n> +for a project, this tool verifies that any changes to [Stable] mojom types are\n> +backward-compatible with the previous version.\n> +\n> +This can be used e.g. by a presubmit check to prevent developers from making\n> +breaking changes to stable mojoms.\"\"\"\n> +\n> +import argparse\n> +import errno\n> +import io\n> +import json\n> +import os\n> +import os.path\n> +import shutil\n> +import six\n> +import sys\n> +import tempfile\n> +\n> +from mojom.generate import module\n> +from mojom.generate import translate\n> +from mojom.parse import parser\n> +\n> +\n> +class ParseError(Exception):\n> +  pass\n> +\n> +\n> +def _ValidateDelta(root, delta):\n> +  \"\"\"Parses all modified mojoms (including all transitive mojom dependencies,\n> +  even if unmodified) to perform backward-compatibility checks on any types\n> +  marked with the [Stable] attribute.\n> +\n> +  Note that unlike the normal build-time parser in mojom_parser.py, this does\n> +  not produce or rely on cached module translations, but instead parses the full\n> +  transitive closure of a mojom's input dependencies all at once.\n> +  \"\"\"\n> +\n> +  # First build a map of all files covered by the delta\n> +  affected_files = set()\n> +  old_files = {}\n> +  new_files = {}\n> +  for change in delta:\n> +    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.\n> +    filename = change['filename'].replace('\\\\', '/')\n> +    affected_files.add(filename)\n> +    if change['old']:\n> +      old_files[filename] = change['old']\n> +    if change['new']:\n> +      new_files[filename] = change['new']\n> +\n> +  # Parse and translate all mojoms relevant to the delta, including transitive\n> +  # imports that weren't modified.\n> +  unmodified_modules = {}\n> +\n> +  def parseMojom(mojom, file_overrides, override_modules):\n> +    if mojom in unmodified_modules or mojom in override_modules:\n> +      return\n> +\n> +    contents = file_overrides.get(mojom)\n> +    if contents:\n> +      modules = override_modules\n> +    else:\n> +      modules = unmodified_modules\n> +      with io.open(os.path.join(root, mojom), encoding='utf-8') as f:\n> +        contents = f.read()\n> +\n> +    try:\n> +      ast = parser.Parse(contents, mojom)\n> +    except Exception as e:\n> +      six.reraise(\n> +          ParseError,\n> +          'encountered exception {0} while parsing {1}'.format(e, mojom),\n> +          sys.exc_info()[2])\n> +    for imp in ast.import_list:\n> +      parseMojom(imp.import_filename, file_overrides, override_modules)\n> +\n> +    # Now that the transitive set of dependencies has been imported and parsed\n> +    # above, translate each mojom AST into a Module so that all types are fully\n> +    # defined and can be inspected.\n> +    all_modules = {}\n> +    all_modules.update(unmodified_modules)\n> +    all_modules.update(override_modules)\n> +    modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)\n> +\n> +  old_modules = {}\n> +  for mojom in old_files.keys():\n> +    parseMojom(mojom, old_files, old_modules)\n> +  new_modules = {}\n> +  for mojom in new_files.keys():\n> +    parseMojom(mojom, new_files, new_modules)\n> +\n> +  # At this point we have a complete set of translated Modules from both the\n> +  # pre- and post-diff mojom contents. Now we can analyze backward-compatibility\n> +  # of the deltas.\n> +  #\n> +  # Note that for backward-compatibility checks we only care about types which\n> +  # were marked [Stable] before the diff. Types newly marked as [Stable] are not\n> +  # checked.\n> +  def collectTypes(modules):\n> +    types = {}\n> +    for m in modules.values():\n> +      for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n> +        for kind in kinds:\n> +          types[kind.qualified_name] = kind\n> +    return types\n> +\n> +  old_types = collectTypes(old_modules)\n> +  new_types = collectTypes(new_modules)\n> +\n> +  # Collect any renamed types so they can be compared accordingly.\n> +  renamed_types = {}\n> +  for name, kind in new_types.items():\n> +    old_name = kind.attributes and kind.attributes.get('RenamedFrom')\n> +    if old_name:\n> +      renamed_types[old_name] = name\n> +\n> +  for qualified_name, kind in old_types.items():\n> +    if not kind.stable:\n> +      continue\n> +\n> +    new_name = renamed_types.get(qualified_name, qualified_name)\n> +    if new_name not in new_types:\n> +      raise Exception(\n> +          'Stable type %s appears to be deleted by this change. If it was '\n> +          'renamed, please add a [RenamedFrom] attribute to the new type. This '\n> +          'can be deleted by a subsequent change.' % qualified_name)\n> +\n> +    if not new_types[new_name].IsBackwardCompatible(kind):\n> +      raise Exception('Stable type %s appears to have changed in a way which '\n> +                      'breaks backward-compatibility. Please fix!\\n\\nIf you '\n> +                      'believe this assessment to be incorrect, please file a '\n> +                      'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> +                      'component.' % qualified_name)\n> +\n> +\n> +def Run(command_line, delta=None):\n> +  \"\"\"Runs the tool with the given command_line. Normally this will read the\n> +  change description from stdin as a JSON-encoded list, but tests may pass a\n> +  delta directly for convenience.\"\"\"\n> +  arg_parser = argparse.ArgumentParser(\n> +      description='Verifies backward-compatibility of mojom type changes.',\n> +      epilog=\"\"\"\n> +This tool reads a change description from stdin and verifies that all modified\n> +[Stable] mojom types will retain backward-compatibility. The change description\n> +must be a JSON-encoded list of objects, each with a \"filename\" key (path to a\n> +changed mojom file, relative to ROOT); an \"old\" key whose value is a string of\n> +the full file contents before the change, or null if the file is being added;\n> +and a \"new\" key whose value is a string of the full file contents after the\n> +change, or null if the file is being deleted.\"\"\")\n> +  arg_parser.add_argument(\n> +      '--src-root',\n> +      required=True,\n> +      action='store',\n> +      metavar='ROOT',\n> +      help='The root of the source tree in which the checked mojoms live.')\n> +\n> +  args, _ = arg_parser.parse_known_args(command_line)\n> +  if not delta:\n> +    delta = json.load(sys.stdin)\n> +  _ValidateDelta(args.src_root, delta)\n> +\n> +\n> +if __name__ == '__main__':\n> +  Run(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> new file mode 100755\n> index 00000000..9f51ea77\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> @@ -0,0 +1,260 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import json\n> +import os\n> +import os.path\n> +import shutil\n> +import tempfile\n> +import unittest\n> +\n> +import check_stable_mojom_compatibility\n> +\n> +from mojom.generate import module\n> +\n> +\n> +class Change(object):\n> +  \"\"\"Helper to clearly define a mojom file delta to be analyzed.\"\"\"\n> +\n> +  def __init__(self, filename, old=None, new=None):\n> +    \"\"\"If old is None, this is a file addition. If new is None, this is a file\n> +    deletion. Otherwise it's a file change.\"\"\"\n> +    self.filename = filename\n> +    self.old = old\n> +    self.new = new\n> +\n> +\n> +class UnchangedFile(Change):\n> +  def __init__(self, filename, contents):\n> +    super(UnchangedFile, self).__init__(filename, old=contents, new=contents)\n> +\n> +\n> +class CheckStableMojomCompatibilityTest(unittest.TestCase):\n> +  \"\"\"Tests covering the behavior of the compatibility checking tool. Note that\n> +  details of different compatibility checks and relevant failure modes are NOT\n> +  covered by these tests. Those are instead covered by unittests in\n> +  version_compatibility_unittest.py. Additionally, the tests which ensure a\n> +  given set of [Stable] mojom definitions are indeed plausibly stable (i.e. they\n> +  have no unstable dependencies) are covered by stable_attribute_unittest.py.\n> +\n> +  These tests cover higher-level concerns of the compatibility checking tool,\n> +  like file or symbol, renames, changes spread over multiple files, etc.\"\"\"\n> +\n> +  def verifyBackwardCompatibility(self, changes):\n> +    \"\"\"Helper for implementing assertBackwardCompatible and\n> +    assertNotBackwardCompatible\"\"\"\n> +\n> +    temp_dir = tempfile.mkdtemp()\n> +    for change in changes:\n> +      if change.old:\n> +        # Populate the old file on disk in our temporary fake source root\n> +        file_path = os.path.join(temp_dir, change.filename)\n> +        dir_path = os.path.dirname(file_path)\n> +        if not os.path.exists(dir_path):\n> +          os.makedirs(dir_path)\n> +        with open(file_path, 'w') as f:\n> +          f.write(change.old)\n> +\n> +    delta = []\n> +    for change in changes:\n> +      if change.old != change.new:\n> +        delta.append({\n> +            'filename': change.filename,\n> +            'old': change.old,\n> +            'new': change.new\n> +        })\n> +\n> +    try:\n> +      check_stable_mojom_compatibility.Run(['--src-root', temp_dir],\n> +                                           delta=delta)\n> +    finally:\n> +      shutil.rmtree(temp_dir)\n> +\n> +  def assertBackwardCompatible(self, changes):\n> +    self.verifyBackwardCompatibility(changes)\n> +\n> +  def assertNotBackwardCompatible(self, changes):\n> +    try:\n> +      self.verifyBackwardCompatibility(changes)\n> +    except Exception:\n> +      return\n> +\n> +    raise Exception('Change unexpectedly passed a backward-compatibility check')\n> +\n> +  def testBasicCompatibility(self):\n> +    \"\"\"Minimal smoke test to verify acceptance of a simple valid change.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Stable] struct S {};',\n> +               new='[Stable] struct S { [MinVersion=1] int32 x; };')\n> +    ])\n> +\n> +  def testBasicIncompatibility(self):\n> +    \"\"\"Minimal smoke test to verify rejection of a simple invalid change.\"\"\"\n> +    self.assertNotBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Stable] struct S {};',\n> +               new='[Stable] struct S { int32 x; };')\n> +    ])\n> +\n> +  def testIgnoreIfNotStable(self):\n> +    \"\"\"We don't care about types not marked [Stable]\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='struct S {};',\n> +               new='struct S { int32 x; };')\n> +    ])\n> +\n> +  def testRename(self):\n> +    \"\"\"We can do checks for renamed types.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Stable] struct S {};',\n> +               new='[Stable, RenamedFrom=\"S\"] struct T {};')\n> +    ])\n> +    self.assertNotBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Stable] struct S {};',\n> +               new='[Stable, RenamedFrom=\"S\"] struct T { int32 x; };')\n> +    ])\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Stable] struct S {};',\n> +               new=\"\"\"\\\n> +               [Stable, RenamedFrom=\"S\"]\n> +               struct T { [MinVersion=1] int32 x; };\n> +               \"\"\")\n> +    ])\n> +\n> +  def testNewlyStable(self):\n> +    \"\"\"We don't care about types newly marked as [Stable].\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='struct S {};',\n> +               new='[Stable] struct S { int32 x; };')\n> +    ])\n> +\n> +  def testFileRename(self):\n> +    \"\"\"Make sure we can still do compatibility checks after a file rename.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom', old='[Stable] struct S {};', new=None),\n> +        Change('bar/bar.mojom',\n> +               old=None,\n> +               new='[Stable] struct S { [MinVersion=1] int32 x; };')\n> +    ])\n> +    self.assertNotBackwardCompatible([\n> +        Change('foo/foo.mojom', old='[Stable] struct S {};', new=None),\n> +        Change('bar/bar.mojom', old=None, new='[Stable] struct S { int32 x; };')\n> +    ])\n> +\n> +  def testWithImport(self):\n> +    \"\"\"Ensure that cross-module dependencies do not break the compatibility\n> +    checking tool.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old=\"\"\"\\\n> +               module foo;\n> +               [Stable] struct S {};\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module foo;\n> +               [Stable] struct S { [MinVersion=2] int32 x; };\n> +               \"\"\"),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; [MinVersion=1] int32 y; };\n> +               \"\"\")\n> +    ])\n> +\n> +  def testWithMovedDefinition(self):\n> +    \"\"\"If a definition moves from one file to another, we should still be able\n> +    to check compatibility accurately.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old=\"\"\"\\\n> +               module foo;\n> +               [Stable] struct S {};\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module foo;\n> +               \"\"\"),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable, RenamedFrom=\"foo.S\"] struct S {\n> +                 [MinVersion=2] int32 x;\n> +               };\n> +               [Stable] struct T { S s; [MinVersion=1] int32 y; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old=\"\"\"\\\n> +               module foo;\n> +               [Stable] struct S {};\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module foo;\n> +               \"\"\"),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable, RenamedFrom=\"foo.S\"] struct S { int32 x; };\n> +               [Stable] struct T { S s; [MinVersion=1] int32 y; };\n> +               \"\"\")\n> +    ])\n> +\n> +  def testWithUnmodifiedImport(self):\n> +    \"\"\"Unchanged files in the filesystem are still parsed by the compatibility\n> +    checking tool if they're imported by a changed file.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; [MinVersion=1] int32 x; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; int32 x; };\n> +               \"\"\")\n> +    ])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> new file mode 100644\n> index 00000000..cb42dfac\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> @@ -0,0 +1,90 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +from mojom.generate import module as mojom\n> +\n> +\n> +class ConstTest(MojomParserTestCase):\n> +  \"\"\"Tests constant parsing behavior.\"\"\"\n> +\n> +  def testLiteralInt(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'const int32 k = 42;')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(1, len(a.constants))\n> +    self.assertEqual('k', a.constants[0].mojom_name)\n> +    self.assertEqual('42', a.constants[0].value)\n> +\n> +  def testLiteralFloat(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'const float k = 42.5;')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(1, len(a.constants))\n> +    self.assertEqual('k', a.constants[0].mojom_name)\n> +    self.assertEqual('42.5', a.constants[0].value)\n> +\n> +  def testLiteralString(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'const string k = \"woot\";')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(1, len(a.constants))\n> +    self.assertEqual('k', a.constants[0].mojom_name)\n> +    self.assertEqual('\"woot\"', a.constants[0].value)\n> +\n> +  def testEnumConstant(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'module a; enum E { kA = 41, kB };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(\n> +        b_mojom, \"\"\"\\\n> +      import \"a.mojom\";\n> +      const a.E kE1 = a.E.kB;\n> +\n> +      // We also allow value names to be unqualified, implying scope from the\n> +      // constant's type.\n> +      const a.E kE2 = kB;\n> +      \"\"\")\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(1, len(a.enums))\n> +    self.assertEqual('E', a.enums[0].mojom_name)\n> +    self.assertEqual(2, len(b.constants))\n> +    self.assertEqual('kE1', b.constants[0].mojom_name)\n> +    self.assertEqual(a.enums[0], b.constants[0].kind)\n> +    self.assertEqual(a.enums[0].fields[1], b.constants[0].value.field)\n> +    self.assertEqual(42, b.constants[0].value.field.numeric_value)\n> +    self.assertEqual('kE2', b.constants[1].mojom_name)\n> +    self.assertEqual(a.enums[0].fields[1], b.constants[1].value.field)\n> +    self.assertEqual(42, b.constants[1].value.field.numeric_value)\n> +\n> +  def testConstantReference(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'const int32 kA = 42; const int32 kB = kA;')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(2, len(a.constants))\n> +    self.assertEqual('kA', a.constants[0].mojom_name)\n> +    self.assertEqual('42', a.constants[0].value)\n> +    self.assertEqual('kB', a.constants[1].mojom_name)\n> +    self.assertEqual('42', a.constants[1].value)\n> +\n> +  def testImportedConstantReference(self):\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'const int32 kA = 42;')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(b_mojom, 'import \"a.mojom\"; const int32 kB = kA;')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(1, len(a.constants))\n> +    self.assertEqual(1, len(b.constants))\n> +    self.assertEqual('kA', a.constants[0].mojom_name)\n> +    self.assertEqual('42', a.constants[0].value)\n> +    self.assertEqual('kB', b.constants[0].mojom_name)\n> +    self.assertEqual('42', b.constants[0].value)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> new file mode 100644\n> index 00000000..d9005078\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> @@ -0,0 +1,92 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class EnumTest(MojomParserTestCase):\n> +  \"\"\"Tests enum parsing behavior.\"\"\"\n> +\n> +  def testExplicitValues(self):\n> +    \"\"\"Verifies basic parsing of assigned integral values.\"\"\"\n> +    types = self.ExtractTypes('enum E { kFoo=0, kBar=2, kBaz };')\n> +    self.assertEqual('kFoo', types['E'].fields[0].mojom_name)\n> +    self.assertEqual(0, types['E'].fields[0].numeric_value)\n> +    self.assertEqual('kBar', types['E'].fields[1].mojom_name)\n> +    self.assertEqual(2, types['E'].fields[1].numeric_value)\n> +    self.assertEqual('kBaz', types['E'].fields[2].mojom_name)\n> +    self.assertEqual(3, types['E'].fields[2].numeric_value)\n> +\n> +  def testImplicitValues(self):\n> +    \"\"\"Verifies basic automatic assignment of integral values at parse time.\"\"\"\n> +    types = self.ExtractTypes('enum E { kFoo, kBar, kBaz };')\n> +    self.assertEqual('kFoo', types['E'].fields[0].mojom_name)\n> +    self.assertEqual(0, types['E'].fields[0].numeric_value)\n> +    self.assertEqual('kBar', types['E'].fields[1].mojom_name)\n> +    self.assertEqual(1, types['E'].fields[1].numeric_value)\n> +    self.assertEqual('kBaz', types['E'].fields[2].mojom_name)\n> +    self.assertEqual(2, types['E'].fields[2].numeric_value)\n> +\n> +  def testSameEnumReference(self):\n> +    \"\"\"Verifies that an enum value can be assigned from the value of another\n> +    field within the same enum.\"\"\"\n> +    types = self.ExtractTypes('enum E { kA, kB, kFirst=kA };')\n> +    self.assertEqual('kA', types['E'].fields[0].mojom_name)\n> +    self.assertEqual(0, types['E'].fields[0].numeric_value)\n> +    self.assertEqual('kB', types['E'].fields[1].mojom_name)\n> +    self.assertEqual(1, types['E'].fields[1].numeric_value)\n> +    self.assertEqual('kFirst', types['E'].fields[2].mojom_name)\n> +    self.assertEqual(0, types['E'].fields[2].numeric_value)\n> +\n> +  def testSameModuleOtherEnumReference(self):\n> +    \"\"\"Verifies that an enum value can be assigned from the value of a field\n> +    in another enum within the same module.\"\"\"\n> +    types = self.ExtractTypes('enum E { kA, kB }; enum F { kA = E.kB };')\n> +    self.assertEqual(1, types['F'].fields[0].numeric_value)\n> +\n> +  def testImportedEnumReference(self):\n> +    \"\"\"Verifies that an enum value can be assigned from the value of a field\n> +    in another enum within a different module.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'module a; enum E { kFoo=42, kBar };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(b_mojom,\n> +                   'module b; import \"a.mojom\"; enum F { kFoo = a.E.kBar };')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +\n> +    self.assertEqual('F', b.enums[0].mojom_name)\n> +    self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n> +    self.assertEqual(43, b.enums[0].fields[0].numeric_value)\n> +\n> +  def testConstantReference(self):\n> +    \"\"\"Verifies that an enum value can be assigned from the value of an\n> +    integral constant within the same module.\"\"\"\n> +    types = self.ExtractTypes('const int32 kFoo = 42; enum E { kA = kFoo };')\n> +    self.assertEqual(42, types['E'].fields[0].numeric_value)\n> +\n> +  def testInvalidConstantReference(self):\n> +    \"\"\"Verifies that enum values cannot be assigned from the value of\n> +    non-integral constants.\"\"\"\n> +    with self.assertRaisesRegexp(ValueError, 'not an integer'):\n> +      self.ExtractTypes('const float kFoo = 1.0; enum E { kA = kFoo };')\n> +    with self.assertRaisesRegexp(ValueError, 'not an integer'):\n> +      self.ExtractTypes('const double kFoo = 1.0; enum E { kA = kFoo };')\n> +    with self.assertRaisesRegexp(ValueError, 'not an integer'):\n> +      self.ExtractTypes('const string kFoo = \"lol\"; enum E { kA = kFoo };')\n> +\n> +  def testImportedConstantReference(self):\n> +    \"\"\"Verifies that an enum value can be assigned from the value of an integral\n> +    constant within an imported module.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'module a; const int32 kFoo = 37;')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(b_mojom,\n> +                   'module b; import \"a.mojom\"; enum F { kFoo = a.kFoo };')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +\n> +    self.assertEqual('F', b.enums[0].mojom_name)\n> +    self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n> +    self.assertEqual(37, b.enums[0].fields[0].numeric_value)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> new file mode 100644\n> index 00000000..7416ef19\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> @@ -0,0 +1,43 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +group(\"mojom\") {\n> +  data = [\n> +    \"__init__.py\",\n> +    \"error.py\",\n> +    \"fileutil.py\",\n> +    \"generate/__init__.py\",\n> +    \"generate/constant_resolver.py\",\n> +    \"generate/generator.py\",\n> +    \"generate/module.py\",\n> +    \"generate/pack.py\",\n> +    \"generate/template_expander.py\",\n> +    \"generate/translate.py\",\n> +    \"parse/__init__.py\",\n> +    \"parse/ast.py\",\n> +    \"parse/conditional_features.py\",\n> +    \"parse/lexer.py\",\n> +    \"parse/parser.py\",\n> +\n> +    # Third-party module dependencies\n> +    \"//third_party/jinja2/\",\n> +    \"//third_party/ply/\",\n> +  ]\n> +}\n> +\n> +group(\"tests\") {\n> +  data = [\n> +    \"fileutil_unittest.py\",\n> +    \"generate/generator_unittest.py\",\n> +    \"generate/module_unittest.py\",\n> +    \"generate/pack_unittest.py\",\n> +    \"generate/translate_unittest.py\",\n> +    \"parse/ast_unittest.py\",\n> +    \"parse/conditional_features_unittest.py\",\n> +    \"parse/lexer_unittest.py\",\n> +    \"parse/parser_unittest.py\",\n> +  ]\n> +\n> +  public_deps = [ \":mojom\" ]\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py\n> new file mode 100644\n> index 00000000..e69de29b\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> new file mode 100644\n> index 00000000..8a1e03da\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> @@ -0,0 +1,28 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +\n> +class Error(Exception):\n> +  \"\"\"Base class for Mojo IDL bindings parser/generator errors.\"\"\"\n> +\n> +  def __init__(self, filename, message, lineno=None, addenda=None, **kwargs):\n> +    \"\"\"|filename| is the (primary) file which caused the error, |message| is the\n> +    error message, |lineno| is the 1-based line number (or |None| if not\n> +    applicable/available), and |addenda| is a list of additional lines to append\n> +    to the final error message.\"\"\"\n> +    Exception.__init__(self, **kwargs)\n> +    self.filename = filename\n> +    self.message = message\n> +    self.lineno = lineno\n> +    self.addenda = addenda\n> +\n> +  def __str__(self):\n> +    if self.lineno:\n> +      s = \"%s:%d: Error: %s\" % (self.filename, self.lineno, self.message)\n> +    else:\n> +      s = \"%s: Error: %s\" % (self.filename, self.message)\n> +    return \"\\n\".join([s] + self.addenda) if self.addenda else s\n> +\n> +  def __repr__(self):\n> +    return str(self)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> new file mode 100644\n> index 00000000..bf626f54\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> @@ -0,0 +1,45 @@\n> +# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import errno\n> +import imp\n> +import os.path\n> +import sys\n> +\n> +\n> +def _GetDirAbove(dirname):\n> +  \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n> +  also be \"above\" this file).\"\"\"\n> +  path = os.path.abspath(__file__)\n> +  while True:\n> +    path, tail = os.path.split(path)\n> +    if not tail:\n> +      return None\n> +    if tail == dirname:\n> +      return path\n> +\n> +\n> +def EnsureDirectoryExists(path, always_try_to_create=False):\n> +  \"\"\"A wrapper for os.makedirs that does not error if the directory already\n> +  exists. A different process could be racing to create this directory.\"\"\"\n> +\n> +  if not os.path.exists(path) or always_try_to_create:\n> +    try:\n> +      os.makedirs(path)\n> +    except OSError as e:\n> +      # There may have been a race to create this directory.\n> +      if e.errno != errno.EEXIST:\n> +        raise\n> +\n> +\n> +def AddLocalRepoThirdPartyDirToModulePath():\n> +  \"\"\"Helper function to find the top-level directory of this script's repository\n> +  assuming the script falls somewhere within a 'mojo' directory, and insert the\n> +  top-level 'third_party' directory early in the module search path. Used to\n> +  ensure that third-party dependencies provided within the repository itself\n> +  (e.g. Chromium sources include snapshots of jinja2 and ply) are preferred over\n> +  locally installed system library packages.\"\"\"\n> +  toplevel_dir = _GetDirAbove('mojo')\n> +  if toplevel_dir:\n> +    sys.path.insert(1, os.path.join(toplevel_dir, 'third_party'))\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> new file mode 100644\n> index 00000000..ff5753a2\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> @@ -0,0 +1,40 @@\n> +# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import shutil\n> +import sys\n> +import tempfile\n> +import unittest\n> +\n> +from mojom import fileutil\n> +\n> +\n> +class FileUtilTest(unittest.TestCase):\n> +  def testEnsureDirectoryExists(self):\n> +    \"\"\"Test that EnsureDirectoryExists fuctions correctly.\"\"\"\n> +\n> +    temp_dir = tempfile.mkdtemp()\n> +    try:\n> +      self.assertTrue(os.path.exists(temp_dir))\n> +\n> +      # Directory does not exist, yet.\n> +      full = os.path.join(temp_dir, \"foo\", \"bar\")\n> +      self.assertFalse(os.path.exists(full))\n> +\n> +      # Create the directory.\n> +      fileutil.EnsureDirectoryExists(full)\n> +      self.assertTrue(os.path.exists(full))\n> +\n> +      # Trying to create it again does not cause an error.\n> +      fileutil.EnsureDirectoryExists(full)\n> +      self.assertTrue(os.path.exists(full))\n> +\n> +      # Bypass check for directory existence to tickle error handling that\n> +      # occurs in response to a race.\n> +      fileutil.EnsureDirectoryExists(full, always_try_to_create=True)\n> +      self.assertTrue(os.path.exists(full))\n> +    finally:\n> +      shutil.rmtree(temp_dir)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py\n> new file mode 100644\n> index 00000000..e69de29b\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> new file mode 100644\n> index 00000000..0dfd996e\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> @@ -0,0 +1,93 @@\n> +# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Resolves the values used for constants and enums.\"\"\"\n> +\n> +from itertools import ifilter\n> +\n> +from mojom.generate import module as mojom\n> +\n> +\n> +def ResolveConstants(module, expression_to_text):\n> +  in_progress = set()\n> +  computed = set()\n> +\n> +  def GetResolvedValue(named_value):\n> +    assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))\n> +    if isinstance(named_value, mojom.EnumValue):\n> +      field = next(\n> +          ifilter(lambda field: field.name == named_value.name,\n> +                  named_value.enum.fields), None)\n> +      if not field:\n> +        raise RuntimeError(\n> +            'Unable to get computed value for field %s of enum %s' %\n> +            (named_value.name, named_value.enum.name))\n> +      if field not in computed:\n> +        ResolveEnum(named_value.enum)\n> +      return field.resolved_value\n> +    else:\n> +      ResolveConstant(named_value.constant)\n> +      named_value.resolved_value = named_value.constant.resolved_value\n> +      return named_value.resolved_value\n> +\n> +  def ResolveConstant(constant):\n> +    if constant in computed:\n> +      return\n> +    if constant in in_progress:\n> +      raise RuntimeError('Circular dependency for constant: %s' % constant.name)\n> +    in_progress.add(constant)\n> +    if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):\n> +      resolved_value = GetResolvedValue(constant.value)\n> +    else:\n> +      resolved_value = expression_to_text(constant.value)\n> +    constant.resolved_value = resolved_value\n> +    in_progress.remove(constant)\n> +    computed.add(constant)\n> +\n> +  def ResolveEnum(enum):\n> +    def ResolveEnumField(enum, field, default_value):\n> +      if field in computed:\n> +        return\n> +      if field in in_progress:\n> +        raise RuntimeError('Circular dependency for enum: %s' % enum.name)\n> +      in_progress.add(field)\n> +      if field.value:\n> +        if isinstance(field.value, mojom.EnumValue):\n> +          resolved_value = GetResolvedValue(field.value)\n> +        elif isinstance(field.value, str):\n> +          resolved_value = int(field.value, 0)\n> +        else:\n> +          raise RuntimeError('Unexpected value: %s' % field.value)\n> +      else:\n> +        resolved_value = default_value\n> +      field.resolved_value = resolved_value\n> +      in_progress.remove(field)\n> +      computed.add(field)\n> +\n> +    current_value = 0\n> +    for field in enum.fields:\n> +      ResolveEnumField(enum, field, current_value)\n> +      current_value = field.resolved_value + 1\n> +\n> +  for constant in module.constants:\n> +    ResolveConstant(constant)\n> +\n> +  for enum in module.enums:\n> +    ResolveEnum(enum)\n> +\n> +  for struct in module.structs:\n> +    for constant in struct.constants:\n> +      ResolveConstant(constant)\n> +    for enum in struct.enums:\n> +      ResolveEnum(enum)\n> +    for field in struct.fields:\n> +      if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):\n> +        field.default.resolved_value = GetResolvedValue(field.default)\n> +\n> +  for interface in module.interfaces:\n> +    for constant in interface.constants:\n> +      ResolveConstant(constant)\n> +    for enum in interface.enums:\n> +      ResolveEnum(enum)\n> +\n> +  return module\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> new file mode 100644\n> index 00000000..de62260a\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> @@ -0,0 +1,325 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Code shared by the various language-specific code generators.\"\"\"\n> +\n> +from __future__ import print_function\n> +\n> +from functools import partial\n> +import os.path\n> +import re\n> +\n> +from mojom import fileutil\n> +from mojom.generate import module as mojom\n> +from mojom.generate import pack\n> +\n> +\n> +def ExpectedArraySize(kind):\n> +  if mojom.IsArrayKind(kind):\n> +    return kind.length\n> +  return None\n> +\n> +\n> +def SplitCamelCase(identifier):\n> +  \"\"\"Splits a camel-cased |identifier| and returns a list of lower-cased\n> +  strings.\n> +  \"\"\"\n> +  # Add underscores after uppercase letters when appropriate. An uppercase\n> +  # letter is considered the end of a word if it is followed by an upper and a\n> +  # lower. E.g. URLLoaderFactory -> URL_LoaderFactory\n> +  identifier = re.sub('([A-Z][0-9]*)(?=[A-Z][0-9]*[a-z])', r'\\1_', identifier)\n> +  # Add underscores after lowercase letters when appropriate. A lowercase letter\n> +  # is considered the end of a word if it is followed by an upper.\n> +  # E.g. URLLoaderFactory -> URLLoader_Factory\n> +  identifier = re.sub('([a-z][0-9]*)(?=[A-Z])', r'\\1_', identifier)\n> +  return [x.lower() for x in identifier.split('_')]\n> +\n> +\n> +def ToCamel(identifier, lower_initial=False, digits_split=False, delimiter='_'):\n> +  \"\"\"Splits |identifier| using |delimiter|, makes the first character of each\n> +  word uppercased (but makes the first character of the first word lowercased\n> +  if |lower_initial| is set to True), and joins the words. Please note that for\n> +  each word, all the characters except the first one are untouched.\n> +  \"\"\"\n> +  result = ''\n> +  capitalize_next = True\n> +  for i in range(len(identifier)):\n> +    if identifier[i] == delimiter:\n> +      capitalize_next = True\n> +    elif digits_split and identifier[i].isdigit():\n> +      capitalize_next = True\n> +      result += identifier[i]\n> +    elif capitalize_next:\n> +      capitalize_next = False\n> +      result += identifier[i].upper()\n> +    else:\n> +      result += identifier[i]\n> +\n> +  if lower_initial and result:\n> +    result = result[0].lower() + result[1:]\n> +\n> +  return result\n> +\n> +\n> +def _ToSnakeCase(identifier, upper=False):\n> +  \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n> +  word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n> +  \"URL_LOADER_FACTORY\" if upper, otherwise \"url_loader_factory\".\n> +  \"\"\"\n> +  words = SplitCamelCase(identifier)\n> +  if words[0] == 'k' and len(words) > 1:\n> +    words = words[1:]\n> +\n> +  # Variables cannot start with a digit\n> +  if (words[0][0].isdigit()):\n> +    words[0] = '_' + words[0]\n> +\n> +\n> +  if upper:\n> +    words = map(lambda x: x.upper(), words)\n> +\n> +  return '_'.join(words)\n> +\n> +\n> +def ToUpperSnakeCase(identifier):\n> +  \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n> +  word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n> +  \"URL_LOADER_FACTORY\".\n> +  \"\"\"\n> +  return _ToSnakeCase(identifier, upper=True)\n> +\n> +\n> +def ToLowerSnakeCase(identifier):\n> +  \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n> +  word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n> +  \"url_loader_factory\".\n> +  \"\"\"\n> +  return _ToSnakeCase(identifier, upper=False)\n> +\n> +\n> +class Stylizer(object):\n> +  \"\"\"Stylizers specify naming rules to map mojom names to names in generated\n> +  code. For example, if you would like method_name in mojom to be mapped to\n> +  MethodName in the generated code, you need to define a subclass of Stylizer\n> +  and override StylizeMethod to do the conversion.\"\"\"\n> +\n> +  def StylizeConstant(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeField(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeStruct(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeUnion(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeParameter(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeMethod(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeInterface(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeEnumField(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeEnum(self, mojom_name):\n> +    return mojom_name\n> +\n> +  def StylizeModule(self, mojom_namespace):\n> +    return mojom_namespace\n> +\n> +\n> +def WriteFile(contents, full_path):\n> +  # If |contents| is same with the file content, we skip updating.\n> +  if os.path.isfile(full_path):\n> +    with open(full_path, 'rb') as destination_file:\n> +      if destination_file.read() == contents:\n> +        return\n> +\n> +  # Make sure the containing directory exists.\n> +  full_dir = os.path.dirname(full_path)\n> +  fileutil.EnsureDirectoryExists(full_dir)\n> +\n> +  # Dump the data to disk.\n> +  with open(full_path, \"wb\") as f:\n> +    if not isinstance(contents, bytes):\n> +      f.write(contents.encode('utf-8'))\n> +    else:\n> +      f.write(contents)\n> +\n> +\n> +def AddComputedData(module):\n> +  \"\"\"Adds computed data to the given module. The data is computed once and\n> +  used repeatedly in the generation process.\"\"\"\n> +\n> +  def _AddStructComputedData(exported, struct):\n> +    struct.packed = pack.PackedStruct(struct)\n> +    struct.bytes = pack.GetByteLayout(struct.packed)\n> +    struct.versions = pack.GetVersionInfo(struct.packed)\n> +    struct.exported = exported\n> +\n> +  def _AddInterfaceComputedData(interface):\n> +    interface.version = 0\n> +    for method in interface.methods:\n> +      # this field is never scrambled\n> +      method.sequential_ordinal = method.ordinal\n> +\n> +      if method.min_version is not None:\n> +        interface.version = max(interface.version, method.min_version)\n> +\n> +      method.param_struct = _GetStructFromMethod(method)\n> +      if interface.stable:\n> +        method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True\n> +        if method.explicit_ordinal is None:\n> +          raise Exception(\n> +              'Stable interfaces must declare explicit method ordinals. The '\n> +              'method %s on stable interface %s does not declare an explicit '\n> +              'ordinal.' % (method.mojom_name, interface.qualified_name))\n> +      interface.version = max(interface.version,\n> +                              method.param_struct.versions[-1].version)\n> +\n> +      if method.response_parameters is not None:\n> +        method.response_param_struct = _GetResponseStructFromMethod(method)\n> +        if interface.stable:\n> +          method.response_param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True\n> +        interface.version = max(\n> +            interface.version,\n> +            method.response_param_struct.versions[-1].version)\n> +      else:\n> +        method.response_param_struct = None\n> +\n> +  def _GetStructFromMethod(method):\n> +    \"\"\"Converts a method's parameters into the fields of a struct.\"\"\"\n> +    params_class = \"%s_%s_Params\" % (method.interface.mojom_name,\n> +                                     method.mojom_name)\n> +    struct = mojom.Struct(params_class,\n> +                          module=method.interface.module,\n> +                          attributes={})\n> +    for param in method.parameters:\n> +      struct.AddField(\n> +          param.mojom_name,\n> +          param.kind,\n> +          param.ordinal,\n> +          attributes=param.attributes)\n> +    _AddStructComputedData(False, struct)\n> +    return struct\n> +\n> +  def _GetResponseStructFromMethod(method):\n> +    \"\"\"Converts a method's response_parameters into the fields of a struct.\"\"\"\n> +    params_class = \"%s_%s_ResponseParams\" % (method.interface.mojom_name,\n> +                                             method.mojom_name)\n> +    struct = mojom.Struct(params_class,\n> +                          module=method.interface.module,\n> +                          attributes={})\n> +    for param in method.response_parameters:\n> +      struct.AddField(\n> +          param.mojom_name,\n> +          param.kind,\n> +          param.ordinal,\n> +          attributes=param.attributes)\n> +    _AddStructComputedData(False, struct)\n> +    return struct\n> +\n> +  for struct in module.structs:\n> +    _AddStructComputedData(True, struct)\n> +  for interface in module.interfaces:\n> +    _AddInterfaceComputedData(interface)\n> +\n> +\n> +class Generator(object):\n> +  # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all\n> +  # files to stdout.\n> +  def __init__(self,\n> +               module,\n> +               output_dir=None,\n> +               typemap=None,\n> +               variant=None,\n> +               bytecode_path=None,\n> +               for_blink=False,\n> +               js_bindings_mode=\"new\",\n> +               js_generate_struct_deserializers=False,\n> +               export_attribute=None,\n> +               export_header=None,\n> +               generate_non_variant_code=False,\n> +               support_lazy_serialization=False,\n> +               disallow_native_types=False,\n> +               disallow_interfaces=False,\n> +               generate_message_ids=False,\n> +               generate_fuzzing=False,\n> +               enable_kythe_annotations=False,\n> +               extra_cpp_template_paths=None,\n> +               generate_extra_cpp_only=False):\n> +    self.module = module\n> +    self.output_dir = output_dir\n> +    self.typemap = typemap or {}\n> +    self.variant = variant\n> +    self.bytecode_path = bytecode_path\n> +    self.for_blink = for_blink\n> +    self.js_bindings_mode = js_bindings_mode\n> +    self.js_generate_struct_deserializers = js_generate_struct_deserializers\n> +    self.export_attribute = export_attribute\n> +    self.export_header = export_header\n> +    self.generate_non_variant_code = generate_non_variant_code\n> +    self.support_lazy_serialization = support_lazy_serialization\n> +    self.disallow_native_types = disallow_native_types\n> +    self.disallow_interfaces = disallow_interfaces\n> +    self.generate_message_ids = generate_message_ids\n> +    self.generate_fuzzing = generate_fuzzing\n> +    self.enable_kythe_annotations = enable_kythe_annotations\n> +    self.extra_cpp_template_paths = extra_cpp_template_paths\n> +    self.generate_extra_cpp_only = generate_extra_cpp_only\n> +\n> +  def Write(self, contents, filename):\n> +    if self.output_dir is None:\n> +      print(contents)\n> +      return\n> +    full_path = os.path.join(self.output_dir, filename)\n> +    WriteFile(contents, full_path)\n> +\n> +  def OptimizeEmpty(self, contents):\n> +    # Look for .cc files that contain no actual code. There are many of these\n> +    # and they collectively take a while to compile.\n> +    lines = contents.splitlines()\n> +\n> +    for line in lines:\n> +      if line.startswith('#') or line.startswith('//'):\n> +        continue\n> +      if re.match(r'namespace .* {', line) or re.match(r'}.*//.*namespace',\n> +                                                       line):\n> +        continue\n> +      if line.strip():\n> +        # There is some actual code - return the unmodified contents.\n> +        return contents\n> +\n> +    # If we reach here then we have a .cc file with no actual code. The\n> +    # includes are therefore unneeded and can be removed.\n> +    new_lines = [line for line in lines if not line.startswith('#include')]\n> +    if len(new_lines) < len(lines):\n> +      new_lines.append('')\n> +      new_lines.append('// Includes removed due to no code being generated.')\n> +    return '\\n'.join(new_lines)\n> +\n> +  def WriteWithComment(self, contents, filename):\n> +    generator_name = \"mojom_bindings_generator.py\"\n> +    comment = r\"// %s is auto generated by %s, do not edit\" % (filename,\n> +                                                               generator_name)\n> +    contents = comment + '\\n' + '\\n' + contents;\n> +    if filename.endswith('.cc'):\n> +      contents = self.OptimizeEmpty(contents)\n> +    self.Write(contents, filename)\n> +\n> +  def GenerateFiles(self, args):\n> +    raise NotImplementedError(\"Subclasses must override/implement this method\")\n> +\n> +  def GetJinjaParameters(self):\n> +    \"\"\"Returns default constructor parameters for the jinja environment.\"\"\"\n> +    return {}\n> +\n> +  def GetGlobals(self):\n> +    \"\"\"Returns global mappings for the template generation.\"\"\"\n> +    return {}\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> new file mode 100644\n> index 00000000..32c884a8\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> @@ -0,0 +1,74 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +import unittest\n> +\n> +\n> +def _GetDirAbove(dirname):\n> +  \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n> +  also be \"above\" this file).\"\"\"\n> +  path = os.path.abspath(__file__)\n> +  while True:\n> +    path, tail = os.path.split(path)\n> +    assert tail\n> +    if tail == dirname:\n> +      return path\n> +\n> +\n> +try:\n> +  imp.find_module(\"mojom\")\n> +except ImportError:\n> +  sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n> +from mojom.generate import generator\n> +\n> +\n> +class StringManipulationTest(unittest.TestCase):\n> +  \"\"\"generator contains some string utilities, this tests only those.\"\"\"\n> +\n> +  def testSplitCamelCase(self):\n> +    self.assertEquals([\"camel\", \"case\"], generator.SplitCamelCase(\"CamelCase\"))\n> +    self.assertEquals([\"url\", \"loader\", \"factory\"],\n> +                      generator.SplitCamelCase('URLLoaderFactory'))\n> +    self.assertEquals([\"get99\", \"entries\"],\n> +                      generator.SplitCamelCase('Get99Entries'))\n> +    self.assertEquals([\"get99entries\"],\n> +                      generator.SplitCamelCase('Get99entries'))\n> +\n> +  def testToCamel(self):\n> +    self.assertEquals(\"CamelCase\", generator.ToCamel(\"camel_case\"))\n> +    self.assertEquals(\"CAMELCASE\", generator.ToCamel(\"CAMEL_CASE\"))\n> +    self.assertEquals(\"camelCase\",\n> +                      generator.ToCamel(\"camel_case\", lower_initial=True))\n> +    self.assertEquals(\"CamelCase\", generator.ToCamel(\n> +        \"camel case\", delimiter=' '))\n> +    self.assertEquals(\"CaMelCaSe\", generator.ToCamel(\"caMel_caSe\"))\n> +    self.assertEquals(\"L2Tp\", generator.ToCamel(\"l2tp\", digits_split=True))\n> +    self.assertEquals(\"l2tp\", generator.ToCamel(\"l2tp\", lower_initial=True))\n> +\n> +  def testToSnakeCase(self):\n> +    self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"SnakeCase\"))\n> +    self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"snakeCase\"))\n> +    self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"SnakeCASE\"))\n> +    self.assertEquals(\"snake_d3d11_case\",\n> +                      generator.ToLowerSnakeCase(\"SnakeD3D11Case\"))\n> +    self.assertEquals(\"snake_d3d11_case\",\n> +                      generator.ToLowerSnakeCase(\"SnakeD3d11Case\"))\n> +    self.assertEquals(\"snake_d3d11_case\",\n> +                      generator.ToLowerSnakeCase(\"snakeD3d11Case\"))\n> +    self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"SnakeCase\"))\n> +    self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"snakeCase\"))\n> +    self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"SnakeCASE\"))\n> +    self.assertEquals(\"SNAKE_D3D11_CASE\",\n> +                      generator.ToUpperSnakeCase(\"SnakeD3D11Case\"))\n> +    self.assertEquals(\"SNAKE_D3D11_CASE\",\n> +                      generator.ToUpperSnakeCase(\"SnakeD3d11Case\"))\n> +    self.assertEquals(\"SNAKE_D3D11_CASE\",\n> +                      generator.ToUpperSnakeCase(\"snakeD3d11Case\"))\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> new file mode 100644\n> index 00000000..8547ff64\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> @@ -0,0 +1,1635 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +# This module's classes provide an interface to mojo modules. Modules are\n> +# collections of interfaces and structs to be used by mojo ipc clients and\n> +# servers.\n> +#\n> +# A simple interface would be created this way:\n> +# module = mojom.generate.module.Module('Foo')\n> +# interface = module.AddInterface('Bar')\n> +# method = interface.AddMethod('Tat', 0)\n> +# method.AddParameter('baz', 0, mojom.INT32)\n> +\n> +import pickle\n> +\n> +# We use our own version of __repr__ when displaying the AST, as the\n> +# AST currently doesn't capture which nodes are reference (e.g. to\n> +# types) and which nodes are definitions. This allows us to e.g. print\n> +# the definition of a struct when it's defined inside a module, but\n> +# only print its name when it's referenced in e.g. a method parameter.\n> +def Repr(obj, as_ref=True):\n> +  \"\"\"A version of __repr__ that can distinguish references.\n> +\n> +  Sometimes we like to print an object's full representation\n> +  (e.g. with its fields) and sometimes we just want to reference an\n> +  object that was printed in full elsewhere. This function allows us\n> +  to make that distinction.\n> +\n> +  Args:\n> +    obj: The object whose string representation we compute.\n> +    as_ref: If True, use the short reference representation.\n> +\n> +  Returns:\n> +    A str representation of |obj|.\n> +  \"\"\"\n> +  if hasattr(obj, 'Repr'):\n> +    return obj.Repr(as_ref=as_ref)\n> +  # Since we cannot implement Repr for existing container types, we\n> +  # handle them here.\n> +  elif isinstance(obj, list):\n> +    if not obj:\n> +      return '[]'\n> +    else:\n> +      return ('[\\n%s\\n]' % (',\\n'.join(\n> +          '    %s' % Repr(elem, as_ref).replace('\\n', '\\n    ')\n> +          for elem in obj)))\n> +  elif isinstance(obj, dict):\n> +    if not obj:\n> +      return '{}'\n> +    else:\n> +      return ('{\\n%s\\n}' % (',\\n'.join(\n> +          '    %s: %s' % (Repr(key, as_ref).replace('\\n', '\\n    '),\n> +                          Repr(val, as_ref).replace('\\n', '\\n    '))\n> +          for key, val in obj.items())))\n> +  else:\n> +    return repr(obj)\n> +\n> +\n> +def GenericRepr(obj, names):\n> +  \"\"\"Compute generic Repr for |obj| based on the attributes in |names|.\n> +\n> +  Args:\n> +    obj: The object to compute a Repr for.\n> +    names: A dict from attribute names to include, to booleans\n> +        specifying whether those attributes should be shown as\n> +        references or not.\n> +\n> +  Returns:\n> +    A str representation of |obj|.\n> +  \"\"\"\n> +\n> +  def ReprIndent(name, as_ref):\n> +    return '    %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace(\n> +        '\\n', '\\n    '))\n> +\n> +  return '%s(\\n%s\\n)' % (obj.__class__.__name__, ',\\n'.join(\n> +      ReprIndent(name, as_ref) for (name, as_ref) in names.items()))\n> +\n> +\n> +class Kind(object):\n> +  \"\"\"Kind represents a type (e.g. int8, string).\n> +\n> +  Attributes:\n> +    spec: A string uniquely identifying the type. May be None.\n> +    module: {Module} The defining module. Set to None for built-in types.\n> +    parent_kind: The enclosing type. For example, an enum defined\n> +        inside an interface has that interface as its parent. May be None.\n> +  \"\"\"\n> +\n> +  def __init__(self, spec=None, module=None):\n> +    self.spec = spec\n> +    self.module = module\n> +    self.parent_kind = None\n> +\n> +  def Repr(self, as_ref=True):\n> +    # pylint: disable=unused-argument\n> +    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)\n> +\n> +  def __repr__(self):\n> +    # Gives us a decent __repr__ for all kinds.\n> +    return self.Repr()\n> +\n> +  def __eq__(self, rhs):\n> +    # pylint: disable=unidiomatic-typecheck\n> +    return (type(self) == type(rhs)\n> +            and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))\n> +\n> +  def __hash__(self):\n> +    # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind\n> +    # and its subclasses. This is to support existing generator code which uses\n> +    # some primitive Kinds as dict keys. The default hash (object identity)\n> +    # breaks these dicts when a pickled Module instance is unpickled and used\n> +    # during a subsequent run of the parser.\n> +    return hash((self.spec, self.parent_kind))\n> +\n> +\n> +class ReferenceKind(Kind):\n> +  \"\"\"ReferenceKind represents pointer and handle types.\n> +\n> +  A type is nullable if null (for pointer types) or invalid handle (for handle\n> +  types) is a legal value for the type.\n> +\n> +  Attributes:\n> +    is_nullable: True if the type is nullable.\n> +  \"\"\"\n> +\n> +  def __init__(self, spec=None, is_nullable=False, module=None):\n> +    assert spec is None or is_nullable == spec.startswith('?')\n> +    Kind.__init__(self, spec, module)\n> +    self.is_nullable = is_nullable\n> +    self.shared_definition = {}\n> +\n> +  def Repr(self, as_ref=True):\n> +    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n> +                                            self.is_nullable)\n> +\n> +  def MakeNullableKind(self):\n> +    assert not self.is_nullable\n> +\n> +    if self == STRING:\n> +      return NULLABLE_STRING\n> +    if self == HANDLE:\n> +      return NULLABLE_HANDLE\n> +    if self == DCPIPE:\n> +      return NULLABLE_DCPIPE\n> +    if self == DPPIPE:\n> +      return NULLABLE_DPPIPE\n> +    if self == MSGPIPE:\n> +      return NULLABLE_MSGPIPE\n> +    if self == SHAREDBUFFER:\n> +      return NULLABLE_SHAREDBUFFER\n> +    if self == PLATFORMHANDLE:\n> +      return NULLABLE_PLATFORMHANDLE\n> +\n> +    nullable_kind = type(self)()\n> +    nullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      nullable_kind.spec = '?' + self.spec\n> +    nullable_kind.is_nullable = True\n> +    nullable_kind.parent_kind = self.parent_kind\n> +    nullable_kind.module = self.module\n> +\n> +    return nullable_kind\n> +\n> +  @classmethod\n> +  def AddSharedProperty(cls, name):\n> +    \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n> +       |shared_definition|.\n> +\n> +       The reason of adding such indirection is to enable sharing definition\n> +       between a reference kind and its nullable variation. For example:\n> +         a = Struct('test_struct_1')\n> +         b = a.MakeNullableKind()\n> +         a.name = 'test_struct_2'\n> +         print(b.name)  # Outputs 'test_struct_2'.\n> +    \"\"\"\n> +\n> +    def Get(self):\n> +      try:\n> +        return self.shared_definition[name]\n> +      except KeyError:  # Must raise AttributeError if property doesn't exist.\n> +        raise AttributeError\n> +\n> +    def Set(self, value):\n> +      self.shared_definition[name] = value\n> +\n> +    setattr(cls, name, property(Get, Set))\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, ReferenceKind)\n> +            and super(ReferenceKind, self).__eq__(rhs)\n> +            and self.is_nullable == rhs.is_nullable)\n> +\n> +  def __hash__(self):\n> +    return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))\n> +\n> +\n> +# Initialize the set of primitive types. These can be accessed by clients.\n> +BOOL = Kind('b')\n> +INT8 = Kind('i8')\n> +INT16 = Kind('i16')\n> +INT32 = Kind('i32')\n> +INT64 = Kind('i64')\n> +UINT8 = Kind('u8')\n> +UINT16 = Kind('u16')\n> +UINT32 = Kind('u32')\n> +UINT64 = Kind('u64')\n> +FLOAT = Kind('f')\n> +DOUBLE = Kind('d')\n> +STRING = ReferenceKind('s')\n> +HANDLE = ReferenceKind('h')\n> +DCPIPE = ReferenceKind('h:d:c')\n> +DPPIPE = ReferenceKind('h:d:p')\n> +MSGPIPE = ReferenceKind('h:m')\n> +SHAREDBUFFER = ReferenceKind('h:s')\n> +PLATFORMHANDLE = ReferenceKind('h:p')\n> +NULLABLE_STRING = ReferenceKind('?s', True)\n> +NULLABLE_HANDLE = ReferenceKind('?h', True)\n> +NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True)\n> +NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True)\n> +NULLABLE_MSGPIPE = ReferenceKind('?h:m', True)\n> +NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True)\n> +NULLABLE_PLATFORMHANDLE = ReferenceKind('?h:p', True)\n> +\n> +# Collection of all Primitive types\n> +PRIMITIVES = (\n> +    BOOL,\n> +    INT8,\n> +    INT16,\n> +    INT32,\n> +    INT64,\n> +    UINT8,\n> +    UINT16,\n> +    UINT32,\n> +    UINT64,\n> +    FLOAT,\n> +    DOUBLE,\n> +    STRING,\n> +    HANDLE,\n> +    DCPIPE,\n> +    DPPIPE,\n> +    MSGPIPE,\n> +    SHAREDBUFFER,\n> +    PLATFORMHANDLE,\n> +    NULLABLE_STRING,\n> +    NULLABLE_HANDLE,\n> +    NULLABLE_DCPIPE,\n> +    NULLABLE_DPPIPE,\n> +    NULLABLE_MSGPIPE,\n> +    NULLABLE_SHAREDBUFFER,\n> +    NULLABLE_PLATFORMHANDLE,\n> +)\n> +\n> +ATTRIBUTE_MIN_VERSION = 'MinVersion'\n> +ATTRIBUTE_EXTENSIBLE = 'Extensible'\n> +ATTRIBUTE_STABLE = 'Stable'\n> +ATTRIBUTE_SYNC = 'Sync'\n> +\n> +\n> +class NamedValue(object):\n> +  def __init__(self, module, parent_kind, mojom_name):\n> +    self.module = module\n> +    self.parent_kind = parent_kind\n> +    self.mojom_name = mojom_name\n> +\n> +  def GetSpec(self):\n> +    return (self.module.GetNamespacePrefix() +\n> +            (self.parent_kind and\n> +             (self.parent_kind.mojom_name + '.') or \"\") + self.mojom_name)\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, NamedValue)\n> +            and (self.parent_kind, self.mojom_name) == (rhs.parent_kind,\n> +                                                        rhs.mojom_name))\n> +\n> +\n> +class BuiltinValue(object):\n> +  def __init__(self, value):\n> +    self.value = value\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, BuiltinValue) and self.value == rhs.value\n> +\n> +\n> +class ConstantValue(NamedValue):\n> +  def __init__(self, module, parent_kind, constant):\n> +    NamedValue.__init__(self, module, parent_kind, constant.mojom_name)\n> +    self.constant = constant\n> +\n> +  @property\n> +  def name(self):\n> +    return self.constant.name\n> +\n> +\n> +class EnumValue(NamedValue):\n> +  def __init__(self, module, enum, field):\n> +    NamedValue.__init__(self, module, enum.parent_kind, field.mojom_name)\n> +    self.field = field\n> +    self.enum = enum\n> +\n> +  def GetSpec(self):\n> +    return (self.module.GetNamespacePrefix() +\n> +            (self.parent_kind and (self.parent_kind.mojom_name + '.') or \"\") +\n> +            self.enum.mojom_name + '.' + self.mojom_name)\n> +\n> +  @property\n> +  def name(self):\n> +    return self.field.name\n> +\n> +\n> +class Constant(object):\n> +  def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.kind = kind\n> +    self.value = value\n> +    self.parent_kind = parent_kind\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeConstant(self.mojom_name)\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Constant)\n> +            and (self.mojom_name, self.kind, self.value,\n> +                 self.parent_kind) == (rhs.mojom_name, rhs.kind, rhs.value,\n> +                                       rhs.parent_kind))\n> +\n> +\n> +class Field(object):\n> +  def __init__(self,\n> +               mojom_name=None,\n> +               kind=None,\n> +               ordinal=None,\n> +               default=None,\n> +               attributes=None):\n> +    if self.__class__.__name__ == 'Field':\n> +      raise Exception()\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.kind = kind\n> +    self.ordinal = ordinal\n> +    self.default = default\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    # pylint: disable=unused-argument\n> +    # Fields are only referenced by objects which define them and thus\n> +    # they are always displayed as non-references.\n> +    return GenericRepr(self, {'mojom_name': False, 'kind': True})\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeField(self.mojom_name)\n> +\n> +  @property\n> +  def min_version(self):\n> +    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n> +        if self.attributes else None\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Field)\n> +            and (self.mojom_name, self.kind, self.ordinal, self.default,\n> +                 self.attributes) == (rhs.mojom_name, rhs.kind, rhs.ordinal,\n> +                                      rhs.default, rhs.attributes))\n> +\n> +  def __hash__(self):\n> +    return hash((self.mojom_name, self.kind, self.ordinal, self.default))\n> +\n> +\n> +class StructField(Field):\n> +  pass\n> +\n> +\n> +class UnionField(Field):\n> +  pass\n> +\n> +\n> +def _IsFieldBackwardCompatible(new_field, old_field):\n> +  if (new_field.min_version or 0) != (old_field.min_version or 0):\n> +    return False\n> +\n> +  if isinstance(new_field.kind, (Enum, Struct, Union)):\n> +    return new_field.kind.IsBackwardCompatible(old_field.kind)\n> +\n> +  return new_field.kind == old_field.kind\n> +\n> +\n> +class Struct(ReferenceKind):\n> +  \"\"\"A struct with typed fields.\n> +\n> +  Attributes:\n> +    mojom_name: {str} The name of the struct type as defined in mojom.\n> +    name: {str} The stylized name.\n> +    native_only: {bool} Does the struct have a body (i.e. any fields) or is it\n> +        purely a native struct.\n> +    custom_serializer: {bool} Should we generate a serializer for the struct or\n> +        will one be provided by non-generated code.\n> +    fields: {List[StructField]} The members of the struct.\n> +    enums: {List[Enum]} The enums defined in the struct scope.\n> +    constants: {List[Constant]} The constants defined in the struct scope.\n> +    attributes: {dict} Additional information about the struct, such as\n> +        if it's a native struct.\n> +  \"\"\"\n> +\n> +  ReferenceKind.AddSharedProperty('mojom_name')\n> +  ReferenceKind.AddSharedProperty('name')\n> +  ReferenceKind.AddSharedProperty('native_only')\n> +  ReferenceKind.AddSharedProperty('custom_serializer')\n> +  ReferenceKind.AddSharedProperty('fields')\n> +  ReferenceKind.AddSharedProperty('enums')\n> +  ReferenceKind.AddSharedProperty('constants')\n> +  ReferenceKind.AddSharedProperty('attributes')\n> +\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    ReferenceKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.native_only = False\n> +    self.custom_serializer = False\n> +    self.fields = []\n> +    self.enums = []\n> +    self.constants = []\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,\n> +                                               self.mojom_name,\n> +                                               Repr(self.module, as_ref=True))\n> +    else:\n> +      return GenericRepr(self, {\n> +          'mojom_name': False,\n> +          'fields': False,\n> +          'module': True\n> +      })\n> +\n> +  def AddField(self,\n> +               mojom_name,\n> +               kind,\n> +               ordinal=None,\n> +               default=None,\n> +               attributes=None):\n> +    field = StructField(mojom_name, kind, ordinal, default, attributes)\n> +    self.fields.append(field)\n> +    return field\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeStruct(self.mojom_name)\n> +    for field in self.fields:\n> +      field.Stylize(stylizer)\n> +    for enum in self.enums:\n> +      enum.Stylize(stylizer)\n> +    for constant in self.constants:\n> +      constant.Stylize(stylizer)\n> +\n> +  def IsBackwardCompatible(self, older_struct):\n> +    \"\"\"This struct is backward-compatible with older_struct if and only if all\n> +    of the following conditions hold:\n> +      - Any newly added field is tagged with a [MinVersion] attribute specifying\n> +        a version number greater than all previously used [MinVersion]\n> +        attributes within the struct.\n> +      - All fields present in older_struct remain present in the new struct,\n> +        with the same ordinal position, same optional or non-optional status,\n> +        same (or backward-compatible) type and where applicable, the same\n> +        [MinVersion] attribute value.\n> +      - All [MinVersion] attributes must be non-decreasing in ordinal order.\n> +      - All reference-typed (string, array, map, struct, or union) fields tagged\n> +        with a [MinVersion] greater than zero must be optional.\n> +    \"\"\"\n> +\n> +    def buildOrdinalFieldMap(struct):\n> +      fields_by_ordinal = {}\n> +      for field in struct.fields:\n> +        if field.ordinal in fields_by_ordinal:\n> +          raise Exception('Multiple fields with ordinal %s in struct %s.' %\n> +                          (field.ordinal, struct.mojom_name))\n> +        fields_by_ordinal[field.ordinal] = field\n> +      return fields_by_ordinal\n> +\n> +    new_fields = buildOrdinalFieldMap(self)\n> +    old_fields = buildOrdinalFieldMap(older_struct)\n> +    if len(new_fields) < len(old_fields):\n> +      # At least one field was removed, which is not OK.\n> +      return False\n> +\n> +    # If there are N fields, existing ordinal values must exactly cover the\n> +    # range from 0 to N-1.\n> +    num_old_ordinals = len(old_fields)\n> +    max_old_min_version = 0\n> +    for ordinal in range(num_old_ordinals):\n> +      new_field = new_fields[ordinal]\n> +      old_field = old_fields[ordinal]\n> +      if (old_field.min_version or 0) > max_old_min_version:\n> +        max_old_min_version = old_field.min_version\n> +      if not _IsFieldBackwardCompatible(new_field, old_field):\n> +        # Type or min-version mismatch between old and new versions of the same\n> +        # ordinal field.\n> +        return False\n> +\n> +    # At this point we know all old fields are intact in the new struct\n> +    # definition. Now verify that all new fields have a high enough min version\n> +    # and are appropriately optional where required.\n> +    num_new_ordinals = len(new_fields)\n> +    last_min_version = max_old_min_version\n> +    for ordinal in range(num_old_ordinals, num_new_ordinals):\n> +      new_field = new_fields[ordinal]\n> +      min_version = new_field.min_version or 0\n> +      if min_version <= max_old_min_version:\n> +        # A new field is being added to an existing version, which is not OK.\n> +        return False\n> +      if min_version < last_min_version:\n> +        # The [MinVersion] of a field cannot be lower than the [MinVersion] of\n> +        # a field with lower ordinal value.\n> +        return False\n> +      if IsReferenceKind(new_field.kind) and not IsNullableKind(new_field.kind):\n> +        # New fields whose type can be nullable MUST be nullable.\n> +        return False\n> +\n> +    return True\n> +\n> +  @property\n> +  def stable(self):\n> +    return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> +        if self.attributes else False\n> +\n> +  @property\n> +  def qualified_name(self):\n> +    if self.parent_kind:\n> +      prefix = self.parent_kind.qualified_name + '.'\n> +    else:\n> +      prefix = self.module.GetNamespacePrefix()\n> +    return '%s%s' % (prefix, self.mojom_name)\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Struct) and\n> +            (self.mojom_name, self.native_only, self.fields, self.constants,\n> +             self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n> +                                  rhs.constants, rhs.attributes))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class Union(ReferenceKind):\n> +  \"\"\"A union of several kinds.\n> +\n> +  Attributes:\n> +    mojom_name: {str} The name of the union type as defined in mojom.\n> +    name: {str} The stylized name.\n> +    fields: {List[UnionField]} The members of the union.\n> +    attributes: {dict} Additional information about the union, such as\n> +        which Java class name to use to represent it in the generated\n> +        bindings.\n> +  \"\"\"\n> +  ReferenceKind.AddSharedProperty('mojom_name')\n> +  ReferenceKind.AddSharedProperty('name')\n> +  ReferenceKind.AddSharedProperty('fields')\n> +  ReferenceKind.AddSharedProperty('attributes')\n> +\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    ReferenceKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.fields = []\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s spec=%r is_nullable=%r fields=%s>' % (\n> +          self.__class__.__name__, self.spec, self.is_nullable, Repr(\n> +              self.fields))\n> +    else:\n> +      return GenericRepr(self, {'fields': True, 'is_nullable': False})\n> +\n> +  def AddField(self, mojom_name, kind, ordinal=None, attributes=None):\n> +    field = UnionField(mojom_name, kind, ordinal, None, attributes)\n> +    self.fields.append(field)\n> +    return field\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeUnion(self.mojom_name)\n> +    for field in self.fields:\n> +      field.Stylize(stylizer)\n> +\n> +  def IsBackwardCompatible(self, older_union):\n> +    \"\"\"This union is backward-compatible with older_union if and only if all\n> +    of the following conditions hold:\n> +      - Any newly added field is tagged with a [MinVersion] attribute specifying\n> +        a version number greater than all previously used [MinVersion]\n> +        attributes within the union.\n> +      - All fields present in older_union remain present in the new union,\n> +        with the same ordinal value, same optional or non-optional status,\n> +        same (or backward-compatible) type, and where applicable, the same\n> +        [MinVersion] attribute value.\n> +    \"\"\"\n> +\n> +    def buildOrdinalFieldMap(union):\n> +      fields_by_ordinal = {}\n> +      for field in union.fields:\n> +        if field.ordinal in fields_by_ordinal:\n> +          raise Exception('Multiple fields with ordinal %s in union %s.' %\n> +                          (field.ordinal, union.mojom_name))\n> +        fields_by_ordinal[field.ordinal] = field\n> +      return fields_by_ordinal\n> +\n> +    new_fields = buildOrdinalFieldMap(self)\n> +    old_fields = buildOrdinalFieldMap(older_union)\n> +    if len(new_fields) < len(old_fields):\n> +      # At least one field was removed, which is not OK.\n> +      return False\n> +\n> +    max_old_min_version = 0\n> +    for ordinal, old_field in old_fields.items():\n> +      new_field = new_fields.get(ordinal)\n> +      if not new_field:\n> +        # A field was removed, which is not OK.\n> +        return False\n> +      if not _IsFieldBackwardCompatible(new_field, old_field):\n> +        # An field changed its type or MinVersion, which is not OK.\n> +        return False\n> +      old_min_version = old_field.min_version or 0\n> +      if old_min_version > max_old_min_version:\n> +        max_old_min_version = old_min_version\n> +\n> +    new_ordinals = set(new_fields.keys()) - set(old_fields.keys())\n> +    for ordinal in new_ordinals:\n> +      if (new_fields[ordinal].min_version or 0) <= max_old_min_version:\n> +        # New fields must use a MinVersion greater than any old fields.\n> +        return False\n> +\n> +    return True\n> +\n> +  @property\n> +  def stable(self):\n> +    return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> +        if self.attributes else False\n> +\n> +  @property\n> +  def qualified_name(self):\n> +    if self.parent_kind:\n> +      prefix = self.parent_kind.qualified_name + '.'\n> +    else:\n> +      prefix = self.module.GetNamespacePrefix()\n> +    return '%s%s' % (prefix, self.mojom_name)\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Union) and\n> +            (self.mojom_name, self.fields,\n> +             self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class Array(ReferenceKind):\n> +  \"\"\"An array.\n> +\n> +  Attributes:\n> +    kind: {Kind} The type of the elements. May be None.\n> +    length: The number of elements. None if unknown.\n> +  \"\"\"\n> +\n> +  ReferenceKind.AddSharedProperty('kind')\n> +  ReferenceKind.AddSharedProperty('length')\n> +\n> +  def __init__(self, kind=None, length=None):\n> +    if kind is not None:\n> +      if length is not None:\n> +        spec = 'a%d:%s' % (length, kind.spec)\n> +      else:\n> +        spec = 'a:%s' % kind.spec\n> +\n> +      ReferenceKind.__init__(self, spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +    self.length = length\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (\n> +          self.__class__.__name__, self.spec, self.is_nullable, Repr(\n> +              self.kind), self.length)\n> +    else:\n> +      return GenericRepr(self, {\n> +          'kind': True,\n> +          'length': False,\n> +          'is_nullable': False\n> +      })\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Array)\n> +            and (self.kind, self.length) == (rhs.kind, rhs.length))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class Map(ReferenceKind):\n> +  \"\"\"A map.\n> +\n> +  Attributes:\n> +    key_kind: {Kind} The type of the keys. May be None.\n> +    value_kind: {Kind} The type of the elements. May be None.\n> +  \"\"\"\n> +  ReferenceKind.AddSharedProperty('key_kind')\n> +  ReferenceKind.AddSharedProperty('value_kind')\n> +\n> +  def __init__(self, key_kind=None, value_kind=None):\n> +    if (key_kind is not None and value_kind is not None):\n> +      ReferenceKind.__init__(\n> +          self, 'm[' + key_kind.spec + '][' + value_kind.spec + ']')\n> +      if IsNullableKind(key_kind):\n> +        raise Exception(\"Nullable kinds cannot be keys in maps.\")\n> +      if IsAnyHandleKind(key_kind):\n> +        raise Exception(\"Handles cannot be keys in maps.\")\n> +      if IsAnyInterfaceKind(key_kind):\n> +        raise Exception(\"Interfaces cannot be keys in maps.\")\n> +      if IsArrayKind(key_kind):\n> +        raise Exception(\"Arrays cannot be keys in maps.\")\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +\n> +    self.key_kind = key_kind\n> +    self.value_kind = value_kind\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (\n> +          self.__class__.__name__, self.spec, self.is_nullable,\n> +          Repr(self.key_kind), Repr(self.value_kind))\n> +    else:\n> +      return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Map) and\n> +            (self.key_kind, self.value_kind) == (rhs.key_kind, rhs.value_kind))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class PendingRemote(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            'pending_remote<T> requires T to be an interface type. Got %r' %\n> +            kind.spec)\n> +      ReferenceKind.__init__(self, 'rmt:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, PendingRemote) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class PendingReceiver(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            'pending_receiver<T> requires T to be an interface type. Got %r' %\n> +            kind.spec)\n> +      ReferenceKind.__init__(self, 'rcv:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, PendingReceiver) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class PendingAssociatedRemote(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            'pending_associated_remote<T> requires T to be an interface ' +\n> +            'type. Got %r' % kind.spec)\n> +      ReferenceKind.__init__(self, 'rma:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, PendingAssociatedRemote) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class PendingAssociatedReceiver(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            'pending_associated_receiver<T> requires T to be an interface' +\n> +            'type. Got %r' % kind.spec)\n> +      ReferenceKind.__init__(self, 'rca:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, PendingAssociatedReceiver) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class InterfaceRequest(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            \"Interface request requires %r to be an interface.\" % kind.spec)\n> +      ReferenceKind.__init__(self, 'r:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, InterfaceRequest) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class AssociatedInterfaceRequest(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, InterfaceRequest):\n> +        raise Exception(\n> +            \"Associated interface request requires %r to be an interface \"\n> +            \"request.\" % kind.spec)\n> +      assert not kind.is_nullable\n> +      ReferenceKind.__init__(self, 'asso:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind.kind if kind is not None else None\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, AssociatedInterfaceRequest) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class Parameter(object):\n> +  def __init__(self,\n> +               mojom_name=None,\n> +               kind=None,\n> +               ordinal=None,\n> +               default=None,\n> +               attributes=None):\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.ordinal = ordinal\n> +    self.kind = kind\n> +    self.default = default\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    # pylint: disable=unused-argument\n> +    return '<%s mojom_name=%r kind=%s>' % (\n> +        self.__class__.__name__, self.mojom_name, self.kind.Repr(as_ref=True))\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeParameter(self.mojom_name)\n> +\n> +  @property\n> +  def min_version(self):\n> +    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n> +        if self.attributes else None\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Parameter)\n> +            and (self.mojom_name, self.ordinal, self.kind, self.default,\n> +                 self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.kind,\n> +                                      rhs.default, rhs.attributes))\n> +\n> +\n> +class Method(object):\n> +  def __init__(self, interface, mojom_name, ordinal=None, attributes=None):\n> +    self.interface = interface\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.explicit_ordinal = ordinal\n> +    self.ordinal = ordinal\n> +    self.parameters = []\n> +    self.param_struct = None\n> +    self.response_parameters = None\n> +    self.response_param_struct = None\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> +    else:\n> +      return GenericRepr(self, {\n> +          'mojom_name': False,\n> +          'parameters': True,\n> +          'response_parameters': True\n> +      })\n> +\n> +  def AddParameter(self,\n> +                   mojom_name,\n> +                   kind,\n> +                   ordinal=None,\n> +                   default=None,\n> +                   attributes=None):\n> +    parameter = Parameter(mojom_name, kind, ordinal, default, attributes)\n> +    self.parameters.append(parameter)\n> +    return parameter\n> +\n> +  def AddResponseParameter(self,\n> +                           mojom_name,\n> +                           kind,\n> +                           ordinal=None,\n> +                           default=None,\n> +                           attributes=None):\n> +    if self.response_parameters == None:\n> +      self.response_parameters = []\n> +    parameter = Parameter(mojom_name, kind, ordinal, default, attributes)\n> +    self.response_parameters.append(parameter)\n> +    return parameter\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeMethod(self.mojom_name)\n> +    for param in self.parameters:\n> +      param.Stylize(stylizer)\n> +    if self.response_parameters is not None:\n> +      for param in self.response_parameters:\n> +        param.Stylize(stylizer)\n> +\n> +    if self.param_struct:\n> +      self.param_struct.Stylize(stylizer)\n> +    if self.response_param_struct:\n> +      self.response_param_struct.Stylize(stylizer)\n> +\n> +  @property\n> +  def min_version(self):\n> +    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n> +        if self.attributes else None\n> +\n> +  @property\n> +  def sync(self):\n> +    return self.attributes.get(ATTRIBUTE_SYNC) \\\n> +        if self.attributes else None\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Method) and\n> +            (self.mojom_name, self.ordinal, self.parameters,\n> +             self.response_parameters,\n> +             self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,\n> +                                  rhs.response_parameters, rhs.attributes))\n> +\n> +\n> +class Interface(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('mojom_name')\n> +  ReferenceKind.AddSharedProperty('name')\n> +  ReferenceKind.AddSharedProperty('methods')\n> +  ReferenceKind.AddSharedProperty('enums')\n> +  ReferenceKind.AddSharedProperty('constants')\n> +  ReferenceKind.AddSharedProperty('attributes')\n> +\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    ReferenceKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.methods = []\n> +    self.enums = []\n> +    self.constants = []\n> +    self.attributes = attributes\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> +    else:\n> +      return GenericRepr(self, {\n> +          'mojom_name': False,\n> +          'attributes': False,\n> +          'methods': False\n> +      })\n> +\n> +  def AddMethod(self, mojom_name, ordinal=None, attributes=None):\n> +    method = Method(self, mojom_name, ordinal, attributes)\n> +    self.methods.append(method)\n> +    return method\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeInterface(self.mojom_name)\n> +    for method in self.methods:\n> +      method.Stylize(stylizer)\n> +    for enum in self.enums:\n> +      enum.Stylize(stylizer)\n> +    for constant in self.constants:\n> +      constant.Stylize(stylizer)\n> +\n> +  def IsBackwardCompatible(self, older_interface):\n> +    \"\"\"This interface is backward-compatible with older_interface if and only\n> +    if all of the following conditions hold:\n> +      - All defined methods in older_interface (when identified by ordinal) have\n> +        backward-compatible definitions in this interface. For each method this\n> +        means:\n> +          - The parameter list is backward-compatible, according to backward-\n> +            compatibility rules for structs, where each parameter is essentially\n> +            a struct field.\n> +          - If the old method definition does not specify a reply message, the\n> +            new method definition must not specify a reply message.\n> +          - If the old method definition specifies a reply message, the new\n> +            method definition must also specify a reply message with a parameter\n> +            list that is backward-compatible according to backward-compatibility\n> +            rules for structs.\n> +      - All newly introduced methods in this interface have a [MinVersion]\n> +        attribute specifying a version greater than any method in\n> +        older_interface.\n> +    \"\"\"\n> +\n> +    def buildOrdinalMethodMap(interface):\n> +      methods_by_ordinal = {}\n> +      for method in interface.methods:\n> +        if method.ordinal in methods_by_ordinal:\n> +          raise Exception('Multiple methods with ordinal %s in interface %s.' %\n> +                          (method.ordinal, interface.mojom_name))\n> +        methods_by_ordinal[method.ordinal] = method\n> +      return methods_by_ordinal\n> +\n> +    new_methods = buildOrdinalMethodMap(self)\n> +    old_methods = buildOrdinalMethodMap(older_interface)\n> +    max_old_min_version = 0\n> +    for ordinal, old_method in old_methods.items():\n> +      new_method = new_methods.get(ordinal)\n> +      if not new_method:\n> +        # A method was removed, which is not OK.\n> +        return False\n> +\n> +      if not new_method.param_struct.IsBackwardCompatible(\n> +          old_method.param_struct):\n> +        # The parameter list is not backward-compatible, which is not OK.\n> +        return False\n> +\n> +      if old_method.response_param_struct is None:\n> +        if new_method.response_param_struct is not None:\n> +          # A reply was added to a message which didn't have one before, and\n> +          # this is not OK.\n> +          return False\n> +      else:\n> +        if new_method.response_param_struct is None:\n> +          # A reply was removed from a message, which is not OK.\n> +          return False\n> +        if not new_method.response_param_struct.IsBackwardCompatible(\n> +            old_method.response_param_struct):\n> +          # The new message's reply is not backward-compatible with the old\n> +          # message's reply, which is not OK.\n> +          return False\n> +\n> +      if (old_method.min_version or 0) > max_old_min_version:\n> +        max_old_min_version = old_method.min_version\n> +\n> +    # All the old methods are compatible with their new counterparts. Now verify\n> +    # that newly added methods are properly versioned.\n> +    new_ordinals = set(new_methods.keys()) - set(old_methods.keys())\n> +    for ordinal in new_ordinals:\n> +      new_method = new_methods[ordinal]\n> +      if (new_method.min_version or 0) <= max_old_min_version:\n> +        # A method was added to an existing version, which is not OK.\n> +        return False\n> +\n> +    return True\n> +\n> +  @property\n> +  def stable(self):\n> +    return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> +        if self.attributes else False\n> +\n> +  @property\n> +  def qualified_name(self):\n> +    if self.parent_kind:\n> +      prefix = self.parent_kind.qualified_name + '.'\n> +    else:\n> +      prefix = self.module.GetNamespacePrefix()\n> +    return '%s%s' % (prefix, self.mojom_name)\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Interface)\n> +            and (self.mojom_name, self.methods, self.enums, self.constants,\n> +                 self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,\n> +                                      rhs.constants, rhs.attributes))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class AssociatedInterface(ReferenceKind):\n> +  ReferenceKind.AddSharedProperty('kind')\n> +\n> +  def __init__(self, kind=None):\n> +    if kind is not None:\n> +      if not isinstance(kind, Interface):\n> +        raise Exception(\n> +            \"Associated interface requires %r to be an interface.\" % kind.spec)\n> +      assert not kind.is_nullable\n> +      ReferenceKind.__init__(self, 'asso:' + kind.spec)\n> +    else:\n> +      ReferenceKind.__init__(self)\n> +    self.kind = kind\n> +\n> +  def __eq__(self, rhs):\n> +    return isinstance(rhs, AssociatedInterface) and self.kind == rhs.kind\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class EnumField(object):\n> +  def __init__(self,\n> +               mojom_name=None,\n> +               value=None,\n> +               attributes=None,\n> +               numeric_value=None):\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.value = value\n> +    self.attributes = attributes\n> +    self.numeric_value = numeric_value\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeEnumField(self.mojom_name)\n> +\n> +  @property\n> +  def min_version(self):\n> +    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n> +        if self.attributes else None\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, EnumField)\n> +            and (self.mojom_name, self.value, self.attributes,\n> +                 self.numeric_value) == (rhs.mojom_name, rhs.value,\n> +                                         rhs.attributes, rhs.numeric_value))\n> +\n> +\n> +class Enum(Kind):\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.native_only = False\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    Kind.__init__(self, spec, module)\n> +    self.fields = []\n> +    self.attributes = attributes\n> +    self.min_value = None\n> +    self.max_value = None\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> +    else:\n> +      return GenericRepr(self, {'mojom_name': False, 'fields': False})\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeEnum(self.mojom_name)\n> +    for field in self.fields:\n> +      field.Stylize(stylizer)\n> +\n> +  @property\n> +  def extensible(self):\n> +    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \\\n> +        if self.attributes else False\n> +\n> +  @property\n> +  def stable(self):\n> +    return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> +        if self.attributes else False\n> +\n> +  @property\n> +  def qualified_name(self):\n> +    if self.parent_kind:\n> +      prefix = self.parent_kind.qualified_name + '.'\n> +    else:\n> +      prefix = self.module.GetNamespacePrefix()\n> +    return '%s%s' % (prefix, self.mojom_name)\n> +\n> +  def IsBackwardCompatible(self, older_enum):\n> +    \"\"\"This enum is backward-compatible with older_enum if and only if one of\n> +    the following conditions holds:\n> +        - Neither enum is [Extensible] and both have the exact same set of valid\n> +          numeric values. Field names and aliases for the same numeric value do\n> +          not affect compatibility.\n> +        - older_enum is [Extensible], and for every version defined by\n> +          older_enum, this enum has the exact same set of valid numeric values.\n> +    \"\"\"\n> +\n> +    def buildVersionFieldMap(enum):\n> +      fields_by_min_version = {}\n> +      for field in enum.fields:\n> +        if field.min_version not in fields_by_min_version:\n> +          fields_by_min_version[field.min_version] = set()\n> +        fields_by_min_version[field.min_version].add(field.numeric_value)\n> +      return fields_by_min_version\n> +\n> +    old_fields = buildVersionFieldMap(older_enum)\n> +    new_fields = buildVersionFieldMap(self)\n> +\n> +    if new_fields.keys() != old_fields.keys() and not older_enum.extensible:\n> +      return False\n> +\n> +    for min_version, valid_values in old_fields.items():\n> +      if (min_version not in new_fields\n> +          or new_fields[min_version] != valid_values):\n> +        return False\n> +\n> +    return True\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Enum) and\n> +            (self.mojom_name, self.native_only, self.fields, self.attributes,\n> +             self.min_value,\n> +             self.max_value) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n> +                                 rhs.attributes, rhs.min_value, rhs.max_value))\n> +\n> +  def __hash__(self):\n> +    return id(self)\n> +\n> +\n> +class Module(object):\n> +  def __init__(self, path=None, mojom_namespace=None, attributes=None):\n> +    self.path = path\n> +    self.mojom_namespace = mojom_namespace\n> +    self.namespace = None\n> +    self.structs = []\n> +    self.unions = []\n> +    self.interfaces = []\n> +    self.enums = []\n> +    self.constants = []\n> +    self.kinds = {}\n> +    self.attributes = attributes\n> +    self.imports = []\n> +    self.imported_kinds = {}\n> +\n> +  def __repr__(self):\n> +    # Gives us a decent __repr__ for modules.\n> +    return self.Repr()\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, Module) and\n> +            (self.path, self.attributes, self.mojom_namespace, self.imports,\n> +             self.constants, self.enums, self.structs, self.unions,\n> +             self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,\n> +                                  rhs.imports, rhs.constants, rhs.enums,\n> +                                  rhs.structs, rhs.unions, rhs.interfaces))\n> +\n> +  def Repr(self, as_ref=True):\n> +    if as_ref:\n> +      return '<%s path=%r mojom_namespace=%r>' % (\n> +          self.__class__.__name__, self.path, self.mojom_namespace)\n> +    else:\n> +      return GenericRepr(\n> +          self, {\n> +              'path': False,\n> +              'mojom_namespace': False,\n> +              'attributes': False,\n> +              'structs': False,\n> +              'interfaces': False,\n> +              'unions': False\n> +          })\n> +\n> +  def GetNamespacePrefix(self):\n> +    return '%s.' % self.mojom_namespace if self.mojom_namespace else ''\n> +\n> +  def AddInterface(self, mojom_name, attributes=None):\n> +    interface = Interface(mojom_name, self, attributes)\n> +    self.interfaces.append(interface)\n> +    return interface\n> +\n> +  def AddStruct(self, mojom_name, attributes=None):\n> +    struct = Struct(mojom_name, self, attributes)\n> +    self.structs.append(struct)\n> +    return struct\n> +\n> +  def AddUnion(self, mojom_name, attributes=None):\n> +    union = Union(mojom_name, self, attributes)\n> +    self.unions.append(union)\n> +    return union\n> +\n> +  def Stylize(self, stylizer):\n> +    self.namespace = stylizer.StylizeModule(self.mojom_namespace)\n> +    for struct in self.structs:\n> +      struct.Stylize(stylizer)\n> +    for union in self.unions:\n> +      union.Stylize(stylizer)\n> +    for interface in self.interfaces:\n> +      interface.Stylize(stylizer)\n> +    for enum in self.enums:\n> +      enum.Stylize(stylizer)\n> +    for constant in self.constants:\n> +      constant.Stylize(stylizer)\n> +\n> +    for imported_module in self.imports:\n> +      imported_module.Stylize(stylizer)\n> +\n> +  def Dump(self, f):\n> +    pickle.dump(self, f, 2)\n> +\n> +  @classmethod\n> +  def Load(cls, f):\n> +    result = pickle.load(f)\n> +    assert isinstance(result, Module)\n> +    return result\n> +\n> +\n> +def IsBoolKind(kind):\n> +  return kind.spec == BOOL.spec\n> +\n> +\n> +def IsFloatKind(kind):\n> +  return kind.spec == FLOAT.spec\n> +\n> +\n> +def IsDoubleKind(kind):\n> +  return kind.spec == DOUBLE.spec\n> +\n> +\n> +def IsIntegralKind(kind):\n> +  return (kind.spec == BOOL.spec or kind.spec == INT8.spec\n> +          or kind.spec == INT16.spec or kind.spec == INT32.spec\n> +          or kind.spec == INT64.spec or kind.spec == UINT8.spec\n> +          or kind.spec == UINT16.spec or kind.spec == UINT32.spec\n> +          or kind.spec == UINT64.spec)\n> +\n> +\n> +def IsStringKind(kind):\n> +  return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec\n> +\n> +\n> +def IsGenericHandleKind(kind):\n> +  return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec\n> +\n> +\n> +def IsDataPipeConsumerKind(kind):\n> +  return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec\n> +\n> +\n> +def IsDataPipeProducerKind(kind):\n> +  return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec\n> +\n> +\n> +def IsMessagePipeKind(kind):\n> +  return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec\n> +\n> +\n> +def IsSharedBufferKind(kind):\n> +  return (kind.spec == SHAREDBUFFER.spec\n> +          or kind.spec == NULLABLE_SHAREDBUFFER.spec)\n> +\n> +\n> +def IsPlatformHandleKind(kind):\n> +  return (kind.spec == PLATFORMHANDLE.spec\n> +          or kind.spec == NULLABLE_PLATFORMHANDLE.spec)\n> +\n> +\n> +def IsStructKind(kind):\n> +  return isinstance(kind, Struct)\n> +\n> +\n> +def IsUnionKind(kind):\n> +  return isinstance(kind, Union)\n> +\n> +\n> +def IsArrayKind(kind):\n> +  return isinstance(kind, Array)\n> +\n> +\n> +def IsInterfaceKind(kind):\n> +  return isinstance(kind, Interface)\n> +\n> +\n> +def IsAssociatedInterfaceKind(kind):\n> +  return isinstance(kind, AssociatedInterface)\n> +\n> +\n> +def IsInterfaceRequestKind(kind):\n> +  return isinstance(kind, InterfaceRequest)\n> +\n> +\n> +def IsAssociatedInterfaceRequestKind(kind):\n> +  return isinstance(kind, AssociatedInterfaceRequest)\n> +\n> +\n> +def IsPendingRemoteKind(kind):\n> +  return isinstance(kind, PendingRemote)\n> +\n> +\n> +def IsPendingReceiverKind(kind):\n> +  return isinstance(kind, PendingReceiver)\n> +\n> +\n> +def IsPendingAssociatedRemoteKind(kind):\n> +  return isinstance(kind, PendingAssociatedRemote)\n> +\n> +\n> +def IsPendingAssociatedReceiverKind(kind):\n> +  return isinstance(kind, PendingAssociatedReceiver)\n> +\n> +\n> +def IsEnumKind(kind):\n> +  return isinstance(kind, Enum)\n> +\n> +\n> +def IsReferenceKind(kind):\n> +  return isinstance(kind, ReferenceKind)\n> +\n> +\n> +def IsNullableKind(kind):\n> +  return IsReferenceKind(kind) and kind.is_nullable\n> +\n> +\n> +def IsMapKind(kind):\n> +  return isinstance(kind, Map)\n> +\n> +\n> +def IsObjectKind(kind):\n> +  return IsPointerKind(kind) or IsUnionKind(kind)\n> +\n> +\n> +def IsPointerKind(kind):\n> +  return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind)\n> +          or IsMapKind(kind))\n> +\n> +\n> +# Please note that it doesn't include any interface kind.\n> +def IsAnyHandleKind(kind):\n> +  return (IsGenericHandleKind(kind) or IsDataPipeConsumerKind(kind)\n> +          or IsDataPipeProducerKind(kind) or IsMessagePipeKind(kind)\n> +          or IsSharedBufferKind(kind) or IsPlatformHandleKind(kind))\n> +\n> +\n> +def IsAnyInterfaceKind(kind):\n> +  return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind)\n> +          or IsAssociatedKind(kind) or IsPendingRemoteKind(kind)\n> +          or IsPendingReceiverKind(kind))\n> +\n> +\n> +def IsAnyHandleOrInterfaceKind(kind):\n> +  return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind)\n> +\n> +\n> +def IsAssociatedKind(kind):\n> +  return (IsAssociatedInterfaceKind(kind)\n> +          or IsAssociatedInterfaceRequestKind(kind)\n> +          or IsPendingAssociatedRemoteKind(kind)\n> +          or IsPendingAssociatedReceiverKind(kind))\n> +\n> +\n> +def HasCallbacks(interface):\n> +  for method in interface.methods:\n> +    if method.response_parameters != None:\n> +      return True\n> +  return False\n> +\n> +\n> +# Finds out whether an interface passes associated interfaces and associated\n> +# interface requests.\n> +def PassesAssociatedKinds(interface):\n> +  visited_kinds = set()\n> +  for method in interface.methods:\n> +    if MethodPassesAssociatedKinds(method, visited_kinds):\n> +      return True\n> +  return False\n> +\n> +\n> +def _AnyMethodParameterRecursive(method, predicate, visited_kinds=None):\n> +  def _HasProperty(kind):\n> +    if kind in visited_kinds:\n> +      # No need to examine the kind again.\n> +      return False\n> +    visited_kinds.add(kind)\n> +    if predicate(kind):\n> +      return True\n> +    if IsArrayKind(kind):\n> +      return _HasProperty(kind.kind)\n> +    if IsStructKind(kind) or IsUnionKind(kind):\n> +      for field in kind.fields:\n> +        if _HasProperty(field.kind):\n> +          return True\n> +    if IsMapKind(kind):\n> +      if _HasProperty(kind.key_kind) or _HasProperty(kind.value_kind):\n> +        return True\n> +    return False\n> +\n> +  if visited_kinds is None:\n> +    visited_kinds = set()\n> +\n> +  for param in method.parameters:\n> +    if _HasProperty(param.kind):\n> +      return True\n> +  if method.response_parameters != None:\n> +    for param in method.response_parameters:\n> +      if _HasProperty(param.kind):\n> +        return True\n> +  return False\n> +\n> +\n> +# Finds out whether a method passes associated interfaces and associated\n> +# interface requests.\n> +def MethodPassesAssociatedKinds(method, visited_kinds=None):\n> +  return _AnyMethodParameterRecursive(\n> +      method, IsAssociatedKind, visited_kinds=visited_kinds)\n> +\n> +\n> +# Determines whether a method passes interfaces.\n> +def MethodPassesInterfaces(method):\n> +  return _AnyMethodParameterRecursive(method, IsInterfaceKind)\n> +\n> +\n> +def HasSyncMethods(interface):\n> +  for method in interface.methods:\n> +    if method.sync:\n> +      return True\n> +  return False\n> +\n> +\n> +def ContainsHandlesOrInterfaces(kind):\n> +  \"\"\"Check if the kind contains any handles.\n> +\n> +  This check is recursive so it checks all struct fields, containers elements,\n> +  etc.\n> +\n> +  Args:\n> +    struct: {Kind} The kind to check.\n> +\n> +  Returns:\n> +    {bool}: True if the kind contains handles.\n> +  \"\"\"\n> +  # We remember the types we already checked to avoid infinite recursion when\n> +  # checking recursive (or mutually recursive) types:\n> +  checked = set()\n> +\n> +  def Check(kind):\n> +    if kind.spec in checked:\n> +      return False\n> +    checked.add(kind.spec)\n> +    if IsStructKind(kind):\n> +      return any(Check(field.kind) for field in kind.fields)\n> +    elif IsUnionKind(kind):\n> +      return any(Check(field.kind) for field in kind.fields)\n> +    elif IsAnyHandleKind(kind):\n> +      return True\n> +    elif IsAnyInterfaceKind(kind):\n> +      return True\n> +    elif IsArrayKind(kind):\n> +      return Check(kind.kind)\n> +    elif IsMapKind(kind):\n> +      return Check(kind.key_kind) or Check(kind.value_kind)\n> +    else:\n> +      return False\n> +\n> +  return Check(kind)\n> +\n> +\n> +def ContainsNativeTypes(kind):\n> +  \"\"\"Check if the kind contains any native type (struct or enum).\n> +\n> +  This check is recursive so it checks all struct fields, scoped interface\n> +  enums, etc.\n> +\n> +  Args:\n> +    struct: {Kind} The kind to check.\n> +\n> +  Returns:\n> +    {bool}: True if the kind contains native types.\n> +  \"\"\"\n> +  # We remember the types we already checked to avoid infinite recursion when\n> +  # checking recursive (or mutually recursive) types:\n> +  checked = set()\n> +\n> +  def Check(kind):\n> +    if kind.spec in checked:\n> +      return False\n> +    checked.add(kind.spec)\n> +    if IsEnumKind(kind):\n> +      return kind.native_only\n> +    elif IsStructKind(kind):\n> +      if kind.native_only:\n> +        return True\n> +      if any(enum.native_only for enum in kind.enums):\n> +        return True\n> +      return any(Check(field.kind) for field in kind.fields)\n> +    elif IsUnionKind(kind):\n> +      return any(Check(field.kind) for field in kind.fields)\n> +    elif IsInterfaceKind(kind):\n> +      return any(enum.native_only for enum in kind.enums)\n> +    elif IsArrayKind(kind):\n> +      return Check(kind.kind)\n> +    elif IsMapKind(kind):\n> +      return Check(kind.key_kind) or Check(kind.value_kind)\n> +    else:\n> +      return False\n> +\n> +  return Check(kind)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> new file mode 100644\n> index 00000000..e8fd4936\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> @@ -0,0 +1,31 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import sys\n> +import unittest\n> +\n> +from mojom.generate import module as mojom\n> +\n> +\n> +class ModuleTest(unittest.TestCase):\n> +  def testNonInterfaceAsInterfaceRequest(self):\n> +    \"\"\"Tests that a non-interface cannot be used for interface requests.\"\"\"\n> +    module = mojom.Module('test_module', 'test_namespace')\n> +    struct = mojom.Struct('TestStruct', module=module)\n> +    with self.assertRaises(Exception) as e:\n> +      mojom.InterfaceRequest(struct)\n> +    self.assertEquals(\n> +        e.exception.__str__(),\n> +        'Interface request requires \\'x:TestStruct\\' to be an interface.')\n> +\n> +  def testNonInterfaceAsAssociatedInterface(self):\n> +    \"\"\"Tests that a non-interface type cannot be used for associated interfaces.\n> +    \"\"\"\n> +    module = mojom.Module('test_module', 'test_namespace')\n> +    struct = mojom.Struct('TestStruct', module=module)\n> +    with self.assertRaises(Exception) as e:\n> +      mojom.AssociatedInterface(struct)\n> +    self.assertEquals(\n> +        e.exception.__str__(),\n> +        'Associated interface requires \\'x:TestStruct\\' to be an interface.')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> new file mode 100644\n> index 00000000..88b77c98\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> @@ -0,0 +1,258 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom.generate import module as mojom\n> +\n> +# This module provides a mechanism for determining the packed order and offsets\n> +# of a mojom.Struct.\n> +#\n> +# ps = pack.PackedStruct(struct)\n> +# ps.packed_fields will access a list of PackedField objects, each of which\n> +# will have an offset, a size and a bit (for mojom.BOOLs).\n> +\n> +# Size of struct header in bytes: num_bytes [4B] + version [4B].\n> +HEADER_SIZE = 8\n> +\n> +\n> +class PackedField(object):\n> +  kind_to_size = {\n> +      mojom.BOOL: 1,\n> +      mojom.INT8: 1,\n> +      mojom.UINT8: 1,\n> +      mojom.INT16: 2,\n> +      mojom.UINT16: 2,\n> +      mojom.INT32: 4,\n> +      mojom.UINT32: 4,\n> +      mojom.FLOAT: 4,\n> +      mojom.HANDLE: 4,\n> +      mojom.MSGPIPE: 4,\n> +      mojom.SHAREDBUFFER: 4,\n> +      mojom.PLATFORMHANDLE: 4,\n> +      mojom.DCPIPE: 4,\n> +      mojom.DPPIPE: 4,\n> +      mojom.NULLABLE_HANDLE: 4,\n> +      mojom.NULLABLE_MSGPIPE: 4,\n> +      mojom.NULLABLE_SHAREDBUFFER: 4,\n> +      mojom.NULLABLE_PLATFORMHANDLE: 4,\n> +      mojom.NULLABLE_DCPIPE: 4,\n> +      mojom.NULLABLE_DPPIPE: 4,\n> +      mojom.INT64: 8,\n> +      mojom.UINT64: 8,\n> +      mojom.DOUBLE: 8,\n> +      mojom.STRING: 8,\n> +      mojom.NULLABLE_STRING: 8\n> +  }\n> +\n> +  @classmethod\n> +  def GetSizeForKind(cls, kind):\n> +    if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface,\n> +                         mojom.AssociatedInterface, mojom.PendingRemote,\n> +                         mojom.PendingAssociatedRemote)):\n> +      return 8\n> +    if isinstance(kind, mojom.Union):\n> +      return 16\n> +    if isinstance(kind, (mojom.InterfaceRequest, mojom.PendingReceiver)):\n> +      kind = mojom.MSGPIPE\n> +    if isinstance(\n> +        kind,\n> +        (mojom.AssociatedInterfaceRequest, mojom.PendingAssociatedReceiver)):\n> +      return 4\n> +    if isinstance(kind, mojom.Enum):\n> +      # TODO(mpcomplete): what about big enums?\n> +      return cls.kind_to_size[mojom.INT32]\n> +    if not kind in cls.kind_to_size:\n> +      raise Exception(\"Undefined type: %s. Did you forget to import the file \"\n> +                      \"containing the definition?\" % kind.spec)\n> +    return cls.kind_to_size[kind]\n> +\n> +  @classmethod\n> +  def GetAlignmentForKind(cls, kind):\n> +    if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface,\n> +                         mojom.PendingRemote, mojom.PendingAssociatedRemote)):\n> +      return 4\n> +    if isinstance(kind, mojom.Union):\n> +      return 8\n> +    return cls.GetSizeForKind(kind)\n> +\n> +  def __init__(self, field, index, ordinal):\n> +    \"\"\"\n> +    Args:\n> +      field: the original field.\n> +      index: the position of the original field in the struct.\n> +      ordinal: the ordinal of the field for serialization.\n> +    \"\"\"\n> +    self.field = field\n> +    self.index = index\n> +    self.ordinal = ordinal\n> +    self.size = self.GetSizeForKind(field.kind)\n> +    self.alignment = self.GetAlignmentForKind(field.kind)\n> +    self.offset = None\n> +    self.bit = None\n> +    self.min_version = None\n> +\n> +\n> +def GetPad(offset, alignment):\n> +  \"\"\"Returns the pad necessary to reserve space so that |offset + pad| equals to\n> +  some multiple of |alignment|.\"\"\"\n> +  return (alignment - (offset % alignment)) % alignment\n> +\n> +\n> +def GetFieldOffset(field, last_field):\n> +  \"\"\"Returns a 2-tuple of the field offset and bit (for BOOLs).\"\"\"\n> +  if (field.field.kind == mojom.BOOL and last_field.field.kind == mojom.BOOL\n> +      and last_field.bit < 7):\n> +    return (last_field.offset, last_field.bit + 1)\n> +\n> +  offset = last_field.offset + last_field.size\n> +  pad = GetPad(offset, field.alignment)\n> +  return (offset + pad, 0)\n> +\n> +\n> +def GetPayloadSizeUpToField(field):\n> +  \"\"\"Returns the payload size (not including struct header) if |field| is the\n> +  last field.\n> +  \"\"\"\n> +  if not field:\n> +    return 0\n> +  offset = field.offset + field.size\n> +  pad = GetPad(offset, 8)\n> +  return offset + pad\n> +\n> +\n> +class PackedStruct(object):\n> +  def __init__(self, struct):\n> +    self.struct = struct\n> +    # |packed_fields| contains all the fields, in increasing offset order.\n> +    self.packed_fields = []\n> +    # |packed_fields_in_ordinal_order| refers to the same fields as\n> +    # |packed_fields|, but in ordinal order.\n> +    self.packed_fields_in_ordinal_order = []\n> +\n> +    # No fields.\n> +    if (len(struct.fields) == 0):\n> +      return\n> +\n> +    # Start by sorting by ordinal.\n> +    src_fields = self.packed_fields_in_ordinal_order\n> +    ordinal = 0\n> +    for index, field in enumerate(struct.fields):\n> +      if field.ordinal is not None:\n> +        ordinal = field.ordinal\n> +      src_fields.append(PackedField(field, index, ordinal))\n> +      ordinal += 1\n> +    src_fields.sort(key=lambda field: field.ordinal)\n> +\n> +    # Set |min_version| for each field.\n> +    next_min_version = 0\n> +    for packed_field in src_fields:\n> +      if packed_field.field.min_version is None:\n> +        assert next_min_version == 0\n> +      else:\n> +        assert packed_field.field.min_version >= next_min_version\n> +        next_min_version = packed_field.field.min_version\n> +      packed_field.min_version = next_min_version\n> +\n> +      if (packed_field.min_version != 0\n> +          and mojom.IsReferenceKind(packed_field.field.kind)\n> +          and not packed_field.field.kind.is_nullable):\n> +        raise Exception(\"Non-nullable fields are only allowed in version 0 of \"\n> +                        \"a struct. %s.%s is defined with [MinVersion=%d].\" %\n> +                        (self.struct.name, packed_field.field.name,\n> +                         packed_field.min_version))\n> +\n> +    src_field = src_fields[0]\n> +    src_field.offset = 0\n> +    src_field.bit = 0\n> +    dst_fields = self.packed_fields\n> +    dst_fields.append(src_field)\n> +\n> +    # Then find first slot that each field will fit.\n> +    for src_field in src_fields[1:]:\n> +      last_field = dst_fields[0]\n> +      for i in range(1, len(dst_fields)):\n> +        next_field = dst_fields[i]\n> +        offset, bit = GetFieldOffset(src_field, last_field)\n> +        if offset + src_field.size <= next_field.offset:\n> +          # Found hole.\n> +          src_field.offset = offset\n> +          src_field.bit = bit\n> +          dst_fields.insert(i, src_field)\n> +          break\n> +        last_field = next_field\n> +      if src_field.offset is None:\n> +        # Add to end\n> +        src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field)\n> +        dst_fields.append(src_field)\n> +\n> +\n> +class ByteInfo(object):\n> +  def __init__(self):\n> +    self.is_padding = False\n> +    self.packed_fields = []\n> +\n> +\n> +def GetByteLayout(packed_struct):\n> +  total_payload_size = GetPayloadSizeUpToField(\n> +      packed_struct.packed_fields[-1] if packed_struct.packed_fields else None)\n> +  byte_info = [ByteInfo() for i in range(total_payload_size)]\n> +\n> +  limit_of_previous_field = 0\n> +  for packed_field in packed_struct.packed_fields:\n> +    for i in range(limit_of_previous_field, packed_field.offset):\n> +      byte_info[i].is_padding = True\n> +    byte_info[packed_field.offset].packed_fields.append(packed_field)\n> +    limit_of_previous_field = packed_field.offset + packed_field.size\n> +\n> +  for i in range(limit_of_previous_field, len(byte_info)):\n> +    byte_info[i].is_padding = True\n> +\n> +  for byte in byte_info:\n> +    # A given byte cannot both be padding and have a fields packed into it.\n> +    assert not (byte.is_padding and byte.packed_fields)\n> +\n> +  return byte_info\n> +\n> +\n> +class VersionInfo(object):\n> +  def __init__(self, version, num_fields, num_bytes):\n> +    self.version = version\n> +    self.num_fields = num_fields\n> +    self.num_bytes = num_bytes\n> +\n> +\n> +def GetVersionInfo(packed_struct):\n> +  \"\"\"Get version information for a struct.\n> +\n> +  Args:\n> +    packed_struct: A PackedStruct instance.\n> +\n> +  Returns:\n> +    A non-empty list of VersionInfo instances, sorted by version in increasing\n> +    order.\n> +    Note: The version numbers may not be consecutive.\n> +  \"\"\"\n> +  versions = []\n> +  last_version = 0\n> +  last_num_fields = 0\n> +  last_payload_size = 0\n> +\n> +  for packed_field in packed_struct.packed_fields_in_ordinal_order:\n> +    if packed_field.min_version != last_version:\n> +      versions.append(\n> +          VersionInfo(last_version, last_num_fields,\n> +                      last_payload_size + HEADER_SIZE))\n> +      last_version = packed_field.min_version\n> +\n> +    last_num_fields += 1\n> +    # The fields are iterated in ordinal order here. However, the size of a\n> +    # version is determined by the last field of that version in pack order,\n> +    # instead of ordinal order. Therefore, we need to calculate the max value.\n> +    last_payload_size = max(\n> +        GetPayloadSizeUpToField(packed_field), last_payload_size)\n> +\n> +  assert len(versions) == 0 or last_num_fields != versions[-1].num_fields\n> +  versions.append(\n> +      VersionInfo(last_version, last_num_fields,\n> +                  last_payload_size + HEADER_SIZE))\n> +  return versions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> new file mode 100644\n> index 00000000..98c705ad\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> @@ -0,0 +1,225 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import sys\n> +import unittest\n> +\n> +from mojom.generate import module as mojom\n> +from mojom.generate import pack\n> +\n> +\n> +class PackTest(unittest.TestCase):\n> +  def testOrdinalOrder(self):\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('testfield1', mojom.INT32, 2)\n> +    struct.AddField('testfield2', mojom.INT32, 1)\n> +    ps = pack.PackedStruct(struct)\n> +\n> +    self.assertEqual(2, len(ps.packed_fields))\n> +    self.assertEqual('testfield2', ps.packed_fields[0].field.mojom_name)\n> +    self.assertEqual('testfield1', ps.packed_fields[1].field.mojom_name)\n> +\n> +  def testZeroFields(self):\n> +    struct = mojom.Struct('test')\n> +    ps = pack.PackedStruct(struct)\n> +    self.assertEqual(0, len(ps.packed_fields))\n> +\n> +  def testOneField(self):\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('testfield1', mojom.INT8)\n> +    ps = pack.PackedStruct(struct)\n> +    self.assertEqual(1, len(ps.packed_fields))\n> +\n> +  def _CheckPackSequence(self, kinds, fields, offsets):\n> +    \"\"\"Checks the pack order and offsets of a sequence of mojom.Kinds.\n> +\n> +    Args:\n> +      kinds: A sequence of mojom.Kinds that specify the fields that are to be\n> +      created.\n> +      fields: The expected order of the resulting fields, with the integer \"1\"\n> +      first.\n> +      offsets: The expected order of offsets, with the integer \"0\" first.\n> +    \"\"\"\n> +    struct = mojom.Struct('test')\n> +    index = 1\n> +    for kind in kinds:\n> +      struct.AddField('%d' % index, kind)\n> +      index += 1\n> +    ps = pack.PackedStruct(struct)\n> +    num_fields = len(ps.packed_fields)\n> +    self.assertEqual(len(kinds), num_fields)\n> +    for i in range(num_fields):\n> +      self.assertEqual('%d' % fields[i], ps.packed_fields[i].field.mojom_name)\n> +      self.assertEqual(offsets[i], ps.packed_fields[i].offset)\n> +\n> +  def testPaddingPackedInOrder(self):\n> +    return self._CheckPackSequence((mojom.INT8, mojom.UINT8, mojom.INT32),\n> +                                   (1, 2, 3), (0, 1, 4))\n> +\n> +  def testPaddingPackedOutOfOrder(self):\n> +    return self._CheckPackSequence((mojom.INT8, mojom.INT32, mojom.UINT8),\n> +                                   (1, 3, 2), (0, 1, 4))\n> +\n> +  def testPaddingPackedOverflow(self):\n> +    kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8)\n> +    # 2 bytes should be packed together first, followed by short, then by int.\n> +    fields = (1, 4, 3, 2, 5)\n> +    offsets = (0, 1, 2, 4, 8)\n> +    return self._CheckPackSequence(kinds, fields, offsets)\n> +\n> +  def testNullableTypes(self):\n> +    kinds = (mojom.STRING.MakeNullableKind(), mojom.HANDLE.MakeNullableKind(),\n> +             mojom.Struct('test_struct').MakeNullableKind(),\n> +             mojom.DCPIPE.MakeNullableKind(), mojom.Array().MakeNullableKind(),\n> +             mojom.DPPIPE.MakeNullableKind(),\n> +             mojom.Array(length=5).MakeNullableKind(),\n> +             mojom.MSGPIPE.MakeNullableKind(),\n> +             mojom.Interface('test_interface').MakeNullableKind(),\n> +             mojom.SHAREDBUFFER.MakeNullableKind(),\n> +             mojom.InterfaceRequest().MakeNullableKind())\n> +    fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11)\n> +    offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 56, 60)\n> +    return self._CheckPackSequence(kinds, fields, offsets)\n> +\n> +  def testAllTypes(self):\n> +    return self._CheckPackSequence(\n> +        (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8, mojom.INT16,\n> +         mojom.DOUBLE, mojom.UINT16, mojom.INT32, mojom.UINT32, mojom.INT64,\n> +         mojom.FLOAT, mojom.STRING, mojom.HANDLE, mojom.UINT64,\n> +         mojom.Struct('test'), mojom.Array(), mojom.STRING.MakeNullableKind()),\n> +        (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18),\n> +        (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88))\n> +\n> +  def testPaddingPackedOutOfOrderByOrdinal(self):\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('testfield1', mojom.INT8)\n> +    struct.AddField('testfield3', mojom.UINT8, 3)\n> +    struct.AddField('testfield2', mojom.INT32, 2)\n> +    ps = pack.PackedStruct(struct)\n> +    self.assertEqual(3, len(ps.packed_fields))\n> +\n> +    # Second byte should be packed in behind first, altering order.\n> +    self.assertEqual('testfield1', ps.packed_fields[0].field.mojom_name)\n> +    self.assertEqual('testfield3', ps.packed_fields[1].field.mojom_name)\n> +    self.assertEqual('testfield2', ps.packed_fields[2].field.mojom_name)\n> +\n> +    # Second byte should be packed with first.\n> +    self.assertEqual(0, ps.packed_fields[0].offset)\n> +    self.assertEqual(1, ps.packed_fields[1].offset)\n> +    self.assertEqual(4, ps.packed_fields[2].offset)\n> +\n> +  def testBools(self):\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('bit0', mojom.BOOL)\n> +    struct.AddField('bit1', mojom.BOOL)\n> +    struct.AddField('int', mojom.INT32)\n> +    struct.AddField('bit2', mojom.BOOL)\n> +    struct.AddField('bit3', mojom.BOOL)\n> +    struct.AddField('bit4', mojom.BOOL)\n> +    struct.AddField('bit5', mojom.BOOL)\n> +    struct.AddField('bit6', mojom.BOOL)\n> +    struct.AddField('bit7', mojom.BOOL)\n> +    struct.AddField('bit8', mojom.BOOL)\n> +    ps = pack.PackedStruct(struct)\n> +    self.assertEqual(10, len(ps.packed_fields))\n> +\n> +    # First 8 bits packed together.\n> +    for i in range(8):\n> +      pf = ps.packed_fields[i]\n> +      self.assertEqual(0, pf.offset)\n> +      self.assertEqual(\"bit%d\" % i, pf.field.mojom_name)\n> +      self.assertEqual(i, pf.bit)\n> +\n> +    # Ninth bit goes into second byte.\n> +    self.assertEqual(\"bit8\", ps.packed_fields[8].field.mojom_name)\n> +    self.assertEqual(1, ps.packed_fields[8].offset)\n> +    self.assertEqual(0, ps.packed_fields[8].bit)\n> +\n> +    # int comes last.\n> +    self.assertEqual(\"int\", ps.packed_fields[9].field.mojom_name)\n> +    self.assertEqual(4, ps.packed_fields[9].offset)\n> +\n> +  def testMinVersion(self):\n> +    \"\"\"Tests that |min_version| is properly set for packed fields.\"\"\"\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('field_2', mojom.BOOL, 2)\n> +    struct.AddField('field_0', mojom.INT32, 0)\n> +    struct.AddField('field_1', mojom.INT64, 1)\n> +    ps = pack.PackedStruct(struct)\n> +\n> +    self.assertEqual('field_0', ps.packed_fields[0].field.mojom_name)\n> +    self.assertEqual('field_2', ps.packed_fields[1].field.mojom_name)\n> +    self.assertEqual('field_1', ps.packed_fields[2].field.mojom_name)\n> +\n> +    self.assertEqual(0, ps.packed_fields[0].min_version)\n> +    self.assertEqual(0, ps.packed_fields[1].min_version)\n> +    self.assertEqual(0, ps.packed_fields[2].min_version)\n> +\n> +    struct.fields[0].attributes = {'MinVersion': 1}\n> +    ps = pack.PackedStruct(struct)\n> +\n> +    self.assertEqual(0, ps.packed_fields[0].min_version)\n> +    self.assertEqual(1, ps.packed_fields[1].min_version)\n> +    self.assertEqual(0, ps.packed_fields[2].min_version)\n> +\n> +  def testGetVersionInfoEmptyStruct(self):\n> +    \"\"\"Tests that pack.GetVersionInfo() never returns an empty list, even for\n> +    empty structs.\n> +    \"\"\"\n> +    struct = mojom.Struct('test')\n> +    ps = pack.PackedStruct(struct)\n> +\n> +    versions = pack.GetVersionInfo(ps)\n> +    self.assertEqual(1, len(versions))\n> +    self.assertEqual(0, versions[0].version)\n> +    self.assertEqual(0, versions[0].num_fields)\n> +    self.assertEqual(8, versions[0].num_bytes)\n> +\n> +  def testGetVersionInfoComplexOrder(self):\n> +    \"\"\"Tests pack.GetVersionInfo() using a struct whose definition order,\n> +    ordinal order and pack order for fields are all different.\n> +    \"\"\"\n> +    struct = mojom.Struct('test')\n> +    struct.AddField(\n> +        'field_3', mojom.BOOL, ordinal=3, attributes={'MinVersion': 3})\n> +    struct.AddField('field_0', mojom.INT32, ordinal=0)\n> +    struct.AddField(\n> +        'field_1', mojom.INT64, ordinal=1, attributes={'MinVersion': 2})\n> +    struct.AddField(\n> +        'field_2', mojom.INT64, ordinal=2, attributes={'MinVersion': 3})\n> +    ps = pack.PackedStruct(struct)\n> +\n> +    versions = pack.GetVersionInfo(ps)\n> +    self.assertEqual(3, len(versions))\n> +\n> +    self.assertEqual(0, versions[0].version)\n> +    self.assertEqual(1, versions[0].num_fields)\n> +    self.assertEqual(16, versions[0].num_bytes)\n> +\n> +    self.assertEqual(2, versions[1].version)\n> +    self.assertEqual(2, versions[1].num_fields)\n> +    self.assertEqual(24, versions[1].num_bytes)\n> +\n> +    self.assertEqual(3, versions[2].version)\n> +    self.assertEqual(4, versions[2].num_fields)\n> +    self.assertEqual(32, versions[2].num_bytes)\n> +\n> +  def testInterfaceAlignment(self):\n> +    \"\"\"Tests that interfaces are aligned on 4-byte boundaries, although the size\n> +    of an interface is 8 bytes.\n> +    \"\"\"\n> +    kinds = (mojom.INT32, mojom.Interface('test_interface'))\n> +    fields = (1, 2)\n> +    offsets = (0, 4)\n> +    self._CheckPackSequence(kinds, fields, offsets)\n> +\n> +  def testAssociatedInterfaceAlignment(self):\n> +    \"\"\"Tests that associated interfaces are aligned on 4-byte boundaries,\n> +    although the size of an associated interface is 8 bytes.\n> +    \"\"\"\n> +    kinds = (mojom.INT32,\n> +             mojom.AssociatedInterface(mojom.Interface('test_interface')))\n> +    fields = (1, 2)\n> +    offsets = (0, 4)\n> +    self._CheckPackSequence(kinds, fields, offsets)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> new file mode 100644\n> index 00000000..7a300560\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> @@ -0,0 +1,83 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +# Based on third_party/WebKit/Source/build/scripts/template_expander.py.\n> +\n> +import os.path\n> +import sys\n> +\n> +from mojom import fileutil\n> +\n> +fileutil.AddLocalRepoThirdPartyDirToModulePath()\n> +import jinja2\n> +\n> +\n> +def ApplyTemplate(mojo_generator, path_to_template, params, **kwargs):\n> +  loader = jinja2.ModuleLoader(\n> +      os.path.join(mojo_generator.bytecode_path,\n> +                   \"%s.zip\" % mojo_generator.GetTemplatePrefix()))\n> +  final_kwargs = dict(mojo_generator.GetJinjaParameters())\n> +  final_kwargs.update(kwargs)\n> +\n> +  jinja_env = jinja2.Environment(\n> +      loader=loader, keep_trailing_newline=True, **final_kwargs)\n> +  jinja_env.globals.update(mojo_generator.GetGlobals())\n> +  jinja_env.filters.update(mojo_generator.GetFilters())\n> +  template = jinja_env.get_template(path_to_template)\n> +  return template.render(params)\n> +\n> +\n> +def UseJinja(path_to_template, **kwargs):\n> +  def RealDecorator(generator):\n> +    def GeneratorInternal(*args, **kwargs2):\n> +      parameters = generator(*args, **kwargs2)\n> +      return ApplyTemplate(args[0], path_to_template, parameters, **kwargs)\n> +\n> +    GeneratorInternal.__name__ = generator.__name__\n> +    return GeneratorInternal\n> +\n> +  return RealDecorator\n> +\n> +\n> +def ApplyImportedTemplate(mojo_generator, path_to_template, filename, params,\n> +                          **kwargs):\n> +  loader = jinja2.FileSystemLoader(searchpath=path_to_template)\n> +  final_kwargs = dict(mojo_generator.GetJinjaParameters())\n> +  final_kwargs.update(kwargs)\n> +\n> +  jinja_env = jinja2.Environment(\n> +      loader=loader, keep_trailing_newline=True, **final_kwargs)\n> +  jinja_env.globals.update(mojo_generator.GetGlobals())\n> +  jinja_env.filters.update(mojo_generator.GetFilters())\n> +  template = jinja_env.get_template(filename)\n> +  return template.render(params)\n> +\n> +\n> +def UseJinjaForImportedTemplate(func):\n> +  def wrapper(*args, **kwargs):\n> +    parameters = func(*args, **kwargs)\n> +    path_to_template = args[1]\n> +    filename = args[2]\n> +    return ApplyImportedTemplate(args[0], path_to_template, filename,\n> +                                 parameters)\n> +\n> +  wrapper.__name__ = func.__name__\n> +  return wrapper\n> +\n> +\n> +def PrecompileTemplates(generator_modules, output_dir):\n> +  for module in generator_modules.values():\n> +    generator = module.Generator(None)\n> +    jinja_env = jinja2.Environment(\n> +        loader=jinja2.FileSystemLoader([\n> +            os.path.join(\n> +                os.path.dirname(module.__file__), generator.GetTemplatePrefix())\n> +        ]))\n> +    jinja_env.filters.update(generator.GetFilters())\n> +    jinja_env.compile_templates(\n> +        os.path.join(output_dir, \"%s.zip\" % generator.GetTemplatePrefix()),\n> +        extensions=[\"tmpl\"],\n> +        zip=\"stored\",\n> +        py_compile=True,\n> +        ignore_errors=False)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> new file mode 100644\n> index 00000000..d6df3ca6\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> @@ -0,0 +1,854 @@\n> +# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Convert parse tree to AST.\n> +\n> +This module converts the parse tree to the AST we use for code generation. The\n> +main entry point is OrderedModule, which gets passed the parser\n> +representation of a mojom file. When called it's assumed that all imports have\n> +already been parsed and converted to ASTs before.\n> +\"\"\"\n> +\n> +import itertools\n> +import os\n> +import re\n> +import sys\n> +\n> +from mojom.generate import generator\n> +from mojom.generate import module as mojom\n> +from mojom.parse import ast\n> +\n> +\n> +def _IsStrOrUnicode(x):\n> +  if sys.version_info[0] < 3:\n> +    return isinstance(x, (unicode, str))\n> +  return isinstance(x, str)\n> +\n> +\n> +def _DuplicateName(values):\n> +  \"\"\"Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name'\n> +  has already been encountered. If there are no duplicates, returns None.\"\"\"\n> +  names = set()\n> +  for value in values:\n> +    if value.mojom_name in names:\n> +      return value.mojom_name\n> +    names.add(value.mojom_name)\n> +  return None\n> +\n> +\n> +def _ElemsOfType(elems, elem_type, scope):\n> +  \"\"\"Find all elements of the given type.\n> +\n> +  Args:\n> +    elems: {Sequence[Any]} Sequence of elems.\n> +    elem_type: {Type[C]} Extract all elems of this type.\n> +    scope: {str} The name of the surrounding scope (e.g. struct\n> +        definition). Used in error messages.\n> +\n> +  Returns:\n> +    {List[C]} All elems of matching type.\n> +  \"\"\"\n> +  assert isinstance(elem_type, type)\n> +  result = [elem for elem in elems if isinstance(elem, elem_type)]\n> +  duplicate_name = _DuplicateName(result)\n> +  if duplicate_name:\n> +    raise Exception('Names in mojom must be unique within a scope. The name '\n> +                    '\"%s\" is used more than once within the scope \"%s\".' %\n> +                    (duplicate_name, scope))\n> +  return result\n> +\n> +\n> +def _ProcessElements(scope, elements, operations_by_type):\n> +  \"\"\"Iterates over the given elements, running a function from\n> +  operations_by_type for any element that matches a key in that dict. The scope\n> +  is the name of the surrounding scope, such as a filename or struct name, used\n> +  only in error messages.\"\"\"\n> +  names_in_this_scope = set()\n> +  for element in elements:\n> +    # pylint: disable=unidiomatic-typecheck\n> +    element_type = type(element)\n> +    if element_type in operations_by_type:\n> +      if element.mojom_name in names_in_this_scope:\n> +        raise Exception('Names must be unique within a scope. The name \"%s\" is '\n> +                        'used more than once within the scope \"%s\".' %\n> +                        (duplicate_name, scope))\n> +      operations_by_type[element_type](element)\n> +\n> +\n> +def _MapKind(kind):\n> +  map_to_kind = {\n> +      'bool': 'b',\n> +      'int8': 'i8',\n> +      'int16': 'i16',\n> +      'int32': 'i32',\n> +      'int64': 'i64',\n> +      'uint8': 'u8',\n> +      'uint16': 'u16',\n> +      'uint32': 'u32',\n> +      'uint64': 'u64',\n> +      'float': 'f',\n> +      'double': 'd',\n> +      'string': 's',\n> +      'handle': 'h',\n> +      'handle<data_pipe_consumer>': 'h:d:c',\n> +      'handle<data_pipe_producer>': 'h:d:p',\n> +      'handle<message_pipe>': 'h:m',\n> +      'handle<shared_buffer>': 'h:s',\n> +      'handle<platform>': 'h:p'\n> +  }\n> +  if kind.endswith('?'):\n> +    base_kind = _MapKind(kind[0:-1])\n> +    # NOTE: This doesn't rule out enum types. Those will be detected later, when\n> +    # cross-reference is established.\n> +    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',\n> +                       'rma', 'rca')\n> +    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:\n> +      raise Exception('A type (spec \"%s\") cannot be made nullable' % base_kind)\n> +    return '?' + base_kind\n> +  if kind.endswith('}'):\n> +    lbracket = kind.rfind('{')\n> +    value = kind[0:lbracket]\n> +    return 'm[' + _MapKind(kind[lbracket + 1:-1]) + '][' + _MapKind(value) + ']'\n> +  if kind.endswith(']'):\n> +    lbracket = kind.rfind('[')\n> +    typename = kind[0:lbracket]\n> +    return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)\n> +  if kind.endswith('&'):\n> +    return 'r:' + _MapKind(kind[0:-1])\n> +  if kind.startswith('asso<'):\n> +    assert kind.endswith('>')\n> +    return 'asso:' + _MapKind(kind[5:-1])\n> +  if kind.startswith('rmt<'):\n> +    assert kind.endswith('>')\n> +    return 'rmt:' + _MapKind(kind[4:-1])\n> +  if kind.startswith('rcv<'):\n> +    assert kind.endswith('>')\n> +    return 'rcv:' + _MapKind(kind[4:-1])\n> +  if kind.startswith('rma<'):\n> +    assert kind.endswith('>')\n> +    return 'rma:' + _MapKind(kind[4:-1])\n> +  if kind.startswith('rca<'):\n> +    assert kind.endswith('>')\n> +    return 'rca:' + _MapKind(kind[4:-1])\n> +  if kind in map_to_kind:\n> +    return map_to_kind[kind]\n> +  return 'x:' + kind\n> +\n> +\n> +def _AttributeListToDict(attribute_list):\n> +  if attribute_list is None:\n> +    return None\n> +  assert isinstance(attribute_list, ast.AttributeList)\n> +  # TODO(vtl): Check for duplicate keys here.\n> +  return dict(\n> +      [(attribute.key, attribute.value) for attribute in attribute_list])\n> +\n> +\n> +builtin_values = frozenset([\n> +    \"double.INFINITY\", \"double.NEGATIVE_INFINITY\", \"double.NAN\",\n> +    \"float.INFINITY\", \"float.NEGATIVE_INFINITY\", \"float.NAN\"\n> +])\n> +\n> +\n> +def _IsBuiltinValue(value):\n> +  return value in builtin_values\n> +\n> +\n> +def _LookupKind(kinds, spec, scope):\n> +  \"\"\"Tries to find which Kind a spec refers to, given the scope in which its\n> +  referenced. Starts checking from the narrowest scope to most general. For\n> +  example, given a struct field like\n> +    Foo.Bar x;\n> +  Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner\n> +  type 'Bar' in the struct 'Foo' in the current namespace.\n> +\n> +  |scope| is a tuple that looks like (namespace, struct/interface), referring\n> +  to the location where the type is referenced.\"\"\"\n> +  if spec.startswith('x:'):\n> +    mojom_name = spec[2:]\n> +    for i in range(len(scope), -1, -1):\n> +      test_spec = 'x:'\n> +      if i > 0:\n> +        test_spec += '.'.join(scope[:i]) + '.'\n> +      test_spec += mojom_name\n> +      kind = kinds.get(test_spec)\n> +      if kind:\n> +        return kind\n> +\n> +  return kinds.get(spec)\n> +\n> +\n> +def _GetScopeForKind(module, kind):\n> +  \"\"\"For a given kind, returns a tuple of progressively more specific names\n> +  used to qualify the kind. For example if kind is an enum named Bar nested in a\n> +  struct Foo within module 'foo', this would return ('foo', 'Foo', 'Bar')\"\"\"\n> +  if isinstance(kind, mojom.Enum) and kind.parent_kind:\n> +    # Enums may be nested in other kinds.\n> +    return _GetScopeForKind(module, kind.parent_kind) + (kind.mojom_name, )\n> +\n> +  module_fragment = (module.mojom_namespace, ) if module.mojom_namespace else ()\n> +  kind_fragment = (kind.mojom_name, ) if kind else ()\n> +  return module_fragment + kind_fragment\n> +\n> +\n> +def _LookupValueInScope(module, kind, identifier):\n> +  \"\"\"Given a kind and an identifier, this attempts to resolve the given\n> +  identifier to a concrete NamedValue within the scope of the given kind.\"\"\"\n> +  scope = _GetScopeForKind(module, kind)\n> +  for i in reversed(range(len(scope) + 1)):\n> +    qualified_name = '.'.join(scope[:i] + (identifier, ))\n> +    value = module.values.get(qualified_name)\n> +    if value:\n> +      return value\n> +  return None\n> +\n> +\n> +def _LookupValue(module, parent_kind, implied_kind, ast_leaf_node):\n> +  \"\"\"Resolves a leaf node in the form ('IDENTIFIER', 'x') to a constant value\n> +  identified by 'x' in some mojom definition. parent_kind is used as context\n> +  when resolving the identifier. If the given leaf node is not an IDENTIFIER\n> +  (e.g. already a constant value), it is returned as-is.\n> +\n> +  If implied_kind is provided, the parsed identifier may also be resolved within\n> +  its scope as fallback. This can be useful for more concise value references\n> +  when assigning enum-typed constants or field values.\"\"\"\n> +  if not isinstance(ast_leaf_node, tuple) or ast_leaf_node[0] != 'IDENTIFIER':\n> +    return ast_leaf_node\n> +\n> +  # First look for a known user-defined identifier to resolve this within the\n> +  # enclosing scope.\n> +  identifier = ast_leaf_node[1]\n> +\n> +  value = _LookupValueInScope(module, parent_kind, identifier)\n> +  if value:\n> +    return value\n> +\n> +  # Next look in the scope of implied_kind, if provided.\n> +  value = (implied_kind and implied_kind.module and _LookupValueInScope(\n> +      implied_kind.module, implied_kind, identifier))\n> +  if value:\n> +    return value\n> +\n> +  # Fall back on defined builtin symbols\n> +  if _IsBuiltinValue(identifier):\n> +    return mojom.BuiltinValue(identifier)\n> +\n> +  raise ValueError('Unknown identifier %s' % identifier)\n> +\n> +\n> +def _Kind(kinds, spec, scope):\n> +  \"\"\"Convert a type name into a mojom.Kind object.\n> +\n> +  As a side-effect this function adds the result to 'kinds'.\n> +\n> +  Args:\n> +    kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by\n> +        their names.\n> +    spec: {str} A name uniquely identifying a type.\n> +    scope: {Tuple[str, str]} A tuple that looks like (namespace,\n> +        struct/interface), referring to the location where the type is\n> +        referenced.\n> +\n> +  Returns:\n> +    {mojom.Kind} The type corresponding to 'spec'.\n> +  \"\"\"\n> +  kind = _LookupKind(kinds, spec, scope)\n> +  if kind:\n> +    return kind\n> +\n> +  if spec.startswith('?'):\n> +    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()\n> +  elif spec.startswith('a:'):\n> +    kind = mojom.Array(_Kind(kinds, spec[2:], scope))\n> +  elif spec.startswith('asso:'):\n> +    inner_kind = _Kind(kinds, spec[5:], scope)\n> +    if isinstance(inner_kind, mojom.InterfaceRequest):\n> +      kind = mojom.AssociatedInterfaceRequest(inner_kind)\n> +    else:\n> +      kind = mojom.AssociatedInterface(inner_kind)\n> +  elif spec.startswith('a'):\n> +    colon = spec.find(':')\n> +    length = int(spec[1:colon])\n> +    kind = mojom.Array(_Kind(kinds, spec[colon + 1:], scope), length)\n> +  elif spec.startswith('r:'):\n> +    kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope))\n> +  elif spec.startswith('rmt:'):\n> +    kind = mojom.PendingRemote(_Kind(kinds, spec[4:], scope))\n> +  elif spec.startswith('rcv:'):\n> +    kind = mojom.PendingReceiver(_Kind(kinds, spec[4:], scope))\n> +  elif spec.startswith('rma:'):\n> +    kind = mojom.PendingAssociatedRemote(_Kind(kinds, spec[4:], scope))\n> +  elif spec.startswith('rca:'):\n> +    kind = mojom.PendingAssociatedReceiver(_Kind(kinds, spec[4:], scope))\n> +  elif spec.startswith('m['):\n> +    # Isolate the two types from their brackets.\n> +\n> +    # It is not allowed to use map as key, so there shouldn't be nested ']'s\n> +    # inside the key type spec.\n> +    key_end = spec.find(']')\n> +    assert key_end != -1 and key_end < len(spec) - 1\n> +    assert spec[key_end + 1] == '[' and spec[-1] == ']'\n> +\n> +    first_kind = spec[2:key_end]\n> +    second_kind = spec[key_end + 2:-1]\n> +\n> +    kind = mojom.Map(\n> +        _Kind(kinds, first_kind, scope), _Kind(kinds, second_kind, scope))\n> +  else:\n> +    kind = mojom.Kind(spec)\n> +\n> +  kinds[spec] = kind\n> +  return kind\n> +\n> +\n> +def _Import(module, import_module):\n> +  # Copy the struct kinds from our imports into the current module.\n> +  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)\n> +  for kind in import_module.kinds.values():\n> +    if (isinstance(kind, importable_kinds)\n> +        and kind.module.path == import_module.path):\n> +      module.kinds[kind.spec] = kind\n> +  # Ditto for values.\n> +  for value in import_module.values.values():\n> +    if value.module.path == import_module.path:\n> +      module.values[value.GetSpec()] = value\n> +\n> +  return import_module\n> +\n> +\n> +def _Struct(module, parsed_struct):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_struct: {ast.Struct} Parsed struct.\n> +\n> +  Returns:\n> +    {mojom.Struct} AST struct.\n> +  \"\"\"\n> +  struct = mojom.Struct(module=module)\n> +  struct.mojom_name = parsed_struct.mojom_name\n> +  struct.native_only = parsed_struct.body is None\n> +  struct.spec = 'x:' + module.GetNamespacePrefix() + struct.mojom_name\n> +  module.kinds[struct.spec] = struct\n> +  struct.enums = []\n> +  struct.constants = []\n> +  struct.fields_data = []\n> +  if not struct.native_only:\n> +    _ProcessElements(\n> +        parsed_struct.mojom_name, parsed_struct.body, {\n> +            ast.Enum:\n> +            lambda enum: struct.enums.append(_Enum(module, enum, struct)),\n> +            ast.Const:\n> +            lambda const: struct.constants.append(\n> +                _Constant(module, const, struct)),\n> +            ast.StructField:\n> +            struct.fields_data.append,\n> +        })\n> +\n> +  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)\n> +\n> +  # Enforce that a [Native] attribute is set to make native-only struct\n> +  # declarations more explicit.\n> +  if struct.native_only:\n> +    if not struct.attributes or not struct.attributes.get('Native', False):\n> +      raise Exception(\"Native-only struct declarations must include a \" +\n> +                      \"Native attribute.\")\n> +\n> +  if struct.attributes and struct.attributes.get('CustomSerializer', False):\n> +    struct.custom_serializer = True\n> +\n> +  return struct\n> +\n> +\n> +def _Union(module, parsed_union):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_union: {ast.Union} Parsed union.\n> +\n> +  Returns:\n> +    {mojom.Union} AST union.\n> +  \"\"\"\n> +  union = mojom.Union(module=module)\n> +  union.mojom_name = parsed_union.mojom_name\n> +  union.spec = 'x:' + module.GetNamespacePrefix() + union.mojom_name\n> +  module.kinds[union.spec] = union\n> +  # Stash fields parsed_union here temporarily.\n> +  union.fields_data = []\n> +  _ProcessElements(parsed_union.mojom_name, parsed_union.body,\n> +                   {ast.UnionField: union.fields_data.append})\n> +  union.attributes = _AttributeListToDict(parsed_union.attribute_list)\n> +  return union\n> +\n> +\n> +def _StructField(module, parsed_field, struct):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_field: {ast.StructField} Parsed struct field.\n> +    struct: {mojom.Struct} Struct this field belongs to.\n> +\n> +  Returns:\n> +    {mojom.StructField} AST struct field.\n> +  \"\"\"\n> +  field = mojom.StructField()\n> +  field.mojom_name = parsed_field.mojom_name\n> +  field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n> +                     (module.mojom_namespace, struct.mojom_name))\n> +  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n> +  field.default = _LookupValue(module, struct, field.kind,\n> +                               parsed_field.default_value)\n> +  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  return field\n> +\n> +\n> +def _UnionField(module, parsed_field, union):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_field: {ast.UnionField} Parsed union field.\n> +    union: {mojom.Union} Union this fields belong to.\n> +\n> +  Returns:\n> +    {mojom.UnionField} AST union.\n> +  \"\"\"\n> +  field = mojom.UnionField()\n> +  field.mojom_name = parsed_field.mojom_name\n> +  field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n> +                     (module.mojom_namespace, union.mojom_name))\n> +  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n> +  field.default = None\n> +  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  return field\n> +\n> +\n> +def _Parameter(module, parsed_param, interface):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_param: {ast.Parameter} Parsed parameter.\n> +    union: {mojom.Interface} Interface this parameter belongs to.\n> +\n> +  Returns:\n> +    {mojom.Parameter} AST parameter.\n> +  \"\"\"\n> +  parameter = mojom.Parameter()\n> +  parameter.mojom_name = parsed_param.mojom_name\n> +  parameter.kind = _Kind(module.kinds, _MapKind(parsed_param.typename),\n> +                         (module.mojom_namespace, interface.mojom_name))\n> +  parameter.ordinal = (parsed_param.ordinal.value\n> +                       if parsed_param.ordinal else None)\n> +  parameter.default = None  # TODO(tibell): We never have these. Remove field?\n> +  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)\n> +  return parameter\n> +\n> +\n> +def _Method(module, parsed_method, interface):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_method: {ast.Method} Parsed method.\n> +    interface: {mojom.Interface} Interface this method belongs to.\n> +\n> +  Returns:\n> +    {mojom.Method} AST method.\n> +  \"\"\"\n> +  method = mojom.Method(\n> +      interface,\n> +      parsed_method.mojom_name,\n> +      ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)\n> +  method.parameters = list(\n> +      map(lambda parameter: _Parameter(module, parameter, interface),\n> +          parsed_method.parameter_list))\n> +  if parsed_method.response_parameter_list is not None:\n> +    method.response_parameters = list(\n> +        map(lambda parameter: _Parameter(module, parameter, interface),\n> +            parsed_method.response_parameter_list))\n> +  method.attributes = _AttributeListToDict(parsed_method.attribute_list)\n> +\n> +  # Enforce that only methods with response can have a [Sync] attribute.\n> +  if method.sync and method.response_parameters is None:\n> +    raise Exception(\"Only methods with response can include a [Sync] \"\n> +                    \"attribute. If no response parameters are needed, you \"\n> +                    \"could use an empty response parameter list, i.e., \"\n> +                    \"\\\"=> ()\\\".\")\n> +\n> +  return method\n> +\n> +\n> +def _Interface(module, parsed_iface):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_iface: {ast.Interface} Parsed interface.\n> +\n> +  Returns:\n> +    {mojom.Interface} AST interface.\n> +  \"\"\"\n> +  interface = mojom.Interface(module=module)\n> +  interface.mojom_name = parsed_iface.mojom_name\n> +  interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name\n> +  module.kinds[interface.spec] = interface\n> +  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)\n> +  interface.enums = []\n> +  interface.constants = []\n> +  interface.methods_data = []\n> +  _ProcessElements(\n> +      parsed_iface.mojom_name, parsed_iface.body, {\n> +          ast.Enum:\n> +          lambda enum: interface.enums.append(_Enum(module, enum, interface)),\n> +          ast.Const:\n> +          lambda const: interface.constants.append(\n> +              _Constant(module, const, interface)),\n> +          ast.Method:\n> +          interface.methods_data.append,\n> +      })\n> +  return interface\n> +\n> +\n> +def _EnumField(module, enum, parsed_field):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    enum: {mojom.Enum} Enum this field belongs to.\n> +    parsed_field: {ast.EnumValue} Parsed enum value.\n> +\n> +  Returns:\n> +    {mojom.EnumField} AST enum field.\n> +  \"\"\"\n> +  field = mojom.EnumField()\n> +  field.mojom_name = parsed_field.mojom_name\n> +  field.value = _LookupValue(module, enum, None, parsed_field.value)\n> +  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  value = mojom.EnumValue(module, enum, field)\n> +  module.values[value.GetSpec()] = value\n> +  return field\n> +\n> +\n> +def _ResolveNumericEnumValues(enum):\n> +  \"\"\"\n> +  Given a reference to a mojom.Enum, resolves and assigns the numeric value of\n> +  each field, and also computes the min_value and max_value of the enum.\n> +  \"\"\"\n> +\n> +  # map of <mojom_name> -> integral value\n> +  prev_value = -1\n> +  min_value = None\n> +  max_value = None\n> +  for field in enum.fields:\n> +    # This enum value is +1 the previous enum value (e.g: BEGIN).\n> +    if field.value is None:\n> +      prev_value += 1\n> +\n> +    # Integral value (e.g: BEGIN = -0x1).\n> +    elif _IsStrOrUnicode(field.value):\n> +      prev_value = int(field.value, 0)\n> +\n> +    # Reference to a previous enum value (e.g: INIT = BEGIN).\n> +    elif isinstance(field.value, mojom.EnumValue):\n> +      prev_value = field.value.field.numeric_value\n> +    elif isinstance(field.value, mojom.ConstantValue):\n> +      constant = field.value.constant\n> +      kind = constant.kind\n> +      if not mojom.IsIntegralKind(kind) or mojom.IsBoolKind(kind):\n> +        raise ValueError('Enum values must be integers. %s is not an integer.' %\n> +                         constant.mojom_name)\n> +      prev_value = int(constant.value, 0)\n> +    else:\n> +      raise Exception('Unresolved enum value for %s' % field.value.GetSpec())\n> +\n> +    #resolved_enum_values[field.mojom_name] = prev_value\n> +    field.numeric_value = prev_value\n> +    if min_value is None or prev_value < min_value:\n> +      min_value = prev_value\n> +    if max_value is None or prev_value > max_value:\n> +      max_value = prev_value\n> +\n> +  enum.min_value = min_value\n> +  enum.max_value = max_value\n> +\n> +\n> +def _Enum(module, parsed_enum, parent_kind):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_enum: {ast.Enum} Parsed enum.\n> +\n> +  Returns:\n> +    {mojom.Enum} AST enum.\n> +  \"\"\"\n> +  enum = mojom.Enum(module=module)\n> +  enum.mojom_name = parsed_enum.mojom_name\n> +  enum.native_only = parsed_enum.enum_value_list is None\n> +  mojom_name = enum.mojom_name\n> +  if parent_kind:\n> +    mojom_name = parent_kind.mojom_name + '.' + mojom_name\n> +  enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)\n> +  enum.parent_kind = parent_kind\n> +  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)\n> +\n> +  if not enum.native_only:\n> +    enum.fields = list(\n> +        map(lambda field: _EnumField(module, enum, field),\n> +            parsed_enum.enum_value_list))\n> +    _ResolveNumericEnumValues(enum)\n> +\n> +  module.kinds[enum.spec] = enum\n> +\n> +  # Enforce that a [Native] attribute is set to make native-only enum\n> +  # declarations more explicit.\n> +  if enum.native_only:\n> +    if not enum.attributes or not enum.attributes.get('Native', False):\n> +      raise Exception(\"Native-only enum declarations must include a \" +\n> +                      \"Native attribute.\")\n> +\n> +  return enum\n> +\n> +\n> +def _Constant(module, parsed_const, parent_kind):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_const: {ast.Const} Parsed constant.\n> +\n> +  Returns:\n> +    {mojom.Constant} AST constant.\n> +  \"\"\"\n> +  constant = mojom.Constant()\n> +  constant.mojom_name = parsed_const.mojom_name\n> +  if parent_kind:\n> +    scope = (module.mojom_namespace, parent_kind.mojom_name)\n> +  else:\n> +    scope = (module.mojom_namespace, )\n> +  # TODO(mpcomplete): maybe we should only support POD kinds.\n> +  constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)\n> +  constant.parent_kind = parent_kind\n> +  constant.value = _LookupValue(module, parent_kind, constant.kind,\n> +                                parsed_const.value)\n> +\n> +  # Iteratively resolve this constant reference to a concrete value\n> +  while isinstance(constant.value, mojom.ConstantValue):\n> +    constant.value = constant.value.constant.value\n> +\n> +  value = mojom.ConstantValue(module, parent_kind, constant)\n> +  module.values[value.GetSpec()] = value\n> +  return constant\n> +\n> +\n> +def _CollectReferencedKinds(module, all_defined_kinds):\n> +  \"\"\"\n> +  Takes a {mojom.Module} object and a list of all defined kinds within that\n> +  module, and enumerates the complete dict of user-defined mojom types\n> +  (as {mojom.Kind} objects) referenced by the module's own defined kinds (i.e.\n> +  as types of struct or union or interface parameters. The returned dict is\n> +  keyed by kind spec.\n> +  \"\"\"\n> +\n> +  def extract_referenced_user_kinds(kind):\n> +    if mojom.IsArrayKind(kind):\n> +      return extract_referenced_user_kinds(kind.kind)\n> +    if mojom.IsMapKind(kind):\n> +      return (extract_referenced_user_kinds(kind.key_kind) +\n> +              extract_referenced_user_kinds(kind.value_kind))\n> +    if mojom.IsInterfaceRequestKind(kind) or mojom.IsAssociatedKind(kind):\n> +      return [kind.kind]\n> +    if mojom.IsStructKind(kind):\n> +      return [kind]\n> +    if (mojom.IsInterfaceKind(kind) or mojom.IsEnumKind(kind)\n> +        or mojom.IsUnionKind(kind)):\n> +      return [kind]\n> +    return []\n> +\n> +  def sanitize_kind(kind):\n> +    \"\"\"Removes nullability from a kind\"\"\"\n> +    if kind.spec.startswith('?'):\n> +      return _Kind(module.kinds, kind.spec[1:], (module.mojom_namespace, ''))\n> +    return kind\n> +\n> +  referenced_user_kinds = {}\n> +  for defined_kind in all_defined_kinds:\n> +    if mojom.IsStructKind(defined_kind) or mojom.IsUnionKind(defined_kind):\n> +      for field in defined_kind.fields:\n> +        for referenced_kind in extract_referenced_user_kinds(field.kind):\n> +          sanitized_kind = sanitize_kind(referenced_kind)\n> +          referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n> +\n> +  # Also scan for references in parameter lists\n> +  for interface in module.interfaces:\n> +    for method in interface.methods:\n> +      for param in itertools.chain(method.parameters or [],\n> +                                   method.response_parameters or []):\n> +        if (mojom.IsStructKind(param.kind) or mojom.IsUnionKind(param.kind)\n> +            or mojom.IsEnumKind(param.kind)\n> +            or mojom.IsAnyInterfaceKind(param.kind)):\n> +          for referenced_kind in extract_referenced_user_kinds(param.kind):\n> +            sanitized_kind = sanitize_kind(referenced_kind)\n> +            referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n> +\n> +  return referenced_user_kinds\n> +\n> +\n> +def _AssignDefaultOrdinals(items):\n> +  \"\"\"Assigns default ordinal values to a sequence of items if necessary.\"\"\"\n> +  next_ordinal = 0\n> +  for item in items:\n> +    if item.ordinal is not None:\n> +      next_ordinal = item.ordinal + 1\n> +    else:\n> +      item.ordinal = next_ordinal\n> +      next_ordinal += 1\n> +\n> +\n> +def _AssertTypeIsStable(kind):\n> +  \"\"\"Raises an error if a type is not stable, meaning it is composed of at least\n> +  one type that is not marked [Stable].\"\"\"\n> +\n> +  def assertDependencyIsStable(dependency):\n> +    if (mojom.IsEnumKind(dependency) or mojom.IsStructKind(dependency)\n> +        or mojom.IsUnionKind(dependency) or mojom.IsInterfaceKind(dependency)):\n> +      if not dependency.stable:\n> +        raise Exception(\n> +            '%s is marked [Stable] but cannot be stable because it depends on '\n> +            '%s, which is not marked [Stable].' %\n> +            (kind.mojom_name, dependency.mojom_name))\n> +    elif mojom.IsArrayKind(dependency) or mojom.IsAnyInterfaceKind(dependency):\n> +      assertDependencyIsStable(dependency.kind)\n> +    elif mojom.IsMapKind(dependency):\n> +      assertDependencyIsStable(dependency.key_kind)\n> +      assertDependencyIsStable(dependency.value_kind)\n> +\n> +  if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):\n> +    for field in kind.fields:\n> +      assertDependencyIsStable(field.kind)\n> +  elif mojom.IsInterfaceKind(kind):\n> +    for method in kind.methods:\n> +      for param in method.param_struct.fields:\n> +        assertDependencyIsStable(param.kind)\n> +      if method.response_param_struct:\n> +        for response_param in method.response_param_struct.fields:\n> +          assertDependencyIsStable(response_param.kind)\n> +\n> +\n> +def _Module(tree, path, imports):\n> +  \"\"\"\n> +  Args:\n> +    tree: {ast.Mojom} The parse tree.\n> +    path: {str} The path to the mojom file.\n> +    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in\n> +        the import list, to already processed modules. Used to process imports.\n> +\n> +  Returns:\n> +    {mojom.Module} An AST for the mojom.\n> +  \"\"\"\n> +  module = mojom.Module(path=path)\n> +  module.kinds = {}\n> +  for kind in mojom.PRIMITIVES:\n> +    module.kinds[kind.spec] = kind\n> +\n> +  module.values = {}\n> +\n> +  module.mojom_namespace = tree.module.mojom_namespace[1] if tree.module else ''\n> +  # Imports must come first, because they add to module.kinds which is used\n> +  # by by the others.\n> +  module.imports = [\n> +      _Import(module, imports[imp.import_filename]) for imp in tree.import_list\n> +  ]\n> +  if tree.module and tree.module.attribute_list:\n> +    assert isinstance(tree.module.attribute_list, ast.AttributeList)\n> +    # TODO(vtl): Check for duplicate keys here.\n> +    module.attributes = dict((attribute.key, attribute.value)\n> +                             for attribute in tree.module.attribute_list)\n> +\n> +  filename = os.path.basename(path)\n> +  # First pass collects kinds.\n> +  module.constants = []\n> +  module.enums = []\n> +  module.structs = []\n> +  module.unions = []\n> +  module.interfaces = []\n> +  _ProcessElements(\n> +      filename, tree.definition_list, {\n> +          ast.Const:\n> +          lambda const: module.constants.append(_Constant(module, const, None)),\n> +          ast.Enum:\n> +          lambda enum: module.enums.append(_Enum(module, enum, None)),\n> +          ast.Struct:\n> +          lambda struct: module.structs.append(_Struct(module, struct)),\n> +          ast.Union:\n> +          lambda union: module.unions.append(_Union(module, union)),\n> +          ast.Interface:\n> +          lambda interface: module.interfaces.append(\n> +              _Interface(module, interface)),\n> +      })\n> +\n> +  # Second pass expands fields and methods. This allows fields and parameters\n> +  # to refer to kinds defined anywhere in the mojom.\n> +  all_defined_kinds = {}\n> +  for struct in module.structs:\n> +    struct.fields = list(\n> +        map(lambda field: _StructField(module, field, struct),\n> +            struct.fields_data))\n> +    _AssignDefaultOrdinals(struct.fields)\n> +    del struct.fields_data\n> +    all_defined_kinds[struct.spec] = struct\n> +    for enum in struct.enums:\n> +      all_defined_kinds[enum.spec] = enum\n> +\n> +  for union in module.unions:\n> +    union.fields = list(\n> +        map(lambda field: _UnionField(module, field, union), union.fields_data))\n> +    _AssignDefaultOrdinals(union.fields)\n> +    del union.fields_data\n> +    all_defined_kinds[union.spec] = union\n> +\n> +  for interface in module.interfaces:\n> +    interface.methods = list(\n> +        map(lambda method: _Method(module, method, interface),\n> +            interface.methods_data))\n> +    _AssignDefaultOrdinals(interface.methods)\n> +    del interface.methods_data\n> +    all_defined_kinds[interface.spec] = interface\n> +    for enum in interface.enums:\n> +      all_defined_kinds[enum.spec] = enum\n> +  for enum in module.enums:\n> +    all_defined_kinds[enum.spec] = enum\n> +\n> +  all_referenced_kinds = _CollectReferencedKinds(module,\n> +                                                 all_defined_kinds.values())\n> +  imported_kind_specs = set(all_referenced_kinds.keys()).difference(\n> +      set(all_defined_kinds.keys()))\n> +  module.imported_kinds = dict(\n> +      (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)\n> +\n> +  generator.AddComputedData(module)\n> +  for iface in module.interfaces:\n> +    for method in iface.methods:\n> +      if method.param_struct:\n> +        _AssignDefaultOrdinals(method.param_struct.fields)\n> +      if method.response_param_struct:\n> +        _AssignDefaultOrdinals(method.response_param_struct.fields)\n> +\n> +  # Ensure that all types marked [Stable] are actually stable. Enums are\n> +  # automatically OK since they don't depend on other definitions.\n> +  for kinds in (module.structs, module.unions, module.interfaces):\n> +    for kind in kinds:\n> +      if kind.stable:\n> +        _AssertTypeIsStable(kind)\n> +\n> +  return module\n> +\n> +\n> +def OrderedModule(tree, path, imports):\n> +  \"\"\"Convert parse tree to AST module.\n> +\n> +  Args:\n> +    tree: {ast.Mojom} The parse tree.\n> +    path: {str} The path to the mojom file.\n> +    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in\n> +        the import list, to already processed modules. Used to process imports.\n> +\n> +  Returns:\n> +    {mojom.Module} An AST for the mojom.\n> +  \"\"\"\n> +  module = _Module(tree, path, imports)\n> +  return module\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> new file mode 100644\n> index 00000000..19905c8a\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> @@ -0,0 +1,73 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +import unittest\n> +\n> +from mojom.generate import module as mojom\n> +from mojom.generate import translate\n> +from mojom.parse import ast\n> +\n> +\n> +class TranslateTest(unittest.TestCase):\n> +  \"\"\"Tests |parser.Parse()|.\"\"\"\n> +\n> +  def testSimpleArray(self):\n> +    \"\"\"Tests a simple int32[].\"\"\"\n> +    # pylint: disable=W0212\n> +    self.assertEquals(translate._MapKind(\"int32[]\"), \"a:i32\")\n> +\n> +  def testAssociativeArray(self):\n> +    \"\"\"Tests a simple uint8{string}.\"\"\"\n> +    # pylint: disable=W0212\n> +    self.assertEquals(translate._MapKind(\"uint8{string}\"), \"m[s][u8]\")\n> +\n> +  def testLeftToRightAssociativeArray(self):\n> +    \"\"\"Makes sure that parsing is done from right to left on the internal kinds\n> +       in the presence of an associative array.\"\"\"\n> +    # pylint: disable=W0212\n> +    self.assertEquals(translate._MapKind(\"uint8[]{string}\"), \"m[s][a:u8]\")\n> +\n> +  def testTranslateSimpleUnions(self):\n> +    \"\"\"Makes sure that a simple union is translated correctly.\"\"\"\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\n> +            \"SomeUnion\", None,\n> +            ast.UnionBody([\n> +                ast.UnionField(\"a\", None, None, \"int32\"),\n> +                ast.UnionField(\"b\", None, None, \"string\")\n> +            ]))\n> +    ])\n> +\n> +    translation = translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertEqual(1, len(translation.unions))\n> +\n> +    union = translation.unions[0]\n> +    self.assertTrue(isinstance(union, mojom.Union))\n> +    self.assertEqual(\"SomeUnion\", union.mojom_name)\n> +    self.assertEqual(2, len(union.fields))\n> +    self.assertEqual(\"a\", union.fields[0].mojom_name)\n> +    self.assertEqual(mojom.INT32.spec, union.fields[0].kind.spec)\n> +    self.assertEqual(\"b\", union.fields[1].mojom_name)\n> +    self.assertEqual(mojom.STRING.spec, union.fields[1].kind.spec)\n> +\n> +  def testMapKindRaisesWithDuplicate(self):\n> +    \"\"\"Verifies _MapTreeForType() raises when passed two values with the same\n> +       name.\"\"\"\n> +    methods = [\n> +        ast.Method('dup', None, None, ast.ParameterList(), None),\n> +        ast.Method('dup', None, None, ast.ParameterList(), None)\n> +    ]\n> +    with self.assertRaises(Exception):\n> +      translate._ElemsOfType(methods, ast.Method, 'scope')\n> +\n> +  def testAssociatedKinds(self):\n> +    \"\"\"Tests type spec translation of associated interfaces and requests.\"\"\"\n> +    # pylint: disable=W0212\n> +    self.assertEquals(\n> +        translate._MapKind(\"asso<SomeInterface>?\"), \"?asso:x:SomeInterface\")\n> +    self.assertEquals(\n> +        translate._MapKind(\"asso<SomeInterface&>?\"), \"?asso:r:x:SomeInterface\")\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py\n> new file mode 100644\n> index 00000000..e69de29b\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> new file mode 100644\n> index 00000000..1f0db200\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> @@ -0,0 +1,427 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Node classes for the AST for a Mojo IDL file.\"\"\"\n> +\n> +# Note: For convenience of testing, you probably want to define __eq__() methods\n> +# for all node types; it's okay to be slightly lax (e.g., not compare filename\n> +# and lineno). You may also define __repr__() to help with analyzing test\n> +# failures, especially for more complex types.\n> +\n> +\n> +import sys\n> +\n> +\n> +def _IsStrOrUnicode(x):\n> +  if sys.version_info[0] < 3:\n> +    return isinstance(x, (unicode, str))\n> +  return isinstance(x, str)\n> +\n> +\n> +class NodeBase(object):\n> +  \"\"\"Base class for nodes in the AST.\"\"\"\n> +\n> +  def __init__(self, filename=None, lineno=None):\n> +    self.filename = filename\n> +    self.lineno = lineno\n> +\n> +  def __eq__(self, other):\n> +    # We want strict comparison of the two object's types. Disable pylint's\n> +    # insistence upon recommending isinstance().\n> +    # pylint: disable=unidiomatic-typecheck\n> +    return type(self) == type(other)\n> +\n> +  # Make != the inverse of ==. (Subclasses shouldn't have to override this.)\n> +  def __ne__(self, other):\n> +    return not self == other\n> +\n> +\n> +# TODO(vtl): Some of this is complicated enough that it should be tested.\n> +class NodeListBase(NodeBase):\n> +  \"\"\"Represents a list of other nodes, all having the same type. (This is meant\n> +  to be subclassed, with subclasses defining _list_item_type to be the class (or\n> +  classes, in a tuple) of the members of the list.)\"\"\"\n> +\n> +  def __init__(self, item_or_items=None, **kwargs):\n> +    super(NodeListBase, self).__init__(**kwargs)\n> +    self.items = []\n> +    if item_or_items is None:\n> +      pass\n> +    elif isinstance(item_or_items, list):\n> +      for item in item_or_items:\n> +        assert isinstance(item, self._list_item_type)\n> +        self.Append(item)\n> +    else:\n> +      assert isinstance(item_or_items, self._list_item_type)\n> +      self.Append(item_or_items)\n> +\n> +  # Support iteration. For everything else, users should just access |items|\n> +  # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so\n> +  # |bool(NodeListBase())| is true.)\n> +  def __iter__(self):\n> +    return self.items.__iter__()\n> +\n> +  def __eq__(self, other):\n> +    return super(NodeListBase, self).__eq__(other) and \\\n> +           self.items == other.items\n> +\n> +  # Implement this so that on failure, we get slightly more sensible output.\n> +  def __repr__(self):\n> +    return self.__class__.__name__ + \"([\" + \\\n> +           \", \".join([repr(elem) for elem in self.items]) + \"])\"\n> +\n> +  def Insert(self, item):\n> +    \"\"\"Inserts item at the front of the list.\"\"\"\n> +\n> +    assert isinstance(item, self._list_item_type)\n> +    self.items.insert(0, item)\n> +    self._UpdateFilenameAndLineno()\n> +\n> +  def Append(self, item):\n> +    \"\"\"Appends item to the end of the list.\"\"\"\n> +\n> +    assert isinstance(item, self._list_item_type)\n> +    self.items.append(item)\n> +    self._UpdateFilenameAndLineno()\n> +\n> +  def _UpdateFilenameAndLineno(self):\n> +    if self.items:\n> +      self.filename = self.items[0].filename\n> +      self.lineno = self.items[0].lineno\n> +\n> +\n> +class Definition(NodeBase):\n> +  \"\"\"Represents a definition of anything that has a global name (e.g., enums,\n> +  enum values, consts, structs, struct fields, interfaces). (This does not\n> +  include parameter definitions.) This class is meant to be subclassed.\"\"\"\n> +\n> +  def __init__(self, mojom_name, **kwargs):\n> +    assert _IsStrOrUnicode(mojom_name)\n> +    NodeBase.__init__(self, **kwargs)\n> +    self.mojom_name = mojom_name\n> +\n> +\n> +################################################################################\n> +\n> +\n> +class Attribute(NodeBase):\n> +  \"\"\"Represents an attribute.\"\"\"\n> +\n> +  def __init__(self, key, value, **kwargs):\n> +    assert _IsStrOrUnicode(key)\n> +    super(Attribute, self).__init__(**kwargs)\n> +    self.key = key\n> +    self.value = value\n> +\n> +  def __eq__(self, other):\n> +    return super(Attribute, self).__eq__(other) and \\\n> +           self.key == other.key and \\\n> +           self.value == other.value\n> +\n> +\n> +class AttributeList(NodeListBase):\n> +  \"\"\"Represents a list attributes.\"\"\"\n> +\n> +  _list_item_type = Attribute\n> +\n> +\n> +class Const(Definition):\n> +  \"\"\"Represents a const definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    # The typename is currently passed through as a string.\n> +    assert _IsStrOrUnicode(typename)\n> +    # The value is either a literal (currently passed through as a string) or a\n> +    # \"wrapped identifier\".\n> +    assert _IsStrOrUnicode or isinstance(value, tuple)\n> +    super(Const, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.typename = typename\n> +    self.value = value\n> +\n> +  def __eq__(self, other):\n> +    return super(Const, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.typename == other.typename and \\\n> +           self.value == other.value\n> +\n> +\n> +class Enum(Definition):\n> +  \"\"\"Represents an enum definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)\n> +    super(Enum, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.enum_value_list = enum_value_list\n> +\n> +  def __eq__(self, other):\n> +    return super(Enum, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.enum_value_list == other.enum_value_list\n> +\n> +\n> +class EnumValue(Definition):\n> +  \"\"\"Represents a definition of an enum value.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, value, **kwargs):\n> +    # The optional value is either an int (which is current a string) or a\n> +    # \"wrapped identifier\".\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)\n> +    super(EnumValue, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.value = value\n> +\n> +  def __eq__(self, other):\n> +    return super(EnumValue, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.value == other.value\n> +\n> +\n> +class EnumValueList(NodeListBase):\n> +  \"\"\"Represents a list of enum value definitions (i.e., the \"body\" of an enum\n> +  definition).\"\"\"\n> +\n> +  _list_item_type = EnumValue\n> +\n> +\n> +class Import(NodeBase):\n> +  \"\"\"Represents an import statement.\"\"\"\n> +\n> +  def __init__(self, attribute_list, import_filename, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert _IsStrOrUnicode(import_filename)\n> +    super(Import, self).__init__(**kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.import_filename = import_filename\n> +\n> +  def __eq__(self, other):\n> +    return super(Import, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.import_filename == other.import_filename\n> +\n> +\n> +class ImportList(NodeListBase):\n> +  \"\"\"Represents a list (i.e., sequence) of import statements.\"\"\"\n> +\n> +  _list_item_type = Import\n> +\n> +\n> +class Interface(Definition):\n> +  \"\"\"Represents an interface definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, body, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert isinstance(body, InterfaceBody)\n> +    super(Interface, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.body = body\n> +\n> +  def __eq__(self, other):\n> +    return super(Interface, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.body == other.body\n> +\n> +\n> +class Method(Definition):\n> +  \"\"\"Represents a method definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, ordinal, parameter_list,\n> +               response_parameter_list, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert ordinal is None or isinstance(ordinal, Ordinal)\n> +    assert isinstance(parameter_list, ParameterList)\n> +    assert response_parameter_list is None or \\\n> +           isinstance(response_parameter_list, ParameterList)\n> +    super(Method, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.ordinal = ordinal\n> +    self.parameter_list = parameter_list\n> +    self.response_parameter_list = response_parameter_list\n> +\n> +  def __eq__(self, other):\n> +    return super(Method, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.ordinal == other.ordinal and \\\n> +           self.parameter_list == other.parameter_list and \\\n> +           self.response_parameter_list == other.response_parameter_list\n> +\n> +\n> +# This needs to be declared after |Method|.\n> +class InterfaceBody(NodeListBase):\n> +  \"\"\"Represents the body of (i.e., list of definitions inside) an interface.\"\"\"\n> +\n> +  _list_item_type = (Const, Enum, Method)\n> +\n> +\n> +class Module(NodeBase):\n> +  \"\"\"Represents a module statement.\"\"\"\n> +\n> +  def __init__(self, mojom_namespace, attribute_list, **kwargs):\n> +    # |mojom_namespace| is either none or a \"wrapped identifier\".\n> +    assert mojom_namespace is None or isinstance(mojom_namespace, tuple)\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    super(Module, self).__init__(**kwargs)\n> +    self.mojom_namespace = mojom_namespace\n> +    self.attribute_list = attribute_list\n> +\n> +  def __eq__(self, other):\n> +    return super(Module, self).__eq__(other) and \\\n> +           self.mojom_namespace == other.mojom_namespace and \\\n> +           self.attribute_list == other.attribute_list\n> +\n> +\n> +class Mojom(NodeBase):\n> +  \"\"\"Represents an entire .mojom file. (This is the root node.)\"\"\"\n> +\n> +  def __init__(self, module, import_list, definition_list, **kwargs):\n> +    assert module is None or isinstance(module, Module)\n> +    assert isinstance(import_list, ImportList)\n> +    assert isinstance(definition_list, list)\n> +    super(Mojom, self).__init__(**kwargs)\n> +    self.module = module\n> +    self.import_list = import_list\n> +    self.definition_list = definition_list\n> +\n> +  def __eq__(self, other):\n> +    return super(Mojom, self).__eq__(other) and \\\n> +           self.module == other.module and \\\n> +           self.import_list == other.import_list and \\\n> +           self.definition_list == other.definition_list\n> +\n> +  def __repr__(self):\n> +    return \"%s(%r, %r, %r)\" % (self.__class__.__name__, self.module,\n> +                               self.import_list, self.definition_list)\n> +\n> +\n> +class Ordinal(NodeBase):\n> +  \"\"\"Represents an ordinal value labeling, e.g., a struct field.\"\"\"\n> +\n> +  def __init__(self, value, **kwargs):\n> +    assert isinstance(value, int)\n> +    super(Ordinal, self).__init__(**kwargs)\n> +    self.value = value\n> +\n> +  def __eq__(self, other):\n> +    return super(Ordinal, self).__eq__(other) and \\\n> +           self.value == other.value\n> +\n> +\n> +class Parameter(NodeBase):\n> +  \"\"\"Represents a method request or response parameter.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> +    assert _IsStrOrUnicode(mojom_name)\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert ordinal is None or isinstance(ordinal, Ordinal)\n> +    assert _IsStrOrUnicode(typename)\n> +    super(Parameter, self).__init__(**kwargs)\n> +    self.mojom_name = mojom_name\n> +    self.attribute_list = attribute_list\n> +    self.ordinal = ordinal\n> +    self.typename = typename\n> +\n> +  def __eq__(self, other):\n> +    return super(Parameter, self).__eq__(other) and \\\n> +           self.mojom_name == other.mojom_name and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.ordinal == other.ordinal and \\\n> +           self.typename == other.typename\n> +\n> +\n> +class ParameterList(NodeListBase):\n> +  \"\"\"Represents a list of (method request or response) parameters.\"\"\"\n> +\n> +  _list_item_type = Parameter\n> +\n> +\n> +class Struct(Definition):\n> +  \"\"\"Represents a struct definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, body, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert isinstance(body, StructBody) or body is None\n> +    super(Struct, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.body = body\n> +\n> +  def __eq__(self, other):\n> +    return super(Struct, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.body == other.body\n> +\n> +\n> +class StructField(Definition):\n> +  \"\"\"Represents a struct field definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, ordinal, typename,\n> +               default_value, **kwargs):\n> +    assert _IsStrOrUnicode(mojom_name)\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert ordinal is None or isinstance(ordinal, Ordinal)\n> +    assert _IsStrOrUnicode(typename)\n> +    # The optional default value is currently either a value as a string or a\n> +    # \"wrapped identifier\".\n> +    assert default_value is None or _IsStrOrUnicode(default_value) or \\\n> +        isinstance(default_value, tuple)\n> +    super(StructField, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.ordinal = ordinal\n> +    self.typename = typename\n> +    self.default_value = default_value\n> +\n> +  def __eq__(self, other):\n> +    return super(StructField, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.ordinal == other.ordinal and \\\n> +           self.typename == other.typename and \\\n> +           self.default_value == other.default_value\n> +\n> +\n> +# This needs to be declared after |StructField|.\n> +class StructBody(NodeListBase):\n> +  \"\"\"Represents the body of (i.e., list of definitions inside) a struct.\"\"\"\n> +\n> +  _list_item_type = (Const, Enum, StructField)\n> +\n> +\n> +class Union(Definition):\n> +  \"\"\"Represents a union definition.\"\"\"\n> +\n> +  def __init__(self, mojom_name, attribute_list, body, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert isinstance(body, UnionBody)\n> +    super(Union, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.body = body\n> +\n> +  def __eq__(self, other):\n> +    return super(Union, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.body == other.body\n> +\n> +\n> +class UnionField(Definition):\n> +  def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> +    assert _IsStrOrUnicode(mojom_name)\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert ordinal is None or isinstance(ordinal, Ordinal)\n> +    assert _IsStrOrUnicode(typename)\n> +    super(UnionField, self).__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.ordinal = ordinal\n> +    self.typename = typename\n> +\n> +  def __eq__(self, other):\n> +    return super(UnionField, self).__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.ordinal == other.ordinal and \\\n> +           self.typename == other.typename\n> +\n> +\n> +class UnionBody(NodeListBase):\n> +\n> +  _list_item_type = UnionField\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> new file mode 100644\n> index 00000000..62798631\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> @@ -0,0 +1,121 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +import unittest\n> +\n> +from mojom.parse import ast\n> +\n> +\n> +class _TestNode(ast.NodeBase):\n> +  \"\"\"Node type for tests.\"\"\"\n> +\n> +  def __init__(self, value, **kwargs):\n> +    super(_TestNode, self).__init__(**kwargs)\n> +    self.value = value\n> +\n> +  def __eq__(self, other):\n> +    return super(_TestNode, self).__eq__(other) and self.value == other.value\n> +\n> +\n> +class _TestNodeList(ast.NodeListBase):\n> +  \"\"\"Node list type for tests.\"\"\"\n> +\n> +  _list_item_type = _TestNode\n> +\n> +\n> +class ASTTest(unittest.TestCase):\n> +  \"\"\"Tests various AST classes.\"\"\"\n> +\n> +  def testNodeBase(self):\n> +    # Test |__eq__()|; this is only used for testing, where we want to do\n> +    # comparison by value and ignore filenames/line numbers (for convenience).\n> +    node1 = ast.NodeBase(filename=\"hello.mojom\", lineno=123)\n> +    node2 = ast.NodeBase()\n> +    self.assertEquals(node1, node2)\n> +    self.assertEquals(node2, node1)\n> +\n> +    # Check that |__ne__()| just defers to |__eq__()| properly.\n> +    self.assertFalse(node1 != node2)\n> +    self.assertFalse(node2 != node1)\n> +\n> +    # Check that |filename| and |lineno| are set properly (and are None by\n> +    # default).\n> +    self.assertEquals(node1.filename, \"hello.mojom\")\n> +    self.assertEquals(node1.lineno, 123)\n> +    self.assertIsNone(node2.filename)\n> +    self.assertIsNone(node2.lineno)\n> +\n> +    # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()|\n> +    # should first defer to its superclass's).\n> +    node3 = _TestNode(123)\n> +    self.assertNotEqual(node1, node3)\n> +    self.assertNotEqual(node3, node1)\n> +    # Also test |__eq__()| directly.\n> +    self.assertFalse(node1 == node3)\n> +    self.assertFalse(node3 == node1)\n> +\n> +    node4 = _TestNode(123, filename=\"world.mojom\", lineno=123)\n> +    self.assertEquals(node4, node3)\n> +    node5 = _TestNode(456)\n> +    self.assertNotEquals(node5, node4)\n> +\n> +  def testNodeListBase(self):\n> +    node1 = _TestNode(1, filename=\"foo.mojom\", lineno=1)\n> +    # Equal to, but not the same as, |node1|:\n> +    node1b = _TestNode(1, filename=\"foo.mojom\", lineno=1)\n> +    node2 = _TestNode(2, filename=\"foo.mojom\", lineno=2)\n> +\n> +    nodelist1 = _TestNodeList()  # Contains: (empty).\n> +    self.assertEquals(nodelist1, nodelist1)\n> +    self.assertEquals(nodelist1.items, [])\n> +    self.assertIsNone(nodelist1.filename)\n> +    self.assertIsNone(nodelist1.lineno)\n> +\n> +    nodelist2 = _TestNodeList(node1)  # Contains: 1.\n> +    self.assertEquals(nodelist2, nodelist2)\n> +    self.assertEquals(nodelist2.items, [node1])\n> +    self.assertNotEqual(nodelist2, nodelist1)\n> +    self.assertEquals(nodelist2.filename, \"foo.mojom\")\n> +    self.assertEquals(nodelist2.lineno, 1)\n> +\n> +    nodelist3 = _TestNodeList([node2])  # Contains: 2.\n> +    self.assertEquals(nodelist3.items, [node2])\n> +    self.assertNotEqual(nodelist3, nodelist1)\n> +    self.assertNotEqual(nodelist3, nodelist2)\n> +    self.assertEquals(nodelist3.filename, \"foo.mojom\")\n> +    self.assertEquals(nodelist3.lineno, 2)\n> +\n> +    nodelist1.Append(node1b)  # Contains: 1.\n> +    self.assertEquals(nodelist1.items, [node1])\n> +    self.assertEquals(nodelist1, nodelist2)\n> +    self.assertNotEqual(nodelist1, nodelist3)\n> +    self.assertEquals(nodelist1.filename, \"foo.mojom\")\n> +    self.assertEquals(nodelist1.lineno, 1)\n> +\n> +    nodelist1.Append(node2)  # Contains: 1, 2.\n> +    self.assertEquals(nodelist1.items, [node1, node2])\n> +    self.assertNotEqual(nodelist1, nodelist2)\n> +    self.assertNotEqual(nodelist1, nodelist3)\n> +    self.assertEquals(nodelist1.lineno, 1)\n> +\n> +    nodelist2.Append(node2)  # Contains: 1, 2.\n> +    self.assertEquals(nodelist2.items, [node1, node2])\n> +    self.assertEquals(nodelist2, nodelist1)\n> +    self.assertNotEqual(nodelist2, nodelist3)\n> +    self.assertEquals(nodelist2.lineno, 1)\n> +\n> +    nodelist3.Insert(node1)  # Contains: 1, 2.\n> +    self.assertEquals(nodelist3.items, [node1, node2])\n> +    self.assertEquals(nodelist3, nodelist1)\n> +    self.assertEquals(nodelist3, nodelist2)\n> +    self.assertEquals(nodelist3.lineno, 1)\n> +\n> +    # Test iteration:\n> +    i = 1\n> +    for item in nodelist1:\n> +      self.assertEquals(item.value, i)\n> +      i += 1\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> new file mode 100644\n> index 00000000..3cb73c5d\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> @@ -0,0 +1,82 @@\n> +# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Helpers for processing conditionally enabled features in a mojom.\"\"\"\n> +\n> +from mojom.error import Error\n> +from mojom.parse import ast\n> +\n> +\n> +class EnableIfError(Error):\n> +  \"\"\" Class for errors from .\"\"\"\n> +\n> +  def __init__(self, filename, message, lineno=None):\n> +    Error.__init__(self, filename, message, lineno=lineno, addenda=None)\n> +\n> +\n> +def _IsEnabled(definition, enabled_features):\n> +  \"\"\"Returns true if a definition is enabled.\n> +\n> +  A definition is enabled if it has no EnableIf attribute, or if the value of\n> +  the EnableIf attribute is in enabled_features.\n> +  \"\"\"\n> +  if not hasattr(definition, \"attribute_list\"):\n> +    return True\n> +  if not definition.attribute_list:\n> +    return True\n> +\n> +  already_defined = False\n> +  for a in definition.attribute_list:\n> +    if a.key == 'EnableIf':\n> +      if already_defined:\n> +        raise EnableIfError(\n> +            definition.filename,\n> +            \"EnableIf attribute may only be defined once per field.\",\n> +            definition.lineno)\n> +      already_defined = True\n> +\n> +  for attribute in definition.attribute_list:\n> +    if attribute.key == 'EnableIf' and attribute.value not in enabled_features:\n> +      return False\n> +  return True\n> +\n> +\n> +def _FilterDisabledFromNodeList(node_list, enabled_features):\n> +  if not node_list:\n> +    return\n> +  assert isinstance(node_list, ast.NodeListBase)\n> +  node_list.items = [\n> +      item for item in node_list.items if _IsEnabled(item, enabled_features)\n> +  ]\n> +  for item in node_list.items:\n> +    _FilterDefinition(item, enabled_features)\n> +\n> +\n> +def _FilterDefinition(definition, enabled_features):\n> +  \"\"\"Filters definitions with a body.\"\"\"\n> +  if isinstance(definition, ast.Enum):\n> +    _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)\n> +  elif isinstance(definition, ast.Interface):\n> +    _FilterDisabledFromNodeList(definition.body, enabled_features)\n> +  elif isinstance(definition, ast.Method):\n> +    _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)\n> +    _FilterDisabledFromNodeList(definition.response_parameter_list,\n> +                                enabled_features)\n> +  elif isinstance(definition, ast.Struct):\n> +    _FilterDisabledFromNodeList(definition.body, enabled_features)\n> +  elif isinstance(definition, ast.Union):\n> +    _FilterDisabledFromNodeList(definition.body, enabled_features)\n> +\n> +\n> +def RemoveDisabledDefinitions(mojom, enabled_features):\n> +  \"\"\"Removes conditionally disabled definitions from a Mojom node.\"\"\"\n> +  mojom.import_list = ast.ImportList([\n> +      imported_file for imported_file in mojom.import_list\n> +      if _IsEnabled(imported_file, enabled_features)\n> +  ])\n> +  mojom.definition_list = [\n> +      definition for definition in mojom.definition_list\n> +      if _IsEnabled(definition, enabled_features)\n> +  ]\n> +  for definition in mojom.definition_list:\n> +    _FilterDefinition(definition, enabled_features)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> new file mode 100644\n> index 00000000..aa609be7\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> @@ -0,0 +1,233 @@\n> +# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os\n> +import sys\n> +import unittest\n> +\n> +\n> +def _GetDirAbove(dirname):\n> +  \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n> +  also be \"above\" this file).\"\"\"\n> +  path = os.path.abspath(__file__)\n> +  while True:\n> +    path, tail = os.path.split(path)\n> +    assert tail\n> +    if tail == dirname:\n> +      return path\n> +\n> +\n> +try:\n> +  imp.find_module('mojom')\n> +except ImportError:\n> +  sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))\n> +import mojom.parse.ast as ast\n> +import mojom.parse.conditional_features as conditional_features\n> +import mojom.parse.parser as parser\n> +\n> +ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})\n> +\n> +\n> +class ConditionalFeaturesTest(unittest.TestCase):\n> +  \"\"\"Tests |mojom.parse.conditional_features|.\"\"\"\n> +\n> +  def parseAndAssertEqual(self, source, expected_source):\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    conditional_features.RemoveDisabledDefinitions(definition, ENABLED_FEATURES)\n> +    expected = parser.Parse(expected_source, \"my_file.mojom\")\n> +    self.assertEquals(definition, expected)\n> +\n> +  def testFilterConst(self):\n> +    \"\"\"Test that Consts are correctly filtered.\"\"\"\n> +    const_source = \"\"\"\n> +      [EnableIf=blue]\n> +      const int kMyConst1 = 1;\n> +      [EnableIf=orange]\n> +      const double kMyConst2 = 2;\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIf=blue]\n> +      const int kMyConst1 = 1;\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(const_source, expected_source)\n> +\n> +  def testFilterEnum(self):\n> +    \"\"\"Test that EnumValues are correctly filtered from an Enum.\"\"\"\n> +    enum_source = \"\"\"\n> +      enum MyEnum {\n> +        [EnableIf=purple]\n> +        VALUE1,\n> +        [EnableIf=blue]\n> +        VALUE2,\n> +        VALUE3,\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      enum MyEnum {\n> +        [EnableIf=blue]\n> +        VALUE2,\n> +        VALUE3\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(enum_source, expected_source)\n> +\n> +  def testFilterImport(self):\n> +    \"\"\"Test that imports are correctly filtered from a Mojom.\"\"\"\n> +    import_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      import \"bar.mojom\";\n> +      [EnableIf=purple]\n> +      import \"baz.mojom\";\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      import \"bar.mojom\";\n> +    \"\"\"\n> +    self.parseAndAssertEqual(import_source, expected_source)\n> +\n> +  def testFilterInterface(self):\n> +    \"\"\"Test that definitions are correctly filtered from an Interface.\"\"\"\n> +    interface_source = \"\"\"\n> +      interface MyInterface {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          [EnableIf=purple]\n> +          VALUE1,\n> +          VALUE2,\n> +        };\n> +        [EnableIf=blue]\n> +        const int32 kMyConst = 123;\n> +        [EnableIf=purple]\n> +        MyMethod();\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      interface MyInterface {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE2,\n> +        };\n> +        [EnableIf=blue]\n> +        const int32 kMyConst = 123;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(interface_source, expected_source)\n> +\n> +  def testFilterMethod(self):\n> +    \"\"\"Test that Parameters are correctly filtered from a Method.\"\"\"\n> +    method_source = \"\"\"\n> +      interface MyInterface {\n> +        [EnableIf=blue]\n> +        MyMethod([EnableIf=purple] int32 a) => ([EnableIf=red] int32 b);\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      interface MyInterface {\n> +        [EnableIf=blue]\n> +        MyMethod() => ([EnableIf=red] int32 b);\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(method_source, expected_source)\n> +\n> +  def testFilterStruct(self):\n> +    \"\"\"Test that definitions are correctly filtered from a Struct.\"\"\"\n> +    struct_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +          [EnableIf=purple]\n> +          VALUE2,\n> +        };\n> +        [EnableIf=yellow]\n> +        const double kMyConst = 1.23;\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIf=purple]\n> +        int32 c;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +        [EnableIf=orange]\n> +        double f;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +        };\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(struct_source, expected_source)\n> +\n> +  def testFilterUnion(self):\n> +    \"\"\"Test that UnionFields are correctly filtered from a Union.\"\"\"\n> +    union_source = \"\"\"\n> +      union MyUnion {\n> +        [EnableIf=yellow]\n> +        int32 a;\n> +        [EnableIf=red]\n> +        bool b;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      union MyUnion {\n> +        [EnableIf=red]\n> +        bool b;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(union_source, expected_source)\n> +\n> +  def testSameNameFields(self):\n> +    mojom_source = \"\"\"\n> +      enum Foo {\n> +        [EnableIf=red]\n> +        VALUE1 = 5,\n> +        [EnableIf=yellow]\n> +        VALUE1 = 6,\n> +      };\n> +      [EnableIf=red]\n> +      const double kMyConst = 1.23;\n> +      [EnableIf=yellow]\n> +      const double kMyConst = 4.56;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      enum Foo {\n> +        [EnableIf=red]\n> +        VALUE1 = 5,\n> +      };\n> +      [EnableIf=red]\n> +      const double kMyConst = 1.23;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(mojom_source, expected_source)\n> +\n> +  def testMultipleEnableIfs(self):\n> +    source = \"\"\"\n> +      enum Foo {\n> +        [EnableIf=red,EnableIf=yellow]\n> +        kBarValue = 5,\n> +      };\n> +    \"\"\"\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertRaises(conditional_features.EnableIfError,\n> +                      conditional_features.RemoveDisabledDefinitions,\n> +                      definition, ENABLED_FEATURES)\n> +\n> +\n> +if __name__ == '__main__':\n> +  unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> new file mode 100644\n> index 00000000..3e084bbf\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> @@ -0,0 +1,251 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +\n> +from mojom import fileutil\n> +from mojom.error import Error\n> +\n> +fileutil.AddLocalRepoThirdPartyDirToModulePath()\n> +from ply.lex import TOKEN\n> +\n> +\n> +class LexError(Error):\n> +  \"\"\"Class for errors from the lexer.\"\"\"\n> +\n> +  def __init__(self, filename, message, lineno):\n> +    Error.__init__(self, filename, message, lineno=lineno)\n> +\n> +\n> +# We have methods which look like they could be functions:\n> +# pylint: disable=R0201\n> +class Lexer(object):\n> +  def __init__(self, filename):\n> +    self.filename = filename\n> +\n> +  ######################--   PRIVATE   --######################\n> +\n> +  ##\n> +  ## Internal auxiliary methods\n> +  ##\n> +  def _error(self, msg, token):\n> +    raise LexError(self.filename, msg, token.lineno)\n> +\n> +  ##\n> +  ## Reserved keywords\n> +  ##\n> +  keywords = (\n> +      'HANDLE',\n> +      'IMPORT',\n> +      'MODULE',\n> +      'STRUCT',\n> +      'UNION',\n> +      'INTERFACE',\n> +      'ENUM',\n> +      'CONST',\n> +      'TRUE',\n> +      'FALSE',\n> +      'DEFAULT',\n> +      'ARRAY',\n> +      'MAP',\n> +      'ASSOCIATED',\n> +      'PENDING_REMOTE',\n> +      'PENDING_RECEIVER',\n> +      'PENDING_ASSOCIATED_REMOTE',\n> +      'PENDING_ASSOCIATED_RECEIVER',\n> +  )\n> +\n> +  keyword_map = {}\n> +  for keyword in keywords:\n> +    keyword_map[keyword.lower()] = keyword\n> +\n> +  ##\n> +  ## All the tokens recognized by the lexer\n> +  ##\n> +  tokens = keywords + (\n> +      # Identifiers\n> +      'NAME',\n> +\n> +      # Constants\n> +      'ORDINAL',\n> +      'INT_CONST_DEC',\n> +      'INT_CONST_HEX',\n> +      'FLOAT_CONST',\n> +\n> +      # String literals\n> +      'STRING_LITERAL',\n> +\n> +      # Operators\n> +      'MINUS',\n> +      'PLUS',\n> +      'AMP',\n> +      'QSTN',\n> +\n> +      # Assignment\n> +      'EQUALS',\n> +\n> +      # Request / response\n> +      'RESPONSE',\n> +\n> +      # Delimiters\n> +      'LPAREN',\n> +      'RPAREN',  # ( )\n> +      'LBRACKET',\n> +      'RBRACKET',  # [ ]\n> +      'LBRACE',\n> +      'RBRACE',  # { }\n> +      'LANGLE',\n> +      'RANGLE',  # < >\n> +      'SEMI',  # ;\n> +      'COMMA',\n> +      'DOT'  # , .\n> +  )\n> +\n> +  ##\n> +  ## Regexes for use in tokens\n> +  ##\n> +\n> +  # valid C identifiers (K&R2: A.2.3)\n> +  identifier = r'[a-zA-Z_][0-9a-zA-Z_]*'\n> +\n> +  hex_prefix = '0[xX]'\n> +  hex_digits = '[0-9a-fA-F]+'\n> +\n> +  # integer constants (K&R2: A.2.5.1)\n> +  decimal_constant = '0|([1-9][0-9]*)'\n> +  hex_constant = hex_prefix + hex_digits\n> +  # Don't allow octal constants (even invalid octal).\n> +  octal_constant_disallowed = '0[0-9]+'\n> +\n> +  # character constants (K&R2: A.2.5.2)\n> +  # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line\n> +  # directives with Windows paths as filenames (..\\..\\dir\\file)\n> +  # For the same reason, decimal_escape allows all digit sequences. We want to\n> +  # parse all correct code, even if it means to sometimes parse incorrect\n> +  # code.\n> +  #\n> +  simple_escape = r\"\"\"([a-zA-Z._~!=&\\^\\-\\\\?'\"])\"\"\"\n> +  decimal_escape = r\"\"\"(\\d+)\"\"\"\n> +  hex_escape = r\"\"\"(x[0-9a-fA-F]+)\"\"\"\n> +  bad_escape = r\"\"\"([\\\\][^a-zA-Z._~^!=&\\^\\-\\\\?'\"x0-7])\"\"\"\n> +\n> +  escape_sequence = \\\n> +      r\"\"\"(\\\\(\"\"\"+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))'\n> +\n> +  # string literals (K&R2: A.2.6)\n> +  string_char = r\"\"\"([^\"\\\\\\n]|\"\"\" + escape_sequence + ')'\n> +  string_literal = '\"' + string_char + '*\"'\n> +  bad_string_literal = '\"' + string_char + '*' + bad_escape + string_char + '*\"'\n> +\n> +  # floating constants (K&R2: A.2.5.3)\n> +  exponent_part = r\"\"\"([eE][-+]?[0-9]+)\"\"\"\n> +  fractional_constant = r\"\"\"([0-9]*\\.[0-9]+)|([0-9]+\\.)\"\"\"\n> +  floating_constant = \\\n> +      '(((('+fractional_constant+')'+ \\\n> +      exponent_part+'?)|([0-9]+'+exponent_part+')))'\n> +\n> +  # Ordinals\n> +  ordinal = r'@[0-9]+'\n> +  missing_ordinal_value = r'@'\n> +  # Don't allow ordinal values in octal (even invalid octal, like 09) or\n> +  # hexadecimal.\n> +  octal_or_hex_ordinal_disallowed = (\n> +      r'@((0[0-9]+)|(' + hex_prefix + hex_digits + '))')\n> +\n> +  ##\n> +  ## Rules for the normal state\n> +  ##\n> +  t_ignore = ' \\t\\r'\n> +\n> +  # Newlines\n> +  def t_NEWLINE(self, t):\n> +    r'\\n+'\n> +    t.lexer.lineno += len(t.value)\n> +\n> +  # Operators\n> +  t_MINUS = r'-'\n> +  t_PLUS = r'\\+'\n> +  t_AMP = r'&'\n> +  t_QSTN = r'\\?'\n> +\n> +  # =\n> +  t_EQUALS = r'='\n> +\n> +  # =>\n> +  t_RESPONSE = r'=>'\n> +\n> +  # Delimiters\n> +  t_LPAREN = r'\\('\n> +  t_RPAREN = r'\\)'\n> +  t_LBRACKET = r'\\['\n> +  t_RBRACKET = r'\\]'\n> +  t_LBRACE = r'\\{'\n> +  t_RBRACE = r'\\}'\n> +  t_LANGLE = r'<'\n> +  t_RANGLE = r'>'\n> +  t_COMMA = r','\n> +  t_DOT = r'\\.'\n> +  t_SEMI = r';'\n> +\n> +  t_STRING_LITERAL = string_literal\n> +\n> +  # The following floating and integer constants are defined as\n> +  # functions to impose a strict order (otherwise, decimal\n> +  # is placed before the others because its regex is longer,\n> +  # and this is bad)\n> +  #\n> +  @TOKEN(floating_constant)\n> +  def t_FLOAT_CONST(self, t):\n> +    return t\n> +\n> +  @TOKEN(hex_constant)\n> +  def t_INT_CONST_HEX(self, t):\n> +    return t\n> +\n> +  @TOKEN(octal_constant_disallowed)\n> +  def t_OCTAL_CONSTANT_DISALLOWED(self, t):\n> +    msg = \"Octal values not allowed\"\n> +    self._error(msg, t)\n> +\n> +  @TOKEN(decimal_constant)\n> +  def t_INT_CONST_DEC(self, t):\n> +    return t\n> +\n> +  # unmatched string literals are caught by the preprocessor\n> +\n> +  @TOKEN(bad_string_literal)\n> +  def t_BAD_STRING_LITERAL(self, t):\n> +    msg = \"String contains invalid escape code\"\n> +    self._error(msg, t)\n> +\n> +  # Handle ordinal-related tokens in the right order:\n> +  @TOKEN(octal_or_hex_ordinal_disallowed)\n> +  def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t):\n> +    msg = \"Octal and hexadecimal ordinal values not allowed\"\n> +    self._error(msg, t)\n> +\n> +  @TOKEN(ordinal)\n> +  def t_ORDINAL(self, t):\n> +    return t\n> +\n> +  @TOKEN(missing_ordinal_value)\n> +  def t_BAD_ORDINAL(self, t):\n> +    msg = \"Missing ordinal value\"\n> +    self._error(msg, t)\n> +\n> +  @TOKEN(identifier)\n> +  def t_NAME(self, t):\n> +    t.type = self.keyword_map.get(t.value, \"NAME\")\n> +    return t\n> +\n> +  # Ignore C and C++ style comments\n> +  def t_COMMENT(self, t):\n> +    r'(/\\*(.|\\n)*?\\*/)|(//.*(\\n[ \\t]*//.*)*)'\n> +    t.lexer.lineno += t.value.count(\"\\n\")\n> +\n> +  def t_error(self, t):\n> +    msg = \"Illegal character %s\" % repr(t.value[0])\n> +    self._error(msg, t)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> new file mode 100644\n> index 00000000..eadc6587\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> @@ -0,0 +1,198 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +import unittest\n> +\n> +\n> +def _GetDirAbove(dirname):\n> +  \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n> +  also be \"above\" this file).\"\"\"\n> +  path = os.path.abspath(__file__)\n> +  while True:\n> +    path, tail = os.path.split(path)\n> +    assert tail\n> +    if tail == dirname:\n> +      return path\n> +\n> +\n> +sys.path.insert(1, os.path.join(_GetDirAbove(\"mojo\"), \"third_party\"))\n> +from ply import lex\n> +\n> +try:\n> +  imp.find_module(\"mojom\")\n> +except ImportError:\n> +  sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n> +import mojom.parse.lexer\n> +\n> +\n> +# This (monkey-patching LexToken to make comparison value-based) is evil, but\n> +# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing\n> +# for object identity.)\n> +def _LexTokenEq(self, other):\n> +  return self.type == other.type and self.value == other.value and \\\n> +         self.lineno == other.lineno and self.lexpos == other.lexpos\n> +\n> +\n> +setattr(lex.LexToken, '__eq__', _LexTokenEq)\n> +\n> +\n> +def _MakeLexToken(token_type, value, lineno=1, lexpos=0):\n> +  \"\"\"Makes a LexToken with the given parameters. (Note that lineno is 1-based,\n> +  but lexpos is 0-based.)\"\"\"\n> +  rv = lex.LexToken()\n> +  rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos\n> +  return rv\n> +\n> +\n> +def _MakeLexTokenForKeyword(keyword, **kwargs):\n> +  \"\"\"Makes a LexToken for the given keyword.\"\"\"\n> +  return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs)\n> +\n> +\n> +class LexerTest(unittest.TestCase):\n> +  \"\"\"Tests |mojom.parse.lexer.Lexer|.\"\"\"\n> +\n> +  def __init__(self, *args, **kwargs):\n> +    unittest.TestCase.__init__(self, *args, **kwargs)\n> +    # Clone all lexer instances from this one, since making a lexer is slow.\n> +    self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer(\"my_file.mojom\"))\n> +\n> +  def testValidKeywords(self):\n> +    \"\"\"Tests valid keywords.\"\"\"\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"handle\"), _MakeLexTokenForKeyword(\"handle\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"import\"), _MakeLexTokenForKeyword(\"import\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"module\"), _MakeLexTokenForKeyword(\"module\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"struct\"), _MakeLexTokenForKeyword(\"struct\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"union\"), _MakeLexTokenForKeyword(\"union\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"interface\"),\n> +        _MakeLexTokenForKeyword(\"interface\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"enum\"), _MakeLexTokenForKeyword(\"enum\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"const\"), _MakeLexTokenForKeyword(\"const\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"true\"), _MakeLexTokenForKeyword(\"true\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"false\"), _MakeLexTokenForKeyword(\"false\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"default\"),\n> +        _MakeLexTokenForKeyword(\"default\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"array\"), _MakeLexTokenForKeyword(\"array\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"map\"), _MakeLexTokenForKeyword(\"map\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"associated\"),\n> +        _MakeLexTokenForKeyword(\"associated\"))\n> +\n> +  def testValidIdentifiers(self):\n> +    \"\"\"Tests identifiers.\"\"\"\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"abcd\"), _MakeLexToken(\"NAME\", \"abcd\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"AbC_d012_\"),\n> +        _MakeLexToken(\"NAME\", \"AbC_d012_\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"_0123\"), _MakeLexToken(\"NAME\", \"_0123\"))\n> +\n> +  def testInvalidIdentifiers(self):\n> +    with self.assertRaisesRegexp(\n> +        mojom.parse.lexer.LexError,\n> +        r\"^my_file\\.mojom:1: Error: Illegal character '\\$'$\"):\n> +      self._TokensForInput(\"$abc\")\n> +    with self.assertRaisesRegexp(\n> +        mojom.parse.lexer.LexError,\n> +        r\"^my_file\\.mojom:1: Error: Illegal character '\\$'$\"):\n> +      self._TokensForInput(\"a$bc\")\n> +\n> +  def testDecimalIntegerConstants(self):\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"0\"), _MakeLexToken(\"INT_CONST_DEC\", \"0\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"1\"), _MakeLexToken(\"INT_CONST_DEC\", \"1\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"123\"), _MakeLexToken(\"INT_CONST_DEC\", \"123\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"10\"), _MakeLexToken(\"INT_CONST_DEC\", \"10\"))\n> +\n> +  def testValidTokens(self):\n> +    \"\"\"Tests valid tokens (which aren't tested elsewhere).\"\"\"\n> +    # Keywords tested in |testValidKeywords|.\n> +    # NAME tested in |testValidIdentifiers|.\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"@123\"), _MakeLexToken(\"ORDINAL\", \"@123\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"456\"), _MakeLexToken(\"INT_CONST_DEC\", \"456\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"0x01aB2eF3\"),\n> +        _MakeLexToken(\"INT_CONST_HEX\", \"0x01aB2eF3\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"123.456\"),\n> +        _MakeLexToken(\"FLOAT_CONST\", \"123.456\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"\\\"hello\\\"\"),\n> +        _MakeLexToken(\"STRING_LITERAL\", \"\\\"hello\\\"\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"+\"), _MakeLexToken(\"PLUS\", \"+\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"-\"), _MakeLexToken(\"MINUS\", \"-\"))\n> +    self.assertEquals(self._SingleTokenForInput(\"&\"), _MakeLexToken(\"AMP\", \"&\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"?\"), _MakeLexToken(\"QSTN\", \"?\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"=\"), _MakeLexToken(\"EQUALS\", \"=\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"=>\"), _MakeLexToken(\"RESPONSE\", \"=>\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"(\"), _MakeLexToken(\"LPAREN\", \"(\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\")\"), _MakeLexToken(\"RPAREN\", \")\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"[\"), _MakeLexToken(\"LBRACKET\", \"[\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"]\"), _MakeLexToken(\"RBRACKET\", \"]\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"{\"), _MakeLexToken(\"LBRACE\", \"{\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"}\"), _MakeLexToken(\"RBRACE\", \"}\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\"<\"), _MakeLexToken(\"LANGLE\", \"<\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\">\"), _MakeLexToken(\"RANGLE\", \">\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\";\"), _MakeLexToken(\"SEMI\", \";\"))\n> +    self.assertEquals(\n> +        self._SingleTokenForInput(\",\"), _MakeLexToken(\"COMMA\", \",\"))\n> +    self.assertEquals(self._SingleTokenForInput(\".\"), _MakeLexToken(\"DOT\", \".\"))\n> +\n> +  def _TokensForInput(self, input_string):\n> +    \"\"\"Gets a list of tokens for the given input string.\"\"\"\n> +    lexer = self._zygote_lexer.clone()\n> +    lexer.input(input_string)\n> +    rv = []\n> +    while True:\n> +      tok = lexer.token()\n> +      if not tok:\n> +        return rv\n> +      rv.append(tok)\n> +\n> +  def _SingleTokenForInput(self, input_string):\n> +    \"\"\"Gets the single token for the given input string. (Raises an exception if\n> +    the input string does not result in exactly one token.)\"\"\"\n> +    toks = self._TokensForInput(input_string)\n> +    assert len(toks) == 1\n> +    return toks[0]\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> new file mode 100644\n> index 00000000..b3b803d6\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> @@ -0,0 +1,488 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Generates a syntax tree from a Mojo IDL file.\"\"\"\n> +\n> +import os.path\n> +import sys\n> +\n> +from mojom import fileutil\n> +from mojom.error import Error\n> +from mojom.parse import ast\n> +from mojom.parse.lexer import Lexer\n> +\n> +fileutil.AddLocalRepoThirdPartyDirToModulePath()\n> +from ply import lex\n> +from ply import yacc\n> +\n> +_MAX_ORDINAL_VALUE = 0xffffffff\n> +_MAX_ARRAY_SIZE = 0xffffffff\n> +\n> +\n> +class ParseError(Error):\n> +  \"\"\"Class for errors from the parser.\"\"\"\n> +\n> +  def __init__(self, filename, message, lineno=None, snippet=None):\n> +    Error.__init__(\n> +        self,\n> +        filename,\n> +        message,\n> +        lineno=lineno,\n> +        addenda=([snippet] if snippet else None))\n> +\n> +\n> +# We have methods which look like they could be functions:\n> +# pylint: disable=R0201\n> +class Parser(object):\n> +  def __init__(self, lexer, source, filename):\n> +    self.tokens = lexer.tokens\n> +    self.source = source\n> +    self.filename = filename\n> +\n> +  # Names of functions\n> +  #\n> +  # In general, we name functions after the left-hand-side of the rule(s) that\n> +  # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|.\n> +  #\n> +  # There may be multiple functions handling rules for the same left-hand-side;\n> +  # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|),\n> +  # where N is a number (numbered starting from 1). Note that using multiple\n> +  # functions is actually more efficient than having single functions handle\n> +  # multiple rules (and, e.g., distinguishing them by examining |len(p)|).\n> +  #\n> +  # It's also possible to have a function handling multiple rules with different\n> +  # left-hand-sides. We do not do this.\n> +  #\n> +  # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.\n> +\n> +  # TODO(vtl): Get rid of the braces in the module \"statement\". (Consider\n> +  # renaming \"module\" -> \"package\".) Then we'll be able to have a single rule\n> +  # for root (by making module \"optional\").\n> +  def p_root_1(self, p):\n> +    \"\"\"root : \"\"\"\n> +    p[0] = ast.Mojom(None, ast.ImportList(), [])\n> +\n> +  def p_root_2(self, p):\n> +    \"\"\"root : root module\"\"\"\n> +    if p[1].module is not None:\n> +      raise ParseError(\n> +          self.filename,\n> +          \"Multiple \\\"module\\\" statements not allowed:\",\n> +          p[2].lineno,\n> +          snippet=self._GetSnippet(p[2].lineno))\n> +    if p[1].import_list.items or p[1].definition_list:\n> +      raise ParseError(\n> +          self.filename,\n> +          \"\\\"module\\\" statements must precede imports and definitions:\",\n> +          p[2].lineno,\n> +          snippet=self._GetSnippet(p[2].lineno))\n> +    p[0] = p[1]\n> +    p[0].module = p[2]\n> +\n> +  def p_root_3(self, p):\n> +    \"\"\"root : root import\"\"\"\n> +    if p[1].definition_list:\n> +      raise ParseError(\n> +          self.filename,\n> +          \"\\\"import\\\" statements must precede definitions:\",\n> +          p[2].lineno,\n> +          snippet=self._GetSnippet(p[2].lineno))\n> +    p[0] = p[1]\n> +    p[0].import_list.Append(p[2])\n> +\n> +  def p_root_4(self, p):\n> +    \"\"\"root : root definition\"\"\"\n> +    p[0] = p[1]\n> +    p[0].definition_list.append(p[2])\n> +\n> +  def p_import(self, p):\n> +    \"\"\"import : attribute_section IMPORT STRING_LITERAL SEMI\"\"\"\n> +    # 'eval' the literal to strip the quotes.\n> +    # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.\n> +    p[0] = ast.Import(\n> +        p[1], eval(p[3]), filename=self.filename, lineno=p.lineno(2))\n> +\n> +  def p_module(self, p):\n> +    \"\"\"module : attribute_section MODULE identifier_wrapped SEMI\"\"\"\n> +    p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))\n> +\n> +  def p_definition(self, p):\n> +    \"\"\"definition : struct\n> +                  | union\n> +                  | interface\n> +                  | enum\n> +                  | const\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_attribute_section_1(self, p):\n> +    \"\"\"attribute_section : \"\"\"\n> +    p[0] = None\n> +\n> +  def p_attribute_section_2(self, p):\n> +    \"\"\"attribute_section : LBRACKET attribute_list RBRACKET\"\"\"\n> +    p[0] = p[2]\n> +\n> +  def p_attribute_list_1(self, p):\n> +    \"\"\"attribute_list : \"\"\"\n> +    p[0] = ast.AttributeList()\n> +\n> +  def p_attribute_list_2(self, p):\n> +    \"\"\"attribute_list : nonempty_attribute_list\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_nonempty_attribute_list_1(self, p):\n> +    \"\"\"nonempty_attribute_list : attribute\"\"\"\n> +    p[0] = ast.AttributeList(p[1])\n> +\n> +  def p_nonempty_attribute_list_2(self, p):\n> +    \"\"\"nonempty_attribute_list : nonempty_attribute_list COMMA attribute\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[3])\n> +\n> +  def p_attribute_1(self, p):\n> +    \"\"\"attribute : NAME EQUALS evaled_literal\n> +                 | NAME EQUALS NAME\"\"\"\n> +    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n> +\n> +  def p_attribute_2(self, p):\n> +    \"\"\"attribute : NAME\"\"\"\n> +    p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))\n> +\n> +  def p_evaled_literal(self, p):\n> +    \"\"\"evaled_literal : literal\"\"\"\n> +    # 'eval' the literal to strip the quotes. Handle keywords \"true\" and \"false\"\n> +    # specially since they cannot directly be evaluated to python boolean\n> +    # values.\n> +    if p[1] == \"true\":\n> +      p[0] = True\n> +    elif p[1] == \"false\":\n> +      p[0] = False\n> +    else:\n> +      p[0] = eval(p[1])\n> +\n> +  def p_struct_1(self, p):\n> +    \"\"\"struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI\"\"\"\n> +    p[0] = ast.Struct(p[3], p[1], p[5])\n> +\n> +  def p_struct_2(self, p):\n> +    \"\"\"struct : attribute_section STRUCT NAME SEMI\"\"\"\n> +    p[0] = ast.Struct(p[3], p[1], None)\n> +\n> +  def p_struct_body_1(self, p):\n> +    \"\"\"struct_body : \"\"\"\n> +    p[0] = ast.StructBody()\n> +\n> +  def p_struct_body_2(self, p):\n> +    \"\"\"struct_body : struct_body const\n> +                   | struct_body enum\n> +                   | struct_body struct_field\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[2])\n> +\n> +  def p_struct_field(self, p):\n> +    \"\"\"struct_field : attribute_section typename NAME ordinal default SEMI\"\"\"\n> +    p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])\n> +\n> +  def p_union(self, p):\n> +    \"\"\"union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI\"\"\"\n> +    p[0] = ast.Union(p[3], p[1], p[5])\n> +\n> +  def p_union_body_1(self, p):\n> +    \"\"\"union_body : \"\"\"\n> +    p[0] = ast.UnionBody()\n> +\n> +  def p_union_body_2(self, p):\n> +    \"\"\"union_body : union_body union_field\"\"\"\n> +    p[0] = p[1]\n> +    p[1].Append(p[2])\n> +\n> +  def p_union_field(self, p):\n> +    \"\"\"union_field : attribute_section typename NAME ordinal SEMI\"\"\"\n> +    p[0] = ast.UnionField(p[3], p[1], p[4], p[2])\n> +\n> +  def p_default_1(self, p):\n> +    \"\"\"default : \"\"\"\n> +    p[0] = None\n> +\n> +  def p_default_2(self, p):\n> +    \"\"\"default : EQUALS constant\"\"\"\n> +    p[0] = p[2]\n> +\n> +  def p_interface(self, p):\n> +    \"\"\"interface : attribute_section INTERFACE NAME LBRACE interface_body \\\n> +                       RBRACE SEMI\"\"\"\n> +    p[0] = ast.Interface(p[3], p[1], p[5])\n> +\n> +  def p_interface_body_1(self, p):\n> +    \"\"\"interface_body : \"\"\"\n> +    p[0] = ast.InterfaceBody()\n> +\n> +  def p_interface_body_2(self, p):\n> +    \"\"\"interface_body : interface_body const\n> +                      | interface_body enum\n> +                      | interface_body method\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[2])\n> +\n> +  def p_response_1(self, p):\n> +    \"\"\"response : \"\"\"\n> +    p[0] = None\n> +\n> +  def p_response_2(self, p):\n> +    \"\"\"response : RESPONSE LPAREN parameter_list RPAREN\"\"\"\n> +    p[0] = p[3]\n> +\n> +  def p_method(self, p):\n> +    \"\"\"method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \\\n> +                    response SEMI\"\"\"\n> +    p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])\n> +\n> +  def p_parameter_list_1(self, p):\n> +    \"\"\"parameter_list : \"\"\"\n> +    p[0] = ast.ParameterList()\n> +\n> +  def p_parameter_list_2(self, p):\n> +    \"\"\"parameter_list : nonempty_parameter_list\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_nonempty_parameter_list_1(self, p):\n> +    \"\"\"nonempty_parameter_list : parameter\"\"\"\n> +    p[0] = ast.ParameterList(p[1])\n> +\n> +  def p_nonempty_parameter_list_2(self, p):\n> +    \"\"\"nonempty_parameter_list : nonempty_parameter_list COMMA parameter\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[3])\n> +\n> +  def p_parameter(self, p):\n> +    \"\"\"parameter : attribute_section typename NAME ordinal\"\"\"\n> +    p[0] = ast.Parameter(\n> +        p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))\n> +\n> +  def p_typename(self, p):\n> +    \"\"\"typename : nonnullable_typename QSTN\n> +                | nonnullable_typename\"\"\"\n> +    if len(p) == 2:\n> +      p[0] = p[1]\n> +    else:\n> +      p[0] = p[1] + \"?\"\n> +\n> +  def p_nonnullable_typename(self, p):\n> +    \"\"\"nonnullable_typename : basictypename\n> +                            | array\n> +                            | fixed_array\n> +                            | associative_array\n> +                            | interfacerequest\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_basictypename(self, p):\n> +    \"\"\"basictypename : remotetype\n> +                     | receivertype\n> +                     | associatedremotetype\n> +                     | associatedreceivertype\n> +                     | identifier\n> +                     | ASSOCIATED identifier\n> +                     | handletype\"\"\"\n> +    if len(p) == 2:\n> +      p[0] = p[1]\n> +    else:\n> +      p[0] = \"asso<\" + p[2] + \">\"\n> +\n> +  def p_remotetype(self, p):\n> +    \"\"\"remotetype : PENDING_REMOTE LANGLE identifier RANGLE\"\"\"\n> +    p[0] = \"rmt<%s>\" % p[3]\n> +\n> +  def p_receivertype(self, p):\n> +    \"\"\"receivertype : PENDING_RECEIVER LANGLE identifier RANGLE\"\"\"\n> +    p[0] = \"rcv<%s>\" % p[3]\n> +\n> +  def p_associatedremotetype(self, p):\n> +    \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \\\n> +                                  RANGLE\"\"\"\n> +    p[0] = \"rma<%s>\" % p[3]\n> +\n> +  def p_associatedreceivertype(self, p):\n> +    \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \\\n> +                                    RANGLE\"\"\"\n> +    p[0] = \"rca<%s>\" % p[3]\n> +\n> +  def p_handletype(self, p):\n> +    \"\"\"handletype : HANDLE\n> +                  | HANDLE LANGLE NAME RANGLE\"\"\"\n> +    if len(p) == 2:\n> +      p[0] = p[1]\n> +    else:\n> +      if p[3] not in ('data_pipe_consumer', 'data_pipe_producer',\n> +                      'message_pipe', 'shared_buffer', 'platform'):\n> +        # Note: We don't enable tracking of line numbers for everything, so we\n> +        # can't use |p.lineno(3)|.\n> +        raise ParseError(\n> +            self.filename,\n> +            \"Invalid handle type %r:\" % p[3],\n> +            lineno=p.lineno(1),\n> +            snippet=self._GetSnippet(p.lineno(1)))\n> +      p[0] = \"handle<\" + p[3] + \">\"\n> +\n> +  def p_array(self, p):\n> +    \"\"\"array : ARRAY LANGLE typename RANGLE\"\"\"\n> +    p[0] = p[3] + \"[]\"\n> +\n> +  def p_fixed_array(self, p):\n> +    \"\"\"fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE\"\"\"\n> +    value = int(p[5])\n> +    if value == 0 or value > _MAX_ARRAY_SIZE:\n> +      raise ParseError(\n> +          self.filename,\n> +          \"Fixed array size %d invalid:\" % value,\n> +          lineno=p.lineno(5),\n> +          snippet=self._GetSnippet(p.lineno(5)))\n> +    p[0] = p[3] + \"[\" + p[5] + \"]\"\n> +\n> +  def p_associative_array(self, p):\n> +    \"\"\"associative_array : MAP LANGLE identifier COMMA typename RANGLE\"\"\"\n> +    p[0] = p[5] + \"{\" + p[3] + \"}\"\n> +\n> +  def p_interfacerequest(self, p):\n> +    \"\"\"interfacerequest : identifier AMP\n> +                        | ASSOCIATED identifier AMP\"\"\"\n> +    if len(p) == 3:\n> +      p[0] = p[1] + \"&\"\n> +    else:\n> +      p[0] = \"asso<\" + p[2] + \"&>\"\n> +\n> +  def p_ordinal_1(self, p):\n> +    \"\"\"ordinal : \"\"\"\n> +    p[0] = None\n> +\n> +  def p_ordinal_2(self, p):\n> +    \"\"\"ordinal : ORDINAL\"\"\"\n> +    value = int(p[1][1:])\n> +    if value > _MAX_ORDINAL_VALUE:\n> +      raise ParseError(\n> +          self.filename,\n> +          \"Ordinal value %d too large:\" % value,\n> +          lineno=p.lineno(1),\n> +          snippet=self._GetSnippet(p.lineno(1)))\n> +    p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))\n> +\n> +  def p_enum_1(self, p):\n> +    \"\"\"enum : attribute_section ENUM NAME LBRACE enum_value_list \\\n> +                  RBRACE SEMI\n> +            | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \\\n> +                  COMMA RBRACE SEMI\"\"\"\n> +    p[0] = ast.Enum(\n> +        p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))\n> +\n> +  def p_enum_2(self, p):\n> +    \"\"\"enum : attribute_section ENUM NAME SEMI\"\"\"\n> +    p[0] = ast.Enum(\n> +        p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))\n> +\n> +  def p_enum_value_list_1(self, p):\n> +    \"\"\"enum_value_list : \"\"\"\n> +    p[0] = ast.EnumValueList()\n> +\n> +  def p_enum_value_list_2(self, p):\n> +    \"\"\"enum_value_list : nonempty_enum_value_list\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_nonempty_enum_value_list_1(self, p):\n> +    \"\"\"nonempty_enum_value_list : enum_value\"\"\"\n> +    p[0] = ast.EnumValueList(p[1])\n> +\n> +  def p_nonempty_enum_value_list_2(self, p):\n> +    \"\"\"nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[3])\n> +\n> +  def p_enum_value(self, p):\n> +    \"\"\"enum_value : attribute_section NAME\n> +                  | attribute_section NAME EQUALS int\n> +                  | attribute_section NAME EQUALS identifier_wrapped\"\"\"\n> +    p[0] = ast.EnumValue(\n> +        p[2],\n> +        p[1],\n> +        p[4] if len(p) == 5 else None,\n> +        filename=self.filename,\n> +        lineno=p.lineno(2))\n> +\n> +  def p_const(self, p):\n> +    \"\"\"const : attribute_section CONST typename NAME EQUALS constant SEMI\"\"\"\n> +    p[0] = ast.Const(p[4], p[1], p[3], p[6])\n> +\n> +  def p_constant(self, p):\n> +    \"\"\"constant : literal\n> +                | identifier_wrapped\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_identifier_wrapped(self, p):\n> +    \"\"\"identifier_wrapped : identifier\"\"\"\n> +    p[0] = ('IDENTIFIER', p[1])\n> +\n> +  # TODO(vtl): Make this produce a \"wrapped\" identifier (probably as an\n> +  # |ast.Identifier|, to be added) and get rid of identifier_wrapped.\n> +  def p_identifier(self, p):\n> +    \"\"\"identifier : NAME\n> +                  | NAME DOT identifier\"\"\"\n> +    p[0] = ''.join(p[1:])\n> +\n> +  def p_literal(self, p):\n> +    \"\"\"literal : int\n> +               | float\n> +               | TRUE\n> +               | FALSE\n> +               | DEFAULT\n> +               | STRING_LITERAL\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_int(self, p):\n> +    \"\"\"int : int_const\n> +           | PLUS int_const\n> +           | MINUS int_const\"\"\"\n> +    p[0] = ''.join(p[1:])\n> +\n> +  def p_int_const(self, p):\n> +    \"\"\"int_const : INT_CONST_DEC\n> +                 | INT_CONST_HEX\"\"\"\n> +    p[0] = p[1]\n> +\n> +  def p_float(self, p):\n> +    \"\"\"float : FLOAT_CONST\n> +             | PLUS FLOAT_CONST\n> +             | MINUS FLOAT_CONST\"\"\"\n> +    p[0] = ''.join(p[1:])\n> +\n> +  def p_error(self, e):\n> +    if e is None:\n> +      # Unexpected EOF.\n> +      # TODO(vtl): Can we figure out what's missing?\n> +      raise ParseError(self.filename, \"Unexpected end of file\")\n> +\n> +    raise ParseError(\n> +        self.filename,\n> +        \"Unexpected %r:\" % e.value,\n> +        lineno=e.lineno,\n> +        snippet=self._GetSnippet(e.lineno))\n> +\n> +  def _GetSnippet(self, lineno):\n> +    return self.source.split('\\n')[lineno - 1]\n> +\n> +\n> +def Parse(source, filename):\n> +  \"\"\"Parse source file to AST.\n> +\n> +  Args:\n> +    source: The source text as a str (Python 2 or 3) or unicode (Python 2).\n> +    filename: The filename that |source| originates from.\n> +\n> +  Returns:\n> +    The AST as a mojom.parse.ast.Mojom object.\n> +  \"\"\"\n> +  lexer = Lexer(filename)\n> +  parser = Parser(lexer, source, filename)\n> +\n> +  lex.lex(object=lexer)\n> +  yacc.yacc(module=parser, debug=0, write_tables=0)\n> +\n> +  tree = yacc.parse(source)\n> +  return tree\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> new file mode 100644\n> index 00000000..6d6b7153\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> @@ -0,0 +1,1390 @@\n> +# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import imp\n> +import os.path\n> +import sys\n> +import unittest\n> +\n> +from mojom.parse import ast\n> +from mojom.parse import lexer\n> +from mojom.parse import parser\n> +\n> +\n> +class ParserTest(unittest.TestCase):\n> +  \"\"\"Tests |parser.Parse()|.\"\"\"\n> +\n> +  def testTrivialValidSource(self):\n> +    \"\"\"Tests a trivial, but valid, .mojom source.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        // This is a comment.\n> +\n> +        module my_module;\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testSourceWithCrLfs(self):\n> +    \"\"\"Tests a .mojom source with CR-LFs instead of LFs.\"\"\"\n> +\n> +    source = \"// This is a comment.\\r\\n\\r\\nmodule my_module;\\r\\n\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testUnexpectedEOF(self):\n> +    \"\"\"Tests a \"truncated\" .mojom source.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        // This is a comment.\n> +\n> +        module my_module\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom: Error: Unexpected end of file$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testCommentLineNumbers(self):\n> +    \"\"\"Tests that line numbers are correctly tracked when comments are\n> +    present.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        // Isolated C++-style comments.\n> +\n> +        // Foo.\n> +        asdf1\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:4: Error: Unexpected 'asdf1':\\n *asdf1$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        // Consecutive C++-style comments.\n> +        // Foo.\n> +        // Bar.\n> +\n> +        struct Yada {  // Baz.\n> +                       // Quux.\n> +          int32 x;\n> +        };\n> +\n> +        asdf2\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:10: Error: Unexpected 'asdf2':\\n *asdf2$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        /* Single-line C-style comments. */\n> +        /* Foobar. */\n> +\n> +        /* Baz. */\n> +        asdf3\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:5: Error: Unexpected 'asdf3':\\n *asdf3$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +    source4 = \"\"\"\\\n> +        /* Multi-line C-style comments.\n> +        */\n> +        /*\n> +        Foo.\n> +        Bar.\n> +        */\n> +\n> +        /* Baz\n> +           Quux. */\n> +        asdf4\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:10: Error: Unexpected 'asdf4':\\n *asdf4$\"):\n> +      parser.Parse(source4, \"my_file.mojom\")\n> +\n> +  def testSimpleStruct(self):\n> +    \"\"\"Tests a simple .mojom source that just defines a struct.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        struct MyStruct {\n> +          int32 a;\n> +          double b;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Struct(\n> +                'MyStruct', None,\n> +                ast.StructBody([\n> +                    ast.StructField('a', None, None, 'int32', None),\n> +                    ast.StructField('b', None, None, 'double', None)\n> +                ]))\n> +        ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testSimpleStructWithoutModule(self):\n> +    \"\"\"Tests a simple struct without an explict module statement.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          int32 a;\n> +          double b;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('a', None, None, 'int32', None),\n> +                ast.StructField('b', None, None, 'double', None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testValidStructDefinitions(self):\n> +    \"\"\"Tests all types of definitions that can occur in a struct.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          enum MyEnum { VALUE };\n> +          const double kMyConst = 1.23;\n> +          int32 a;\n> +          SomeOtherStruct b;  // Invalidity detected at another stage.\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.Enum('MyEnum', None,\n> +                         ast.EnumValueList(ast.EnumValue('VALUE', None, None))),\n> +                ast.Const('kMyConst', None, 'double', '1.23'),\n> +                ast.StructField('a', None, None, 'int32', None),\n> +                ast.StructField('b', None, None, 'SomeOtherStruct', None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidStructDefinitions(self):\n> +    \"\"\"Tests that definitions that aren't allowed in a struct are correctly\n> +    detected.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        struct MyStruct {\n> +          MyMethod(int32 a);\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\(':\\n\"\n> +        r\" *MyMethod\\(int32 a\\);$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        struct MyStruct {\n> +          struct MyInnerStruct {\n> +            int32 a;\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyInnerStruct {$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        struct MyStruct {\n> +          interface MyInterface {\n> +            MyMethod(int32 a);\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Unexpected 'interface':\\n\"\n> +        r\" *interface MyInterface {$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testMissingModuleName(self):\n> +    \"\"\"Tests an (invalid) .mojom with a missing module name.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        // Missing module name.\n> +        module ;\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Unexpected ';':\\n *module ;$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    # Another similar case, but make sure that line-number tracking/reporting\n> +    # is correct.\n> +    source2 = \"\"\"\\\n> +        module\n> +        // This line intentionally left unblank.\n> +\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyStruct {$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +  def testMultipleModuleStatements(self):\n> +    \"\"\"Tests an (invalid) .mojom with multiple module statements.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module foo;\n> +        module bar;\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Multiple \\\"module\\\" statements not \"\n> +        r\"allowed:\\n *module bar;$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testModuleStatementAfterImport(self):\n> +    \"\"\"Tests an (invalid) .mojom with a module statement after an import.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        import \"foo.mojom\";\n> +        module foo;\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: \\\"module\\\" statements must precede imports \"\n> +        r\"and definitions:\\n *module foo;$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testModuleStatementAfterDefinition(self):\n> +    \"\"\"Tests an (invalid) .mojom with a module statement after a definition.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        module foo;\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:4: Error: \\\"module\\\" statements must precede imports \"\n> +        r\"and definitions:\\n *module foo;$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testImportStatementAfterDefinition(self):\n> +    \"\"\"Tests an (invalid) .mojom with an import statement after a definition.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        import \"foo.mojom\";\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:4: Error: \\\"import\\\" statements must precede \"\n> +        r\"definitions:\\n *import \\\"foo.mojom\\\";$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testEnums(self):\n> +    \"\"\"Tests that enum statements are correctly parsed.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my_module;\n> +        enum MyEnum1 { VALUE1, VALUE2 };  // No trailing comma.\n> +        enum MyEnum2 {\n> +          VALUE1 = -1,\n> +          VALUE2 = 0,\n> +          VALUE3 = + 987,  // Check that space is allowed.\n> +          VALUE4 = 0xAF12,\n> +          VALUE5 = -0x09bcd,\n> +          VALUE6 = VALUE5,\n> +          VALUE7,  // Leave trailing comma.\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Enum(\n> +                'MyEnum1', None,\n> +                ast.EnumValueList([\n> +                    ast.EnumValue('VALUE1', None, None),\n> +                    ast.EnumValue('VALUE2', None, None)\n> +                ])),\n> +            ast.Enum(\n> +                'MyEnum2', None,\n> +                ast.EnumValueList([\n> +                    ast.EnumValue('VALUE1', None, '-1'),\n> +                    ast.EnumValue('VALUE2', None, '0'),\n> +                    ast.EnumValue('VALUE3', None, '+987'),\n> +                    ast.EnumValue('VALUE4', None, '0xAF12'),\n> +                    ast.EnumValue('VALUE5', None, '-0x09bcd'),\n> +                    ast.EnumValue('VALUE6', None, ('IDENTIFIER', 'VALUE5')),\n> +                    ast.EnumValue('VALUE7', None, None)\n> +                ]))\n> +        ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidEnumInitializers(self):\n> +    \"\"\"Tests that invalid enum initializers are correctly detected.\"\"\"\n> +\n> +    # Floating point value.\n> +    source2 = \"enum MyEnum { VALUE = 0.123 };\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '0\\.123':\\n\"\n> +        r\"enum MyEnum { VALUE = 0\\.123 };$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    # Boolean value.\n> +    source2 = \"enum MyEnum { VALUE = true };\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected 'true':\\n\"\n> +        r\"enum MyEnum { VALUE = true };$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +  def testConsts(self):\n> +    \"\"\"Tests some constants and struct members initialized with them.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        struct MyStruct {\n> +          const int8 kNumber = -1;\n> +          int8 number@0 = kNumber;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Struct(\n> +                'MyStruct', None,\n> +                ast.StructBody([\n> +                    ast.Const('kNumber', None, 'int8', '-1'),\n> +                    ast.StructField('number', None, ast.Ordinal(0), 'int8',\n> +                                    ('IDENTIFIER', 'kNumber'))\n> +                ]))\n> +        ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testNoConditionals(self):\n> +    \"\"\"Tests that ?: is not allowed.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        enum MyEnum {\n> +          MY_ENUM_1 = 1 ? 2 : 3\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected '\\?':\\n\"\n> +        r\" *MY_ENUM_1 = 1 \\? 2 : 3$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testSimpleOrdinals(self):\n> +    \"\"\"Tests that (valid) ordinal values are scanned correctly.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        // This isn't actually valid .mojom, but the problem (missing ordinals)\n> +        // should be handled at a different level.\n> +        struct MyStruct {\n> +          int32 a0@0;\n> +          int32 a1@1;\n> +          int32 a2@2;\n> +          int32 a9@9;\n> +          int32 a10 @10;\n> +          int32 a11 @11;\n> +          int32 a29 @29;\n> +          int32 a1234567890 @1234567890;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Struct(\n> +                'MyStruct', None,\n> +                ast.StructBody([\n> +                    ast.StructField('a0', None, ast.Ordinal(0), 'int32', None),\n> +                    ast.StructField('a1', None, ast.Ordinal(1), 'int32', None),\n> +                    ast.StructField('a2', None, ast.Ordinal(2), 'int32', None),\n> +                    ast.StructField('a9', None, ast.Ordinal(9), 'int32', None),\n> +                    ast.StructField('a10', None, ast.Ordinal(10), 'int32',\n> +                                    None),\n> +                    ast.StructField('a11', None, ast.Ordinal(11), 'int32',\n> +                                    None),\n> +                    ast.StructField('a29', None, ast.Ordinal(29), 'int32',\n> +                                    None),\n> +                    ast.StructField('a1234567890', None,\n> +                                    ast.Ordinal(1234567890), 'int32', None)\n> +                ]))\n> +        ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidOrdinals(self):\n> +    \"\"\"Tests that (lexically) invalid ordinals are correctly detected.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        module my_module;\n> +\n> +        struct MyStruct {\n> +          int32 a_missing@;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        lexer.LexError, r\"^my_file\\.mojom:4: Error: Missing ordinal value$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        module my_module;\n> +\n> +        struct MyStruct {\n> +          int32 a_octal@01;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        lexer.LexError, r\"^my_file\\.mojom:4: Error: \"\n> +        r\"Octal and hexadecimal ordinal values not allowed$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        module my_module; struct MyStruct { int32 a_invalid_octal@08; };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n> +        r\"Octal and hexadecimal ordinal values not allowed$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +    source4 = \"module my_module; struct MyStruct { int32 a_hex@0x1aB9; };\"\n> +    with self.assertRaisesRegexp(\n> +        lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n> +        r\"Octal and hexadecimal ordinal values not allowed$\"):\n> +      parser.Parse(source4, \"my_file.mojom\")\n> +\n> +    source5 = \"module my_module; struct MyStruct { int32 a_hex@0X0; };\"\n> +    with self.assertRaisesRegexp(\n> +        lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n> +        r\"Octal and hexadecimal ordinal values not allowed$\"):\n> +      parser.Parse(source5, \"my_file.mojom\")\n> +\n> +    source6 = \"\"\"\\\n> +        struct MyStruct {\n> +          int32 a_too_big@999999999999;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: \"\n> +        r\"Ordinal value 999999999999 too large:\\n\"\n> +        r\" *int32 a_too_big@999999999999;$\"):\n> +      parser.Parse(source6, \"my_file.mojom\")\n> +\n> +  def testNestedNamespace(self):\n> +    \"\"\"Tests that \"nested\" namespaces work.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        module my.mod;\n> +\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my.mod'), None), ast.ImportList(), [\n> +            ast.Struct(\n> +                'MyStruct', None,\n> +                ast.StructBody(ast.StructField('a', None, None, 'int32', None)))\n> +        ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testValidHandleTypes(self):\n> +    \"\"\"Tests (valid) handle types.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          handle a;\n> +          handle<data_pipe_consumer> b;\n> +          handle <data_pipe_producer> c;\n> +          handle < message_pipe > d;\n> +          handle\n> +            < shared_buffer\n> +            > e;\n> +          handle\n> +            <platform\n> +\n> +            > f;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('a', None, None, 'handle', None),\n> +                ast.StructField('b', None, None, 'handle<data_pipe_consumer>',\n> +                                None),\n> +                ast.StructField('c', None, None, 'handle<data_pipe_producer>',\n> +                                None),\n> +                ast.StructField('d', None, None, 'handle<message_pipe>', None),\n> +                ast.StructField('e', None, None, 'handle<shared_buffer>', None),\n> +                ast.StructField('f', None, None, 'handle<platform>', None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidHandleType(self):\n> +    \"\"\"Tests an invalid (unknown) handle type.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          handle<wtf_is_this> foo;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: \"\n> +        r\"Invalid handle type 'wtf_is_this':\\n\"\n> +        r\" *handle<wtf_is_this> foo;$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testValidDefaultValues(self):\n> +    \"\"\"Tests default values that are valid (to the parser).\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          int16 a0 = 0;\n> +          uint16 a1 = 0x0;\n> +          uint16 a2 = 0x00;\n> +          uint16 a3 = 0x01;\n> +          uint16 a4 = 0xcd;\n> +          int32 a5 = 12345;\n> +          int64 a6 = -12345;\n> +          int64 a7 = +12345;\n> +          uint32 a8 = 0x12cd3;\n> +          uint32 a9 = -0x12cD3;\n> +          uint32 a10 = +0x12CD3;\n> +          bool a11 = true;\n> +          bool a12 = false;\n> +          float a13 = 1.2345;\n> +          float a14 = -1.2345;\n> +          float a15 = +1.2345;\n> +          float a16 = 123.;\n> +          float a17 = .123;\n> +          double a18 = 1.23E10;\n> +          double a19 = 1.E-10;\n> +          double a20 = .5E+10;\n> +          double a21 = -1.23E10;\n> +          double a22 = +.123E10;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('a0', None, None, 'int16', '0'),\n> +                ast.StructField('a1', None, None, 'uint16', '0x0'),\n> +                ast.StructField('a2', None, None, 'uint16', '0x00'),\n> +                ast.StructField('a3', None, None, 'uint16', '0x01'),\n> +                ast.StructField('a4', None, None, 'uint16', '0xcd'),\n> +                ast.StructField('a5', None, None, 'int32', '12345'),\n> +                ast.StructField('a6', None, None, 'int64', '-12345'),\n> +                ast.StructField('a7', None, None, 'int64', '+12345'),\n> +                ast.StructField('a8', None, None, 'uint32', '0x12cd3'),\n> +                ast.StructField('a9', None, None, 'uint32', '-0x12cD3'),\n> +                ast.StructField('a10', None, None, 'uint32', '+0x12CD3'),\n> +                ast.StructField('a11', None, None, 'bool', 'true'),\n> +                ast.StructField('a12', None, None, 'bool', 'false'),\n> +                ast.StructField('a13', None, None, 'float', '1.2345'),\n> +                ast.StructField('a14', None, None, 'float', '-1.2345'),\n> +                ast.StructField('a15', None, None, 'float', '+1.2345'),\n> +                ast.StructField('a16', None, None, 'float', '123.'),\n> +                ast.StructField('a17', None, None, 'float', '.123'),\n> +                ast.StructField('a18', None, None, 'double', '1.23E10'),\n> +                ast.StructField('a19', None, None, 'double', '1.E-10'),\n> +                ast.StructField('a20', None, None, 'double', '.5E+10'),\n> +                ast.StructField('a21', None, None, 'double', '-1.23E10'),\n> +                ast.StructField('a22', None, None, 'double', '+.123E10')\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testValidFixedSizeArray(self):\n> +    \"\"\"Tests parsing a fixed size array.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          array<int32> normal_array;\n> +          array<int32, 1> fixed_size_array_one_entry;\n> +          array<int32, 10> fixed_size_array_ten_entries;\n> +          array<array<array<int32, 1>>, 2> nested_arrays;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('normal_array', None, None, 'int32[]', None),\n> +                ast.StructField('fixed_size_array_one_entry', None, None,\n> +                                'int32[1]', None),\n> +                ast.StructField('fixed_size_array_ten_entries', None, None,\n> +                                'int32[10]', None),\n> +                ast.StructField('nested_arrays', None, None, 'int32[1][][2]',\n> +                                None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testValidNestedArray(self):\n> +    \"\"\"Tests parsing a nested array.\"\"\"\n> +\n> +    source = \"struct MyStruct { array<array<int32>> nested_array; };\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody(\n> +                ast.StructField('nested_array', None, None, 'int32[][]', None)))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidFixedArraySize(self):\n> +    \"\"\"Tests that invalid fixed array bounds are correctly detected.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        struct MyStruct {\n> +          array<int32, 0> zero_size_array;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Fixed array size 0 invalid:\\n\"\n> +        r\" *array<int32, 0> zero_size_array;$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        struct MyStruct {\n> +          array<int32, 999999999999> too_big_array;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Fixed array size 999999999999 invalid:\\n\"\n> +        r\" *array<int32, 999999999999> too_big_array;$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        struct MyStruct {\n> +          array<int32, abcdefg> not_a_number;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'abcdefg':\\n\"\n> +        r\" *array<int32, abcdefg> not_a_number;\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testValidAssociativeArrays(self):\n> +    \"\"\"Tests that we can parse valid associative array structures.\"\"\"\n> +\n> +    source1 = \"struct MyStruct { map<string, uint8> data; };\"\n> +    expected1 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody(\n> +                [ast.StructField('data', None, None, 'uint8{string}', None)]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n> +\n> +    source2 = \"interface MyInterface { MyMethod(map<string, uint8> a); };\"\n> +    expected2 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody(\n> +                ast.Method(\n> +                    'MyMethod', None, None,\n> +                    ast.ParameterList(\n> +                        ast.Parameter('a', None, None, 'uint8{string}')),\n> +                    None)))\n> +    ])\n> +    self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n> +\n> +    source3 = \"struct MyStruct { map<string, array<uint8>> data; };\"\n> +    expected3 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody(\n> +                [ast.StructField('data', None, None, 'uint8[]{string}', None)]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n> +\n> +  def testValidMethod(self):\n> +    \"\"\"Tests parsing method declarations.\"\"\"\n> +\n> +    source1 = \"interface MyInterface { MyMethod(int32 a); };\"\n> +    expected1 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody(\n> +                ast.Method(\n> +                    'MyMethod', None, None,\n> +                    ast.ParameterList(ast.Parameter('a', None, None, 'int32')),\n> +                    None)))\n> +    ])\n> +    self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n> +\n> +    source2 = \"\"\"\\\n> +        interface MyInterface {\n> +          MyMethod1@0(int32 a@0, int64 b@1);\n> +          MyMethod2@1() => ();\n> +        };\n> +        \"\"\"\n> +    expected2 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody([\n> +                ast.Method(\n> +                    'MyMethod1', None, ast.Ordinal(0),\n> +                    ast.ParameterList([\n> +                        ast.Parameter('a', None, ast.Ordinal(0), 'int32'),\n> +                        ast.Parameter('b', None, ast.Ordinal(1), 'int64')\n> +                    ]), None),\n> +                ast.Method('MyMethod2', None, ast.Ordinal(1),\n> +                           ast.ParameterList(), ast.ParameterList())\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n> +\n> +    source3 = \"\"\"\\\n> +        interface MyInterface {\n> +          MyMethod(string a) => (int32 a, bool b);\n> +        };\n> +        \"\"\"\n> +    expected3 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody(\n> +                ast.Method(\n> +                    'MyMethod', None, None,\n> +                    ast.ParameterList(ast.Parameter('a', None, None, 'string')),\n> +                    ast.ParameterList([\n> +                        ast.Parameter('a', None, None, 'int32'),\n> +                        ast.Parameter('b', None, None, 'bool')\n> +                    ]))))\n> +    ])\n> +    self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n> +\n> +  def testInvalidMethods(self):\n> +    \"\"\"Tests that invalid method declarations are correctly detected.\"\"\"\n> +\n> +    # No trailing commas.\n> +    source1 = \"\"\"\\\n> +        interface MyInterface {\n> +          MyMethod(string a,);\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\)':\\n\"\n> +        r\" *MyMethod\\(string a,\\);$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    # No leading commas.\n> +    source2 = \"\"\"\\\n> +        interface MyInterface {\n> +          MyMethod(, string a);\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected ',':\\n\"\n> +        r\" *MyMethod\\(, string a\\);$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +  def testValidInterfaceDefinitions(self):\n> +    \"\"\"Tests all types of definitions that can occur in an interface.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        interface MyInterface {\n> +          enum MyEnum { VALUE };\n> +          const int32 kMyConst = 123;\n> +          MyMethod(int32 x) => (MyEnum y);\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody([\n> +                ast.Enum('MyEnum', None,\n> +                         ast.EnumValueList(ast.EnumValue('VALUE', None, None))),\n> +                ast.Const('kMyConst', None, 'int32', '123'),\n> +                ast.Method(\n> +                    'MyMethod', None, None,\n> +                    ast.ParameterList(ast.Parameter('x', None, None, 'int32')),\n> +                    ast.ParameterList(ast.Parameter('y', None, None, 'MyEnum')))\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidInterfaceDefinitions(self):\n> +    \"\"\"Tests that definitions that aren't allowed in an interface are correctly\n> +    detected.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        interface MyInterface {\n> +          struct MyStruct {\n> +            int32 a;\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyStruct {$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        interface MyInterface {\n> +          interface MyInnerInterface {\n> +            MyMethod(int32 x);\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Unexpected 'interface':\\n\"\n> +        r\" *interface MyInnerInterface {$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        interface MyInterface {\n> +          int32 my_field;\n> +        };\n> +        \"\"\"\n> +    # The parser thinks that \"int32\" is a plausible name for a method, so it's\n> +    # \"my_field\" that gives it away.\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'my_field':\\n\"\n> +        r\" *int32 my_field;$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testValidAttributes(self):\n> +    \"\"\"Tests parsing attributes (and attribute lists).\"\"\"\n> +\n> +    # Note: We use structs because they have (optional) attribute lists.\n> +\n> +    # Empty attribute list.\n> +    source1 = \"[] struct MyStruct {};\"\n> +    expected1 = ast.Mojom(\n> +        None, ast.ImportList(),\n> +        [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())])\n> +    self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n> +\n> +    # One-element attribute list, with name value.\n> +    source2 = \"[MyAttribute=MyName] struct MyStruct {};\"\n> +    expected2 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct('MyStruct',\n> +                   ast.AttributeList(ast.Attribute(\"MyAttribute\", \"MyName\")),\n> +                   ast.StructBody())\n> +    ])\n> +    self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n> +\n> +    # Two-element attribute list, with one string value and one integer value.\n> +    source3 = \"[MyAttribute1 = \\\"hello\\\", MyAttribute2 = 5] struct MyStruct {};\"\n> +    expected3 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct',\n> +            ast.AttributeList([\n> +                ast.Attribute(\"MyAttribute1\", \"hello\"),\n> +                ast.Attribute(\"MyAttribute2\", 5)\n> +            ]), ast.StructBody())\n> +    ])\n> +    self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n> +\n> +    # Various places that attribute list is allowed.\n> +    source4 = \"\"\"\\\n> +        [Attr0=0] module my_module;\n> +\n> +        [Attr1=1] import \"my_import\";\n> +\n> +        [Attr2=2] struct MyStruct {\n> +          [Attr3=3] int32 a;\n> +        };\n> +        [Attr4=4] union MyUnion {\n> +          [Attr5=5] int32 a;\n> +        };\n> +        [Attr6=6] enum MyEnum {\n> +          [Attr7=7] a\n> +        };\n> +        [Attr8=8] interface MyInterface {\n> +          [Attr9=9] MyMethod([Attr10=10] int32 a) => ([Attr11=11] bool b);\n> +        };\n> +        [Attr12=12] const double kMyConst = 1.23;\n> +        \"\"\"\n> +    expected4 = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'),\n> +                   ast.AttributeList([ast.Attribute(\"Attr0\", 0)])),\n> +        ast.ImportList(\n> +            ast.Import(\n> +                ast.AttributeList([ast.Attribute(\"Attr1\", 1)]), \"my_import\")),\n> +        [\n> +            ast.Struct(\n> +                'MyStruct', ast.AttributeList(ast.Attribute(\"Attr2\", 2)),\n> +                ast.StructBody(\n> +                    ast.StructField(\n> +                        'a', ast.AttributeList([ast.Attribute(\"Attr3\", 3)]),\n> +                        None, 'int32', None))),\n> +            ast.Union(\n> +                'MyUnion', ast.AttributeList(ast.Attribute(\"Attr4\", 4)),\n> +                ast.UnionBody(\n> +                    ast.UnionField(\n> +                        'a', ast.AttributeList([ast.Attribute(\"Attr5\", 5)]),\n> +                        None, 'int32'))),\n> +            ast.Enum(\n> +                'MyEnum', ast.AttributeList(ast.Attribute(\"Attr6\", 6)),\n> +                ast.EnumValueList(\n> +                    ast.EnumValue(\n> +                        'VALUE', ast.AttributeList([ast.Attribute(\"Attr7\", 7)]),\n> +                        None))),\n> +            ast.Interface(\n> +                'MyInterface', ast.AttributeList(ast.Attribute(\"Attr8\", 8)),\n> +                ast.InterfaceBody(\n> +                    ast.Method(\n> +                        'MyMethod', ast.AttributeList(\n> +                            ast.Attribute(\"Attr9\", 9)), None,\n> +                        ast.ParameterList(\n> +                            ast.Parameter(\n> +                                'a',\n> +                                ast.AttributeList([ast.Attribute(\"Attr10\", 10)\n> +                                                   ]), None, 'int32')),\n> +                        ast.ParameterList(\n> +                            ast.Parameter(\n> +                                'b',\n> +                                ast.AttributeList([ast.Attribute(\"Attr11\", 11)\n> +                                                   ]), None, 'bool'))))),\n> +            ast.Const('kMyConst', ast.AttributeList(\n> +                ast.Attribute(\"Attr12\", 12)), 'double', '1.23')\n> +        ])\n> +    self.assertEquals(parser.Parse(source4, \"my_file.mojom\"), expected4)\n> +\n> +    # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|\n> +    # literal (non-name) values, which is extremely dubious.)\n> +\n> +  def testInvalidAttributes(self):\n> +    \"\"\"Tests that invalid attributes and attribute lists are correctly\n> +    detected.\"\"\"\n> +\n> +    # Trailing commas not allowed.\n> +    source1 = \"[MyAttribute=MyName,] struct MyStruct {};\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '\\]':\\n\"\n> +        r\"\\[MyAttribute=MyName,\\] struct MyStruct {};$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    # Missing value.\n> +    source2 = \"[MyAttribute=] struct MyStruct {};\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '\\]':\\n\"\n> +        r\"\\[MyAttribute=\\] struct MyStruct {};$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    # Missing key.\n> +    source3 = \"[=MyName] struct MyStruct {};\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '=':\\n\"\n> +        r\"\\[=MyName\\] struct MyStruct {};$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testValidImports(self):\n> +    \"\"\"Tests parsing import statements.\"\"\"\n> +\n> +    # One import (no module statement).\n> +    source1 = \"import \\\"somedir/my.mojom\\\";\"\n> +    expected1 = ast.Mojom(None,\n> +                          ast.ImportList(ast.Import(None, \"somedir/my.mojom\")),\n> +                          [])\n> +    self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n> +\n> +    # Two imports (no module statement).\n> +    source2 = \"\"\"\\\n> +        import \"somedir/my1.mojom\";\n> +        import \"somedir/my2.mojom\";\n> +        \"\"\"\n> +    expected2 = ast.Mojom(\n> +        None,\n> +        ast.ImportList([\n> +            ast.Import(None, \"somedir/my1.mojom\"),\n> +            ast.Import(None, \"somedir/my2.mojom\")\n> +        ]), [])\n> +    self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n> +\n> +    # Imports with module statement.\n> +    source3 = \"\"\"\\\n> +        module my_module;\n> +        import \"somedir/my1.mojom\";\n> +        import \"somedir/my2.mojom\";\n> +        \"\"\"\n> +    expected3 = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None),\n> +        ast.ImportList([\n> +            ast.Import(None, \"somedir/my1.mojom\"),\n> +            ast.Import(None, \"somedir/my2.mojom\")\n> +        ]), [])\n> +    self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n> +\n> +  def testInvalidImports(self):\n> +    \"\"\"Tests that invalid import statements are correctly detected.\"\"\"\n> +\n> +    source1 = \"\"\"\\\n> +        // Make the error occur on line 2.\n> +        import invalid\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'invalid':\\n\"\n> +        r\" *import invalid$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        import  // Missing string.\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyStruct {$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        import \"foo.mojom\"  // Missing semicolon.\n> +        struct MyStruct {\n> +          int32 a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyStruct {$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testValidNullableTypes(self):\n> +    \"\"\"Tests parsing nullable types.\"\"\"\n> +\n> +    source = \"\"\"\\\n> +        struct MyStruct {\n> +          int32? a;  // This is actually invalid, but handled at a different\n> +                     // level.\n> +          string? b;\n> +          array<int32> ? c;\n> +          array<string ? > ? d;\n> +          array<array<int32>?>? e;\n> +          array<int32, 1>? f;\n> +          array<string?, 1>? g;\n> +          some_struct? h;\n> +          handle? i;\n> +          handle<data_pipe_consumer>? j;\n> +          handle<data_pipe_producer>? k;\n> +          handle<message_pipe>? l;\n> +          handle<shared_buffer>? m;\n> +          some_interface&? n;\n> +          handle<platform>? o;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('a', None, None, 'int32?', None),\n> +                ast.StructField('b', None, None, 'string?', None),\n> +                ast.StructField('c', None, None, 'int32[]?', None),\n> +                ast.StructField('d', None, None, 'string?[]?', None),\n> +                ast.StructField('e', None, None, 'int32[]?[]?', None),\n> +                ast.StructField('f', None, None, 'int32[1]?', None),\n> +                ast.StructField('g', None, None, 'string?[1]?', None),\n> +                ast.StructField('h', None, None, 'some_struct?', None),\n> +                ast.StructField('i', None, None, 'handle?', None),\n> +                ast.StructField('j', None, None, 'handle<data_pipe_consumer>?',\n> +                                None),\n> +                ast.StructField('k', None, None, 'handle<data_pipe_producer>?',\n> +                                None),\n> +                ast.StructField('l', None, None, 'handle<message_pipe>?', None),\n> +                ast.StructField('m', None, None, 'handle<shared_buffer>?',\n> +                                None),\n> +                ast.StructField('n', None, None, 'some_interface&?', None),\n> +                ast.StructField('o', None, None, 'handle<platform>?', None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n> +\n> +  def testInvalidNullableTypes(self):\n> +    \"\"\"Tests that invalid nullable types are correctly detected.\"\"\"\n> +    source1 = \"\"\"\\\n> +        struct MyStruct {\n> +          string?? a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\?':\\n\"\n> +        r\" *string\\?\\? a;$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        struct MyStruct {\n> +          handle?<data_pipe_consumer> a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '<':\\n\"\n> +        r\" *handle\\?<data_pipe_consumer> a;$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        struct MyStruct {\n> +          some_interface?& a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '&':\\n\"\n> +        r\" *some_interface\\?& a;$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +  def testSimpleUnion(self):\n> +    \"\"\"Tests a simple .mojom source that just defines a union.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          int32 a;\n> +          double b;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Union(\n> +                'MyUnion', None,\n> +                ast.UnionBody([\n> +                    ast.UnionField('a', None, None, 'int32'),\n> +                    ast.UnionField('b', None, None, 'double')\n> +                ]))\n> +        ])\n> +    actual = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertEquals(actual, expected)\n> +\n> +  def testUnionWithOrdinals(self):\n> +    \"\"\"Test that ordinals are assigned to fields.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          int32 a @10;\n> +          double b @30;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Union(\n> +                'MyUnion', None,\n> +                ast.UnionBody([\n> +                    ast.UnionField('a', None, ast.Ordinal(10), 'int32'),\n> +                    ast.UnionField('b', None, ast.Ordinal(30), 'double')\n> +                ]))\n> +        ])\n> +    actual = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertEquals(actual, expected)\n> +\n> +  def testUnionWithStructMembers(self):\n> +    \"\"\"Test that struct members are accepted.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          SomeStruct s;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Union(\n> +                'MyUnion', None,\n> +                ast.UnionBody([ast.UnionField('s', None, None, 'SomeStruct')]))\n> +        ])\n> +    actual = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertEquals(actual, expected)\n> +\n> +  def testUnionWithArrayMember(self):\n> +    \"\"\"Test that array members are accepted.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          array<int32> a;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Union(\n> +                'MyUnion', None,\n> +                ast.UnionBody([ast.UnionField('a', None, None, 'int32[]')]))\n> +        ])\n> +    actual = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertEquals(actual, expected)\n> +\n> +  def testUnionWithMapMember(self):\n> +    \"\"\"Test that map members are accepted.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          map<int32, string> m;\n> +        };\n> +        \"\"\"\n> +    expected = ast.Mojom(\n> +        ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n> +            ast.Union(\n> +                'MyUnion', None,\n> +                ast.UnionBody(\n> +                    [ast.UnionField('m', None, None, 'string{int32}')]))\n> +        ])\n> +    actual = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertEquals(actual, expected)\n> +\n> +  def testUnionDisallowNestedStruct(self):\n> +    \"\"\"Tests that structs cannot be nested in unions.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          struct MyStruct {\n> +            int32 a;\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'struct':\\n\"\n> +        r\" *struct MyStruct {$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testUnionDisallowNestedInterfaces(self):\n> +    \"\"\"Tests that interfaces cannot be nested in unions.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          interface MyInterface {\n> +            MyMethod(int32 a);\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:4: Error: Unexpected 'interface':\\n\"\n> +        r\" *interface MyInterface {$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testUnionDisallowNestedUnion(self):\n> +    \"\"\"Tests that unions cannot be nested in unions.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          union MyOtherUnion {\n> +            int32 a;\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'union':\\n\"\n> +        r\" *union MyOtherUnion {$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testUnionDisallowNestedEnum(self):\n> +    \"\"\"Tests that enums cannot be nested in unions.\"\"\"\n> +    source = \"\"\"\\\n> +        module my_module;\n> +\n> +        union MyUnion {\n> +          enum MyEnum {\n> +            A,\n> +          };\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'enum':\\n\"\n> +        r\" *enum MyEnum {$\"):\n> +      parser.Parse(source, \"my_file.mojom\")\n> +\n> +  def testValidAssociatedKinds(self):\n> +    \"\"\"Tests parsing associated interfaces and requests.\"\"\"\n> +    source1 = \"\"\"\\\n> +        struct MyStruct {\n> +          associated MyInterface a;\n> +          associated MyInterface& b;\n> +          associated MyInterface? c;\n> +          associated MyInterface&? d;\n> +        };\n> +        \"\"\"\n> +    expected1 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Struct(\n> +            'MyStruct', None,\n> +            ast.StructBody([\n> +                ast.StructField('a', None, None, 'asso<MyInterface>', None),\n> +                ast.StructField('b', None, None, 'asso<MyInterface&>', None),\n> +                ast.StructField('c', None, None, 'asso<MyInterface>?', None),\n> +                ast.StructField('d', None, None, 'asso<MyInterface&>?', None)\n> +            ]))\n> +    ])\n> +    self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n> +\n> +    source2 = \"\"\"\\\n> +        interface MyInterface {\n> +          MyMethod(associated A a) =>(associated B& b);\n> +        };\"\"\"\n> +    expected2 = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Interface(\n> +            'MyInterface', None,\n> +            ast.InterfaceBody(\n> +                ast.Method(\n> +                    'MyMethod', None, None,\n> +                    ast.ParameterList(\n> +                        ast.Parameter('a', None, None, 'asso<A>')),\n> +                    ast.ParameterList(\n> +                        ast.Parameter('b', None, None, 'asso<B&>')))))\n> +    ])\n> +    self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n> +\n> +  def testInvalidAssociatedKinds(self):\n> +    \"\"\"Tests that invalid associated interfaces and requests are correctly\n> +    detected.\"\"\"\n> +    source1 = \"\"\"\\\n> +        struct MyStruct {\n> +          associated associated SomeInterface a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError,\n> +        r\"^my_file\\.mojom:2: Error: Unexpected 'associated':\\n\"\n> +        r\" *associated associated SomeInterface a;$\"):\n> +      parser.Parse(source1, \"my_file.mojom\")\n> +\n> +    source2 = \"\"\"\\\n> +        struct MyStruct {\n> +          associated handle a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'handle':\\n\"\n> +        r\" *associated handle a;$\"):\n> +      parser.Parse(source2, \"my_file.mojom\")\n> +\n> +    source3 = \"\"\"\\\n> +        struct MyStruct {\n> +          associated? MyInterface& a;\n> +        };\n> +        \"\"\"\n> +    with self.assertRaisesRegexp(\n> +        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\?':\\n\"\n> +        r\" *associated\\? MyInterface& a;$\"):\n> +      parser.Parse(source3, \"my_file.mojom\")\n> +\n> +\n> +if __name__ == \"__main__\":\n> +  unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> new file mode 100755\n> index 00000000..12adbfb9\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> @@ -0,0 +1,361 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Parses mojom IDL files.\n> +\n> +This script parses one or more input mojom files and produces corresponding\n> +module files fully describing the definitions contained within each mojom. The\n> +module data is pickled and can be easily consumed by other tools to, e.g.,\n> +generate usable language bindings.\n> +\"\"\"\n> +\n> +import argparse\n> +import codecs\n> +import errno\n> +import json\n> +import os\n> +import os.path\n> +import sys\n> +from collections import defaultdict\n> +\n> +from mojom.generate import module\n> +from mojom.generate import translate\n> +from mojom.parse import parser\n> +from mojom.parse import conditional_features\n> +\n> +\n> +def _ResolveRelativeImportPath(path, roots):\n> +  \"\"\"Attempts to resolve a relative import path against a set of possible roots.\n> +\n> +  Args:\n> +    path: The relative import path to resolve.\n> +    roots: A list of absolute paths which will be checked in descending length\n> +        order for a match against path.\n> +\n> +  Returns:\n> +    A normalized absolute path combining one of the roots with the input path if\n> +    and only if such a file exists.\n> +\n> +  Raises:\n> +    ValueError: The path could not be resolved against any of the given roots.\n> +  \"\"\"\n> +  for root in reversed(sorted(roots, key=len)):\n> +    abs_path = os.path.join(root, path)\n> +    if os.path.isfile(abs_path):\n> +      return os.path.normcase(os.path.normpath(abs_path))\n> +\n> +  raise ValueError('\"%s\" does not exist in any of %s' % (path, roots))\n> +\n> +\n> +def _RebaseAbsolutePath(path, roots):\n> +  \"\"\"Rewrites an absolute file path as relative to an absolute directory path in\n> +  roots.\n> +\n> +  Args:\n> +    path: The absolute path of an existing file.\n> +    roots: A list of absolute directory paths. The given path argument must fall\n> +        within one of these directories.\n> +\n> +  Returns:\n> +    A path equivalent to the input path, but relative to one of the provided\n> +    roots. If the input path falls within multiple roots, the longest root is\n> +    chosen (and thus the shortest relative path is returned).\n> +\n> +    Paths returned by this method always use forward slashes as a separator to\n> +    mirror mojom import syntax.\n> +\n> +  Raises:\n> +    ValueError if the given path does not fall within any of the listed roots.\n> +  \"\"\"\n> +  assert os.path.isabs(path)\n> +  assert os.path.isfile(path)\n> +  assert all(map(os.path.isabs, roots))\n> +\n> +  sorted_roots = list(reversed(sorted(roots, key=len)))\n> +\n> +  def try_rebase_path(path, root):\n> +    head, rebased_path = os.path.split(path)\n> +    while head != root:\n> +      head, tail = os.path.split(head)\n> +      if not tail:\n> +        return None\n> +      rebased_path = os.path.join(tail, rebased_path)\n> +    return rebased_path\n> +\n> +  for root in sorted_roots:\n> +    relative_path = try_rebase_path(path, root)\n> +    if relative_path:\n> +      # TODO(crbug.com/953884): Use pathlib for this kind of thing once we're\n> +      # fully migrated to Python 3.\n> +      return relative_path.replace('\\\\', '/')\n> +\n> +  raise ValueError('%s does not fall within any of %s' % (path, sorted_roots))\n> +\n> +\n> +def _GetModuleFilename(mojom_filename):\n> +  return mojom_filename + '-module'\n> +\n> +\n> +def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,\n> +                       dependencies, loaded_modules):\n> +  \"\"\"Recursively ensures that a module and its dependencies are loaded.\n> +\n> +  Args:\n> +    mojom_abspath: An absolute file path pointing to a mojom file to load.\n> +    module_path: The relative path used to identify mojom_abspath.\n> +    abs_paths: A mapping from module paths to absolute file paths for all\n> +        inputs given to this execution of the script.\n> +    asts: A map from each input mojom's absolute path to its parsed AST.\n> +    dependencies: A mapping of which input mojoms depend on each other, indexed\n> +        by absolute file path.\n> +    loaded_modules: A mapping of all modules loaded so far, including non-input\n> +        modules that were pulled in as transitive dependencies of the inputs.\n> +    import_set: The working set of mojom imports processed so far in this\n> +        call stack. Used to detect circular dependencies.\n> +    import_stack: An ordered list of imports processed so far in this call\n> +        stack. Used to report circular dependencies.\n> +\n> +  Returns:\n> +    None\n> +\n> +    On return, loaded_modules will be populated with the loaded input mojom's\n> +    Module as well as the Modules of all of its transitive dependencies.\"\"\"\n> +\n> +  if mojom_abspath in loaded_modules:\n> +    # Already done.\n> +    return\n> +\n> +  for dep_abspath, dep_path in dependencies[mojom_abspath]:\n> +    if dep_abspath not in loaded_modules:\n> +      _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,\n> +                         loaded_modules)\n> +\n> +  imports = {}\n> +  for imp in asts[mojom_abspath].import_list:\n> +    path = imp.import_filename\n> +    imports[path] = loaded_modules[abs_paths[path]]\n> +  loaded_modules[mojom_abspath] = translate.OrderedModule(\n> +      asts[mojom_abspath], module_path, imports)\n> +\n> +\n> +def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n> +  allowed_imports = set()\n> +  processed_deps = set()\n> +\n> +  def collect(metadata_filename):\n> +    processed_deps.add(metadata_filename)\n> +    with open(metadata_filename) as f:\n> +      metadata = json.load(f)\n> +      allowed_imports.update(\n> +          map(os.path.normcase, map(os.path.normpath, metadata['sources'])))\n> +      for dep_metadata in metadata['deps']:\n> +        if dep_metadata not in processed_deps:\n> +          collect(dep_metadata)\n> +\n> +  collect(build_metadata_filename)\n> +  return allowed_imports\n> +\n> +\n> +def _ParseMojoms(mojom_files,\n> +                 input_root_paths,\n> +                 output_root_path,\n> +                 enabled_features,\n> +                 allowed_imports=None):\n> +  \"\"\"Parses a set of mojom files and produces serialized module outputs.\n> +\n> +  Args:\n> +    mojom_files: A list of mojom files to process. Paths must be absolute paths\n> +        which fall within one of the input or output root paths.\n> +    input_root_paths: A list of absolute filesystem paths which may be used to\n> +        resolve relative mojom file paths.\n> +    output_root_path: An absolute filesystem path which will service as the root\n> +        for all emitted artifacts. Artifacts produced from a given mojom file\n> +        are based on the mojom's relative path, rebased onto this path.\n> +        Additionally, the script expects this root to contain already-generated\n> +        modules for any transitive dependencies not listed in mojom_files.\n> +    enabled_features: A list of enabled feature names, controlling which AST\n> +        nodes are filtered by [EnableIf] attributes.\n> +\n> +  Returns:\n> +    None.\n> +\n> +    Upon completion, a mojom-module file will be saved for each input mojom.\n> +  \"\"\"\n> +  assert input_root_paths\n> +  assert output_root_path\n> +\n> +  loaded_mojom_asts = {}\n> +  loaded_modules = {}\n> +  input_dependencies = defaultdict(set)\n> +  mojom_files_to_parse = dict((os.path.normcase(abs_path),\n> +                               _RebaseAbsolutePath(abs_path, input_root_paths))\n> +                              for abs_path in mojom_files)\n> +  abs_paths = dict(\n> +      (path, abs_path) for abs_path, path in mojom_files_to_parse.items())\n> +  for mojom_abspath, _ in mojom_files_to_parse.items():\n> +    with codecs.open(mojom_abspath, encoding='utf-8') as f:\n> +      ast = parser.Parse(''.join(f.readlines()), mojom_abspath)\n> +      conditional_features.RemoveDisabledDefinitions(ast, enabled_features)\n> +      loaded_mojom_asts[mojom_abspath] = ast\n> +      invalid_imports = []\n> +      for imp in ast.import_list:\n> +        import_abspath = _ResolveRelativeImportPath(imp.import_filename,\n> +                                                    input_root_paths)\n> +        if allowed_imports and import_abspath not in allowed_imports:\n> +          invalid_imports.append(imp.import_filename)\n> +\n> +        abs_paths[imp.import_filename] = import_abspath\n> +        if import_abspath in mojom_files_to_parse:\n> +          # This import is in the input list, so we're going to translate it\n> +          # into a module below; however it's also a dependency of another input\n> +          # module. We retain record of dependencies to help with input\n> +          # processing later.\n> +          input_dependencies[mojom_abspath].add((import_abspath,\n> +                                                 imp.import_filename))\n> +        else:\n> +          # We have an import that isn't being parsed right now. It must already\n> +          # be parsed and have a module file sitting in a corresponding output\n> +          # location.\n> +          module_path = _GetModuleFilename(imp.import_filename)\n> +          module_abspath = _ResolveRelativeImportPath(module_path,\n> +                                                      [output_root_path])\n> +          with open(module_abspath, 'rb') as module_file:\n> +            loaded_modules[import_abspath] = module.Module.Load(module_file)\n> +\n> +      if invalid_imports:\n> +        raise ValueError(\n> +            '\\nThe file %s imports the following files not allowed by build '\n> +            'dependencies:\\n\\n%s\\n' % (mojom_abspath,\n> +                                       '\\n'.join(invalid_imports)))\n> +\n> +\n> +  # At this point all transitive imports not listed as inputs have been loaded\n> +  # and we have a complete dependency tree of the unprocessed inputs. Now we can\n> +  # load all the inputs, resolving dependencies among them recursively as we go.\n> +  num_existing_modules_loaded = len(loaded_modules)\n> +  for mojom_abspath, mojom_path in mojom_files_to_parse.items():\n> +    _EnsureInputLoaded(mojom_abspath, mojom_path, abs_paths, loaded_mojom_asts,\n> +                       input_dependencies, loaded_modules)\n> +  assert (num_existing_modules_loaded +\n> +          len(mojom_files_to_parse) == len(loaded_modules))\n> +\n> +  # Now we have fully translated modules for every input and every transitive\n> +  # dependency. We can dump the modules to disk for other tools to use.\n> +  for mojom_abspath, mojom_path in mojom_files_to_parse.items():\n> +    module_path = os.path.join(output_root_path, _GetModuleFilename(mojom_path))\n> +    module_dir = os.path.dirname(module_path)\n> +    if not os.path.exists(module_dir):\n> +      try:\n> +        # Python 2 doesn't support exist_ok on makedirs(), so we just ignore\n> +        # that failure if it happens. It's possible during build due to races\n> +        # among build steps with module outputs in the same directory.\n> +        os.makedirs(module_dir)\n> +      except OSError as e:\n> +        if e.errno != errno.EEXIST:\n> +          raise\n> +    with open(module_path, 'wb') as f:\n> +      loaded_modules[mojom_abspath].Dump(f)\n> +\n> +\n> +def Run(command_line):\n> +  arg_parser = argparse.ArgumentParser(\n> +      description=\"\"\"\n> +Parses one or more mojom files and produces corresponding module outputs fully\n> +describing the definitions therein. The output is exhaustive, stable, and\n> +sufficient for another tool to consume and emit e.g. usable language\n> +bindings based on the original mojoms.\"\"\",\n> +      epilog=\"\"\"\n> +Note that each transitive import dependency reachable from the input mojoms must\n> +either also be listed as an input or must have its corresponding compiled module\n> +already present in the provided output root.\"\"\")\n> +\n> +  arg_parser.add_argument(\n> +      '--input-root',\n> +      default=[],\n> +      action='append',\n> +      metavar='ROOT',\n> +      dest='input_root_paths',\n> +      help='Adds ROOT to the set of root paths against which relative input '\n> +      'paths should be resolved. Provided root paths are always searched '\n> +      'in order from longest absolute path to shortest.')\n> +  arg_parser.add_argument(\n> +      '--output-root',\n> +      action='store',\n> +      required=True,\n> +      dest='output_root_path',\n> +      metavar='ROOT',\n> +      help='Use ROOT as the root path in which the parser should emit compiled '\n> +      'modules for each processed input mojom. The path of emitted module is '\n> +      'based on the relative input path, rebased onto this root. Note that '\n> +      'ROOT is also searched for existing modules of any transitive imports '\n> +      'which were not included in the set of inputs.')\n> +  arg_parser.add_argument(\n> +      '--mojoms',\n> +      nargs='+',\n> +      dest='mojom_files',\n> +      default=[],\n> +      metavar='MOJOM_FILE',\n> +      help='Input mojom filename(s). Each filename must be either an absolute '\n> +      'path which falls within one of the given input or output roots, or a '\n> +      'relative path the parser will attempt to resolve using each of those '\n> +      'roots in unspecified order.')\n> +  arg_parser.add_argument(\n> +      '--mojom-file-list',\n> +      action='store',\n> +      metavar='LIST_FILENAME',\n> +      help='Input file whose contents are a list of mojoms to process. This '\n> +      'may be provided in lieu of --mojoms to avoid hitting command line '\n> +      'length limtations')\n> +  arg_parser.add_argument(\n> +      '--enable-feature',\n> +      dest='enabled_features',\n> +      default=[],\n> +      action='append',\n> +      metavar='FEATURE',\n> +      help='Enables a named feature when parsing the given mojoms. Features '\n> +      'are identified by arbitrary string values. Specifying this flag with a '\n> +      'given FEATURE name will cause the parser to process any syntax elements '\n> +      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '\n> +      'provided for a given FEATURE, such tagged elements are discarded by the '\n> +      'parser and will not be present in the compiled output.')\n> +  arg_parser.add_argument(\n> +      '--check-imports',\n> +      dest='build_metadata_filename',\n> +      action='store',\n> +      metavar='METADATA_FILENAME',\n> +      help='Instructs the parser to check imports against a set of allowed '\n> +      'imports. Allowed imports are based on build metadata within '\n> +      'METADATA_FILENAME. This is a JSON file with a `sources` key listing '\n> +      'paths to the set of input mojom files being processed by this parser '\n> +      'run, and a `deps` key listing paths to metadata files for any '\n> +      'dependencies of these inputs. This feature can be used to implement '\n> +      'build-time dependency checking for mojom imports, where each build '\n> +      'metadata file corresponds to a build target in the dependency graph of '\n> +      'a typical build system.')\n> +\n> +  args, _ = arg_parser.parse_known_args(command_line)\n> +  if args.mojom_file_list:\n> +    with open(args.mojom_file_list) as f:\n> +      args.mojom_files.extend(f.read().split())\n> +\n> +  if not args.mojom_files:\n> +    raise ValueError(\n> +        'Must list at least one mojom file via --mojoms or --mojom-file-list')\n> +\n> +  mojom_files = list(map(os.path.abspath, args.mojom_files))\n> +  input_roots = list(map(os.path.abspath, args.input_root_paths))\n> +  output_root = os.path.abspath(args.output_root_path)\n> +\n> +  if args.build_metadata_filename:\n> +    allowed_imports = _CollectAllowedImportsFromBuildMetadata(\n> +        args.build_metadata_filename)\n> +  else:\n> +    allowed_imports = None\n> +\n> +  _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,\n> +               allowed_imports)\n> +\n> +\n> +if __name__ == '__main__':\n> +  Run(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> new file mode 100644\n> index 00000000..e213fbfa\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> @@ -0,0 +1,73 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import json\n> +import os\n> +import os.path\n> +import shutil\n> +import tempfile\n> +import unittest\n> +\n> +import mojom_parser\n> +\n> +from mojom.generate import module\n> +\n> +\n> +class MojomParserTestCase(unittest.TestCase):\n> +  \"\"\"Tests covering the behavior defined by the main mojom_parser.py script.\n> +  This includes behavior around input and output path manipulation, dependency\n> +  resolution, and module serialization and deserialization.\"\"\"\n> +\n> +  def __init__(self, method_name):\n> +    super(MojomParserTestCase, self).__init__(method_name)\n> +    self._temp_dir = None\n> +\n> +  def setUp(self):\n> +    self._temp_dir = tempfile.mkdtemp()\n> +\n> +  def tearDown(self):\n> +    shutil.rmtree(self._temp_dir)\n> +    self._temp_dir = None\n> +\n> +  def GetPath(self, path):\n> +    assert not os.path.isabs(path)\n> +    return os.path.join(self._temp_dir, path)\n> +\n> +  def GetModulePath(self, path):\n> +    assert not os.path.isabs(path)\n> +    return os.path.join(self.GetPath('out'), path) + '-module'\n> +\n> +  def WriteFile(self, path, contents):\n> +    full_path = self.GetPath(path)\n> +    dirname = os.path.dirname(full_path)\n> +    if not os.path.exists(dirname):\n> +      os.makedirs(dirname)\n> +    with open(full_path, 'w') as f:\n> +      f.write(contents)\n> +\n> +  def LoadModule(self, mojom_path):\n> +    with open(self.GetModulePath(mojom_path), 'rb') as f:\n> +      return module.Module.Load(f)\n> +\n> +  def ParseMojoms(self, mojoms, metadata=None):\n> +    \"\"\"Parse all input mojoms relative the temp dir.\"\"\"\n> +    out_dir = self.GetPath('out')\n> +    args = [\n> +        '--input-root', self._temp_dir, '--input-root', out_dir,\n> +        '--output-root', out_dir, '--mojoms'\n> +    ] + list(map(lambda mojom: os.path.join(self._temp_dir, mojom), mojoms))\n> +    if metadata:\n> +      args.extend(['--check-imports', self.GetPath(metadata)])\n> +    mojom_parser.Run(args)\n> +\n> +  def ExtractTypes(self, mojom):\n> +    filename = 'test.mojom'\n> +    self.WriteFile(filename, mojom)\n> +    self.ParseMojoms([filename])\n> +    m = self.LoadModule(filename)\n> +    definitions = {}\n> +    for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n> +      for kind in kinds:\n> +        definitions[kind.mojom_name] = kind\n> +    return definitions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> new file mode 100644\n> index 00000000..a93f34ba\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> @@ -0,0 +1,171 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class MojomParserTest(MojomParserTestCase):\n> +  \"\"\"Tests covering the behavior defined by the main mojom_parser.py script.\n> +  This includes behavior around input and output path manipulation, dependency\n> +  resolution, and module serialization and deserialization.\"\"\"\n> +\n> +  def testBasicParse(self):\n> +    \"\"\"Basic test to verify that we can parse a mojom file and get a module.\"\"\"\n> +    mojom = 'foo/bar.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +        module test;\n> +        enum TestEnum { kFoo };\n> +        \"\"\")\n> +    self.ParseMojoms([mojom])\n> +\n> +    m = self.LoadModule(mojom)\n> +    self.assertEqual('foo/bar.mojom', m.path)\n> +    self.assertEqual('test', m.mojom_namespace)\n> +    self.assertEqual(1, len(m.enums))\n> +\n> +  def testBasicParseWithAbsolutePaths(self):\n> +    \"\"\"Verifies that we can parse a mojom file given an absolute path input.\"\"\"\n> +    mojom = 'foo/bar.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +        module test;\n> +        enum TestEnum { kFoo };\n> +        \"\"\")\n> +    self.ParseMojoms([self.GetPath(mojom)])\n> +\n> +    m = self.LoadModule(mojom)\n> +    self.assertEqual('foo/bar.mojom', m.path)\n> +    self.assertEqual('test', m.mojom_namespace)\n> +    self.assertEqual(1, len(m.enums))\n> +\n> +  def testImport(self):\n> +    \"\"\"Verify imports within the same set of mojom inputs.\"\"\"\n> +    a = 'a.mojom'\n> +    b = 'b.mojom'\n> +    self.WriteFile(\n> +        a, \"\"\"\\\n> +        module a;\n> +        import \"b.mojom\";\n> +        struct Foo { b.Bar bar; };\"\"\")\n> +    self.WriteFile(b, \"\"\"\\\n> +        module b;\n> +        struct Bar {};\"\"\")\n> +    self.ParseMojoms([a, b])\n> +\n> +    ma = self.LoadModule(a)\n> +    mb = self.LoadModule(b)\n> +    self.assertEqual('a.mojom', ma.path)\n> +    self.assertEqual('b.mojom', mb.path)\n> +    self.assertEqual(1, len(ma.imports))\n> +    self.assertEqual(mb, ma.imports[0])\n> +\n> +  def testPreProcessedImport(self):\n> +    \"\"\"Verify imports processed by a previous parser execution can be loaded\n> +    properly when parsing a dependent mojom.\"\"\"\n> +    a = 'a.mojom'\n> +    self.WriteFile(a, \"\"\"\\\n> +        module a;\n> +        struct Bar {};\"\"\")\n> +    self.ParseMojoms([a])\n> +\n> +    b = 'b.mojom'\n> +    self.WriteFile(\n> +        b, \"\"\"\\\n> +        module b;\n> +        import \"a.mojom\";\n> +        struct Foo { a.Bar bar; };\"\"\")\n> +    self.ParseMojoms([b])\n> +\n> +  def testMissingImport(self):\n> +    \"\"\"Verify that an import fails if the imported mojom does not exist.\"\"\"\n> +    a = 'a.mojom'\n> +    self.WriteFile(\n> +        a, \"\"\"\\\n> +        module a;\n> +        import \"non-existent.mojom\";\n> +        struct Bar {};\"\"\")\n> +    with self.assertRaisesRegexp(ValueError, \"does not exist\"):\n> +      self.ParseMojoms([a])\n> +\n> +  def testUnparsedImport(self):\n> +    \"\"\"Verify that an import fails if the imported mojom is not in the set of\n> +    mojoms provided to the parser on this execution AND there is no pre-existing\n> +    parsed output module already on disk for it.\"\"\"\n> +    a = 'a.mojom'\n> +    b = 'b.mojom'\n> +    self.WriteFile(a, \"\"\"\\\n> +        module a;\n> +        struct Bar {};\"\"\")\n> +    self.WriteFile(\n> +        b, \"\"\"\\\n> +        module b;\n> +        import \"a.mojom\";\n> +        struct Foo { a.Bar bar; };\"\"\")\n> +\n> +    # a.mojom has not been parsed yet, so its import will fail when processing\n> +    # b.mojom here.\n> +    with self.assertRaisesRegexp(ValueError, \"does not exist\"):\n> +      self.ParseMojoms([b])\n> +\n> +  def testCheckImportsBasic(self):\n> +    \"\"\"Verify that the parser can handle --check-imports with a valid set of\n> +    inputs, including support for transitive dependency resolution.\"\"\"\n> +    a = 'a.mojom'\n> +    a_metadata = 'out/a.build_metadata'\n> +    b = 'b.mojom'\n> +    b_metadata = 'out/b.build_metadata'\n> +    c = 'c.mojom'\n> +    c_metadata = 'out/c.build_metadata'\n> +    self.WriteFile(a_metadata,\n> +                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +    self.WriteFile(\n> +        b_metadata,\n> +        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(b),\n> +                                                   self.GetPath(a_metadata)))\n> +    self.WriteFile(\n> +        c_metadata,\n> +        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(c),\n> +                                                   self.GetPath(b_metadata)))\n> +    self.WriteFile(a, \"\"\"\\\n> +        module a;\n> +        struct Bar {};\"\"\")\n> +    self.WriteFile(\n> +        b, \"\"\"\\\n> +        module b;\n> +        import \"a.mojom\";\n> +        struct Foo { a.Bar bar; };\"\"\")\n> +    self.WriteFile(\n> +        c, \"\"\"\\\n> +        module c;\n> +        import \"a.mojom\";\n> +        import \"b.mojom\";\n> +        struct Baz { b.Foo foo; };\"\"\")\n> +    self.ParseMojoms([a], metadata=a_metadata)\n> +    self.ParseMojoms([b], metadata=b_metadata)\n> +    self.ParseMojoms([c], metadata=c_metadata)\n> +\n> +  def testCheckImportsMissing(self):\n> +    \"\"\"Verify that the parser rejects valid input mojoms when imports don't\n> +    agree with build metadata given via --check-imports.\"\"\"\n> +    a = 'a.mojom'\n> +    a_metadata = 'out/a.build_metadata'\n> +    b = 'b.mojom'\n> +    b_metadata = 'out/b.build_metadata'\n> +    self.WriteFile(a_metadata,\n> +                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +    self.WriteFile(b_metadata,\n> +                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(b))\n> +    self.WriteFile(a, \"\"\"\\\n> +        module a;\n> +        struct Bar {};\"\"\")\n> +    self.WriteFile(\n> +        b, \"\"\"\\\n> +        module b;\n> +        import \"a.mojom\";\n> +        struct Foo { a.Bar bar; };\"\"\")\n> +\n> +    self.ParseMojoms([a], metadata=a_metadata)\n> +    with self.assertRaisesRegexp(ValueError, \"not allowed by build\"):\n> +      self.ParseMojoms([b], metadata=b_metadata)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> new file mode 100644\n> index 00000000..d45ec586\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> @@ -0,0 +1,127 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +from mojom.generate import module\n> +\n> +\n> +class StableAttributeTest(MojomParserTestCase):\n> +  \"\"\"Tests covering usage of the [Stable] attribute.\"\"\"\n> +\n> +  def testStableAttributeTagging(self):\n> +    \"\"\"Verify that we recognize the [Stable] attribute on relevant definitions\n> +    and the resulting parser outputs are tagged accordingly.\"\"\"\n> +    mojom = 'test.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +        [Stable] enum TestEnum { kFoo };\n> +        enum UnstableEnum { kBar };\n> +        [Stable] struct TestStruct { TestEnum a; };\n> +        struct UnstableStruct { UnstableEnum a; };\n> +        [Stable] union TestUnion { TestEnum a; TestStruct b; };\n> +        union UnstableUnion { UnstableEnum a; UnstableStruct b; };\n> +        [Stable] interface TestInterface { Foo@0(TestUnion x) => (); };\n> +        interface UnstableInterface { Foo(UnstableUnion x) => (); };\n> +        \"\"\")\n> +    self.ParseMojoms([mojom])\n> +\n> +    m = self.LoadModule(mojom)\n> +    self.assertEqual(2, len(m.enums))\n> +    self.assertTrue(m.enums[0].stable)\n> +    self.assertFalse(m.enums[1].stable)\n> +    self.assertEqual(2, len(m.structs))\n> +    self.assertTrue(m.structs[0].stable)\n> +    self.assertFalse(m.structs[1].stable)\n> +    self.assertEqual(2, len(m.unions))\n> +    self.assertTrue(m.unions[0].stable)\n> +    self.assertFalse(m.unions[1].stable)\n> +    self.assertEqual(2, len(m.interfaces))\n> +    self.assertTrue(m.interfaces[0].stable)\n> +    self.assertFalse(m.interfaces[1].stable)\n> +\n> +  def testStableStruct(self):\n> +    \"\"\"A [Stable] struct is valid if all its fields are also stable.\"\"\"\n> +    self.ExtractTypes('[Stable] struct S {};')\n> +    self.ExtractTypes('[Stable] struct S { int32 x; bool b; };')\n> +    self.ExtractTypes('[Stable] enum E { A }; [Stable] struct S { E e; };')\n> +    self.ExtractTypes('[Stable] struct S {}; [Stable] struct T { S s; };')\n> +    self.ExtractTypes(\n> +        '[Stable] struct S {}; [Stable] struct T { array<S> ss; };')\n> +    self.ExtractTypes(\n> +        '[Stable] interface F {}; [Stable] struct T { pending_remote<F> f; };')\n> +\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n> +      self.ExtractTypes('enum E { A }; [Stable] struct S { E e; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on X'):\n> +      self.ExtractTypes('struct X {}; [Stable] struct S { X x; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] struct S { array<T> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] struct S { map<int32, T> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] struct S { map<T, int32> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n> +      self.ExtractTypes(\n> +          'interface F {}; [Stable] struct S { pending_remote<F> f; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n> +      self.ExtractTypes(\n> +          'interface F {}; [Stable] struct S { pending_receiver<F> f; };')\n> +\n> +  def testStableUnion(self):\n> +    \"\"\"A [Stable] union is valid if all its fields' types are also stable.\"\"\"\n> +    self.ExtractTypes('[Stable] union U {};')\n> +    self.ExtractTypes('[Stable] union U { int32 x; bool b; };')\n> +    self.ExtractTypes('[Stable] enum E { A }; [Stable] union U { E e; };')\n> +    self.ExtractTypes('[Stable] struct S {}; [Stable] union U { S s; };')\n> +    self.ExtractTypes(\n> +        '[Stable] struct S {}; [Stable] union U { array<S> ss; };')\n> +    self.ExtractTypes(\n> +        '[Stable] interface F {}; [Stable] union U { pending_remote<F> f; };')\n> +\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n> +      self.ExtractTypes('enum E { A }; [Stable] union U { E e; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on X'):\n> +      self.ExtractTypes('struct X {}; [Stable] union U { X x; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] union U { array<T> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] union U { map<int32, T> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n> +      self.ExtractTypes('struct T {}; [Stable] union U { map<T, int32> xs; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n> +      self.ExtractTypes(\n> +          'interface F {}; [Stable] union U { pending_remote<F> f; };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n> +      self.ExtractTypes(\n> +          'interface F {}; [Stable] union U { pending_receiver<F> f; };')\n> +\n> +  def testStableInterface(self):\n> +    \"\"\"A [Stable] interface is valid if all its methods' parameter types are\n> +    stable, including response parameters where applicable.\"\"\"\n> +    self.ExtractTypes('[Stable] interface F {};')\n> +    self.ExtractTypes('[Stable] interface F { A@0(int32 x); };')\n> +    self.ExtractTypes('[Stable] interface F { A@0(int32 x) => (bool b); };')\n> +    self.ExtractTypes(\"\"\"\\\n> +        [Stable] enum E { A, B, C };\n> +        [Stable] struct S {};\n> +        [Stable] interface F { A@0(E e, S s) => (bool b, array<S> s); };\n> +        \"\"\")\n> +\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n> +      self.ExtractTypes(\n> +          'enum E { A, B, C }; [Stable] interface F { A@0(E e); };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n> +      self.ExtractTypes(\n> +          'enum E { A, B, C }; [Stable] interface F { A@0(int32 x) => (E e); };'\n> +      )\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on S'):\n> +      self.ExtractTypes(\n> +          'struct S {}; [Stable] interface F { A@0(int32 x) => (S s); };')\n> +    with self.assertRaisesRegexp(Exception, 'because it depends on S'):\n> +      self.ExtractTypes(\n> +          'struct S {}; [Stable] interface F { A@0(S s) => (bool b); };')\n> +\n> +    with self.assertRaisesRegexp(Exception, 'explicit method ordinals'):\n> +      self.ExtractTypes('[Stable] interface F { A() => (); };')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> new file mode 100644\n> index 00000000..a0ee150e\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> @@ -0,0 +1,397 @@\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class VersionCompatibilityTest(MojomParserTestCase):\n> +  \"\"\"Tests covering compatibility between two versions of the same mojom type\n> +  definition. This coverage ensures that we can reliably detect unsafe changes\n> +  to definitions that are expected to tolerate version skew in production\n> +  environments.\"\"\"\n> +\n> +  def _GetTypeCompatibilityMap(self, old_mojom, new_mojom):\n> +    \"\"\"Helper to support the implementation of assertBackwardCompatible and\n> +    assertNotBackwardCompatible.\"\"\"\n> +\n> +    old = self.ExtractTypes(old_mojom)\n> +    new = self.ExtractTypes(new_mojom)\n> +    self.assertEqual(set(old.keys()), set(new.keys()),\n> +                     'Old and new test mojoms should use the same type names.')\n> +\n> +    compatibility_map = {}\n> +    for name in old.keys():\n> +      compatibility_map[name] = new[name].IsBackwardCompatible(old[name])\n> +    return compatibility_map\n> +\n> +  def assertBackwardCompatible(self, old_mojom, new_mojom):\n> +    compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom)\n> +    for name, compatible in compatibility_map.items():\n> +      if not compatible:\n> +        raise AssertionError(\n> +            'Given the old mojom:\\n\\n    %s\\n\\nand the new mojom:\\n\\n    %s\\n\\n'\n> +            'The new definition of %s should pass a backward-compatibiity '\n> +            'check, but it does not.' % (old_mojom, new_mojom, name))\n> +\n> +  def assertNotBackwardCompatible(self, old_mojom, new_mojom):\n> +    compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom)\n> +    if all(compatibility_map.values()):\n> +      raise AssertionError(\n> +          'Given the old mojom:\\n\\n    %s\\n\\nand the new mojom:\\n\\n    %s\\n\\n'\n> +          'The new mojom should fail a backward-compatibility check, but it '\n> +          'does not.' % (old_mojom, new_mojom))\n> +\n> +  def testNewNonExtensibleEnumValue(self):\n> +    \"\"\"Adding a value to a non-extensible enum breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n> +                                     'enum E { kFoo, kBar, kBaz };')\n> +\n> +  def testNewNonExtensibleEnumValueWithMinVersion(self):\n> +    \"\"\"Adding a value to a non-extensible enum breaks backward-compatibility,\n> +    even with a new [MinVersion] specified for the value.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        'enum E { kFoo, kBar };', 'enum E { kFoo, kBar, [MinVersion=1] kBaz };')\n> +\n> +  def testNewValueInExistingVersion(self):\n> +    \"\"\"Adding a value to an existing version is not allowed, even if the old\n> +    enum was marked [Extensible]. Note that it is irrelevant whether or not the\n> +    new enum is marked [Extensible].\"\"\"\n> +    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> +                                     'enum E { kFoo, kBar, kBaz };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kFoo, kBar };',\n> +        '[Extensible] enum E { kFoo, kBar, kBaz };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',\n> +        'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')\n> +\n> +  def testEnumValueRemoval(self):\n> +    \"\"\"Removal of an enum value is never valid even for [Extensible] enums.\"\"\"\n> +    self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n> +                                     'enum E { kFoo };')\n> +    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> +                                     '[Extensible] enum E { kFoo };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> +        '[Extensible] enum E { kA, };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB };')\n> +\n> +  def testNewExtensibleEnumValueWithMinVersion(self):\n> +    \"\"\"Adding a new and properly [MinVersion]'d value to an [Extensible] enum\n> +    is a backward-compatible change. Note that it is irrelevant whether or not\n> +    the new enum is marked [Extensible].\"\"\"\n> +    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',\n> +                                  'enum E { kA, kB, [MinVersion=1] kC };')\n> +    self.assertBackwardCompatible(\n> +        '[Extensible] enum E { kA, kB };',\n> +        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')\n> +    self.assertBackwardCompatible(\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')\n> +\n> +  def testRenameEnumValue(self):\n> +    \"\"\"Renaming an enum value does not affect backward-compatibility. Only\n> +    numeric value is relevant.\"\"\"\n> +    self.assertBackwardCompatible('enum E { kA, kB };', 'enum E { kX, kY };')\n> +\n> +  def testAddEnumValueAlias(self):\n> +    \"\"\"Adding new enum fields does not affect backward-compatibility if it does\n> +    not introduce any new numeric values.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'enum E { kA, kB };', 'enum E { kA, kB, kC = kA, kD = 1, kE = kD };')\n> +\n> +  def testEnumIdentity(self):\n> +    \"\"\"An unchanged enum is obviously backward-compatible.\"\"\"\n> +    self.assertBackwardCompatible('enum E { kA, kB, kC };',\n> +                                  'enum E { kA, kB, kC };')\n> +\n> +  def testNewStructFieldUnversioned(self):\n> +    \"\"\"Adding a new field to a struct without a new (i.e. higher than any\n> +    existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string a; };',\n> +                                     'struct S { string a; string b; };')\n> +\n> +  def testStructFieldRemoval(self):\n> +    \"\"\"Removing a field from a struct breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string a; string b; };',\n> +                                     'struct S { string a; };')\n> +\n> +  def testStructFieldTypeChange(self):\n> +    \"\"\"Changing the type of an existing field always breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string a; };',\n> +                                     'struct S { array<int32> a; };')\n> +\n> +  def testStructFieldBecomingOptional(self):\n> +    \"\"\"Changing a field from non-optional to optional breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string a; };',\n> +                                     'struct S { string? a; };')\n> +\n> +  def testStructFieldBecomingNonOptional(self):\n> +    \"\"\"Changing a field from optional to non-optional breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string? a; };',\n> +                                     'struct S { string a; };')\n> +\n> +  def testStructFieldOrderChange(self):\n> +    \"\"\"Changing the order of fields breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('struct S { string a; bool b; };',\n> +                                     'struct S { bool b; string a; };')\n> +    self.assertNotBackwardCompatible('struct S { string a@0; bool b@1; };',\n> +                                     'struct S { string a@1; bool b@0; };')\n> +\n> +  def testStructFieldMinVersionChange(self):\n> +    \"\"\"Changing the MinVersion of a field breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        'struct S { string a; [MinVersion=1] string? b; };',\n> +        'struct S { string a; [MinVersion=2] string? b; };')\n> +\n> +  def testStructFieldTypeChange(self):\n> +    \"\"\"If a struct field's own type definition changes, the containing struct\n> +    is backward-compatible if and only if the field type's change is\n> +    backward-compatible.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'struct S {}; struct T { S s; };',\n> +        'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')\n> +    self.assertBackwardCompatible(\n> +        '[Extensible] enum E { kA }; struct S { E e; };',\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')\n> +    self.assertNotBackwardCompatible(\n> +        'struct S {}; struct T { S s; };',\n> +        'struct S { int32 x; }; struct T { S s; };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kA }; struct S { E e; };',\n> +        '[Extensible] enum E { kA, kB }; struct S { E e; };')\n> +\n> +  def testNewStructFieldWithInvalidMinVersion(self):\n> +    \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> +    compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        \"\"\"\\\n> +        struct S {\n> +          string a;\n> +          [MinVersion=1] string? b;\n> +        };\n> +        \"\"\", \"\"\"\\\n> +        struct S {\n> +          string a;\n> +          [MinVersion=1] string? b;\n> +          [MinVersion=1] string? c;\n> +        };\"\"\")\n> +\n> +  def testNewStructFieldWithValidMinVersion(self):\n> +    \"\"\"Adding a new field is safe if tagged with a MinVersion greater than any\n> +    previously used MinVersion in the struct.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'struct S { int32 a; };',\n> +        'struct S { int32 a; [MinVersion=1] int32 b; };')\n> +    self.assertBackwardCompatible(\n> +        'struct S { int32 a; [MinVersion=1] int32 b; };',\n> +        'struct S { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };')\n> +\n> +  def testNewStructFieldNullableReference(self):\n> +    \"\"\"Adding a new nullable reference-typed field is fine if versioned\n> +    properly.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'struct S { int32 a; };',\n> +        'struct S { int32 a; [MinVersion=1] string? b; };')\n> +\n> +  def testStructFieldRename(self):\n> +    \"\"\"Renaming a field has no effect on backward-compatibility.\"\"\"\n> +    self.assertBackwardCompatible('struct S { int32 x; bool b; };',\n> +                                  'struct S { int32 a; bool b; };')\n> +\n> +  def testStructFieldReorderWithExplicitOrdinals(self):\n> +    \"\"\"Reordering fields has no effect on backward-compatibility when field\n> +    ordinals are explicitly labeled and remain unchanged.\"\"\"\n> +    self.assertBackwardCompatible('struct S { bool b@1; int32 a@0; };',\n> +                                  'struct S { int32 a@0; bool b@1; };')\n> +\n> +  def testNewUnionFieldUnversioned(self):\n> +    \"\"\"Adding a new field to a union without a new (i.e. higher than any\n> +    existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string a; };',\n> +                                     'union U { string a; string b; };')\n> +\n> +  def testUnionFieldRemoval(self):\n> +    \"\"\"Removing a field from a union breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string a; string b; };',\n> +                                     'union U { string a; };')\n> +\n> +  def testUnionFieldTypeChange(self):\n> +    \"\"\"Changing the type of an existing field always breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string a; };',\n> +                                     'union U { array<int32> a; };')\n> +\n> +  def testUnionFieldBecomingOptional(self):\n> +    \"\"\"Changing a field from non-optional to optional breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string a; };',\n> +                                     'union U { string? a; };')\n> +\n> +  def testUnionFieldBecomingNonOptional(self):\n> +    \"\"\"Changing a field from optional to non-optional breaks\n> +    backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string? a; };',\n> +                                     'union U { string a; };')\n> +\n> +  def testUnionFieldOrderChange(self):\n> +    \"\"\"Changing the order of fields breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('union U { string a; bool b; };',\n> +                                     'union U { bool b; string a; };')\n> +    self.assertNotBackwardCompatible('union U { string a@0; bool b@1; };',\n> +                                     'union U { string a@1; bool b@0; };')\n> +\n> +  def testUnionFieldMinVersionChange(self):\n> +    \"\"\"Changing the MinVersion of a field breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        'union U { string a; [MinVersion=1] string b; };',\n> +        'union U { string a; [MinVersion=2] string b; };')\n> +\n> +  def testUnionFieldTypeChange(self):\n> +    \"\"\"If a union field's own type definition changes, the containing union\n> +    is backward-compatible if and only if the field type's change is\n> +    backward-compatible.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'struct S {}; union U { S s; };',\n> +        'struct S { [MinVersion=1] int32 x; }; union U { S s; };')\n> +    self.assertBackwardCompatible(\n> +        '[Extensible] enum E { kA }; union U { E e; };',\n> +        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')\n> +    self.assertNotBackwardCompatible(\n> +        'struct S {}; union U { S s; };',\n> +        'struct S { int32 x; }; union U { S s; };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { kA }; union U { E e; };',\n> +        '[Extensible] enum E { kA, kB }; union U { E e; };')\n> +\n> +  def testNewUnionFieldWithInvalidMinVersion(self):\n> +    \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> +    compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        \"\"\"\\\n> +        union U {\n> +          string a;\n> +          [MinVersion=1] string b;\n> +        };\n> +        \"\"\", \"\"\"\\\n> +        union U {\n> +          string a;\n> +          [MinVersion=1] string b;\n> +          [MinVersion=1] string c;\n> +        };\"\"\")\n> +\n> +  def testNewUnionFieldWithValidMinVersion(self):\n> +    \"\"\"Adding a new field is safe if tagged with a MinVersion greater than any\n> +    previously used MinVersion in the union.\"\"\"\n> +    self.assertBackwardCompatible(\n> +        'union U { int32 a; };',\n> +        'union U { int32 a; [MinVersion=1] int32 b; };')\n> +    self.assertBackwardCompatible(\n> +        'union U { int32 a; [MinVersion=1] int32 b; };',\n> +        'union U { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };')\n> +\n> +  def testUnionFieldRename(self):\n> +    \"\"\"Renaming a field has no effect on backward-compatibility.\"\"\"\n> +    self.assertBackwardCompatible('union U { int32 x; bool b; };',\n> +                                  'union U { int32 a; bool b; };')\n> +\n> +  def testUnionFieldReorderWithExplicitOrdinals(self):\n> +    \"\"\"Reordering fields has no effect on backward-compatibility when field\n> +    ordinals are explicitly labeled and remain unchanged.\"\"\"\n> +    self.assertBackwardCompatible('union U { bool b@1; int32 a@0; };',\n> +                                  'union U { int32 a@0; bool b@1; };')\n> +\n> +  def testNewInterfaceMethodUnversioned(self):\n> +    \"\"\"Adding a new method to an interface without a new (i.e. higher than any\n> +    existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A(); };',\n> +                                     'interface F { A(); B(); };')\n> +\n> +  def testInterfaceMethodRemoval(self):\n> +    \"\"\"Removing a method from an interface breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A(); B(); };',\n> +                                     'interface F { A(); };')\n> +\n> +  def testInterfaceMethodParamsChanged(self):\n> +    \"\"\"Changes to the parameter list are only backward-compatible if they meet\n> +    backward-compatibility requirements of an equivalent struct definition.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A(); };',\n> +                                     'interface F { A(int32 x); };')\n> +    self.assertNotBackwardCompatible('interface F { A(int32 x); };',\n> +                                     'interface F { A(bool x); };')\n> +    self.assertNotBackwardCompatible(\n> +        'interface F { A(int32 x, [MinVersion=1] string? s); };', \"\"\"\\\n> +        interface F {\n> +          A(int32 x, [MinVersion=1] string? s, [MinVersion=1] int32 y);\n> +        };\"\"\")\n> +\n> +    self.assertBackwardCompatible('interface F { A(int32 x); };',\n> +                                  'interface F { A(int32 a); };')\n> +    self.assertBackwardCompatible(\n> +        'interface F { A(int32 x); };',\n> +        'interface F { A(int32 x, [MinVersion=1] string? s); };')\n> +\n> +    self.assertBackwardCompatible(\n> +        'struct S {}; interface F { A(S s); };',\n> +        'struct S { [MinVersion=1] int32 x; }; interface F { A(S s); };')\n> +    self.assertBackwardCompatible(\n> +        'struct S {}; struct T {}; interface F { A(S s); };',\n> +        'struct S {}; struct T {}; interface F { A(T s); };')\n> +    self.assertNotBackwardCompatible(\n> +        'struct S {}; struct T { int32 x; }; interface F { A(S s); };',\n> +        'struct S {}; struct T { int32 x; }; interface F { A(T t); };')\n> +\n> +  def testInterfaceMethodReplyAdded(self):\n> +    \"\"\"Adding a reply to a message breaks backward-compatibilty.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A(); };',\n> +                                     'interface F { A() => (); };')\n> +\n> +  def testInterfaceMethodReplyRemoved(self):\n> +    \"\"\"Removing a reply from a message breaks backward-compatibility.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A() => (); };',\n> +                                     'interface F { A(); };')\n> +\n> +  def testInterfaceMethodReplyParamsChanged(self):\n> +    \"\"\"Similar to request parameters, a change to reply parameters is considered\n> +    backward-compatible if it meets the same backward-compatibility\n> +    requirements imposed on equivalent struct changes.\"\"\"\n> +    self.assertNotBackwardCompatible('interface F { A() => (); };',\n> +                                     'interface F { A() => (int32 x); };')\n> +    self.assertNotBackwardCompatible('interface F { A() => (int32 x); };',\n> +                                     'interface F { A() => (); };')\n> +    self.assertNotBackwardCompatible('interface F { A() => (bool x); };',\n> +                                     'interface F { A() => (int32 x); };')\n> +\n> +    self.assertBackwardCompatible('interface F { A() => (int32 a); };',\n> +                                  'interface F { A() => (int32 x); };')\n> +    self.assertBackwardCompatible(\n> +        'interface F { A() => (int32 x); };',\n> +        'interface F { A() => (int32 x, [MinVersion] string? s); };')\n> +\n> +  def testNewInterfaceMethodWithInvalidMinVersion(self):\n> +    \"\"\"Adding a new method to an existing version is not backward-compatible.\"\"\"\n> +    self.assertNotBackwardCompatible(\n> +        \"\"\"\\\n> +        interface F {\n> +          A();\n> +          [MinVersion=1] B();\n> +        };\n> +        \"\"\", \"\"\"\\\n> +        interface F {\n> +          A();\n> +          [MinVersion=1] B();\n> +          [MinVersion=1] C();\n> +        };\n> +        \"\"\")\n> +\n> +  def testNewInterfaceMethodWithValidMinVersion(self):\n> +    \"\"\"Adding a new method is fine as long as its MinVersion exceeds that of any\n> +    method on the old interface definition.\"\"\"\n> +    self.assertBackwardCompatible('interface F { A(); };',\n> +                                  'interface F { A(); [MinVersion=1] B(); };')\n> diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> new file mode 100755\n> index 00000000..b2010958\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> @@ -0,0 +1,28 @@\n> +#!/usr/bin/env python\n> +# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import os.path\n> +import sys\n> +\n> +_TOOLS_DIR = os.path.dirname(__file__)\n> +_MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')\n> +_SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,\n> +                        os.path.pardir)\n> +\n> +# Ensure that the mojom library is discoverable.\n> +sys.path.append(_MOJOM_DIR)\n> +\n> +# Help Python find typ in //third_party/catapult/third_party/typ/\n> +sys.path.append(\n> +    os.path.join(_SRC_DIR, 'third_party', 'catapult', 'third_party', 'typ'))\n> +import typ\n> +\n> +\n> +def Main():\n> +  return typ.main(top_level_dir=_MOJOM_DIR)\n> +\n> +\n> +if __name__ == '__main__':\n> +  sys.exit(Main())\n> diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> new file mode 100644\n> index 00000000..478fb8c1\n> --- /dev/null\n> +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> @@ -0,0 +1,51 @@\n> +# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +\"\"\"Helper context wrapper for diagnosing crbug.com/1001171.\n> +\n> +This module and all uses thereof can and should be removed once\n> +crbug.com/1001171 has been resolved.\n> +\"\"\"\n> +\n> +from __future__ import print_function\n> +\n> +import contextlib\n> +import os\n> +import sys\n> +\n> +\n> +@contextlib.contextmanager\n> +def DumpStateOnLookupError():\n> +  \"\"\"Prints potentially useful state info in the event of a LookupError.\"\"\"\n> +  try:\n> +    yield\n> +  except LookupError:\n> +    print('LookupError diagnosis for crbug.com/1001171:')\n> +    for path_index, path_entry in enumerate(sys.path):\n> +      desc = 'unknown'\n> +      if not os.path.exists(path_entry):\n> +        desc = 'missing'\n> +      elif os.path.islink(path_entry):\n> +        desc = 'link -> %s' % os.path.realpath(path_entry)\n> +      elif os.path.isfile(path_entry):\n> +        desc = 'file'\n> +      elif os.path.isdir(path_entry):\n> +        desc = 'dir'\n> +      print('  sys.path[%d]: %s (%s)' % (path_index, path_entry, desc))\n> +\n> +      real_path_entry = os.path.realpath(path_entry)\n> +      if (path_entry.endswith(os.path.join('lib', 'python2.7'))\n> +          and os.path.isdir(real_path_entry)):\n> +        encodings_dir = os.path.realpath(\n> +            os.path.join(real_path_entry, 'encodings'))\n> +        if os.path.exists(encodings_dir):\n> +          if os.path.isdir(encodings_dir):\n> +            print('    %s contents: %s' % (encodings_dir,\n> +                                           str(os.listdir(encodings_dir))))\n> +          else:\n> +            print('    %s exists but is not a directory' % encodings_dir)\n> +        else:\n> +          print('    %s missing' % encodings_dir)\n> +\n> +    raise\n> -- \n> 2.27.0\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 426B4BDB89\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 10 Nov 2020 02:49:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8C936630BD;\n\tTue, 10 Nov 2020 03:49:41 +0100 (CET)","from mail-lf1-x12c.google.com (mail-lf1-x12c.google.com\n\t[IPv6:2a00:1450:4864:20::12c])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E25A56308B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 10 Nov 2020 01:22:58 +0100 (CET)","by mail-lf1-x12c.google.com with SMTP id j205so7655158lfj.6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 09 Nov 2020 16:22:58 -0800 (PST)","from localhost (h-209-203.A463.priv.bahnhof.se. [155.4.209.203])\n\tby smtp.gmail.com with ESMTPSA id\n\tj2sm2369508lja.125.2020.11.09.16.22.54\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 09 Nov 2020 16:22:54 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=ragnatech-se.20150623.gappssmtp.com\n\theader.i=@ragnatech-se.20150623.gappssmtp.com\n\theader.b=\"cMZ7INSt\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to;\n\tbh=o9+8UVRwnhyESrGYOTDhXahmyIE7LGJESAl9Gfc//6g=;\n\tb=cMZ7INStygBfyyvRyLkZl3CuyCXSTVJCuGx9pC5zOJxkQZ4AX+OnRO/FWgU8w1NSSR\n\tsI+vXT0TkNoxzhuJ97gCuWO6VJZ48F+kk9vr2P+zN6MXGqlCq5H7GKuzR3oaQLldDeDL\n\t1Y0KKv8v08ve2+aa33Tvu2DdLzEacBHKVJpnbfjx78cKVkFs2EY4pg7/Q9Neu00pU7HG\n\t+/0b6TIlfiaR/pcVxdjGa8+MJPv4UGzhv5hYXyrdvDDnDtzGIpDcP2be47DRJc34hQqG\n\tyDu30yaV3gNj5QtaCQyKkOPnJpyLhvzAEptkBNCodpk4FgwVRWx29wOjd2JkmpbgN/e0\n\tHuWQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to;\n\tbh=o9+8UVRwnhyESrGYOTDhXahmyIE7LGJESAl9Gfc//6g=;\n\tb=rUEs5/DfzYDhidw8Lh2c4flrM7Bw83+36unX6JjC2d+lEizbhbnGvdE+FSg66xH+G2\n\tsqpBC/22n+cm5A0Kg47SmUlFuNBfvSKn+sZgktlvl6CRlkBS6TDQ9s0SaW9O5wFUnf1B\n\tiGqWAOsoGiO1k+/7fsRXMx6HyeJok1o6M8GOXHQI+gzdpo3pOiSFORvKi6SXW6k7KXP/\n\tO+r3A+SUW3sJIGNwM6k71jHwdSr1QruGU3185i7nWUq+EkfzmmdXxoSJCN51nVr9Vk9p\n\thR83fpR0W0f1Nxeg27KkhH4dV8J4xYCYvm9lyUGvtKH7NYotZgNBKZIiV2yh3B+s+BFY\n\tCs1A==","X-Gm-Message-State":"AOAM5339CV8nlXosarmoxKmPruWACHzk2dwS5yByC5idt2zo07faQGUu\n\tq9Fc3F4NpSZTfKv5QNdL0ndk15v70gJNEA==","X-Google-Smtp-Source":"ABdhPJwaE8fLj5Z4v64Azm8Ous8lX8KtY6cK3V9z7JqtTcLpopUiz6a5uIqQKo6xm/ViQPvTeXve8g==","X-Received":"by 2002:a05:6512:34d3:: with SMTP id\n\tw19mr6147870lfr.418.1604967776149; \n\tMon, 09 Nov 2020 16:22:56 -0800 (PST)","Date":"Tue, 10 Nov 2020 01:22:53 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Paul Elder <paul.elder@ideasonboard.com>","Message-ID":"<20201110002253.GB477907@oden.dyn.berto.se>","References":"<20201106103707.49660-1-paul.elder@ideasonboard.com>\n\t<20201106103707.49660-2-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201106103707.49660-2-paul.elder@ideasonboard.com>","X-Mailman-Approved-At":"Tue, 10 Nov 2020 03:49:39 +0100","Subject":"Re: [libcamera-devel] [PATCH v4 01/37] utils: ipc: import mojo","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"iso-8859-1\"","Content-Transfer-Encoding":"quoted-printable","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13658,"web_url":"https://patchwork.libcamera.org/comment/13658/","msgid":"<9a1f7b6c-4dc5-1591-12c2-1e0404e706d4@ideasonboard.com>","date":"2020-11-10T10:03:55","subject":"Re: [libcamera-devel] [PATCH v4 01/37] utils: ipc: import mojo","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi Paul,\n\nOn 10/11/2020 00:22, Niklas Söderlund wrote:\n> Hi Paul,\n> \n> Thanks for your work.\n> \n> On 2020-11-06 19:36:31 +0900, Paul Elder wrote:\n>> Import mojo from the Chromium repository, so that we can use it for\n>> generating code for the IPC mechanism. The commit from which this was\n>> taken is:\n>> a079161ec8c6907b883f9cb84fc8c4e7896cb1d0 \"Add PPAPI constructs for\n>> sending focus object to PdfAccessibilityTree\"\n>>\n>> This tree has been pruned to remove directories that didn't have any\n>> necessary code:\n>> - mojo/* except for mojo/public\n>>   - mojo core, docs, and misc files\n>> - mojo/public/* except for mojo/public/{tools,LICENSE}\n>>   - language bindings for IPC, tests, and some mojo internals\n>> - mojo/public/tools/{fuzzers,chrome_ipc}\n>> - mojo/public/tools/bindings/generators\n>>   - code generation for other languages\n>>\n>> No files were modified.\n>>\n>> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n>> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> This patch is _huge_ and there is no way I will review all files and \n> diff them against the mentioned upstream commit, so I trust your when \n> you say none of the files are modified,\n> \n> Acked-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nHow about we treat patches [1, 2, 3] as a mini-series to provide Mojo\nsupport, and get those in \"immediately\" to prevent reposting large\npatches which aren't expected to change or churn?\n\n--\nKieran\n\n\n\n>>\n>> ---\n>> No change in v4\n>>\n>> No change in v3\n>>\n\n\n<snippity>\n\n\n>> -- \n>> 2.27.0\n>>\n>> _______________________________________________\n>> libcamera-devel mailing list\n>> libcamera-devel@lists.libcamera.org\n>> https://lists.libcamera.org/listinfo/libcamera-devel\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 95BE3BDB89\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 10 Nov 2020 10:04:01 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1899F630BE;\n\tTue, 10 Nov 2020 11:04:01 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B41A7630BA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 10 Nov 2020 11:03:58 +0100 (CET)","from [192.168.0.20]\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 1C1BC556;\n\tTue, 10 Nov 2020 11:03:58 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"gZhXwXjJ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1605002638;\n\tbh=1g4ntr/vdVFsqXpD8vgxugfZOUdXkEDSwNxL0CKc/wc=;\n\th=Reply-To:Subject:To:Cc:References:From:Date:In-Reply-To:From;\n\tb=gZhXwXjJ/nPFLAWxqsyc02MQVLyzP9j9nOGZfTdLr8O1O9oTXhbF4B0TFpM0ITC+W\n\txS7qHGTR49wqNrXEcuukP79O9uUDn3tWnkwm22WlfI9+Jf0WJIsCuksI9zBM3fu627\n\toOPRv3FkMuUehHWE4VHYYHvVCo/2ma3NvAFFsGac=","To":"=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>,\n\tPaul Elder <paul.elder@ideasonboard.com>","References":"<20201106103707.49660-1-paul.elder@ideasonboard.com>\n\t<20201106103707.49660-2-paul.elder@ideasonboard.com>\n\t<20201110002253.GB477907@oden.dyn.berto.se>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\tmQINBFYE/WYBEACs1PwjMD9rgCu1hlIiUA1AXR4rv2v+BCLUq//vrX5S5bjzxKAryRf0uHat\n\tV/zwz6hiDrZuHUACDB7X8OaQcwhLaVlq6byfoBr25+hbZG7G3+5EUl9cQ7dQEdvNj6V6y/SC\n\trRanWfelwQThCHckbobWiQJfK9n7rYNcPMq9B8e9F020LFH7Kj6YmO95ewJGgLm+idg1Kb3C\n\tpotzWkXc1xmPzcQ1fvQMOfMwdS+4SNw4rY9f07Xb2K99rjMwZVDgESKIzhsDB5GY465sCsiQ\n\tcSAZRxqE49RTBq2+EQsbrQpIc8XiffAB8qexh5/QPzCmR4kJgCGeHIXBtgRj+nIkCJPZvZtf\n\tKr2EAbc6tgg6DkAEHJb+1okosV09+0+TXywYvtEop/WUOWQ+zo+Y/OBd+8Ptgt1pDRyOBzL8\n\tRXa8ZqRf0Mwg75D+dKntZeJHzPRJyrlfQokngAAs4PaFt6UfS+ypMAF37T6CeDArQC41V3ko\n\tlPn1yMsVD0p+6i3DPvA/GPIksDC4owjnzVX9kM8Zc5Cx+XoAN0w5Eqo4t6qEVbuettxx55gq\n\t8K8FieAjgjMSxngo/HST8TpFeqI5nVeq0/lqtBRQKumuIqDg+Bkr4L1V/PSB6XgQcOdhtd36\n\tOe9X9dXB8YSNt7VjOcO7BTmFn/Z8r92mSAfHXpb07YJWJosQOQARAQABtDBLaWVyYW4gQmlu\n\tZ2hhbSA8a2llcmFuLmJpbmdoYW1AaWRlYXNvbmJvYXJkLmNvbT6JAlcEEwEKAEECGwMFCwkI\n\tBwIGFQgJCgsCBBYCAwECHgECF4ACGQEWIQSQLdeYP70o/eNy1HqhHkZyEKRh/QUCXWTtygUJ\n\tCyJXZAAKCRChHkZyEKRh/f8dEACTDsbLN2nioNZMwyLuQRUAFcXNolDX48xcUXsWS2QjxaPm\n\tVsJx8Uy8aYkS85mdPBh0C83OovQR/OVbr8AxhGvYqBs3nQvbWuTl/+4od7DfK2VZOoKBAu5S\n\tQK2FYuUcikDqYcFWJ8DQnubxfE8dvzojHEkXw0sA4igINHDDFX3HJGZtLio+WpEFQtCbfTAG\n\tYZslasz1YZRbwEdSsmO3/kqy5eMnczlm8a21A3fKUo3g8oAZEFM+f4DUNzqIltg31OAB/kZS\n\tenKZQ/SWC8PmLg/ZXBrReYakxXtkP6w3FwMlzOlhGxqhIRNiAJfXJBaRhuUWzPOpEDE9q5YJ\n\tBmqQL2WJm1VSNNVxbXJHpaWMH1sA2R00vmvRrPXGwyIO0IPYeUYQa3gsy6k+En/aMQJd27dp\n\taScf9am9PFICPY5T4ppneeJLif2lyLojo0mcHOV+uyrds9XkLpp14GfTkeKPdPMrLLTsHRfH\n\tfA4I4OBpRrEPiGIZB/0im98MkGY/Mu6qxeZmYLCcgD6qz4idOvfgVOrNh+aA8HzIVR+RMW8H\n\tQGBN9f0E3kfwxuhl3omo6V7lDw8XOdmuWZNC9zPq1UfryVHANYbLGz9KJ4Aw6M+OgBC2JpkD\n\thXMdHUkC+d20dwXrwHTlrJi1YNp6rBc+xald3wsUPOZ5z8moTHUX/uPA/qhGsbkCDQRWBP1m\n\tARAAzijkb+Sau4hAncr1JjOY+KyFEdUNxRy+hqTJdJfaYihxyaj0Ee0P0zEi35CbE6lgU0Uz\n\ttih9fiUbSV3wfsWqg1Ut3/5rTKu7kLFp15kF7eqvV4uezXRD3Qu4yjv/rMmEJbbD4cTvGCYI\n\td6MDC417f7vK3hCbCVIZSp3GXxyC1LU+UQr3fFcOyCwmP9vDUR9JV0BSqHHxRDdpUXE26Dk6\n\tmhf0V1YkspE5St814ETXpEus2urZE5yJIUROlWPIL+hm3NEWfAP06vsQUyLvr/GtbOT79vXl\n\tEn1aulcYyu20dRRxhkQ6iILaURcxIAVJJKPi8dsoMnS8pB0QW12AHWuirPF0g6DiuUfPmrA5\n\tPKe56IGlpkjc8cO51lIxHkWTpCMWigRdPDexKX+Sb+W9QWK/0JjIc4t3KBaiG8O4yRX8ml2R\n\t+rxfAVKM6V769P/hWoRGdgUMgYHFpHGSgEt80OKK5HeUPy2cngDUXzwrqiM5Sz6Od0qw5pCk\n\tNlXqI0W/who0iSVM+8+RmyY0OEkxEcci7rRLsGnM15B5PjLJjh1f2ULYkv8s4SnDwMZ/kE04\n\t/UqCMK/KnX8pwXEMCjz0h6qWNpGwJ0/tYIgQJZh6bqkvBrDogAvuhf60Sogw+mH8b+PBlx1L\n\toeTK396wc+4c3BfiC6pNtUS5GpsPMMjYMk7kVvEAEQEAAYkCPAQYAQoAJgIbDBYhBJAt15g/\n\tvSj943LUeqEeRnIQpGH9BQJdizzIBQkLSKZiAAoJEKEeRnIQpGH9eYgQAJpjaWNgqNOnMTmD\n\tMJggbwjIotypzIXfhHNCeTkG7+qCDlSaBPclcPGYrTwCt0YWPU2TgGgJrVhYT20ierN8LUvj\n\t6qOPTd+Uk7NFzL65qkh80ZKNBFddx1AabQpSVQKbdcLb8OFs85kuSvFdgqZwgxA1vl4TFhNz\n\tPZ79NAmXLackAx3sOVFhk4WQaKRshCB7cSl+RIng5S/ThOBlwNlcKG7j7W2MC06BlTbdEkUp\n\tECzuuRBv8wX4OQl+hbWbB/VKIx5HKlLu1eypen/5lNVzSqMMIYkkZcjV2SWQyUGxSwq0O/sx\n\tS0A8/atCHUXOboUsn54qdxrVDaK+6jIAuo8JiRWctP16KjzUM7MO0/+4zllM8EY57rXrj48j\n\tsbEYX0YQnzaj+jO6kJtoZsIaYR7rMMq9aUAjyiaEZpmP1qF/2sYenDx0Fg2BSlLvLvXM0vU8\n\tpQk3kgDu7kb/7PRYrZvBsr21EIQoIjXbZxDz/o7z95frkP71EaICttZ6k9q5oxxA5WC6sTXc\n\tMW8zs8avFNuA9VpXt0YupJd2ijtZy2mpZNG02fFVXhIn4G807G7+9mhuC4XG5rKlBBUXTvPU\n\tAfYnB4JBDLmLzBFavQfvonSfbitgXwCG3vS+9HEwAjU30Bar1PEOmIbiAoMzuKeRm2LVpmq4\n\tWZw01QYHU/GUV/zHJSFk","Organization":"Ideas on Board","Message-ID":"<9a1f7b6c-4dc5-1591-12c2-1e0404e706d4@ideasonboard.com>","Date":"Tue, 10 Nov 2020 10:03:55 +0000","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.10.0","MIME-Version":"1.0","In-Reply-To":"<20201110002253.GB477907@oden.dyn.berto.se>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v4 01/37] utils: ipc: import mojo","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Reply-To":"kieran.bingham@ideasonboard.com","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"base64","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]